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的设置。