优化了2年的性能,没想到最后被数据库连接池坑了一把

优化了2年的性能,没想到最后被数据库连接池坑了一把

好文推荐 guys 来聊聊超大型项目重构这两年公司业务发展比较快,不管是客户体量、数据量、还是业务复杂度都是爆炸式增长,参与、设计 - 掘金

背景

我们系统用了 Pulsar 作为消息引擎,内部沉淀了 sdk, 对GitHub - apache/pulsar-client-go: Apache Pulsar Go Client Library做了一些能力增强,主要是提高并发,另外就是简化使用复杂度。从 1 个月前,系统多次出现消费巨慢无比,甚至停止消费情况,pulsar 买的腾讯云能力登录 - 腾讯云,出问题还是比较方便,找人帮忙协助排查效率高。联系腾讯售后团队,回复是业务测很多消息被 Nack ,导致出现消息空洞,消费会越来越慢。

出现多次腾讯也比较重视,定位了他们内部有一个bug 需要升级组件,在他们上线之前出现问题我们立刻重启服务,重启服务会带来一些后遗症,比如:消息重复消费问题,概率比较低,我们一般在低峰期重启。

N 次之后首次排查到蛛丝马迹

组件升级后,还多次出现了类似问题,业务测都怀疑是三方问题或者是我们内部 sdk 有 bug,业务代码问题很久没有改动了。拉了一批公司相关人员成立临时小分队排查该问题,负责开发 sdk 同事排查之后说是业务测消费太慢导致,我们看了监控发现消费耗时最大的都是100ms以内怎么可能慢,谁也不服谁干脆先重启服务,避免影响线上客户使用,重启服务后恢复正常。

不相信 sdk 开发同学对问题排查,这次我提前把 sdk 代码熟悉了,了解了 sdk 的设计方便未来定位问题,思路大概这样:底层为了提高并发,消费消息和调用业务方逻辑是解耦了的(可以理解为消费和逻辑处理分离),调用业务方用了多个子协程,主协程负责消费消息,它们之间通过通道进行数据共享。主协程消费消息投递至通道(通道大小是10),子协程从通道读取取消息,验证消息后调用业务方,整个链路需要透传 context 的,通过 context 携带了一些些值,比如:链路ID、还有一些内部信息,context 直接用的 context.Background() 没有超时和取消机制,代码如下:

arduino 复制代码
ctx := context.Background()
// .....其它逻辑

// 调用业务方逻辑
invoke(ctx,params)
ack(msg)

sdk 代码了解的七七八八了。没过多久,业务 owner 群里@我又出现不消费问题了,赶紧打开日志系统定位问题,发现某条消息被消费后,在业务入口打印了日志然后再也没有其它日志信息了,看了业务代码,也没啥复杂逻辑大概就是查了多个表、调了其它业务方接口、还有一些数据校验,最后投递其它 topic 中间也没有报错,但还有一些日志没有打印出来。结合 sdk 透传给业务的 context 没有超时机制,怀疑是协程卡住了,如果业务方卡住,sdk 通道满了消费逻辑也就卡住了,从代码逻辑来看跟现在业务表现是符合的,具体为啥卡住就不知道了,需要进一步定位,但没办法下手。

知道卡住就好办了,业务在入口处对 context 进行 2 次包装,如下:

go 复制代码
ctx, cancel := context.WithTimeout(ctx, time.Second*180) // 180s 超时
defer cancel()
// ....其它逻辑省略

当天快速发布上线,等待下次消费问题到来。。。next moment

触发超时机制,问题彻底定位

12 月 18 日下午,群里相关同学@我超时机制被触发了,错误是 context deadline exceeded、sql execute error 一看就是 sql 超时了,sql耗时 3 分钟,"latency":"3m0.000335374s"。

json 复制代码
{"corp_id":"wwd562912547911a99","cdb":"mk_data_20210426","latency":"3m0.000335374s","sql":"...."}

不太对劲,如果是数据库问题或者 sql 执行慢早就被数据库监控抓到了,马上打开 tidb dashboard,这条 sql 都没有走到 tidb ,继续通过日志排查,只有一个 pod 在这个时间段报错,另外 11 个 pod 都是正常的。

有没有可能是因为这个 pod 数据链接池满了在排队?带着疑惑继续排查。。。

按照惯例先看微服务监控,SQL 耗时如下图:

CURD 操作耗时明显比正常时候耗时高的离谱。

出问题 pod 内存有明显上涨趋势,如下图:

最后看数据库连接池,如下图:

有一个 POD 连接池明显被打满了,看来这个问题就是链接池被打满了造成的,但是为啥内存还上涨了?从监控上已经看不出来啥了,只有研究下 gorm源码,肯定是看数据库连接池获取逻辑,如下图:

如果连接池被打满了,将请求 append 到切片中等待被唤醒,所以内存会有一定的上涨。

解决方案

该微服务连接池调整成 32 个,再看连接池使用情况,如下图:

链接池还有富裕空间。

另外,调整连接池后 SQL 执行效率明显提升了,sql>999ms 相比之前明显降低了。

最近 7 天再也没有出现消费问题了,按照之前的频率一周至少出现一次问题。

总结

问题定位是很艰辛的,涉及业务团队、sdk 团队、腾讯云团队,再加上这些代码上线了 2 年中间改动小,我们也没有怀疑是代码或者配置上问题。最后,定位原因之后,才发现我们前不久上线了性能优化技改,为了提高系统吞吐量我们引入了协程池,同比例提高了数据库 CURD 的 qps,出问题时间跟该技改版本上线时间也能吻合。

原本可以更早定位问题,减少客户损失,因为一些客观因素我们没有自省自己的代码,曾子曰:"吾日三省吾身",希望大家引以为鉴。

最后,觉得文章有用,麻烦帮忙点点赞,顺便帮我投投票。

相关推荐
wm104313 分钟前
java web springboot
java·spring boot·后端
仰望大佬00738 分钟前
Avalonia实例实战五:Carousel自动轮播图
数据库·microsoft·c#
学不透java不改名43 分钟前
sqlalchemy连接dm8 get_columns BIGINT VARCHAR字段不显示
数据库
一只路过的猫咪44 分钟前
thinkphp6使用MongoDB多个数据,聚合查询的坑
数据库·mongodb
龙少95432 小时前
【深入理解@EnableCaching】
java·后端·spring
呼啦啦啦啦啦啦啦啦2 小时前
【MySQL篇】事务的认识以及四大特性
数据库·mysql
van叶~2 小时前
探索未来编程:仓颉语言的优雅设计与无限可能
android·java·数据库·仓颉
程序猿会指北3 小时前
【鸿蒙(HarmonyOS)性能优化指南】启动分析工具Launch Profiler
c++·性能优化·harmonyos·openharmony·arkui·启动优化·鸿蒙开发
溟洵4 小时前
Linux下学【MySQL】表中插入和查询的进阶操作(配实操图和SQL语句通俗易懂)
linux·运维·数据库·后端·sql·mysql
轻口味4 小时前
【每日学点鸿蒙知识】DevEco、HDC报错、C调用数据库、测试工具、codegen
数据库·华为·harmonyos