六、使用Hystrix实现微服务的容错处理

应用容错三板斧

  1. 超时机制
    超时机制你懂的,配置一下超时时间,例如1秒——每次请求在1秒内必须返回,否则到点就把线程掐死,释放资源!
    思路:一旦超时,就释放资源。由于释放资源速度较快,应用就不会那么容易被拖死。

  2. 舱壁模式
    软件世界里的仓壁模式可以这样理解:M类使用线程池1,N类使用线程池2,彼此的线程池不同,并且为每个类分配的线程池较小,例如coreSize=10。举个例子:M类调用B服务,N类调用C服务,如果M类和N类使用相同的线程池,那么如果B服务挂了,M类调用B服务的接口并发又很高,你又没有任何保护措施,你的服务就很可能被M类拖死。而如果M类有自己的线程池,N类也有自己的线程池,如果B服务挂了,M类顶多是将自己的线程池占满,不会影响N类的线程池——于是N类依然能正常工作,
    思路:不把鸡蛋放在一个篮子里。你有你的线程池,我有我的线程池,你的线程池满了和我没关系,你挂了也和我没关系。

  3. 断路器
    现实世界的断路器大家肯定都很了解,每个人家里都会有断路器。断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
    软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。
    跳闸一段时间后(例如15秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的”自我修复“。

断路器状态转换图

使用 Hystrix 实现容错

Hystrix 简介

Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

  • 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。
  • 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
  • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换,前面我们已经详细探讨过了,不再赘述。

通用方式整合Hystrix

  1. 引入相关依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  1. 在启动类上添加注解@EnableCircuitBreaker 或 @EnableHystrix
1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
@EnableCircuitBreaker
public class ConsumerMovieApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
  1. 修改 Controller,让其中的 findById 方法具备容错能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
public class MovieController {
private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class);
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "findByIdFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000")},
threadPoolProperties = {
@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, Throwable throwable) {
LOGGER.error("进入回退方法,异常:", throwable);
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}

为findById 方法编写了一个回退方法 findByIdFallback, 该方法与findById 方法具有相同的参数与返回值类型。
在findById 方法上,使用注解 @HystrixCommand 的fallbackMethod 属性,指定回退方法 findByIdFallback。@HystrixCommand 的配置非常灵活, 可使用注解@HystrixProperty 的commandProperties 属性来配置@HystrixCommand。如上
如需获得导致fallback 的原因,只需在fallback 方法上添加Throwable 参数即可。
详细理解注解@HystrixCommand 可前往 https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica#configuration
Hystrix 配置属性可详见 Hystrix Wiki ( https://github.com/Netflix/Hystrix/wiki/Configuration)

Feign 使用Hystrix

Spring Cloud 默认已为 Feign 整合了 Hystrix,要想为Feign 打开Hystrix 支持,只需设置feign.hystrix.enable=true。

  1. 在 application.yml 中添加feign.hystrix.enable=true
1
2
3
4
5
6
feign:
hystrix:
enabled: true
# 说明:请务必注意,从Spring Cloud Dalston开始,Feign默认是不开启Hystrix的。
# 因此,如使用Dalston请务必额外设置属性:feign.hystrix.enabled=true,否则断路器不会生效。
# 而,Spring Cloud Angel/Brixton/Camden中,Feign默认都是开启Hystrix的。无需设置该属性。
  1. 编写Feign 回退类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 回退类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;
}
}
  1. 修改Feign 接口,通过@FeignClient注解的 fallback 属性,为指定名称的Feign 客户端添加回退。
1
2
3
4
5
@FeignClient(name = "microservice-provider-user", fallback = FeignClientFallback.class)
public interface UserFeignClient {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
}
  1. 通过 Fallback Factory 检查回退原因
    对于Feign 获取回退原因,可使用注解 @FeignClient 的 fallbackFactory 属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* UserFeignClient的fallbackFactory类,该类需实现FallbackFactory接口,并覆写create方法
* The fallback factory must produce instances of fallback classes that
* implement the interface annotated by {@link FeignClient}.
* @author 周立
*/
@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;
}
};
}
}

Hystrix 线程隔离策略与传播上下文

Hystrix 的隔离策略有两种:分别是线程隔离和信号量隔离。

  1. THREAD(线程隔离):使用该方法,HystrixCommand 将在调用线程上执行,并发请求受到线程池中的线程数量的限制。
  2. SEMAPHORE(信号量隔离):使用该方法,HystrixCommand 将在调用线程上执行,开销相对较小,并发请求受到信号量个数的限制。

Hystrix 中默认并推荐使用线程隔离(THREAD),因为这种方式有一个除网络超时以外的额外保护壳。
一般来说,只有当调用负载非常高时(例如每个实例每秒调用数百次)才需要使用信号量隔离,因为在这种场景下使用THREAD 开销会比较高。信号量隔离一般仅适用于非网络调用的隔离。
可使用 execution.isolation.strategy 属性指定隔离策略。

Hystrix 的监控

  1. 使用/hystrix.stream 端点监控Hystrix
  2. 使用 Hystrix Dashboard 可视化监控数据
  3. 使用 Turbine 聚合监控数据

详细内容自行百度