服务器容错保护二

274 阅读5分钟

###触发断路器的流程

客户端通过RestTemplate调用远程服务,如果在RestTemplate内部已经实现了重试的话,则还会进行重试,当重试完成后,结果还是失败,则会调用fallback里面指定的方法。

1 创建HyStrixCommand或者HystrixObservableCommand对象 2 执行命令 3 结果是否被缓存 如果开启了缓存,并且该命令缓存命中,则缓存的结果立即以Observable对象的形式返回 4 断路器是否打开

  • 如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑
  • 如果断路器关闭,Hystrix跳到第5步 5 线程池/请求队列/信号量是否占满(依赖隔离,每个依赖服务都有独立的线程池)
  • 如果占满,则直接转到fallback处理逻辑 6 执行run,construct,用户自己写的RestTemplate请求服务 7 计算断路器的健康度 Hystrix会将“成功”、“失败”、“拒绝”、“超时”等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否将断路器打开,来对某个依赖服务的请求进行“熔断/短路”,直到恢复期结束。若在恢复期结束后,根据统计的数据判断如果还是未达到健康指标,就再次“熔断/短路” 8 fallback处理 当命令执行失败时,则会进入fallback,通常称之为“服务降级” 原因: 4,当前命令处于“熔断/短路”状态,断路器是打开的时候 5,当前命令的线程池、请求队列或者信号量被占满 6,HystrixObservableCommand.construct()或者HystrixCommand.run()抛出异常 9 返回成功的响应

###使用详解(注解版用法) 基本使用: 创建同步执行

    @HystrixCommand
    public String hello(){

       return restTemplate.getForObject("http://eureka-client/hello",String.class);
    }

####创建异步执行

    @HystrixCommand
    public Future<String> helloAsync(){

        return new AsyncResult<String>() {
            @Override
            public String invoke() {
                return restTemplate.getForObject("http://eureka-client/hello",String.class);
            }
        };
    }

####定义服务降级

    @HystrixCommand(fallbackMethod = "helloFallback")
    public String hello(){

       return restTemplate.getForObject("http://eureka-client/hello",String.class);
    }

    public String helloFallback(){
        return "hello";
    }

当降级的服务逻辑返回的结果也有可能不稳定时,也可以对其进行降级

    @HystrixCommand(fallbackMethod = "helloFallback")
    public String hello(){

       return restTemplate.getForObject("http://eureka-client/hello",String.class);
    }
    @HystrixCommand(fallbackMethod = "helloDefaultFallback")
    public String helloFallback(){
        return restTemplate.getForObject("http://eureka-client/hello2",String.class);
    }

    public String helloDefaultFallback(){
        return "hello";
    }

有些情况也可以不需要实现降级逻辑 比如:执行写操作逻辑的命令,执行批处理或离线计算的命令,当这些操作出现异常时,只需要返回错误结果即可,不需要进行降级处理。

####异常传播 在HystrixCommand实现的run()方法中抛出异常时,除了HystrixBadRequestException之外,其他的异常均会被Hystrix认为命令执行失败,并触发服务降级的处理逻辑。@HystrixCommand注解的ignoreException参数可以忽略指定异常

    @HystrixCommand(ignoreExceptions = {BadRequestException.class})
    public String hello(){

        return restTemplate.getForObject("http://eureka-client/hello",String.class);
    }

当hello方法抛出了类型为BadRequestException的异常时,Hystrix会将它包装在HystrixBadRequestException中抛出,这样就不会触发后续的fallback逻辑。 ####异常获取

    @HystrixCommand(fallbackMethod = "helloFallback")
    public String hello(){

        return restTemplate.getForObject("http://eureka-client/hello",String.class);
    }
    public String helloFallback(Throwable e){
        return "hello";
    }

通过在降级方法参数列表中加入Throwable 类型的参数,获取异常对象e,可以通过获取异常信息或者异常类型来进行处理。

####命令名称、分组以及线程池划分 通过设置命令组,Hystrix会根据组来组织和统计命令的告警、仪表盘等信息。那么为什么一定要设置命令组呢?因为除了根据组能实现统计之外,Hystrix命令默认的线程划分也是根据命令分组来实现的。默认情况下,Hystrix会让相同组名的命令使用同一个线程池,所以我们需要在创建Hystrix命令时为其指定命令组名来实现默认的线程池划分。 Hystrix还提供了HystrixThreadPoolKey来对线程池进行设置,通过它我们可以实现更细粒度的线程池的划分。 如果在没有特别指定HystrixThreadPoolKey的情况下,依然会使用命令组的方式来划分线程池。通常情况下,尽量通过HystrixThreadPoolKey的方式来指定线程池的划分,而不是通过组名的默认方式实现划分,因为多个不同的命令可能从业务逻辑上来看属于同一个组,但是往往从实现本身上需要跟其他命令进行隔离。

    @HystrixCommand(commandKey = "hello",groupKey = "testGroup",threadPoolKey = "helloThread")
    public String hello(){

        return restTemplate.getForObject("http://eureka-client/hello",String.class);
    }

####请求缓存

  • 设置请求缓存
    @CacheResult
    @HystrixCommand
    public User getUserById(Long id){
        return restTemplate.getForObject("http://eureka-client/getUserById?id={1}",User.class,id);
    }

通过添加 @CacheResult注解,开启缓存功能。缓存的key值使用所有的参数生成。

  • 定义缓存key 第一种方式
    @CacheResult(cacheKeyMethod = "getUserByIdCacheKey")
    @HystrixCommand
    public User getUserById(Long id){
        return restTemplate.getForObject("http://eureka-client/getUserById?id={1}",User.class,id);
    }

    private Long getUserByIdCacheKey(Long id){
        return id;
    }

第二种方式

    @CacheResult
    @HystrixCommand
    public User getUserById(@CacheKey("id") Long id){
        return restTemplate.getForObject("http://eureka-client/getUserById?id={1}",User.class,id);
    }

第二种方式@CacheKey的优先级比第一种方式cacheKeyMethod 的优先级低 第二种方式还支持访问User对象里面的id属性

  • 缓存清理 当使用了update操作更新了对象时,那么就需要对缓存进行清理
    @CacheResult
    @HystrixCommand
    public User getUserById(@CacheKey("id") Long id){
        return restTemplate.getForObject("http://eureka-client/getUserById?id={1}",User.class,id);
    }
    @CacheRemove(commandKey = "getUserById")
    @HystrixCommand
    public User update(User user){
        return restTemplate.postForObject("http://eureka-client/update",User.class,user);
    }

####请求合并 微服务架构中的依赖通常通过远程调用实现,而远程调用中最常见的问题就是通信消耗与连接数占用。在高并发的情况下,因通信次数的增加,总的通信时间消耗将会变得不那么理想。因为依赖服务端 线程池资源有限,将会出现排队等待与响应延迟的情况。Hystrix提供了HystrixCollapser来实现请求的合并,以减少通信消耗和线程数的占用。 HystrixCollapser实现了在HystrixCommand之前放置一个合并处理器,将处于一个很短的时间窗(默认10毫秒)内对同一依赖服务的多个请求进行整合并以批量方式发起请求的功能(服务提供方也需要提供相应的批量实现接口)。

    @HystrixCollapser(batchMethod = "batchGetUserByIds",collapserProperties = {
            @HystrixProperty(name="timerDelayInMilliseconds",value="100")
    })
    public User getUserById(Long id){
        return restTemplate.getForObject("http://eureka-client/getUserById?id={1}",User.class,id);
    }
    public List<User> batchGetUserByIds(List<Long> ids){
        return restTemplate.getForObject("http://eureka-client/batchGetUserByIds?ids={1}",List.class, StringUtils.join(ids,","));
    }

请求合并额外开销

####属性详解