已经很明显了。这里在遍历链表,同时由于形成了环形链表导致这个 e.next
永远不为空,所以这个循环也不会退出了。
到这里其实已经找到问题了,但还有一个疑问是为什么线程池里的任务队列会越堆越多。我第一直觉是任务执行太慢导致的。
仔细查看了代码发现只有一个地方可能会慢:也就是有一个数据库的查询。
把这个 SQL 拿到生产环境执行发现确实不快,查看索引发现都有命中。
但我一看表中的数据发现已经快有 7000W 的数据了。同时经过运维得知 MySQL
那台服务器的 IO
压力也比较大。
所以这个原因也比较明显了:
由于每消费一条数据都要去查询一次数据库,MySQL 本身压力就比较大,加上数据量也很高所以导致这个 IO 响应较慢,导致整个任务处理的就比较慢了。
但还有一个原因也不能忽视;由于所有的业务线程在某个时间点都进入了死循环,根本没有执行完任务的机会,而后面的数据还在源源不断的进入,所以这个队列只会越堆越多!
这其实是一个老应用了,可能会有人问为什么之前没出现问题。
这是因为之前数据量都比较少,即使是并发写入也没有出现并发扩容形成环形链表的情况。这段时间业务量的暴增正好把这个隐藏的雷给揪出来了。所以还是得信墨菲他老人家的话。
总结
至此整个排查结束,而我们后续的调整措施大概如下:
HashSet
不是线程安全的,换为ConcurrentHashMap
同时把value
写死一样可以达到set
的效果。
- 根据我们后面的监控,初始化
ConcurrentHashMap
的大小尽量大一些,避免频繁的扩容。
MySQL
中很多数据都已经不用了,进行冷热处理。尽量降低单表数据量。同时后期考虑分表。
- 查数据那里调整为查缓存,提高查询效率。
- 线程池的名称一定得取的有意义,不然是自己给自己增加难度。
- 根据监控将线程池的队列大小调整为一个具体值,并且要有拒绝策略。
- 升级到
JDK1.8
。
- 再一个是报警邮件酌情考虑为电话通知😂。
HashMap
的死循环问题在网上层出不穷,没想到还真被我遇到了。现在要满足这个条件还是挺少见的,比如 1.8 以下的 JDK
这一条可能大多数人就碰不到,正好又证实了一次墨菲定律。
同时我会将文章更到这里,方便大家阅读和查询。