服务容错保护:Spring Cloud Hystrix

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

快速入门

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


  org.springframework.cloud
  spring-cloud-starter-hystrix

-在消费者工程的主类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 fValue = command.queue();

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

Observable ohValue = command.observe();
Observable ocValue = command.toObservable();

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

依赖隔离

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

使用详解

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

创建请求命令

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

public class UserCommand extends HystrixCommand {
  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 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 getUserByIdAsync(final String id) {
  return new AsyncResult () {
    @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 {
  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)