分布式系统中常常会出现某个基础服务不可用形成整个系统不可用的状况,这种现象被称为服务雪崩效应。为了应对服务雪崩,一种常见的作法是手动服务降级。而 Hystrix 的出现,给咱们提供了另外一种选择。git
Hystrix [hɪst’rɪks] 的中文含义是 “豪猪”,豪猪周身长满了刺,能保护本身不受天敌的伤害,表明了一种防护机制,这与 Hystrix 自己的功能不谋而合,所以 Netflix 团队将该框架命名为 Hystrix,并使用了对应的卡通形象作做为 logo。github
服务雪崩效应
定义
服务雪崩效应是一种因 服务提供者 的不可用致使 服务调用者 的不可用,并将不可用 逐渐放大 的过程。若是所示:
上图中,A 为服务提供者,B 为 A 的服务调用者,C 和 D 是 B 的服务调用者。当 A 的不可用,引发 B 的不可用,并将不可用逐渐放大 C 和 D 时,服务雪崩就造成了。spring
造成的缘由
我把服务雪崩的参与者简化为 服务提供者 和 服务调用者,并将服务雪崩产生的过程分为如下三个阶段来分析造成的缘由:segmentfault
- 服务提供者不可用
- 重试加大流量
- 服务调用者不可用
服务雪崩的每一个阶段均可能由不一样的缘由形成,好比形成 服务不可用 的缘由有:后端
- 硬件故障
- 程序 Bug
- 缓存击穿
- 用户大量请求
硬件故障可能为硬件损坏形成的服务器主机宕机,网络硬件故障形成的服务提供者的不可访问。
缓存击穿通常发生在缓存应用重启,全部缓存被清空时,以及短期内大量缓存失效时。大量的缓存不命中,使请求直击后端,形成服务提供者超负荷运行,引发服务不可用。
在秒杀和大促开始前,若是准备不充分,用户发起大量请求也会形成服务提供者的不可用。缓存
而造成 重试加大流量 的缘由有:服务器
- 用户重试
- 代码逻辑重试
在服务提供者不可用后,用户因为忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单。
服务调用端的会存在大量服务异常后的重试逻辑。
这些重试都会进一步加大请求流量。网络
最后, 服务调用者不可用 产生的主要缘由是:架构
- 同步等待形成的资源耗尽
当服务调用者使用 同步调用 时,会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,因而服务雪崩效应产生了。
应对策略
针对形成服务雪崩的不一样缘由,可使用不一样的应对策略:
- 流量控制
- 改进缓存模式
- 服务自动扩容
- 服务调用者降级服务
流量控制 的具体措施包括:
- 网关限流
- 用户交互限流
- 关闭重试
由于 Nginx 的高性能,目前一线互联网公司大量采用 Nginx+Lua 的网关进行流量控制,由此而来的 OpenResty 也愈来愈热门。
用户交互限流的具体措施有: 1. 采用加载动画,提升用户的忍耐等待时间。2. 提交按钮添增强制等待时间机制。
改进缓存模式 的措施包括:
- 缓存预加载
- 同步改成异步刷新
服务自动扩容 的措施主要有:
- AWS 的 auto scaling
服务调用者降级服务 的措施包括:
- 资源隔离
- 对依赖服务进行分类
- 不可用服务的调用快速失败
资源隔离主要是对调用服务的线程池进行隔离。
咱们根据具体业务,将依赖服务分为: 强依赖和若依赖。强依赖服务不可用会致使当前业务停止,而弱依赖服务的不可用不会致使当前业务的停止。
不可用服务的调用快速失败通常经过 超时机制, 熔断器 和熔断后的 降级方法 来实现。
使用 Hystrix 预防服务雪崩
服务降级(Fallback)
对于查询操做,咱们能够实现一个 fallback 方法,当请求后端服务出现异常的时候,可使用 fallback 方法返回的值。fallback 方法的返回值通常是设置的默认值或者来自缓存。
资源隔离
货船为了进行防止漏水和火灾的扩散,会将货仓分隔为多个,以下图所示:
这种资源隔离减小风险的方式被称为: Bulkheads(舱壁隔离模式)。
Hystrix 将一样的模式运用到了服务调用者上。
在 Hystrix 中,主要经过线程池来实现资源隔离。一般在使用的时候咱们会根据调用的远程服务划分出多个线程池。例如调用产品服务的 Command 放入 A 线程池,调用帐户服务的 Command 放入 B 线程池。这样作的主要优势是运行环境被隔离开了。这样就算调用服务的代码存在 bug 或者因为其余缘由致使本身所在线程池被耗尽时,不会对系统的其余服务形成影响。
经过对依赖服务的线程池隔离实现,能够带来以下优点:
- 应用自身获得彻底的保护,不会受不可控的依赖服务影响。即使给依赖服务分配的线程池被填满,也不会影响应用自身的额其他部分。
- 能够有效的下降接入新服务的风险。若是新服务接入后运行不稳定或存在问题,彻底不会影响到应用其余的请求。
- 当依赖的服务从失效恢复正常后,它的线程池会被清理而且可以立刻恢复健康的服务,相比之下容器级别的清理恢复速度要慢得多。
- 当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(经过失败次数、延迟、超时、拒绝等指标的增长状况)。同时,咱们能够在不影响应用功能的状况下经过实时的动态属性刷新(后续会经过 Spring Cloud Config 与 Spring Cloud Bus 的联合使用来介绍)来处理它。
- 当依赖的服务因实现机制调整等缘由形成其性能出现很大变化的时候,此时线程池的监控指标信息会反映出这样的变化。同时,咱们也能够经过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。
- 除了上面经过线程池隔离服务发挥的优势以外,每一个专有线程池都提供了内置的并发实现,能够利用它为同步的依赖服务构建异步的访问。
总之,经过对依赖服务实现线程池隔离,让咱们的应用更加健壮,不会由于个别依赖服务出现问题而引发非相关服务的异常。同时,也使得咱们的应用变得更加灵活,能够在不中止服务的状况下,配合动态配置刷新实现性能配置上的调整。
虽然线程池隔离的方案带了如此多的好处,可是不少使用者可能会担忧为每个依赖服务都分配一个线程池是否会过多地增长系统的负载和开销。对于这一点,使用者不用过于担忧,由于这些顾虑也是大部分工程师们会考虑到的,Netflix 在设计 Hystrix 的时候,认为线程池上的开销相对于隔离所带来的好处是没法比拟的。同时,Netflix 也针对线程池的开销作了相关的测试,以证实和打消 Hystrix 实现对性能影响的顾虑。
下图是 Netflix Hystrix 官方提供的一个 Hystrix 命令的性能监控,该命令以每秒 60 个请求的速度(QPS)向一个单服务实例进行访问,该服务实例每秒运行的线程数峰值为 350 个。
从图中的统计咱们能够看到,使用线程池隔离与不使用线程池隔离的耗时差别以下表所示:
比较状况 | 未使用线程池隔离 | 使用了线程池隔离 | 耗时差距 |
---|---|---|---|
中位数 | 2ms | 2ms | 2ms |
90 百分位 | 5ms | 8ms | 3ms |
99 百分位 | 28ms | 37ms | 9ms |
在 99% 的状况下,使用线程池隔离的延迟有 9ms,对于大多数需求来讲这样的消耗是微乎其微的,更况且为系统在稳定性和灵活性上所带来的巨大提高。虽然对于大部分的请求咱们能够忽略线程池的额外开销,而对于小部分延迟自己就很是小的请求(可能只须要 1ms),那么 9ms 的延迟开销仍是很是昂贵的。实际上 Hystrix 也为此设计了另外的一个解决方案:信号量(Semaphores)。
Hystrix 中除了使用线程池以外,还可使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,可是它不能设置超时和实现异步访问。因此,只有在依赖服务是足够可靠的状况下才使用信号量。在 HystrixCommand 和 HystrixObservableCommand 中 2 处支持信号量的使用:
- 命令执行:若是隔离策略参数 execution.isolation.strategy 设置为 SEMAPHORE,Hystrix 会使用信号量替代线程池来控制依赖服务的并发控制。
- 降级逻辑:当 Hystrix 尝试降级逻辑时候,它会在调用线程中使用信号量。
信号量的默认值为 10,咱们也能够经过动态刷新配置的方式来控制并发线程的数量。对于信号量大小的估算方法与线程池并发度的估算相似。仅访问内存数据的请求通常耗时在 1ms 之内,性能能够达到 5000rps,这样级别的请求咱们能够将信号量设置为 1 或者 2,咱们能够按此标准并根据实际请求耗时来设置信号量。
断路器模式
断路器模式源于 Martin Fowler 的 Circuit Breaker 一文。“断路器” 自己是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器” 可以及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。
在分布式架构中,断路器模式的做用也是相似的,当某个服务单元发生故障(相似用电器发生短路)以后,经过断路器的故障监控(相似熔断保险丝),直接切断原来的主逻辑调用。可是,在 Hystrix 中的断路器除了切断主逻辑的功能以外,还有更复杂的逻辑,下面咱们来看看它更为深层次的处理逻辑。
断路器开关相互转换的逻辑以下图:
当 Hystrix Command 请求后端服务失败数量超过必定阈值,断路器会切换到开路状态 (Open)。这时全部请求会直接失败而不会发送到后端服务。
这个阈值涉及到三个重要参数:快照时间窗、请求总数下限、错误百分比下限。这个参数的做用分别是:
快照时间窗:断路器肯定是否打开须要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒。
请求总数下限:在快照时间窗内,必须知足请求总数下限才有资格进行熔断。默认为 20,意味着在 10 秒内,若是该 Hystrix Command 的调用此时不足 20 次,即时全部的请求都超时或其余缘由失败,断路器都不会打开。
错误百分比下限:当请求总数在快照时间窗内超过了下限,好比发生了 30 次调用,若是在这 30 次调用中,有 16 次发生了超时异常,也就是超过 50% 的错误百分比,在默认设定 50% 下限状况下,这时候就会将断路器打开。
断路器保持在开路状态一段时间后 (默认 5 秒),自动切换到半开路状态 (HALF-OPEN)。这时会判断下一次请求的返回状况,若是请求成功,断路器切回闭路状态 (CLOSED),不然从新切换到开路状态 (OPEN)。
使用 Feign Hystrix
由于熔断只是做用在服务调用这一端,所以咱们根据上一篇的示例代码只须要改动 eureka-consumer-feign 项目相关代码就能够。
POM 配置
由于 Feign 中已经依赖了 Hystrix 因此在 maven 配置上不用作任何改动。
配置文件
在原来的 application.yml 配置的基础上修改