微服务要实现一个一般功能,通常都要涉及到一连串的微服务调用,而当最底层依赖服务出故障,就可能会导致整个调用链路资源被占用、甚至崩溃。这个时候就需要做一些容错处理。
1、容错手段
1.1、“错”——雪崩效应
微服务之间通过网络通信,存在一定依赖关系。微服务本身出错或者网络问题,都有可能会导致请求失败。我们通常把“基础服务故障”导致的“级联故障”现象称为雪崩效应。
1.2、如何容错
- 网络请求设置超时——保证被占用着的线程/进程资源得到释放
- 使用断路器模式——如果请求某微服务大量超时(不可用),就不再去请求了(断路器打开,直接返回默认值),隔段时间重试恢复正常了,断路器才关闭
2、Hystrix实现容错
2.1、Hystrix简介
- 包裹请求——使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行(设计模式中的命令模式)
- 跳闸机制——当某服务的错误率超过一定阈值,Hystrix可自动或手动跳闸,停止请求该服务一段时间
- 资源隔离——Hystrix为每一个消费者对生产者的调用都维护一个小型线程池(或信号量)。如果线程已满,立即拒绝,加速失败
- 监控——几乎实时监控运行指标和配置变化
- 回退机制——请求出错、断路器打开时就会执行,也就是返回一个我们自己设定的默认值。
- 自我修复——断路器打开一段时间后,会自动“半开”。放个请求过去,成功就关闭断路器了。
2.2、整合Hystrix(都是消费者一侧配置)
2.2.1、添加hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.2.2、在启动类上添加@EnableCircuitBreaker或@EnableHystrix,启用断路器的支持
2.2.3、修改方法,使其具备容错能力
@HystrixCommand(fallbackMethod = "findByIdFallback")
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return this.restTemplate.getForObject("http://microservice-provider-user/" + id,
User.class);
}
public User findByIdFallback(Long id) {
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}
2.2.4、@HystrixCommand的配置非常灵活,可以使用@HystrixProperty的commandProperties属性来配置
@HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties={
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000"),
@HystrixProperty(name="metrics.rollingStats.timeInMilliseconds",value="10000")},
threadPoolPropertity{
@HystrixProperty(name="coreSize",value="1"),
@HystrixProperty(name="maxQueueSize",value="10"),
})
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return this.restTemplate.getForObject("http://microservice-provider-user/" + id, User.class);
}
public User findByIdFallback(Long id) {
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}
注意:线程池是为消费者一侧配置的,配置的是对提供者服务的请求
2.3、Hystrix断路器状态监控与深入理解
添加spring-boot-starter-actuator依赖,然后就可以通过调用/health接口查看服务状态
2.4、Hystrix的线程隔离策略
Hystrix的隔离策略有两种:
- THREAD(线程隔离)——HystrixCommand会在单独线程上执行,并发请求受线程池中线程数量限制
- SEMAPHORE(信号量隔离)——HystrixCommand将会在调用线程上执行,开销小,受信号量个数限制
Hystrix默认并且推荐使用线程隔离,这种多了网络超时以外的保护层;只有当负载非常高,才需要用信号量隔离,因为这时候创建很多线程开销太高。信号量隔离适用于非网络调用的隔离。
2.5、Feign使用Hystrix
SpringCloud默认为Feign整合了Hystrix,默认会包裹所有的方法
2.5.1、为Feign添加回退方法
@FeignClient(name = "microservice-provider-user", fallback = FeignClientFallback.class)
public interface UserFeignClient {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
}
/**
* 回退类FeignClientFallback需实现Feign Client接口
* FeignClientFallback也可以是public class,没有区别
* @author 周立
*/
@Component
class FeignClientFallback implements UserFeignClient {
@Override
public User findById(Long id) {
User user = new User();
user.setId(-1L);
user.setUsername("默认用户");
return user;
}
}
2.5.2、通过FallBack Factory检查回退原因
@Component
class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
private static final Logger LOGGER = LoggerFactory.getLogger(FeignClientFallbackFactory.class);
@Override
public UserFeignClient create(Throwable cause) {
return new UserFeignClient() {
@Override
public User findById(Long id) {
// 日志最好放在各个fallback方法中,而不要直接放在create方法中。
// 否则在引用启动时,就会打印该日志。
// 详见https://github.com/spring-cloud/spring-cloud-netflix/issues/1471
FeignClientFallbackFactory.LOGGER.info("fallback; reason was:", cause);
User user = new User();
user.setId(-1L);
user.setUsername("默认用户");
return user;
}
};
}
}