SpringCloud 05 —— Hystrix(下)SpringCloud使用

Hystrix Ribbon

在Spring Cloud 中,Hystrix 主要用在被调用方,服务A调用服务B,如果服务A调用不同服务B是,及时回退信息给用户,防止因为超时问题引起更多的问题至服务不可用。

本次演示需要先将前面的ribbon-server启动起来,用于提供服务,也就是上图的服务B,现在我们需要自己开发服务A。

新建项目模块cloud-hystrix-ribbon,引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

启动类:

@EnableHystrix
@EnableEurekaClient
@SpringBootApplication
public class HystrixRibbonApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixRibbonApplication.class, args);
    }
}

和前面一样实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    /**
     * 主键Id
     */
    private long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 昵称
     */
    private String rolename;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 备注
     */
    private String remark;
}

创建Ribbon调用类:

这里需要注意我们在远程调用的方法上添加了@HystrixCommand(fallbackMethod = "findUserByIdFailure"),也就是说如果调用失败会执行findUserByIdFailure方法

@Service
@Slf4j
public class EurekaHystrixRibbonService {


    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "findUserByIdFailure")
    public User findUserById(Long id) {
        // http://服务提供者的serviceId/url
        return restTemplate.getForObject("http://ribbon-server/user/" + id, User.class);
    }

    /**
     * 服务feign-server/user/id 调用失败的回调方法
     *
     * @return
     */
    public User findUserByIdFailure(Long id) {
        return new User(id, null, null, null, "网络繁忙,请稍后再试");
    }
}

Ribbon 基本配置

这里添加了一个getServlet()方法,用于后面Hystrix dashboard,见后面。

@Configuration
public class EurekaRibbonConfig {

    @Bean
    @LoadBalanced // 实现负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule ribbonRule() {

        //默认ZoneAvoidanceRule请求,实现自定义的规则
        //自定义成随机

        return new RandomRule();
    }

    /**
     * 解决dashboard显示 Unable to connect to Command Metric Stream
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

}

控制层:

@RestController
public class EurekaHystrixController {
    @Resource
    private EurekaHystrixRibbonService eurekaRibbonService;

    /**
     * 根据id获取用户信息
     *
     * @param id
     * @return
     */
    @GetMapping("/user/{id:\\d+}")
    public User findUserById(@PathVariable long id) {
        return eurekaRibbonService.findUserById(id);
    }
}

应用配置

server:
  port: 8675
spring:
  application:
    name: hystrix-ribbon
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/

启动Eureka和cloud-ribbon-server再启动当前项目,postman测试127.0.0.1:8675/user/1

下面我们将ribbon-server关掉,再测试,可以看到返回就是findUserByIdFailure设置的描述

@HystrixCommand配置拓展

除了指定fallbackMethod以外,还有很多属性可以配置,下图是@HystrixCommand的全部属性

例如
指定回退方法和默认方法都是findUserByIdFailure,也可以不同,指定超时时间100毫秒,线程池大小为1,忽略Exception触发的回退等,具体的属性,根据字面意思应该没问题。一般我们也用不到这么多属性

@HystrixCommand(fallbackMethod = "findUserByIdFailure"
        , groupKey = "MyGroup"
        , commandKey = "MyCommandKey"
        , threadPoolKey = "MyThreadPool"
        , commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")}
        , threadPoolProperties = {
        @HystrixProperty(name = "coreSize", value = "1")}
        , ignoreExceptions = {Exception.class}
        , observableExecutionMode = ObservableExecutionMode.EAGER
        , raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION}
        , defaultFallback = "findUserByIdFailure"
)

默认配置

对于一些默认的配置,可以使用@DefaultProperties,可以减少@HystrixCommand中的代码量,如下

@Service
@Slf4j
@DefaultProperties(groupKey = "MyGroup")
public class EurekaHystrixRibbonService {


    @Autowired
    RestTemplate restTemplate;
    ......

Hystrix 缓存

在上一章原生使用中已经讲过了Hystrix的缓存,我们现在在Spring Cloud 篇也实现一个缓存的功能。
创建缓存我们需要创建一个实现java.servlet.Filter类的过滤器,并且需要初始化请求上下文,跟入门篇一样。这次可以减少每次创建上下文的代码量。

新建过滤器

这里我们对全部的url进行过滤,因此配置了/*

@WebFilter(urlPatterns = "/*",filterName = "hystrixFilter")
@Component
public class HystrixFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(servletRequest,servletResponse);
        }finally {
            context.shutdown();
        }
    }
}

对于接口请求的改造只需要加一个@CacheResult的注解,注意需要配合@HystrixCommand一起使用。

重启项目将ribbon-server启动起来,并且重启当前hystrix项目,侯然Postman 测试接口127.0.0.1:8675/user/1

可以看到,第一次请求之后,同样的请求就走缓存了,后面不会再调用接口了。

缓存其它注解

同样的问题,缓存不能一直不变的,如果数据发生变动,缓存里的数据要可以更新或者删除,否则就会产生脏数据。
可以使用@CacheRemove移除缓存 、@CacheKey修饰方法参数

Hystrix 合并请求注解

同样上一章中我们已经讲过请求合并,Spring Cloud 中也同样支持请求合并,并且Spring Cloud 提供了一个注解,非常方便处理合并请求。
为了方便演示合并请求,我们需要在ribbon-server里添加一个接收方法,用于接收id的集合。

新建ribbon-server的controller方法

    @GetMapping("/list")
    public List<User> getUserById(String  ids, HttpServletRequest req){
        log.info("ids,{}",ids);
        List<User> list=new ArrayList<>();
        String[] splitIds = ids.split(",");
        for (String id : splitIds) {
            String url = req.getRequestURL().toString();
            User user = userService.getUserById(Long.valueOf(id));
            user.setRemark(user.getRemark()+":提供服务的是:"+url);
            list.add(user);
        }
        return list;
    }

在EurekaHystrixRibbonService中新建合并请求方法

在hystrix-ribbon里EurekaHystrixRibbonService类中新建合并