服务容错保护-Hystrix 基础篇
文章目录
前言
本章主要介绍 Hystrix 的相关概念和基本用法,简单的了解 Hystrix 工作机制,配置以及相关监控页面,最后介绍了一些 Hystrix 中的使用经验。下篇源码分析我们再详细了解其中的设计细节。
项目环境
- Java 8
- Spring Cloud Finchley
- 项目地址:https://github.com/huajiexiewenfeng/deep-in-spring-cloud-netflix
1.什么是 Hystrix?
在分布式系统中,服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务会出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞。
Hystrix 是 Netflix 公司开源的一个项目,它提供了熔断器的功能,能够阻止分布式系统中出现的联动故障。
Hystrix 是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。
2.什么是服务雪崩?
微服务架构下,服务与服务之间相关依赖调用,当某个服务不可用时,很容易因为服务之前的依赖关系使故障扩大,甚至造成整个系统不可用的情况,这种现象称为服务雪崩效应。
上图为服务雪崩的发生过程
- 系统服务均正常,客户端调用服务A,服务A调用服务B,服务B调用服务C,结果返回客户端;
- 此时服务C发生故障或者性能出现问题,导致服务B的请求无法及时响应,产生延迟,随着时间的推移,延迟会越来越大,最终导致服务C资源耗尽,无法正常处理请求;
- 因为请求都堵在服务C上,服务B作为调用方,请求也会出现延迟,最终也会导致服务B的资源耗尽,变为不可用状态,再也无法及时响应服务 A 的请求结果;
- 依此类推,最终服务 A 也会被拖垮,导致整个系统不可用,这个过程就是服务雪崩效应。
3.如何解决服务雪崩?
可以从以下两个方面来进行分析:
-
服务提供者方面,对于这种请求量超出承受能力的问题,我们可以进行扩容来支持高并发或者进行限流,自己能处理多少请求就处理多少,处理不了的请求直接拒绝,这样才不会将自己拖垮。
-
服务消费者方面,我们需要做的就是资源隔离,快速失败,这也是最有效的方式,当我们发现被调用方迟迟不响应出现问题的时候,就不要再继续发起调用请求了,此时应该停止,等待被调用方恢复后再发起调用。
4.Hystrix 设计原则
Hystrix 设计下以下原则用来解决前面讲到的这些问题:
- 防止单个服务的故障耗尽整个服务的 Servlet 容器的线程资源
- 快速失败机制,如果某个服务出现故障,则调用该服务的请求快速失败,而不是线程等待
- 提供回退(fallback)方案,在请求发生故障时,提供设定好的回退方案
- 资源隔离,使用熔断机制,防止故障扩散到其他服务
- 提供熔断器的监控组件 Hystrix Dashboard,可以实时监控熔断器的状态
5.Hystrix 的工作机制
- 首先,当服务的某个 API 接口的失败次数在一定时间内小于设定的阈值时,熔断器处于关闭状态,该 API 接口正常提供服务。
- 当该 API 接口处理请求的失败次数大于设定的阈值时,Hystrix 会判断该 API 接口出现故障,打开熔断器,这时请求该 API 接口会执行快速失败的逻辑(即 fallback 回退的逻辑),不执行业务逻辑,请求的线程不会处于阻塞状态。
- 处于打开状态的熔断器,一段时间后会处于半打开状态,并将一定的请求执行正常逻辑,剩余的请求会执行快速失败;若执行正常逻辑的请求失败了,则熔断器打开;若成功了,这熔断器关闭。这样熔断器就有了自我修复的功能。
6.Hystrix 使用
6.1 在 RestTemplate 和 Ribbon 上使用熔断器
新增一个 hystrix-client 的服务,调用方法如下:
- 使用
@HystrixCommand
注解 - 设置超时时间为 100 毫秒
- 回退方法为 fallback,注意方法签名要一致
@RestController
public class HystrixController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/hystrix/getUser")
@HystrixCommand(commandKey = "getUser", groupKey = "user", fallbackMethod = "fallback", threadPoolKey = "tpk1",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")})
public User getUser(Long id) {
Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("id", id);
User user = restTemplate.getForObject("http://user-service/hystrix/getUser?id={id}", User.class, uriVariables);
return user;
}
/**
* 回退方法
* 注意方法签名一致
*
* @param id
* @return
*/
public User fallback(Long id) {
return new User(id, "默认");
}
}
user-service 服务中增加一个对应的接口方法
- await 方法随机睡眠 200 毫秒以内
@GetMapping("/hystrix/getUser")
public User getHystrixUser(Long id) throws InterruptedException {
await();
User user = new User();
user.setId(id);
user.setName("小仙 port:" + port);
return user;
}
private void await() throws InterruptedException {
int value = random.nextInt(200);
System.out.println("port:" + getPort() + " cost " + value + "ms");
Thread.sleep(value);
}
启动 Eureka 注册中心,以及 hystrix-client 和 user-service 两个服务
浏览器输入 http://127.0.0.1:8381/hystrix/getUser?id=2
user-service 后台控制台打印
port:8181 cost 68ms
再次刷新浏览器
user-service 后台控制台打印
port:8181 cost 105ms
可以看到当服务返回延迟超过我们设置的 100ms,那么该请求就会超时,则直接执行 fallback 方法中的逻辑返回给调用方。
6.2 Feign 中使用熔断器
在本系列 Feign 博客中会演示相关示例
- 《声明式服务调用-Feign 基础篇》 4.Feign 整合 Hystrix
6.3 Zuul 中使用熔断器
在本系列 Zuul 博客中会演示相关示例
7.Hystrix 配置
Hystrix 的配置 github 官网地址:https://github.com/Netflix/Hystrix/wiki/Configuration
我们这里只介绍几个比较常用的配置
执行(Execution)相关:
- execution.isolation.strategy
- 隔离策略,可选 Thread / Semaphore
- execution.isolation.thread.timeoutInMilliseconds
- 执行命令超时时间,默认 1000ms
- execution.isolation.semaphore.maxConcurrentRequests
- 最大并发请求数,默认 10,信号量隔离有效
断路器(Circuit Breaker)相关:
- circuitBreaker.enabled
- 开启断路器默认 true
- circuitBreaker.forceOpen
- 强制打开断路器,拒绝所有 request,默认 false
- circuitBreaker.forceClosed
- 强制关闭断路器,无论错误百分比如何,断路器都将允许请求
线程池(Thread Pool)相关:
- coreSize
- 线程池的核心线程数
- maxQueueSize
- 队列
BlockingQueue
的 size 大小。如果你将其设置为-1
随后 SynchronousQueue 将被使用,当值为正数是,使用 LinkedBlockingQueue
- 队列
- queueSizeRejectionThreshold
- 达到该值后,请求也会被拒绝
8.Hystrix 监控
三个依赖:
- spring-cloud-starter-netflix-hystrix
- spring-boot-starter-actuator
- spring-cloud-starter-netflix-hystrix-dashboard
application.yaml 配置文件增加
management:
endpoints:
web:
exposure:
include: hystrix.stream
启动类增加注解 @EnableHystrixDashboard
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
public class HystrixClientBootstrap {
public static void main(String[] args) {
new SpringApplicationBuilder(HystrixClientBootstrap.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
浏览器输入 http://127.0.0.1:8381/hystrix
在输入框输入需要监控的应用地址 http://127.0.0.1:8381/actuator/hystrix.stream
点击 Monitor Stream 按钮,然后一直访问 http://127.0.0.1:8381/hystrix/getUser?id=2
,可以看到控制台的变化。
当然 spring-cloud-starter-netflix-hystrix-dashboard 也可以单独部署一个应用,这里为了方便都放在 hystrix-client 应用服务中。
9.Hystrix 使用小经验
- 配置可以对接配置中心进行动态调整。
Hystrix 的配置项非常多,很多配置,我们都会根据当时的流量情况来进行调整,如果不对接配置中心,这个工作太难了。其实 Hystrix 内部默认使用 Archaius 来实现的动态配置, Archaius 是 Netflix 的配置框架,也可以直接用 Archaius 来动态管理 Hystrix 的配置信息。
- 回退逻辑中可以手动埋点或者通过输出日志进行告警。
当请求失败或者超时,会执行回退逻辑,如果有大量的回退,则证明某些服务出问题了,这个时候我们可以在回退的逻辑中进行埋点操作,上报数据给监控系统,也可以输出回退的日志,统一由日志收集的程序去进行处理,这些方式都可以将问题暴露出去,然后通过实时数据分析进行告警操作,当然这只是一个入口,对 Hystrix 进行监控的方式有很多种,我们可以扩展 Hystrix 的插件进行数据收集,也可以分析 Hystrix.stream 端点的数据来进行告警。
- 用了线程池隔离模式再用 ThreadLocal 会有坑。
一个请求进来,这时是容器的线程在负责执行,对于同一个线程传递上下文 ThreadLocal 是没有问题的,当我们用了线程池隔离模式的时候,被隔离的方法会包装成一个 Command 丢入到独立的线程池中进行执行,这个时候就是从 A 线程切换到了 B 线程,ThreadLocal 的数据就会丢失。
解决方案:
http://cxytiandi.com/blog/detail/18782
http://cxytiandi.com/blog/detail/13331
- 网关中尽量用信号量隔离。
之所以建议在网关中用信号量隔离,是因为网关是所有请求的入口,路由的服务数量会很多,几十个到上百个都有可能,如果用线程池隔离,那么需要创建上百个独立的线程池,开销太大了。用信号量隔离开销就小很多,还能起到限流的作用。
- 插件机制可以实现很多扩展。
Hystrix 提供了插件机制,可以通过插件来改变 Hystrix 的行为,比如我们可以使用事件通知的插件来做一些数据收集和告警的工作,可以使用配置插件来改变配置的默认行为,目前默认是 Archaius,我们可以将其改变成其他的配置组件。可以使用并发插件来改变线程池的行为,可以对 Callable 进行装饰,来解决 ThreadLocal 跨线程传递的问题。
- Hystrix 各种超时配置方式。
Hystrix 中用的最多的配置可能就是超时时间,可以配置全局的默认超时时间,那么在 HystrixCommand、Feign 以及 Zuul 中超时时间怎么配置呢?相关博客地址: Hystrix 超时配置的N种玩法
- commandKey、groupKey、threadPoolKey 的使用。
在使用 HystrixCommand 注解的时候,我们会配置 commandKey、groupKey、threadPoolKey,当然这些也可以不用配置,因为有默认值。commandKey 表示这个请求被封装成了 Command 去执行,commandKey 就是这个 command 的名称,我们可以给指定的 commandKey 进行参数的配置。比如 commandKey1 的超时时间我们设置成 3 秒,commandKey2 的超时时间我们可以设置成 10 秒。
groupKey 是将一组 command 进行分组,groupKey 就是组的名称,同时如果没有设置 threadPoolKey 的话,那么线程池的名称会用 groupKey。
threadPoolKey 是线程池的名称,多个 command 的 threadPoolKey 相同,那么会使用同一个线程池。建议大家手动配置一个简短的、友好的 threadPoolKey,同时使用 threadPoolKey 来对 command 进行线程池隔离的划分。
10.参考
-
《深入理解 Spring Cloud 与微服务架构》 方志朋
-
《300分钟搞懂 Spring Cloud》尹吉欢