(新排版)转自:https://blog.csdn.net/ruihin/article/details/77579794
一、Hystrix说明
1、服务雪崩效应
是一种因服务提供者的不可用导致服务调用者的不可用,并将不可用 逐渐放大 的过程。比如: A为服务提供者, B为A的服务调用者, C和D是B的服务调用者. 当A的不可用,引起B的不可用,并将不可用逐渐放大C和D时, 服务雪崩就形成了
2、雪崩原因:
服务提供者不可用
1) 硬件故障
硬件损坏造成的服务器主机宕机。(网络硬件故障造成的服务提供者的不可访问)
2) 程序bug
3) 缓存击穿
缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用
4) 用户的大量请求
在秒杀和大促开始前,如果准备不充分,用户发起大量请求造成服务提供者的不可用
重试加大流量
服务调用者不可用
同步等待造成的资源耗尽:使用同步调用时, 会产生大量的等待线程占用系统资源,一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 造成服务雪崩效应产生。
3.雪崩应对策略
流量控制
1) 网关限流
因为Nginx的高性能, 目前一线互联网公司大量采用Nginx+Lua的网关进行流量控制, 由此而来的OpenResty也越来越热门。
2) 用户交互限流
具体措施:a、采用加载动画,提高用户的忍耐等待时间。b、提交按钮添加强制等待时间机制。
3) 关闭重试
改进缓存模式
1) 缓存预加载
2) 同步改为异步刷新
服务自动扩容
AWS的auto scaling
服务调用者降级服务
1) 资源隔离
主要是对调用服务的线程池进行隔离
2) 对依赖服务进行分类
依赖服务分为: 强依赖和弱依赖
强依赖服务不可用会导致当前业务中止;而弱依赖服务的不可用不会导致当前业务的中止。
3) 不可用服务的调用快速失败
一般通过超时机制, 熔断器和熔断后的降级方法来实现。
4.解决方案:
使用Hystrix预防服务雪崩,Netflix的Hystrix 是一个帮助解决分布式系统交互时超时处理和容错的类库,它同样拥有保护系统的能力。
Hystrix的设计原则包括:资源隔离、熔断器、命令模式。
二、Hystrix解决方式
Hystrix:通过服务熔断(也可以称为断路)、降级、限流(隔离)、异步RPC等手段控制依赖服务的延迟与失败。
1. Circuit Breaker :熔断器
熔断只是作用在服务调用这一端,只需改consumer端。
熔断器开关相互转换的逻辑
a.服务的健康状况= 请求失败数/ 请求总数.
b.熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的
b1.关闭时, 请求被允许通过熔断器. 如果当前健康状况高于设定阈值, 开关继续保持关闭. 如果当前健康状况低于设定阈值, 开关则切换为打开状态
b2.打开状态, 经过一段时间后, 熔断器会自动进入半开状态, 这时熔断器只允许一个请求通过. 当该请求调用成功时, 熔断器恢复到关闭状态. 若该请求失败, 熔断器继续保持打开状态, 接下来的请求被禁止通过
c.保证服务调用者在调用异常服务时, 快速返回结果, 避免大量的同步等待
d.在一段时间后继续侦测请求执行结果, 提供恢复服务调用的可能
参数设置
a.circuitBreaker.requestVolumeThreshold //滑动窗口的大小,默认为20
b.circuitBreaker.sleepWindowInMilliseconds //过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
c.circuitBreaker.errorThresholdPercentage //错误率,默认50%
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开
2. Downgrade:降级,fallback
1)当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值
3. Isolation:限流(隔离)
1)可在服务端做这个限流逻辑,也可在客户端做
2)采用线程/信号的方式,通过隔离限制依赖的并发量和阻塞扩散
线程隔离:
a1.即将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩
a2.线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器
a3.优点:
a31.使用线程可以完全隔离第三方代码,请求线程可以快速放回
a32.当一个失败的依赖再次变成可用时,线程池将清理,并立即恢复可用,而不是一个长时间的恢复
a33.可以完全模拟异步调用,方便异步编程
a4.缺点:
a41.线程池的主要缺点是它增加了cpu,因为每个命令的执行涉及到排队(默认使用SynchronousQueue避免排队),调度和上下文切换
a42.对使用ThreadLocal等依赖线程状态的代码增加复杂性,需要手动传递和清理线程状态
注:Netflix公司内部认为线程隔离开销足够小,不会造成重大的成本或性能的影响
信号隔离:
b1.用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请)
b2.如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销
b3.信号量的大小可以动态调整, 线程池大小不可以
缺点:不能设置超时和实现异步访问,所以,只有在依赖服务是足够可靠的情况下才使用信号量
3)除了HystrixBadRequestException异常之外,所有从run()方法抛出的异常都算作失败,并触发降级getFallback()和断路器逻辑
4)HystrixBadRequestException用在非法参数或非系统故障异常等不应触发回退逻辑的场景
5)参数解释
CommandKey:依赖命名
1、每个CommandKey代表一个依赖抽象,相同的依赖要使用相同的CommandKey名称
2、依赖隔离的根本就是对相同CommandKey的依赖做隔离.
CommandGroupKey:依赖分组
1、命令分组用于对依赖操作分组,便于统计和汇总
2、CommandGroup是每个命令最少配置的必选参数,在不指定ThreadPoolKey的情况下,字面值用于对不同依赖的线程池/信号区分
ThreadPoolKey:线程池/信号
1、当对同一业务依赖做隔离时使用CommandGroup做区分,但是对同一依赖的不同远程调用如(一个是Redis一个是http),可以使用HystrixThreadPoolKey做隔离区分
2、在业务上都是相同的组,但是需要在资源上做隔离时,可以使用HystrixThreadPoolKey区分
Request-Cache:请求缓存
1、重写getCacheKey方法,实现区分不同请求的逻辑
2、请求缓存可以让(CommandKey/CommandGroup)相同的情况下,直接共享结果,降低依赖调用次数,在高并发和CacheKey碰撞率高场景下可以提升性能
SEMAPHORE:信号量隔离
1、隔离本地代码或可快速返回远程调用(如memcached,redis)可以直接使用信号量隔离,降低线程隔离开销。
4.asynchronous:异步RPC
三、Hystrix metrics:容错计数
1. Metrics
1)Hystrix的Metrics中保存了当前服务的健康状况, 包括服务调用总次数和服务调用失败次数等
2)根据Metrics的计数,熔断器从而能计算出当前服务的调用失败率, 用来和设定的阈值比较从而决定熔断器的状态切换逻辑
version-2.1.5之后的滑动窗口实现,使用RxJava的Observable.window()实现滑动窗口
四、配置参数说明
1. HystrixCommandProperties:HystrixProperty类型
1) Metrics
metricsRollingStatisticalWindowInMilliseconds
统计滚动的时间窗口,默认:5000毫秒(取自circuitBreakerSleepWindowInMilliseconds)
metricsRollingStatisticalWindowBucket
统计窗口的Buckets的数量,默认:10个,每秒一个Buckets统计
metrics.rollingPercentile.enable
是否开启监控统计功能,默认:true
metrics.rollingStats.timeInMilliseconds
metrics.rollingStats.numBuckets
metrics.rollingPercentile.timeInMilliseconds
metrics.rollingPercentile.numBuckets
metrics.rollingPercentile.bucketSize
metrics.healthSnapshot.intervalInMilliseconds
circuitBreaker.requestVolumeThreshold
断路器请求阈值,熔断器在整个统计时间内是否开启的阀值,默认20。也就是在metricsRollingStatisticalWindowInMilliseconds(默认10s)内至少请求20次,熔断器才发挥起作用
Circuit Breaker
circuitBreaker.sleepWindowInMilliseconds
断路器休眠时间,熔断时间窗口,默认:5秒.熔断器中断请求5秒后会进入半打开状态,放下一个请求进来重试,如果该请求成功就关闭熔断器,否则继续等待一个熔断时间窗口
circuitBreaker.enabled
断路器开关,是否启用熔断器,默认true. 启动
circuitBreaker.errorThresholdPercentag
断路器错误请求百分比,默认:50%。当出错率超过50%后熔断器启动
circuitBreaker.forceOpen
断路器强制开启,是否强制开启熔断器阻断所有请求,默认:false,不开启。置为true时,所有请求都将被拒绝,直接到fallback
circuitBreaker.forceClosed
断路器强制关闭,是否允许熔断器忽略错误,默认false, 不开启
2) Execution
execution.isolation.semaphore.maxConcurrentRequests
使用信号量隔离时,命令调用最大的并发数,默认:10
execution.isolation.strategy
使用命令调用隔离方式,默认:采用线程隔离,ExecutionIsolationStrategy.THREAD
execution.isolation.thread.timeoutInMilliseconds
使用线程隔离时,调用超时时间,默认:1秒
executionIsolationThreadPoolKeyOverride
线程池的key,用于决定命令在哪个线程池执行
execution.isolation.thread.interruptOnTimeout
使用线程隔离时,是否对命令执行超时的线程调用中断(Thread.interrupt())操作默认:true
execution.timeout.enabled
3) Fallback
fallback.isolation.semaphore.maxConcurrentRequest
使用信号量隔离时,命令fallback(降级)调用最大的并发数,默认:10
fallback.enabled
是否开启fallback降级策略默认:true
Request Context
requestLogEnabled
是否开启请求日志,默认:true
requestCacheEnabled
是否开启请求缓存,默认:true
2. HystrixCollapserProperties:HystrixProperty类型
maxRequestsInBatch
请求合并是允许的最大请求数,默认: Integer.MAX_VALUE
timerDelayInMilliseconds
批处理过程中每个命令延迟的时间,默认:10毫秒
requestCache.enabled
批处理过程中是否开启请求缓存,默认:开启
3. HystrixThreadPoolProperties:
corePoolSize
配置线程池大小,默认值10个. 建议值:请求高峰时99.5%的平均响应时间+ 向上预留一些即可
maxQueueSize
配置线程值等待队列长度,默认值:-1,建议值:-1,表示不等待直接拒绝,测试表明线程池使用直接决绝策略+ 合适大小的非回缩线程池效率最高.所以不建议修改此值。当使用非回缩线程池时,queueSizeRejectionThreshold,keepAliveTimeMinutes参数无效
queueSizeRejectionThreshold
队列大小拒绝阈值
keepAliveTimeMinutes
metrics.rollingStats.timeInMilliseconds
metrics.rollingStats.numBuckets
五、Spring cloud hystrix示例
1. 熔断
只是作用在服务调用这一端,因此只需要改动consumer项目相关代码
1) pom.xml
Feign中已经依赖了Hystrix,所以在maven配置上不用做任何改动
2) application.properties:
# Hystrix配置:Feign中已经依赖了Hystrix,所以在maven配置上不用做任何改动
feign:
hystrix:
enabled:true
3) **Appliaction.java启动类:
@EnableFeignClients //开启feigin注解
@EnableCircuitBreaker //开启Hystrix
@EnableDiscoveryClient //开启注册中心
@SpringBootApplication //spring-boot启动
注:还可用@SpringCloudApplication代替,@SpringCloudApplication包括以下注解:@Target({ElementType.TYPE}
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@Bean
@LoadBalanced
publicRestTemplaterestTemplate() {
//用于调用"提供者"的方法
return newRestTemplate();
}
4) controller层:
@GetMapping("/order/{userId}/{orderNo}")
publicStringfindOrder(@PathVariableLong userId,@PathVariableString orderNo)throwsInterruptedException {
returnuserConsumerService.findOrder(userId,orderNo);
}
5) service层:添加fallback属性
StringfindOrder(Long userId,String orderNo)throwsInterruptedException;
6) service impl层:创建回调类
@HystrixCommand(fallbackMethod="findOrderFallback",commandProperties= {
//timeoutInMilliseconds 使用线程隔离时,调用超时时间
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1000")
})
publicStringfindOrder(Long userId,String orderNo)throwsInterruptedException {
Random random =newRandom();
sleepRandom= random.nextInt(2000);
System.out.println("sleepRandom="+sleepRandom);
Thread.sleep(sleepRandom);
ConsumerBeehiveUser user = findById(userId);
if(user !=null) {
returnuser.getUsername() +" 的订单"+ orderNo +" 找到啦!sleepRandom="+sleepRandom;
}
return"用户不存在!sleepRandom="+sleepRandom;
}
publicStringfindOrderFallback(Long userId,String orderNo) {
return"订单查找失败!sleepRandom="+sleepRandom;
}
7) 查看hystrix状态:
a.查看hystrix基本情况:http://localhost:8002/health
b.查看hystrix详情:http://localhost:8002/hystrix.stream
8) 参数说明:
a.快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
b.请求总数下限:在快照时间窗内,必须满足请求总数下限才有资格根据熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用此时不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
c.错误百分比下限:当请求总数在快照时间窗内超过了下限,比如发生了30次调用,如果在这30次调用中,有16次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%下限情况下,这时候就会将断路器打开。
注:fallback是降级处理
2. 隔离:
1) *Application:
@SpringBootApplication
@EnableDiscoveryClient//开启eureka服务
@EnableFeignClients//开启feigin注解
@EnableCircuitBreaker//开启Hystrix @EnableCircuitBreaker或@EnableHystrix
@EnableHystrixDashboard//开启dashboard图形监控
public classConsumerBeehiveApplication {
@Bean
@LoadBalanced
publicRestTemplaterestTemplate() {
//用于调用"提供者"的方法
return newRestTemplate();
}
public static voidmain(String[] args) {
SpringApplication.run(ConsumerBeehiveApplication.class,args);
}
}
2) controller层:
@GetMapping("/testCircuitBreaker/{id}")
publicStringtestCircuitBreaker(@PathVariableintid) {
StringBuilder sb =newStringBuilder();
for(inti =0;i <50;i++) {
String result =userConsumerService.testCircuitBreaker(i);
System.out.println("testCircuitBreaker controller:"+result);
sb.append(result).append("\n");
}
returnsb.toString();
}
3) service层:
StringtestCircuitBreaker(intid);
4) service impl层:
@HystrixCommand(fallbackMethod="testCircuitBreakerFallback",commandProperties= {
//errorThresholdPercentage 断路器错误请求百分比
@HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50")
})
publicStringtestCircuitBreaker(intid) {
if(id %2==0&& id <10) {// 直接返回
return"consumer testCircuitBreaker "+id;
}else{// 无限循环模拟超时
intj =0;
while(true) {
j++;
}
}
}
publicStringtestCircuitBreakerFallback(intid) {
String template =restTemplate.getForObject("http://provider-user/user/testCircuitBreaker/"+id,String.class);
return"fallback:"+template;
}