现状

业务部门反应网站访问特别慢,负责运维监控的同事说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() {
@Override
public 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. System、应用程序进程的Binder线程池和Handler消息循环

    首先看一张Android系统启动流程图:

  2. 动态线程池框架 DynamicTp v1.0.6版本发布。还在为Dubbo线程池耗尽烦恼吗?还在为Mq消费积压烦恼吗?

    DynamicTp 简介 DynamicTp 是一个基于配置中心实现的轻量级动态线程池管理工具,主要功能可以总结为 动态调参.通知报警.运行监控.三方包线程池管理等几大类. 经过几个版本迭代,目前最新 ...

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

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

  4. ACE - ACE_Task源码剖析及线程池实现

    原文出自http://www.cnblogs.com/binchen-china,禁止转载. 上篇提到用Reactor模式,利用I/O复用,获得Socket数据并且实现I/O层单线程并发,和dispa ...

  5. 【MINA】OrderedThreadPoolExecutor和UnorderedThreadPoolExecutor的事件监听线程池的选择

    mina中有两个线程池概念 1.处理监听建立连接的线程池  2.处理读写事件的线程池 本文中主要探讨读写事件的线程池的选择 这两种都经过实际项目的使用和检测,说说优缺点 早期的项目是用Unordere ...

  6. Dubbo学习笔记8:Dubbo的线程模型与线程池策略

    Dubbo默认的底层网络通讯使用的是Netty,服务提供方NettyServer使用两级线程池,其中 EventLoopGroup(boss) 主要用来接受客户端的链接请求,并把接受的请求分发给 Ev ...

  7. 基于无锁队列和c++11的高性能线程池

    基于无锁队列和c++11的高性能线程池线程使用c++11库和线程池之间的消息通讯使用一个简单的无锁消息队列适用于linux平台,gcc 4.6以上   标签: <无>   代码片段(6)[ ...

  8. 多线程学习笔记八之线程池ThreadPoolExecutor实现分析

    目录 简介 继承结构 实现分析 ThreadPoolExecutor类属性 线程池状态 构造方法 execute(Runnable command) addWorker(Runnable firstT ...

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

    本文转载自Java线程池实现原理及其在美团业务中的实践 导语 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供 ...

随机推荐

  1. 【Java】Java_06 基本数据类型

    1.基本数据类型 Java是一种强类型语言,每个变量都必须声明其类型. .  Java的数据类型分为两大类:基本类型(primitive type)和引用类型    (reference type) ...

  2. ibatis-java.lang.RuntimeException: Error setting property 'setFileSize'

    ibatis查询问题:      ibatis-java.lang.RuntimeException: Error setting property 'setFileSize'

  3. 网页中多一些常见效果之伸缩菜单(主要是学习js的书写方法)

    效果如下图: 代码很简单,如下: <!doctype html> <html lang="en"> <head> <meta charse ...

  4. 哈希—— POJ 3349 Snowflake Snow Snowflakes

    相应POJ题目:点击打开链接 Snowflake Snow Snowflakes Time Limit: 4000MS   Memory Limit: 65536K Total Submissions ...

  5. NIO之Channel聚集(gather)写入与分散(scatter)读取

    Channel聚集(gather)写入 聚集写入( Gathering Writes)是指将多个 Buffer 中的数据“聚集”到 Channel. 特别注意:按照缓冲区的顺序,写入 position ...

  6. MySQL 慢查询日志(Slow Query Log)

    同大多数关系型数据库一样.日志文件是MySQL数据库的重要组成部分.MySQL有几种不同的日志文件.通常包含错误日志文件,二进制日志,通用日志.慢查询日志.等等.这些日志能够帮助我们定位mysqld内 ...

  7. Atitit.aticmd v4  新特性q39 添加定时器释放功能

    Atitit.aticmd v4  新特性q39 添加定时器释放功能 V1  实现兰cmd V2 标准输入,标准输出,标准错误与重新定向 V3  stdout stderr统一重新定向 V4  添加定 ...

  8. 关于angularjs在IE里的坑——F12工具打开,功能正常,关闭之后,angularjs not working

    前端时间在做项目的时候,用到了angularjs,期间,发现了一个奇葩的问题,就是在IE11浏览器下,点击下方图1上箭头所示的位置,将此处的开关变为图2中箭头所示的样子,但是发觉没有反应,开关还是灰色 ...

  9. Restore IP Addresses -- LeetCode

    原题链接: http://oj.leetcode.com/problems/restore-ip-addresses/  这道题的解法很接近于NP问题.也是採用递归的解法. 基本思路就是取出一个合法的 ...

  10. Spring/SpringMVC在启动完成后执行方法

    在某些情况下,有可能你会有这种需求:在Spring/SpringMVC项目中,当Spring/SpringMVC启动完成后,你需要执行一个方法来完成某些事件(比如创建网站地图,比如从订阅Redis服务 ...