调用量大
故障案例1:故障恢复后,短时间重试待处理任务到单机执行,量太大导致单机线程池满导致服务受损。
● 解法:系统层面需要做一定的限流策略,单机任务瓶颈时应切换到网格型任务。
故障案例2:压测未预热,直接一次性并发到压测值导致线程池满,导致数据库有很多事务等待的慢 SQL。
● 解法:压测应按照一定节奏逐步上量,观察系统负载并及时暂定,而不是开局就决战。
其他
故障案例:查询没加 Limit 导致应用 Full GC
● 该 Case 不涉及线程池满问题,但笔者觉得有一定的代表性因此也分享下。不管是查询还是删除还是更新数据,不管是代码还是日常的 SQL 订正,建议都增加 Limit 来兜底保护自己,缩小影响面。
技术视角
线程池类的故障,一般都是某个地方慢了堵了,从技术角度大多是:
1、远程调用 IO 慢导致耗时增加;
2、计算密集型应用 CPU 飙升导致耗时增加;
3、自定义业务线程池满造成排队等待导致耗时增加;
其中 2 不算常见,笔者也遇到过,发生于某 CPU 密集运算的应用系统,突增的高并发请求引起 CPU 100%;其中 1 比较常见,一般远程调用有:Dubbo、Http、DB、Redis,这些实践中都会使用连接池来与远程服务交互,凡是连接池都是有共性的,有两个需要关注的点:
● 1、尽量减少远程调用本身的 超时时间 以实现 fast-fail 快速失败。一般是设置 ConnectionTimeout 即握手时间 和 SocketTimeout 即业务执行超时时间。
● 2、在连接池满了以后,获取新的连接的 超时时间 也需要设置的小一些以实现 fast-fail 快速失败,这个是很容易忽略的一个点。如 Druid 里设置 MaxWait,Http 连接池里设置 ConnectionRequestTimeout。
下面列一下各个连接池需要关注的点。
Dubbo 线程池
1、线程池做好隔离,避免互相影响
● 如内部运维接口和对外服务的接口做隔离。
● 对外服务里核心接口和非核心接口做隔离。
2、Dubbo consumer 侧设置 timeout,根据 fast-fail 理念设置的越小越好;provider 侧的 timeout 仅仅是起到声明的效果供 consumer 参考,无实际超时杀线程的作用。
Http 连接池
1、设置 ConnectTimeout、SocketTimeout、ConnectionRequestTimeout
● 故障案例:某次发布的代码引入了一个 SDK,该 SDK 集成了 HttpClient,但并没有设置 ConnectionTimeout,在某次网络抖动发生时,Http 连接池被迅速打满,进而导致业务线程池满导致服务受损。
2、DefaultMaxPerRoute 太小也容易导致阻塞。
● 故障案例:某 SDK 默认设置的 128,在某次压测中发现客户端耗时较高,但服务端耗时并无波动,排查后怀疑是 DefaultMaxPerRoute 太小导致的阻塞,调大后问题解决。
数据库连接池 Druid
1、设置 ConnectTimeout、SocketTimeout。
● 故障案例:凌晨 1 点多收到 API 成功率降低报警,排查发现部分 SQL 执行超时,原因是数据库发生了主备切换,进一步排查发现应用侧对数据库连接池没有设置 SocketTimeout 导致切换前的老的连接不会被超时 Kill 导致相关 SQL 执行超时,直到 900秒系统默认超时后才会断开连接再次重连。
2、设置 TransactionTimeout 即事务超时时间,事务就是一把锁,超时时间越长锁越久,导致不在事务里的相关 SQL 锁等待导致性能差。
● 故障案例:在某次变更时由于代码有 bug 导致事务未提交,同时由于事务没设置超时时间,导致大量相关 SQL 超时服务受损。
3、设置 Ibatis 的 defaultStatementTimeout、queryTimeout。
4、设置 MaxWait:获取新连接的等待超时时间。
● 小插曲:之前 Druid 默认设置的 60 秒,后来笔者与作者有过沟通反馈这个默认值太长容易坑大家,后来发现已经改为了 6 秒[1]
自定义线程池
1、线程池设置的队列过长容易造成阻塞影响吞吐。
2、future.get,默认没有超时时间,需显式传入。
● 故障案例:Dubbo 线程池满报警,排查后发现是业务代码里使用了 future.get 没有设置超时时间,同时线程池的拒绝策略设置的是 DiscardPolicy,会导致在线程池满后新的任务被丢弃时 future.get 阻塞,进而导致 Dubbo 线程池满服务受损。
Redis连接池
1、设置 Jedis pool MaxWait,与 Druid 的 MaxWait 类似,也与 Http 连接池的 ConnectionRequestTimeout 类似。
2、设置 ConnectionTimeout、SocketTimeout,与 Druid/Http 连接池的类似。
总结
fast-fail 理念
1、本质上是不浪费系统资源,一些超时时间设置过长其实是在做无效的 IO 等待。
2、有一些个人的经验值贴一下:ConnectionTimeout 建议1-3 秒最佳,最大不超过 5 秒。SocketTimeout 根据业务请求时间情况设置建议最大不超过 10 秒,MaxWait/ConnectionTimeout 建议 3~5 秒,最大不超过 6 秒。
保护好自己:流控/背压
1、数据库后台运维平台设置自动限流,紧急情况下收到预警后第一时间手动执行限流。
2、实现 单机维度、集群维度(Region/AZ)、用户维度、接口维度 流控。
3、消息中间件拉取消息的 Client 实现背压机制