一. 服务熔断的原因
1. 雪崩效应
长链路调用过程中, A->B->C.... 假设链路上 C 出现了调用缓慢->B也会延迟->A也会延迟,堵住的 A 请求会消耗占用系统的线程、IO 等资源. 当对 A 服务的请求越来越多,占用的计算机资源越来越多的时候,会导致系统瓶颈出现,造成其他的请求同样不可用,最终导致业务系统崩溃,这种现象称为雪崩效应。

2. 熔断机制 - 用于解决雪崩效应的问题
当请求失败(超时或者其他异常)次数超过预设值时,熔断器自动打开.
这时所有经过这个熔断器的请求都会直接返回失败(不会堵住A请求),并没有真正到达所依赖的服务上。
二. SpringCloud 使用 Hystrix 来实现服务熔断与降级
1. 实现原理
Hystrix 是一种开关装置,类似于熔断保险丝。在消费者端安装一个 Hystrix 熔断器,当 Hystrix 监控到某个服务发生故障后熔断器会开启,将此服务访问链路断开。
不过 Hystrix 并不会将该服务的消费者阻塞,或向消费者抛出异常,而是向消费者返回一个符合预期的备选响应(FallBack)。 通过 Hystrix 的熔断与降级功能,避免了服务雪崩的发生,同时也考虑到了用户体验。故 Hystrix 是系统的一种防御机制。
2.实现方式: fallbackMethod服务降级 和 fallbackFactory服务降级
fallbackMethod服务降级:
指定该方法要使用服务降级。即当前处理器方法在运行过程中若发生异常,无法给客户端正常响应时,就会调用fallbackMethod指定的方法
// fallbackMethod 指定回调的方法
// HystrixProperty 添加专属于本方法的回调参数
// execution.isolation.thread.timeoutInMilliseconds = 2000 本方法的熔断时长为2s
@HystrixCommand(fallbackMethod = "getHystrixHandler",
commandProperties = @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="2000"))
@GetMapping("/get/{id}")
public Depart getByIdHandler(@PathVariable("id") int id) {
return fegin.getDepartById(id);
}
// 定义服务降级方法,即响应给客户端的备选方案
public Depart getHystrixHandler(@PathVariable("id") int id) {
return new Depart(id," ");
}
fallbackFactory服务降级
1. 实现FallbackFactory 泛型工厂
// 定义 FallbackFactory<T> 的实现类 , 实现 T create(Throwable var1) 方法, 在方法体内实现fallback方法
@Component
public class DepartFallbackFactory implements FallbackFactory<XxxService> {
@Override
public XxxService create(Throwable throwable) {
return new XxxService() {......}
}
}
// 在Fegin接口中指定 fallbackFactory用于指定当前Feign接口的服务降级类
@FeignClient(value = "提供者服务名", fallbackFactory = XxxFallbackFactory.class)
2. 面向接口实现本地失败回调
//面向接口实现失败回调
@Component
public class XxxFallback implements XxxService {
...
}
//接口上指定使用XxxFallback实现类作为失败回调的
@FeignClient(value = "提供者服务名", fallback = XxxFallback.class)
public interface XxxService {
....
}
3. fallbackMethod降级 和 fallbackFactory降级 的优先级
取timeoutInMilliseconds 的最小值生效:
方法级别用@HystrixProperty 独立标记熔断时间和全局配置中hystrix.command.default 的全局通用熔断时间fallbackFactory 级别比 fallbackMethod 高
4. Hystrix 组件的独立性
Hystrix 在并非专用于Fegin 调用的返回失败场景中, 在普通的方法中使用依旧可以生效
@HystrixCommand(fallbackMethod = "getHystrixHandler")
@GetMapping("/get/{id}")
public Depart getByIdHandler(@PathVariable("id") int id) {
return service.get(id);
}
// 定义服务降级方法,即响应给客户端的备选方案
public Depart getHystrixHandler(@PathVariable("id") int id) {
return new Depart(id," ");
}
三. 执行隔离策略
1. 何为执行隔离?
对依赖的请求数量进行限制的这种机制,称为执行隔离。
执行隔离策略有两大作用:防止服务熔断,防止服务雪崩
2. 隔离的类型
线程隔离 thread (默认):系统会创建一个依赖线程池,为每个依赖请求分配一个独立的线程,而每个依赖所拥有的线程数量是有上限的。当对该依赖的调用 请求数量达到上限后再有请求,则该请求阻塞。所以对某依赖的并发量取决于为该依赖 所分配的线程数量。
适用于: 耗时长, 高吞吐量, 例如读数据库, 大计算.
信号量隔离:对依赖的调用所使用的线程仍为请求线程,即不会为依赖请求再新创建新的线程。但系统会为每种依赖分配一定数量的信号量,而每个依赖请求分配一个信号号。当对该依赖的调用请求数量达到上限后再有请求,则该请求阻塞。所以对某依赖的并发 量取决于为该依赖所分配的信号数量。
适用于:耗时短, 低延迟, 例如高频读取缓存
| 线程隔离 | 信号量隔离 | |
|---|---|---|
| 线程 | 请求线程和调用线程是两个分开的线程 | 请求线程和调用线程是同一个线程 |
| 开销 | 排队等待可用线程, 调用上线文CPU切换线程 | 无序切换线程 |
| 异步 | 支持 | 不支持 |
| 并发支持 | 支持(线程池限制) | 支持(设定的信号量限制) |
| 传递header | 无法传递 Http Header | 可传递 Http Header |
| 支持超时 | 可支持超时 | 不支持超时 |
3. 设置
hystrix.command.default.execution.isolation.strategy=thread hystrix.command.default.execution.isolation.strategy=semaphore
执行隔离其它属性
# 若采用线程执行隔离策略, 开启线程的执行时间超时
hystrix.command.default.execution.timeout.enabled = (true)
# 若采用线程执行隔离策略, 执行线程超时时限长度
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = (1000ms)
# 当线程执行超时时是否中断线程的执行
hystrix.command.default.execution.isolation.thread.interruptOnTimeout = (true)
# 请求取消,当前执行线程是否结束
hystrix.command.default.execution.isolation.thread.interruptOnCancel = (false)
# 若采用信号量执行隔离策略,则可通过以下属性修改信号量的数量
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests =
熔断器属性
# 当前应用是否开启熔断器功能
hystrix.command.default.circuitBreaker.enabled = (true)
# 在窗口期内(一个时间段内) 收到的请求数量超过该设置的数量后,将开启熔断器
hystrix.command.default.circuitBreaker.requestVolumeThreshold = (20)
# 窗口期时间
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds = (5000ms)
# 当请求的错误率高于该百分比时,开启熔断器。
hystrix.command.default.circuitBreaker.errorThresholdPercentage = (50 即为50%)
# 强制开启熔断器, 熔断所有请求
hystrix.command.default.circuitBreaker.forceOpen = (false)
# 强制关闭所有熔断器, 通过所有请求
hystrix.command.default.circuitBreaker.forceClosed = (false)
四. 熔断时间设置
对于最终触发熔断超时时长的原因 , 除了 hystrix 的 timeoutInMilliseconds 自生有关 如果 ribbon 的 ReadTimeout 超时也会抛出读超时, 此时也会触发熔断. 如果有Zuul 设置超时时长原理类似
feign:
client:
config:
default:
connectTimeout: 6000 # 指定Feign客户端连接提供者的超时时限
readTimeout: 6000 # 指定Feign客户端连接上提供者后,向提供者进行提交请求,从提交时刻开始,到接收到响应,这个时段的超时时限
ribbon:
ReadTimeout: 5000 #负载均衡超时时间,默认值5000
ConnectTimeout: 2000 #ribbon请求连接的超时时间,默认值2000
hystrix:
command: # 全局配置
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000 # 断路器超时时间,默认1000ms
XxxApi#apiMethod(String,Integer,Boolean): # 特定接口配置局配置 (括号内填写参数类型)
execution:
timeout:
enabled: true # 开启线程的执行时间超时
isolation:
thread:
timeoutInMilliseconds: 6000 # 断路器超时时间,默认1000ms
ribbon 中的 connectTimeout连接时长 和 ReadTimeout读取时长, ribbon: 会被 feign:client 配置覆盖掉
hystrix 中的 timeoutInMilliseconds熔断时间 优先级: 特定接口>全局通用, 特定配置可以在 yml或者通过@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="4000") 进行配置
假设此接口开启了hystrix熔断器的前提下, ReadTimeout 和 timeoutInMilliseconds 取时间短的进行读超时, 读超时会触发熔断
开启熔断器的条件
启动标签包含 @SpringCloudApplication 或者 @EnableCircuitBreaker 支持熔断器 feign:hystrix:enabled: true(默认false不开启 ) OpenFeign全局接口开启熔断器, 使得fegin中的fallback标签生效 hystrix:command:default:execution:timeout:enabled: true 使得@HystrixCommand(fallbackMethod = "回退方法")生效 ribbon还有MaxAutoRetries对当前实例的重试次数,MaxAutoRetriesNextServer对切换实例的重试次数, 如果ribbon的ReadTimeout超时,或者ConnectTimeout连接超时,会进行重试操作
通常 timeoutInMilliseconds 需要配置的比ReadTimeout长,ReadTimeout比ConnectTimeout长,否则还未重试,就熔断了
为了确保重试机制的正常运作,理论上(以实际情况为准)建议hystrix的超时时间为:(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout




