共计 3889 个字符,预计需要花费 10 分钟才能阅读完成。
一、资源隔离
分布式环境中我们不能确保调用的依赖服务不发生问题。诸如基础设施服务器宕机(数据库、缓存),外部依赖服务挂掉,网络抖动引起的连接超时。当这些情况发生时,大量请求迅速涌到链路上的故障节点,就会像繁忙的高速公路上发生交通事故短时间内就会造成交通拥堵,直至瘫痪。
不能让局部服务的故障不断扩散蔓延导致全局系统不可用,造成重大生产事故,所以要对外部依赖进行资源隔离,将故障控制在小范围内。
hystrix 两种隔离策略
线程池隔离策略(默认)
创建副线程去调用依赖服务,执行依赖代码的线程与请求线程 (比如 Tomcat 线程) 分离,当副线程请求调用失败、超时等异常情况而阻塞时不会影响到主线程。
可以利用线程可设置超时的特性给通过网络进行的服务间调用设置超时时间。
线程池机制的优缺点
线程池机制的优点:
(1) 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用;
(2) 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用;
(3) 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦;
(4) 如果一个 client 调用配置有问题,线程池的健康状况随时会报告,比如成功 / 失败 / 拒绝 / 超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机;
(5) 如果一个服务本身发生了修改,需要重新调整配置,此时线程池的健康状况也可以随时发现,比如成功 / 失败 / 拒绝 / 超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机;
(6) 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层;
线程池机制的缺点:
(1) 线程池机制最大的缺点就是增加了 cpu 的开销。
信号量隔离策略
用于隔离本地代码,利用信号量限制同时运行的线程数量,不会创建副线程,开销较小(复杂算法、多重循环,时间复杂度高的情况)。
二、限流
hystrix 利用线程池的工作原理来进行限流。
线程池的工作原理
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
corePoolSize
为线程池的基本大小。 -
maximumPoolSize
为线程池最大线程大小。 -
keepAliveTime
和unit
则是线程空闲后的存活时间。 -
workQueue
用于存放任务的阻塞队列。 -
handler
当队列和最大线程池都满了之后的饱和策略。
四种拒绝策略
AbortPolicy:不处理,直接抛出异常。
CallerRunsPolicy:若线程池还没关闭,调用当前所在线程来运行任务,r.run()执行。
DiscardOldestPolicy:LRU 策略,丢弃队列里最近最久不使用的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉,不抛出异常。
三、主要参数配置
@HystrixCommand(commandKey = "getCompanyInfoById",
groupKey = "company-info",
threadPoolKey = "company-info",
fallbackMethod = "fallbackMethod",
threadPoolProperties = {@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
})
- commandKey: 代表一个接口, 如果不配置,默认是 @HystrixCommand 注解修饰的函数的函数名。
- groupKey: 代表一个服务,一个服务可能会暴露多个接口。Hystrix 会根据组来组织和统计命令的告、仪表盘等信息。Hystrix 命令默认的线程划分也是根据命令组来实现。默认情况下,Hystrix 会让相同组名的命令使用同一个线程池,所以我们需要在创建 Hystrix 命令时为其指定命令组来实现默认的线程池划分。
- threadPoolKey: 对线程池进行更细粒度的配置,默认等于 groupKey 的值。如果依赖服务中的某个接口耗时较长,需要单独特殊处理,最好单独用一个线程池,这时候就可以配置 threadpool key。也可以多个服务接口设置同一个 threadPoolKey 构成线程组。
- fallbackMethod:@HystrixCommand 注解修饰的函数的回调函数,@HystrixCommand 修饰的函数必须和这个回调函数定义在同一个类中,因为定义在了同一个类中,所以 fackback method 可以是 public/private 均可。
- 线程池配置:coreSize 表示核心线程数,hystrix 默认是 10;maxQueueSize 表示线程池的最大队列大小;keepAliveTimeMinutes 表示非核心线程空闲时最大存活时间;queueSizeRejectionThreshold:该参数用来为队列设置拒绝阈值。通过该参数,即使队列没有达到最大值也能拒绝请求。
四、降级
服务提前配置备用措施,当故障发生时无缝启用备用方案(或者返回一些默认值),用户无感知,最终目的都是为了提供 7 *24 小时稳定服务,这样对用户来说才是高价值、可信赖的优质服务。
线程 run()抛出异常,超时,线程池或信号量满了,或短路了,都会触发 fallback 机制。
五、熔断
当请求调用失败率达到阀值自动触发降级(如因网络故障 / 超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
工作流程:
- 如果经过断路器的流量超过了一定的阈值,HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()配置,默认 20。比如,要求在 10s 内,经过短路器的流量必须达到 20 个才会去判断要不要短路;
- 如果断路器统计到的异常调用的占比超过了一定的阈值,HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()配置。如果达到了上面的要求,比如说在 10s 内,经过短路器的请求达到了 30 个;同时其中异常的访问数量,占到了一定的比例(默认 50%),比如 60% 的请求都是异常(报错,timeout,reject),会开启短路;
- 然后断路器从 close 状态转换到 open 状态;
- 断路器打开的时候,所有经过该断路器的请求全部被短路,不调用后端服务,直接走 fallback 降级逻辑;
- 经过了一段时间之后,HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()配置,断路器会 half-open 状态,让一条请求经过短路器,看能不能正常调用。如果调用成功了,那么断路器就自动恢复,转到 close 状态。
六、监控
根据监控结果可以看出合理的超时时长配置和计算出服务集群部署的规模大小。
计算公式:
服务集群部署的规模大小 = 服务在健康状态时每秒支撑的最大请求数 * 第 99 百分位延迟时间(以秒为单位)+ 少量用于缓冲的额外线程
举个例子:某个服务需要支撑的 QPS 为 1000,即每秒需要处理 1000 个请求。假设通过统计得到百分之 99 的服务耗时为 100ms,那一个线程 1 秒内能处理 10 个请求,一台机器的线程池大小一般就使用默认配置为 10,即一台机器 1 秒内能处理 10 * 10 = 100 个请求,最后得出该服务总共需要部署 1000/100 = 10 台机器。
七、请求缓存、合并
请求缓存
在一次请求中,如果有多个 command,参数都是一样的,调用的接口也是一样的,其实结果可以认为也是一样的。这个时候,可以让第一次 command 执行返回的结果,被缓存在内存中,然后在这个请求上下文中,后续的其他对这个依赖的调用全部从内存中取用缓存结果就可以了。
https://blog.csdn.net/zhuchua…
请求合并
高并发的场景下,将一个时间窗口内多个相同请求合并成一个请求,较少网络连接次数。
http://blog.didispace.com/spr…
八、开发中遇到的问题
默认情况下,hystrix 不会将父线程的上下文传播到有 hystrix 命令管理的线程中。例如,在默认情况下,对被父线程调用并由 @HystrixCommand 保护的方法而言,在父线程中设置为 ThreadLocal 的值是不会自动传递到子线程的。实际场景中的例子,调用通用 UserUtil 工具类从请求头获取不到 userId, jwt 串, 租户 id。
两种方法:
(1) 手动设置
手动从父线程 ThreadLocal 中取出需要的 key-value 设置到子线程中。
(2) 代码配置
自定义 HystrixConcurrencyStrategy
https://www.cnblogs.com/duanx…