前言
最近公司所有项目都是spring cloud微服务架构,通过feign组件在调用其它项目时偶尔会现现超时现象,查看一下配置文件,并没有配置接口超时时间,想着目前业务访问理也不大,于是大胆的修改了一下feign默认超时时间(它的默认超时时间是1s),页面刷的慢总比刷不出来强吧,于是就有了下面配置:
ribbon.ConnectTimeout=30000
ribbon.ReadTimeout=30000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
Spring Cloud里一般会用hystrix的线程池来执行接口调用的请求。所以设置超时一般设置两个地方,feign和ribbon那块的超时,还有hystrix那块的超时。其中后者那块的超时一般必须大于前者。
一、问题爆发
在配置了超时间后,一段时间内效果都不错,虽然页面是慢了点,但能用就可以了。
随着时间的推移,公司业务发展,日活用户量猛增。
经常会发现高峰期,系统的某个功能页面,突然就整个hang死了,没法再响应任何请求!所有用户刷新这个页面全部都是无法响应!
原因是调用其中一个微服务响应时间太慢,并且设置的超时时间太长了,很快线程池的线程都用完,并一直在等待响应,直到30s才返回超时
二、优化之路
第一、将慢查询sql查找,
尽量单表查询,业务逻辑在java中进行,加索引等,保证被调用的微服务200ms内返回
第二、修改超时时间,
一般超时定义在1秒以内,是比较通用以及合理的。不要因为服力差就将超时时间设置成几秒、几十秒,否则胡乱设置超时时间是几秒,甚至几十秒,万一下游服务偶然出了点问题响应时间长了点呢?那线程池里的线程立马全部卡死!
现在超时时间设置成合理时长后,最近公司网络偶尔出问题,导致接口响应时长会超过1s
第三、设置一个合理的重试次数
ribbon.ConnectTimeout=1000
ribbon.ReadTimeout=1000
ribbon.OkToRetryOnAllOperations=true
//当前服务最大重试次数
ribbon.MaxAutoRetries=1
//换下一台服务最大重试次数
ribbon.MaxAutoRetriesNextServer=1
Spring Cloud中的Feign + Ribbon的组合,在进行服务调用的时候,如果发现某台机器超时请求失败,会自动重试这台机器,如果还是不行会换另外一台机器重试。
第四步、保证接口幂等性
系统架构中,只要涉及到了重试,那么必须上接口的幂等性保障机制。
幂等性常用手段:
- 可以在数据库里建一个唯一索引,插入数据的时候如果唯一索引冲突了就不会插入重复数据
- 或者是通过redis里放一个唯一id值,然后每次要插入数据,都通过redis判断一下,那个值如果已经存在了,那么就不要插入重复数据了。
第五、设置Hystrix线程池大小
在经过上面优化后,最终服务要达到下面2点
- 要保证一个hystrix线程池可以轻松处理每秒钟的请求
- 同时还有合理的超时时间设置,避免请求太慢卡死线程。
比如,服务A每秒接收20个请求,同时它需要请求服务B,每个请求的响应时长大概在200ms,那么hystrix线程池需要多少个线程呢?
计算公式是:30(每秒请求数量) * 0.2(每个请求的处理秒数) + 4(给点缓冲buffer) = 10(线程数量)。
一个线程200毫秒可以执行完一个请求,那么一个线程1秒可以执行5个请求,理论上,只要6个线程,每秒就可以执行30个请求。
但多留4个线程保留空间
第六、服务降级
如果某个服务挂了,那么hystrix会走熔断器,然后就会降级,所以要考虑到各个服务的降级逻辑。