不恰当使用线程池处理 MQ 消息引起的故障
现状
业务部门反应网站访问特别慢,负责运维监控的同事说MQ消息队列积压了,中间件的说应用服务器内存占用很高,GC 一直回收不了内存,GC 线程占了近 100% 的 CPU,其他的基本上都在等待,数据库很正常,完全没压力。没啥办法,线程、堆 dump 出来后,重启吧,然后应用又正常了。
分析
这种故障之前其实也碰到过了,分析了当时 dump 出来的堆后发现,处理 MQ 消息的线程池的队列长度达百万级别,占用了超过 1.3G 内存,这些内存都是没法回收的。
程序的实现目前是这样的:关联系统把消息推送到 MQ 上,我们再从 MQ 上拉消息下来处理;每种类型的消息都有一个线程负责从 MQ 上拉消息,拉下来后封装成线程池的任务提交给相应的线程池去执行。代码可以简化为:
package net.coderbee.mq.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MQListener {public ExecutorService executor = Executors.newFixedThreadPool(8);public void onMessage(final Object message) {executor.execute(new Runnable() {@Overridepublic void run() {// 耗时且复杂的消息处理逻辑complicateHanlde(message);}});}private void complicateHanlde(Object message) {}
}
这个实现就是导致故障的根源,Executors.newFixedThreadPool(8)
创建的线程池的任务队列是无边界的:
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
当时是关联系统出故障了,他们恢复后,往 MQ 里狂推消息,我们系统里面的 MQListener 不断地从 MQ 拉消息下来,直接塞进线程池里,由于线程池处理消息的速度远远慢于消息进入的速度,所以线程池的队列不断增长,直到把所有的堆内存都占用了,这时不断引发 FullGC,但每次 FullGC 都没法回收到内存,应用也就挂死在那了。
之前那次故障也是线程池队列积压导致的,引起的原因是消息处理逻辑调用了外部接口,由于外部接口的响应非常慢,严重拖慢了消息的处理进度,改成异步调用之后好了些。但问题的根源并没有解决,就像昨天关联系统狂推消息后,我们的系统还是挂了。
解决方法
我的思路其实很简单,MQ 是用来系统间解耦的,也是一个缓冲,目前的实现是把处理消息的线程池又用作一个 MQ 了,消息不能不受控地进入线程池的任务队列,所以,要换成使用定长的阻塞队列,队列满了就暂停拉取消息。把线程池替换成:
private int nThreads = 8;
private int MAX_QUEUQ_SIZE = 2000;
private ExecutorService executor = new ThreadPoolExecutor(nThreads,nThreads, 0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(MAX_QUEUQ_SIZE),new ThreadPoolExecutor.CallerRunsPolicy());
把线程池队列满的时候直接让调用者(也就是 MQListener
)执行任务,这样即延缓了消息拉取的速度,当 MQListener
再去拉取消息时,发现线程池有空间时可以提交到线程池,让线程池的工作线程去处理,它继续保持拉取速度。
这样既控制了线程池占用的内存,又可以让消息处理线程池处理不过来时多一个线程处理消息。
由于上面的代码采用调用者执行的方式,那么要考虑消息处理的顺序问题,比如一个订单的处理可能有多个步骤,对应多条 MQ 消息,那么要考虑这些步骤如果乱序了是否可以接受,因为第3步骤的处理消息可能被 MQListener 处理了,而第2步的处理消息还积压在线程池里。
不恰当使用线程池处理 MQ 消息引起的故障相关推荐
- Java线程池的任务消息队列
多线程队列 Java多线程包括线程池会用到缓存任务的队列,Java提供的线程安全队列分为两种:阻塞队列和非阻塞队列 1.阻塞队列 阻塞队列支持生产者模式和消费者模式互相等待,队列为空,消费线程阻塞,直 ...
- Java线程池实现原理及其在美团业务中的实践
来自:美团技术团队 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池ThreadPoolExecuto ...
- 线程池运用不当的一次线上事故
来自:IT人的职场进阶 在高并发.异步化等场景,线程池的运用可以说无处不在.线程池从本质上来讲,即通过空间换取时间,因为线程的创建和销毁都是要消耗资源和时间的,对于大量使用线程的场景,使用池化管理可以 ...
- JDK 伪异步编程(线程池)
伪异步IO编程 BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接.在高性能服务器应用领域,往往需要面向成千上万个客户 ...
- 线程池在美团的最佳实践
随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员 ...
- 【有料】Java线程池实现原理及其在美团业务中的实践
随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员 ...
- 【开源项目】动态线程池框架Hippo4j源码解析
动态线程池框架Hippo4j源码解析 项目简介 Hippo-4J 通过对 JDK 线程池增强,以及扩展三方框架底层线程池等功能,为业务系统提高线上运行保障能力. 快速开始 https://hippo4 ...
- Dubbo线程池满导致宕机的案例分析解决
1 背景概述 线上事故:在做活动促销的时候,交易中台的商品服务发生了个别节点的宕机而此时间段内QPS并没有超过告警配置的线程数阈值1500. 于是决定做一次压测,当对自己的商品服务做压测20000个请 ...
- Java线程池实现原理及其在美团业务中的实践(转载加总结)
我们知道线程有5种状态分别是新建,就绪,运行,阻塞,死亡,而对应的线程池也有5种状态RUNNING运行,线程池创建就是该状态,SHUTDOWN 不接受新任务,但是处理已存在的任务,STOP不接受新任务 ...
最新文章
- OpenCV3.0中的离散傅里叶变换
- unity2019,打包APK时的gradle错误问题
- POJ 2385 DP
- ssh远程连接网络构建
- 解决Cannot resolve com.lowagie:itext:2.1.7.js6以及.net.jf.jasperresport下com.lowagie:itext标红的问题
- 地图投影——高斯-克吕格投影、墨卡托投影和UTM投影
- abaqus2018安装教程win10_win10怎么安装abaqus v6.12_win10系统abaqus v6.12安装详细教程
- 大数据云端实验室项目实战-微博舆情大数据分析有感
- 论文标题管理----WPS自定义多级自动编号列表
- 端端Clouduolc的安全机制
- 2014九月十月百度,迅雷,华为,阿里巴巴,最新校招笔试面试题
- 最近想给自己的Unity游戏接入广告
- git本地用户配置,及邮箱配置
- GTD方法在项目管理中的实践
- 国密sm2加密算法 前后端加密实现
- MT2601平台L1.MP9版本DWS配置方法
- Word线条边框和表格的应用
- cas入门之二spring配置文件
- 中国营销新闻网新闻发布
- windows的磁盘操作之八——格式化分区的思考