开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
hystrix是用来做熔断降级的,直白说就是你出了问题(超时、失败、宕机等)不能影 防止服务雪崩的解决方案,一般有降级、熔断、缓存、合并和隔离,这里主要介绍降级和熔断。熔断可以直接返回兜底数据、为服务资源分配固定的资源或者直接抛出一个异常(如系统繁忙等)。
熔断也是降级的一种方式。降级分主动和被动:
- 主动降级:直接关闭非关键应用;
- 被动降级:熔断降级、限流降级
用途
- 对通过第三方客户端库访问的依赖项(通常通过网络)造成的延迟和故障提供保护和控制
- 在复杂的分布式系统中停止级联故障
- 快速失败,快速恢复
- 在可能的情况下后退并优雅地降级
- 启用近乎实时的监视、警报和操作控制
原则
- 防止任何单一依赖项耗尽所有容器(如Tomcat)用户线程。
- 快速卸载和故障,而不是排队。
- 在可行的情况下提供后备方案以保护用户不发生故障。
- 使用隔离技术(例如隔板、泳道和断路器模式)来限制任何一个依赖项的影响。
- 通过低延迟传播配置更改和支持Hystrix大多数方面的动态属性更改来优化恢复时间,这允许您使用低延迟反馈循环进行实时操作修改。
- 在整个依赖客户机执行中防止失败,而不仅仅是在网络通信中。
原理
- 将所有对外部系统(或“依赖项”)的调用包装在HystrixCommand或HystrixObservableCommand对象中,该对象通常在单独的线程中执行(这是命令模式的一个示例)
- 超时调用的时间超过您定义的阈值。有一个默认值,但对于大多数依赖项,您可以通过“属性”的方式自定义设置这些超时,以便它们略高于每个依赖项的99.5%的性能测量值
- 为每个依赖项维护一个小的线程池(或信号量);如果它已满,发送到该依赖项的请求将立即被拒绝,而不是排队
- 测量成功、失败(客户端抛出的异常)、超时和线程拒绝。
- 触发断路器,在一段时间内停止对特定服务的所有请求,如果服务的错误百分比超过阈值,可以手动或自动
- 在请求失败、被拒绝、超时或短路时执行回退逻辑
- 近乎实时地监视度量和配置更改
当您使用Hystrix包装每个底层依赖项时,上面图表中所示的体系结构将更改为类似于下面的图表。
每个依赖都是相互隔离的,在延迟发生时它可以饱和的资源中受到限制,并在回退逻辑中覆盖,以决定在依赖中发生任何类型的故障时做出什么响应
向服务发出请求时,发生了什么?
详细过程:
- 构建
HystrixCommand或HystrixObservableCommand,可以根据需要添加多个参数到构造函数:
HystrixCommand command = new HystrixCommand(arg1, arg2);
// or
HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
- 执行命令 有四种方式执行:
execute():阻塞,然后返回从依赖项接收到的单个响应(或在发生错误时抛出异常)queue():返回一个Future,可以从依赖项中获取单个响应observe():订阅来自依赖的响应的Observable,并返回一个复制源Observable的ObservabletoObservable():返回一个Observable,当你订阅它时,它会执行Hystrix命令并发出它的响应
K value = command.execute();
Future<K> fValue = command.queue();
Observable<K> ohValue = command.observe(); //hot observable
Observable<K> ocValue = command.toObservable(); //cold observable
- 是否使用缓存 如果该命令启用了请求缓存,并且请求的响应在缓存中可用,那么这个缓存的响应将立即以Observable的形式返回。(请参阅下面的“请求缓存”。)
- 断路器是否打开 当你执行这个命令时,Hystrix会检查断路器,看电路是否打开。
如果电路是打开的(或“tripped”),那么Hystrix将不会执行命令,而是将流路由到(8)Get the Fallback。
如果电路被关闭,那么流继续到(5)检查是否有可用的容量来运行命令。
- 线程池/队列/信号量是否已满
如果与该命令相关的线程池和队列(或信号量,如果不是在线程中运行)已满,则Hystrix将不会执行该命令,而是立即将流路由到(8)Get the Fallback。
hystrixobservableccommand .construct()或HystrixCommand.run()
HystrixCommand.run() // returns a single response or throws an exception
HystrixObservableCommand.construct() // returns an Observable that emits the response(s) or sends an onError notification
如果run()或construct()方法超过了命令的超时值,线程将抛出TimeoutException(如果命令本身没有在自己的线程中运行,则另一个计时器线程将抛出TimeoutException)。在这种情况下,Hystrix将响应路由到8。获取Fallback,如果run()或construct()方法没有取消/中断,它将丢弃最终返回值。
请注意,没有办法强制潜在线程停止工作——Hystrix在JVM上做的最好的事情是抛出InterruptedException。 如果Hystrix包装的工作不处理InterruptedExceptions, Hystrix线程池中的线程将继续它的工作,尽管客户端已经收到了一个TimeoutException,这种行为会使Hystrix线程池饱和,尽管负载是“正确地释放”的。
大多数Java HTTP客户端库不解释InterruptedExceptions。因此,请确保在HTTP客户端上正确配置连接和读/写超时
如果该命令没有抛出任何异常并且返回响应,则Hystrix在执行一些日志记录和指标报告后返回此响应。
- 计算断路器状态
Hystrix向断路器报告成功、失败、拒绝和超时,断路器维护一组计算统计数据的滚动计数器。
它使用这些统计信息来确定电路何时应该“跳闸”,这时它将使任何后续请求短路,直到恢复期结束,在恢复期结束后,它将在首先检查某些健康检查后再次关闭电路。
- 获取托底返回(fallback)
当发生以下情况时,会执行托底返回:
- construct()和run() 执行抛出异常
- 断路器打开的情况
- 当命令的线程池和队列或信号量达到容量时
- 当命令已超过其超时长度时
降级策略
- 接口响应超时触发降级
- 服务熔断触发降级
- 资源隔离触发降级
响应超时降级处理
基于Openfeign的客户端方式
添加依赖
<!--openfeign中已经依赖了hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
配置
feign:
hystrix:
enabled: true
配置包扫描
在启动类上添加@EnableFeignClients启动Feign扫描 编写接口代理类:
@FeignClient(value = "demo-user",fallback = UserServiceApiFallback.class)
public interface UserServiceApiProxy {
@GetMapping("user/info")
String info(String uid);
@GetMapping("user/list")
List<String> list();
}
编写fallback类:
@Component
public class UserServiceApiFallback implements UserServiceApiProxy {
@Override
public String info(String uid) {
return "超时了老弟";
}
@Override
public List<String> list() {
return Arrays.asList("超时了老兄!");
}
}
基于Hystrix客户端方式
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置包扫描
在启动类上添加@EnableFeignClients启动Feign 在启动类上添加@EnableHystrix,启动Hystrix 编写接口代理类:
//这里不需要fallback属性,下面在调用该接口的地方采用HystrixCommand方式
@FeignClient(value = "demo-user")
public interface UserServiceApiProxy {
@GetMapping("user/info")
String info(String uid);
@GetMapping("user/list")
List<String> list();
}
编写业务逻辑类:
@RestController
@RequestMapping("order")
public class OrderServiceApi {
//这是要通过openfeign调用的其他服务代理接口(就是上面那个接口)
@Autowired
UserServiceApiProxy userServiceApiProxy;
@GetMapping("info")
public String info(String oid){
return "order info by oid";
}
@GetMapping("list")
@HystrixCommand(fallbackMethod = "err",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") // 超时时间
}) //当list中调用服务超时时,会立即执行err函数(代码中的下一个函数)
public List list(){
List list= this.userServiceApiProxy.list();
return Arrays.asList("手机","电脑","书籍","汽车",list);
}
//当降级后被调用的err函数
public List err(){
return Arrays.asList("超时了大哥");
}
}
上述两种写法其实效果一样,第二种把降级代码和业务代码混在一起是不好的实践,但是第二种可以更精细化配置,在HystrixCommand中可以使用HystrixProperties配置什么情况下允许执行降级。
服务熔断降级处理
//这个服务熔断的意思是10s内请求10次失败率达到60%就熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
// 请求次数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
// 时间窗口期,熔断后10s内,正常的调用也只会返回拖地数据“系统繁忙”,10s后才能正常访问
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
// 失败率达到多少后跳闸,这是50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50")
})
@GetMapping("list")
public String list(@RequestParam("name") String name){return "bk";}
// 参数必须和上面list函数一致
public String paymentCircuitBreaker_fallback(String name){
return "系统繁忙"; //返回兜底数据
}
// 在启动类,添加 @EnableCircuitBreaker 启用
在同一个滑动窗口10秒内,如果有20次请求,且请求失败率在50%以上,会打开断路器,熔断,后续请求不再处理。
一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,如果调用仍然失败,则回到熔断状态,如果调用成功,则回到断路器闭合状态;
资源隔离降级处理
为了防止一个服务占用资源导致整个平台宕机,会采用隔离手段:
- 平台隔离
- 部署隔离
- 业务隔离
- 服务隔离
- 资源隔离
feign:
hystrix:
enabled: true
hystrix:
command:
default : #全局配置 feignClient#method(params)
execution:
timeout:
enable: true
isolation: # execution.isolation.thread.timeoutInMilliseconds配置超时时间,设为3s
thread:
timeoutInMilliseconds: 3000
# 除了default默认设置,也可以为每个方法进行个性化设置
OrderServiceFeignClient#getAllOrders(): #针对具体某个方法进行配置
execution:
isolation:
strategy: SEMAPHORE # execution.isolation.strategy 隔离策略 信号量; com.netflix.hystrix.HystrixCommandProperties.default_executionIsolationStrategy 枚举值
semaphore:
maxConcurrentRequests: 10 # execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数
OrderServiceFeignClient#insertOrder() :
execution:
isolation:
strategy: THREAD # execution.isolation.strategy 隔离策略 线程池
threadpool:
shen-order-service:
coreSize : 2
maxQueueSize : 1000 # 队列长度
queueSizeRejectionThreshold : 800 # 超过800开始拒绝
ribbon: # 因为openFeign内部是使用ribbon进行负载均衡调用,需要设置ribbon的超时时间
ReadTimeout: 8000
ConnectTimeout : 8000