服务容错保护:Spring Cloud Hystrix

作者: SUNOW2 | 来源:发表于2018-05-05 17:27 被阅读85次

    Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护措施,它也是基于Netflix的开源框架Hystrix实现的,该框架的目标是通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

    快速入门

    -在服务消费者工程的pom.xml中添加spring-cloud-starter-hystrix依赖

    <dependency>
      <groupId>org.springframework.cloud<groupId>
      <artifactId>spring-cloud-starter-hystrix</artifactId>
    </dependency>
    

    -在消费者工程的主类ConsumerApplication中使用@EnableCircuitBreaker注解开启断路器功能。

    @EnableCircuitBreaker
    @EnableDiscoveryClient
    @SpringBootApplication
    public class ConsumerApplication {
      @Bean
      @LoadBalanced
      RestTemplate restTemplate() {
        return new RestTemplate();
      }
    
      public static void main(String args[]) {
       SpringApplication.run(ConsumerApplication.class, args);
      }
    }
    

    -改造服务消费方式,新增HelloService类,注入RestTemplate实例,然后,将在ConsumerController中对RestTemplate的使用迁移到HelloService函数中,最后在HelloService函数上增加@HystrixCommand注解来指定回调方法:

    @Service
    public class HelloService {
    
      @Autowired
      RestTemplate restTemplate;
    
      @HystrixCommand(fallbackMethod="helloFallback")
      public String helloService() {
        return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
      }
    
      public String helloFallback() {
        return "error";
      }
    }
    

    -修改ConsumerController类,注入上面实现的HelloService实例,并在helloConsumer中进行调用

    @RestController
    public class ConsumerController {
      @Autowired
      HelloService helloService;
    
      @RequestMapping(value="/ribbon-consumer", method=RequestMethod.GET)
      public String helloConsumer() {
        return helloService.helloService();
      }
    }
    

    原理分析

    工作流程

    1、创建HystrixCommand或者HystrixObservableCommand对象
    -HystrixCommand:用在依赖的服务返回单个操作结果的时候。
    -HystrixObservableCommand:用在依赖的服务返回多个操作结果的时候。
    2、命令执行
    Hystrix共有四种命令的执行方式,HystrixCommand实现了两种执行方式,分别为:
    -execute():同步执行,从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常
    -queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果的对象

    R value = command.execute();
    Future<R> fValue = command.queue();
    

    HystrixObservableCommand实现了另外两种执行方式
    -observe():返回Observable对象,它代表了操作的多个结果,它是一个HotObservable。
    -toObservable():同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable。

    Observable<R> ohValue = command.observe();
    Observable<R> ocValue = command.toObservable();
    

    3、结果是否被缓存
    4、断路器是否打开
    5、线程池/请求队列/信号量是否占满

    依赖隔离

    Hystrix使用“舱壁隔离”模式实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会陀满其他的依赖服务。

    使用详解

    Hystrix的核心注解是@HystrixCommand,通过它创建了HystrixCommand的实现,同时利用fallback属性指定了服务降级的实现方法。

    创建请求命令

    Hystraix命令就是HystrixCommand,用于封装具体的依赖服务调用逻辑。通过继承的方式来实现

    public class UserCommand extends HystrixCommand<User> {
      private RestTemplate restTemplate;
      private Long id;
      public UserCommand(Setter setter, RestTemplate restTemplate, Long id) {
        super(setter);
        this.restTemplate = restTemplate;
        this.id = id;
      }
    
      @Override
      protected User run() {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
      }
    }
    

    -同步执行:User u = new UserCommand(restTemplate, 1L).execute();
    -异步执行:Future<User> futureUser = new UserFuture(restTemplate,1L).queue();异步执行的时候,可以通过对返回的futureUser调用get方法来获得结果。
    另外,也可以通过@HystrixCommand注解来实现Hystrix命令

    public class UserService {
      @Autowired
      private RestTemplate restTemplate;
    
      @HystrixCommand
      public User getUserById(Long id) {
        return restTemplate.getForObject()
      }
    }
    

    虽然@HystrixCommand注解可以定义Hystrix命令的实现,但是如上定义的getUserById方式只是同步执行的实现,若要实现异步执行则还需要另外定义

    @HystrixCommand
    public Future<User> getUserByIdAsync(final String id) {
      return new AsyncResult<User> () {
        @Override
        public User invoke() {
          return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
        }
      }
    }
    

    在使用@HystrixCommand注解实现响应式命令时,可以通过observableExecutionMode参数来控制使用observe()还是toObservable()的执行方式。该参数有两种设置方式。
    -@HystrixCommand(observableExecutionMode=ObservableExecutionMode.EAGER);EAGER是该参数的模式值,表示toObserve()执行方式
    -@HystrixCommand(observableExecutionMode=ObservableExecutionMode.LAZY);表示使用toObservable()执行方式。

    定义服务降级

    fallback是Hystrix命令执行失败时使用的后备方法,用来实现服务的降级处理逻辑。在HystrixCommand中可以通过重载getFallback()方法来实现服务降级逻辑,Hystrix会在run()执行过程中出现错误、超时、线程池拒绝、断路器等情况时,执行getFallback()方法内的逻辑

    public class UserCommand extends HystrixCommand<User> {
      private RestTemplate restTemplate;
      private Long id;
    
      public UserCommand(Setter setter, RestTemplate restTemplate,Long id) {
        super(setter);
        this.restTemplate = restTemplate;
        this.id = id;
      }
    
      @Override
      protected User run() {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
      }
    
      @Override
      protected User getFallback() {
        return new User();
      }
    }
    

    使用注解实现降级服务只需要使用@HystrixCommand中的fallbackMethod参数来指定具体的服务降级实现方法

    public class UserService {
      @Autowired
      private RestTemplate restTemplate;
    
      @HystrixCommand(fallbackMethod="defaultUser")
      public User getUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id)
      }
    
      public User defaultUser() {
        return new User();
      } 
    }
    

    在使用注解定义服务降级逻辑时,我们需要将具体的Hystrix命令与fallback的实现函数定义在同一个类中,并且fallbackMethod的值必须与实现fallback方法的名字相同。由于必须定义在一个类中,所以对于fallback的访问修饰符没有特定的要求,定义为private、protected、public均可。

    异常处理

    异常传播

    当HystrixCommand实现的run()方法抛出异常时,这些异常会被Hystrix认为是命令执行失败并触发服务降级的处理逻辑。
    使用注解配置实现Hystrix命令时,它还支持忽略指定异常类型功能

    @HystrixCommand(ignoreExceptions={BadRequestException.class})
    public User getUserById(Long id) {
      return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
    }
    

    异常处理

    命令名称、分组以及线程池划分

    以继承方式实现的Hystrix命令使用类名作为作为默认名称,我们也可以在构造函数中通过Setter静态类来设置

    public UserCommand() {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Groupname")).andCommandKey(HystrixCommandKey.Factory.asKey("CommandName")))
    }
    

    使用注解的时候设置命令名称、分组以及线程池

    @HystrixCommand(commandKey="getByUserId", groupKey="UserGroup", threadPoolKey="getUserByIdThread")
    public User getUserById(Long id) {
      return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
    }
    

    请求缓存

    开启请求缓存的功能:在实现HystrixCommand或HystrixObservableCommand时,通过重载getCacheKey()方法来开启请求缓存。
    清理失效缓存功能:在Hystrix中,通过HystrixRequestCache.clear()方法进行缓存清理。

    工作原理

    尝试获取请求:Hystrix命令在执行前会根据之前提到的isRequestCachingEnabled方法来判断当前命令是否启用了请求缓存。如果开启了请求缓存且重写了getCacheKey方法,并返回了一个非null的缓存Key值,那么就使用getCacheKey返回的Key值调用HystrixRequestCache的get(String cacheKey)获取缓存的HystrixCachedObservable对象。
    将请求结果加入缓存
    -设置请求缓存:添加@CacheResult注解,Hystrix会将结果加入请求缓存中,而它的缓存Key值会使用所有的参数

    @CacheResult
    @HystrixCommand
    public User getUserById(Long id) {
      return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
    }
    

    -定义缓存key:可以使用@CacheResult和@CacheRemove注解的cacheKeymethod方法来指定具体的生成函数;也可以通过使用@CacheKey注解在方法中指定用于组装缓存Key的元素。

    @CacheResult(cacheKeyMethod="getUserByIdCacheKey")
    @HystrixCommand
    public User getUserById(Long id) {
      return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
    }
    private Long getUserByIdCacheKey(Long id) {
      return id;
    }
    

    使用@CacheKey注解实现方式更简单,但是它的优先级比cacheKeyMethod的优先级低,如果已经使用了cacheKeyMethod指定了缓存key的生成函数,那么@CacheKey注解将不会生效

    @CacheResult
    @HystrixCommand
    public User getUserById(@CacheKey("id") Long id) {
     return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
    }
    

    @CacheKey注解除了可以指定方法参数作为缓存Key之外,它还允许访问参数对象的内部属性作为缓存Key

    @CacheKey
    @HystrixCommand
    public User getUserById(@CacheKey("id") User user) {
      return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,user.getId())
    }
    

    -缓存清理,通过@CacheRemove注解来实现失效缓存的清理

    @CacheResult
    @HystrixCommand
    public User getUserById (@CacheKey ("id") Long id) {
      return restTemplate.getForObject("http://USER-SERVICE/users/{1} ", User. class);
     }
    
    @CacheRemove(commandKey="getUserById")
    @HystrixCommand
    public void update(@CacheKey("id") User user) {
      return restTemplate.postForObject("http://USER-SERVICE/users",user,User.class);
    }
    

    @CacheRemove注解的commandKey属性必须要指定的,它用来指明需要使用请求缓存的请求命令

    请求合并

    属性详解

    command属性

    command属性主要用来控制的是HystrixCommand命令的行为,主要有五种不同类型的属性配置
    -execution配置,主要用来控制HystrixCommand.run()的执行
    -fallback配置,主要用于控制HystrixCommand.getFallback()的执行,这些属性同时适用于线程池的信号量的隔离策略。
    -circuitBreaker配置,主要用于HystrixCircuitBreaker断路器的行为
    -metrics配置,与HystrixCommand和HystrixObservableCommand执行中捕获的指标信息有关。
    -requestContext配置,与HystrixCommand使用的HystrixRequestContext的设置。

    相关文章

      网友评论

        本文标题:服务容错保护:Spring Cloud Hystrix

        本文链接:https://www.haomeiwen.com/subject/nbwdrftx.html