现状

业务部门反应网站访问特别慢,负责运维监控的同事说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 消息引起的故障相关推荐

  1. Java线程池的任务消息队列

    多线程队列 Java多线程包括线程池会用到缓存任务的队列,Java提供的线程安全队列分为两种:阻塞队列和非阻塞队列 1.阻塞队列 阻塞队列支持生产者模式和消费者模式互相等待,队列为空,消费线程阻塞,直 ...

  2. Java线程池实现原理及其在美团业务中的实践

    来自:美团技术团队 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池ThreadPoolExecuto ...

  3. 线程池运用不当的一次线上事故

    来自:IT人的职场进阶 在高并发.异步化等场景,线程池的运用可以说无处不在.线程池从本质上来讲,即通过空间换取时间,因为线程的创建和销毁都是要消耗资源和时间的,对于大量使用线程的场景,使用池化管理可以 ...

  4. JDK 伪异步编程(线程池)

    伪异步IO编程 BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接.在高性能服务器应用领域,往往需要面向成千上万个客户 ...

  5. 线程池在美团的最佳实践

    随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员 ...

  6. 【有料】Java线程池实现原理及其在美团业务中的实践

    随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员 ...

  7. 【开源项目】动态线程池框架Hippo4j源码解析

    动态线程池框架Hippo4j源码解析 项目简介 Hippo-4J 通过对 JDK 线程池增强,以及扩展三方框架底层线程池等功能,为业务系统提高线上运行保障能力. 快速开始 https://hippo4 ...

  8. Dubbo线程池满导致宕机的案例分析解决

    1 背景概述 线上事故:在做活动促销的时候,交易中台的商品服务发生了个别节点的宕机而此时间段内QPS并没有超过告警配置的线程数阈值1500. 于是决定做一次压测,当对自己的商品服务做压测20000个请 ...

  9. Java线程池实现原理及其在美团业务中的实践(转载加总结)

    我们知道线程有5种状态分别是新建,就绪,运行,阻塞,死亡,而对应的线程池也有5种状态RUNNING运行,线程池创建就是该状态,SHUTDOWN 不接受新任务,但是处理已存在的任务,STOP不接受新任务 ...

最新文章

  1. OpenCV3.0中的离散傅里叶变换
  2. unity2019,打包APK时的gradle错误问题
  3. POJ 2385 DP
  4. ssh远程连接网络构建
  5. 解决Cannot resolve com.lowagie:itext:2.1.7.js6以及.net.jf.jasperresport下com.lowagie:itext标红的问题
  6. 地图投影——高斯-克吕格投影、墨卡托投影和UTM投影
  7. abaqus2018安装教程win10_win10怎么安装abaqus v6.12_win10系统abaqus v6.12安装详细教程
  8. 大数据云端实验室项目实战-微博舆情大数据分析有感
  9. 论文标题管理----WPS自定义多级自动编号列表
  10. 端端Clouduolc的安全机制
  11. 2014九月十月百度,迅雷,华为,阿里巴巴,最新校招笔试面试题
  12. 最近想给自己的Unity游戏接入广告
  13. git本地用户配置,及邮箱配置
  14. GTD方法在项目管理中的实践
  15. 国密sm2加密算法 前后端加密实现
  16. MT2601平台L1.MP9版本DWS配置方法
  17. Word线条边框和表格的应用
  18. cas入门之二spring配置文件
  19. 中国营销新闻网新闻发布
  20. windows的磁盘操作之八——格式化分区的思考

热门文章

  1. Docker Pull非常缓慢(使用阿里云镜像加速器)
  2. 华硕发布全球首款8G内存手机ZenFone VR
  3. 万豪国际扩大北京奢华酒店布局,JW万豪将落户东城区
  4. winpe装双系统linux_制作win7+ubuntu +winPE+CDlinux多系统启动U盘
  5. Element table各种合并单元格
  6. 麦当劳如何吸引消费者走进店里
  7. 五百亿!又一大型平台崩了!85后女老板跑路!
  8. 多文件批量下载打包成.zip
  9. js脚本根据身份证号获取性别、年龄、家庭地址、生日
  10. Archlinux中pacman,yaourt的常用用法