一:背景
1. 讲故事
年初有位朋友找到我,说他们的管理系统不响应了,让我帮忙看下到底咋回事? 手上也有dump,那就来分析吧。
二:为什么没有响应
1. 线程池队列有积压吗?
朋友的系统是一个web系统,那web系统的无响应我们首先要关注的就是 线程池
,使用 !sos tpq
命令,参考输出如下:
从卦中可以看到确实存在线程池积压的情况,那为什么会有积压呢?条件反射告诉我,是不是因为锁的原因,使用 !syncblk
观察。
从卦中看和锁没半毛钱关系,那就只能深入各个消费线程,看看这些线程为什么这么不给力。。。。
2. 线程都在干什么
要想观察各个线程都在做什么,可以用 ~*e !clrstack
观察各个线程调用栈,输出的调用栈太长,但仔细观察之后,发现很多线程都停留在 TryGetConnnection
上,截图如下:
从卦中可以看到大概有143个线程卡在 TryGetConnection 上,不知道 Connection 为啥取不到了,接下来观察问题代码,简化后如下:
从卦中源码看是 _transactedCxns
返回 null 所致,看样子是_transactedCxns中的Conenction耗尽,接下来使用 !dso
从 DbConnectionPool 字段中去挖,输出如下:
从上面的卦中数据可知三点信息:
-
100 _totalObjects
当前的线程池存着100个Connection。 -
0 _count
当前100个Connection全部耗尽。 -
143 _waitCount
表示当前有 143 个线程在获取 Connection 上进行等待。
3. 池中之物都去了哪里
要想找到这个答案,继续观察线程栈,比如搜索TDS传输层方法 Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte
,可以看到刚好是 100 个,上层大多是 xxxx.GetRoomNosInDltParams
方法,截图如下:
挖掘各个线程栈,大概都是下面的sql,格式化如下:
反编译源码发现有一个 useCached 字段形同虚设,导致每次都是从 数据库 读取,截图如下:
到这里基本上就能推测出来的,程序的卡死主要是 Connection 耗尽,优化建议如下:
- SQL 加上 nolock,避免锁问题。
- GetRoomNosInDltParams 方法尽量使用缓存。
- 观察数据库层的锁和负载情况。
- 设置更大的数据库连接池。
三:总结
这次卡死的生产事故,是大量数据库的慢请求导致SDK侧的数据库连接池(100)耗尽所致,如果有数据库侧的监控工具,我想一眼就能找到答案。