[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/design/Hystrix-optimization.html
简述
本文说的其实就是
- 合理配置熔断,防止依赖的第三方接口响应过慢导致系统tomcat链接大量阻塞,最终导致系统崩溃的问题
- 顺便,将熔断配置从配置文件中提取出来,动态配置中心中,这样就可以通过动态配置中心来动态配置熔断参数了
- 除此,主要是团队里大家对
单线程并发数
与QPS
概念有些混淆且计算方式了解不多,以及信号量
与线程池
方案选择上有些歧义,需要花了不少时间在会议上让团队达成一致。
背景&分析问题
SISP客服系统、SISP查单系统等系统提供的服务都通过HTTP依赖于大量外部各种接口的响应,
这些接口的响应时间不可控,有时候会出现响应时间过长的情况,这时候如果不做任何处理,那么这些请求就会一直等待,这样就会导致系统的响应时间过长,甚至出现系统崩溃的情况。
双十一期间,SISP客服系统出现了系统崩溃问题,经排查发现tomcat线程池被耗尽,进一步排查是因为所依赖的PIS接口响应慢了
简单来说问题大概长这样:
其实在进入团队前,其实关键服务已经配置了相关的熔断,不过仔细看了下配置:execution.isolation.semaphore.maxConcurrentRequests=100000
这代表着需要同时有100000个线程进入该程序里才有可能触发熔断,在这种接口响应缓慢要死不死的情况下简直形同虚设
而hystrix错误率50%因为时间窗口太短+外部接口可用性也并不是在50%以下,难以触发,故难以触发熔断机制,导致系统崩溃;
以下是系统曾经的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @HystrixCommand( fallbackMethod = "singleBack", commandProperties = { @HystrixProperty(name = "execution.timeout.enabled", value = "false"), // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候, // 当在配置时间窗口内达到此数量19的失败后,进行短路,默认20。 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下, // 如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间.默认:10000 @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000"), // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令, // 如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。默认:5000 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"), @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "100000") })
|
方案论述
看出问题就得出方案了,其实上面问题总结来说就2点:
1.
熔断配置在配置文件中,不方便动态调整(人看着都已经出问题了,还不能手动熔断快速恢复,领导都急疯了hhh)
2.
熔断配置maxConcurrentRequests不合理,导致熔断不起作用
maxConcurrentRequests配置
对于1.
就不做过多叙述了,对于2.
这里与团队成员有点分歧。。。
他们认为maxConcurrentRequests应该配置为接口的并发数。。。实际上,这个配置应该是单个节点最大并发数,而不是接口的并发数,这里花了我不少时间给团队成员解释这个问题。。。有点醉了。。。
这里简单说下,如果是单节点调用,那么maxConcurrentRequests
就是接口的并发数,如果是多节点调用,那么maxConcurrentRequests
就是单个节点的并发数
所以得出QPS和RT的关系:
- 对于单线程:QPS=1000/RT
- 对于多线程:QPS=1000*单节点并发线程数量/RT
- 对于多线程多接点:QPS=1000单节点并发线程数量节点数量/RT
因此,我们可以得出:
1 2 3
| maxConcurrentRequests(单节点线程数) = QPS /节点数/ ( 1000 / 被熔断方法的P99耗时ms ) 即: 27000/128/(1000/20)=4.2QPS
|
所以得出,maxConcurrentRequests配置为5,理论上即可满足需求。
考虑到我们主要是为了解决单节点因为单个服务耗光tomcat容器所有http线程,这个值不需要设置得太严格,只要能保证单节点不会因为单个服务耗光tomcat容器所有http线程即可。所以,我们保守地设置为100
其实查阅官网,maxConcurrentRequests默认是10,并且说对于绝大部分、正常服务,一般来说都不需要修改这个值,他可以很好的满足绝大部分的场景,一定程度上说明我们上面求出的服务的maxConcurrentRequests=5也算是合理的
Other More
部门新来了架构师,在我在团队方案论述的说Hystrix就得用线程池
,不要用信号量
,他们以前公司就是这样用的,他和网上也推荐这样用,说可以异步、不占用tomcat线程什么的。
也许这个架构师不太理解我们的系统,又或者多Hystrix有什么误解,又或者过于相信网上的言论,当然,也可能是我个人理解有误,但是我觉得这个方案是不合理的,我这里说下我的理解:
(信号量和线程池的区别这里就不多叙述了,自查吧 )
- 因为我们系统有大量面向前端的服务,这些服务很多都依赖于其他第三方接口服务,如果每个都加上熔断,每个都设置自己的线程池,那么将会是一个恐怖的线程
开销
; - 同时,我们99%的前端、后端业务调用这些服务,都需要这些服务有一个
同步的响应
,因此额外开线程池,释放tomcat线程就无从说起了,因此线程池的方式并不适合我们的场景。
因此当时否决回去了,但因团队成员坚持保守看法,所以后面又开了个会论述了一下这个事,终于如愿达成一致,最后还是采用了信号量
的方式。
关键代码
动态配置关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
@Component @DisconfFile(filename = HystrixConfig.HYSTRIX_CONFIG_NAME) @DisconfUpdateService(classes = {HystrixConfig.class}) public class HystrixConfig implements InitializingBean {
public static final String HYSTRIX_CONFIG_NAME = "hystrix.properties"; private static final Logger logger = LoggerFactory.getLogger(HystrixConfig.class); private static final int REFRESH_TIME = (int) TimeUnit.SECONDS.toMillis(30L);
@Override public void afterPropertiesSet() { PolledConfigurationSource source = newConfigurationSource(); AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler(REFRESH_TIME, REFRESH_TIME, false); DynamicConfiguration configuration = new DynamicConfiguration(source, scheduler); ConfigurationManager.install(configuration); }
private PolledConfigurationSource newConfigurationSource() { return (initial, checkPoint) -> { Properties properties = new Properties(); try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(HYSTRIX_CONFIG_NAME)) { properties.load(is); } catch (Exception e) { logger.error("fail load hystrix configs", e); throw e; } return PollResult.createFull((Map) properties); }; } }
|
熔断配置文件
各个服务通用default,各个服务可以单独配置,配置会覆盖default配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
hystrix.command.default.execution.isolation.strategy=SEMAPHORE
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=100000
hystrix.command.default.execution.timeout.enabled=false
hystrix.command.default.circuitBreaker.enabled=true
hystrix.command.default.circuitBreaker.forceOpen=false
hystrix.command.default.circuitBreaker.forceClosed=false
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
hystrix.command.default.metrics.rollingStats.timeInMilliseconds=10000
hystrix.command.remarkMask.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.waybillQuery.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.batchWaybillQuery.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.batchChildWaybillQuery.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.hisWaybillQuery.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.dslWaybillQuery.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.conditionsWaybillQuery.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.pisTimeQuery.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.insideRoute.execution.isolation.semaphore.maxConcurrentRequests=100
hystrix.command.outsideRoute.execution.isolation.semaphore.maxConcurrentRequests=100
|