一、问题描述
1、生产环境主站每隔一段时间就会出现卡顿,接口响应慢,甚至超时的情况、
2、测试环境重现不了(一抹诡异的光)
二、问题排查
针对响应慢的接口进行优化,之前的代码风格也存在问题,还是有些滥用sync/await,一些没有依赖关系的操作,全部分开每行await同步执行,分析后把部分DB操作合并一个Promise执行 阿里云查了一下mysql的slow_log,有挺多的慢查询,优化了一部分SQL,业务逻辑太复杂,但是!没有解决问题,主站还是隔一段时间就卡 找到部分接口日志,超时的接口返回的是Knex.js数据库管理工具抛出的异常,
KnexTimeoutError: Knex: Timeout acquiring a connection. The pool is probably full.
,可能是连接池已满,获取连接失败导致的 看了一下数据库连接池的配置,最大连接数是30,获取连接的超时时间是60s,可能是并发量大加上部分操作未释放连接导致后续的操作无法正常获取数据库连接池的连接,大概又定位了一下可能的问题点
- 1,主站的信息列表会隔几秒钟轮询,获取最新的数据,如果1000个用户在线,轮询周期内就会有1000个查询,中间也没有做缓存处理,导致并发到DB的请求会比较多
- 2,为了维护DB的状态统一,用户的部分操作用了事务,一些事务内包含了太多操作(感觉是长期占用连接未释放的罪魁祸首)
- 3,测试环境重现不了是因为没有经过压测,只测试了功能,没有测试性能,日常测试也没有大并发
三、问题验证
将数据库连接池的数量改成了1,使用事务的接口中做了延时的
transaction.commit()
操作,然后另外一个请求再去正常查询,结果显示,如果一个用户调用了事务操作的接口,然后再调用查询接口,查询会一直阻塞在获取连接的步骤,直至事务commit之后释放连接,如果在配置的timeout时间之前没有获取到,Knex就会抛出
Timeout acquiring a connection. The pool is probably full
的异常,也侧面印证了为什么测试环境复现不了这种情况,毕竟在没有压测的前提下。两个测试通过手动操作,并发量是达不到配置的数量的,也就不会出现卡顿的情况
四、解决方案
1,优化长事务的操作,减少不必要的事务,提高处理效率(难度较大,业务逻辑比较复杂);
2,合理范围内增加数据库连接池的最大连接数配置,线上的mysql可连接300个,后端3个服务,现在配置是10、30、30,先把主站改50看看,连接数太大也会导致磁盘I/O效率大幅降低又会导致其他问题;
3,轮询获取列表的操作,可以改成服务端主动去推(使用socket.io),然后加一个中间缓存(redis),毕竟列表数据变化的频率不是很高;
4,数据库扩展成读写分离,update和insert的操作直接操作主库,大部分select操作转移到从库,即使有部分的事务操作慢,也不会导致主站的基本查询卡住
五、写在最后
系统的业务逻辑比较复杂,从业务代码层面下手成本还是比较高,接口的耦合都比较高,重构都比改的成本低,开始的设计,可能也没有考虑扩展的问题,并发的问题等, 包括每个服务之间的通信问题,后期再慢慢优化吧,不怕有问题,就怕一直没遇到过问题!加油~。