1、问题场景

  1. 上游生产者服务在高并发下快速投递消息,一段时间后出现tomcat服务无响应,页面持续loading,后台无错误日志
  2. ActiveMQ所在机器磁盘空间报警日志:Persistent store is Full, 100% of 52428800. Stopping producer to prevent flooding queue://XXX

2、原因分析

  1. 快生产,慢消费导致ActiveMQ大量持久消息过期,长期积压在DLQ中,致磁盘空间不足
  2. MQ的默认流控机制使生产者被阻塞(即使是异步投递)
  3. 流量控制:当代理(broker)检测到目标(destination)的内存,或temp-或file-store超过了限制,消息的流量可以被减慢。生产者将会被阻塞直至资源可用,或者收到一个JMSException异常
  4. 流控的两种状态:阻塞(默认)、非阻塞抛出JMSException(<systemUsage>中配置

3、程序现状

  • 生产者采用异步投递方式
  • MQ连接工厂没有使用连接池
  • ttl5分钟
  • 没有配置过期后不使用DLQ(默认过期就入DLQ)
  • 问题队列的下游并行消费者少

4、解决思路

       该问题的根本原因是mq中间件磁盘空间不足进而触发生产者流控机制,阻塞了上游服务,导致看似生产者服务被“挂起”的假死现象,实际上生产者只是被减速生产,等待mq的资源释放,一旦mq磁盘恢复正常,会继续投递消息到broker。消费端不受影响。

       故关键在于提高下游消费能力、清理mq磁盘空间。

5、解决方案

  1. 针对磁盘空间的清理,可人为操作也可配置为对死信队列的自动清除策略,常用清除DLQ的两种方式见
  2. 针对下游消费能力,可适当增加并行消费数concurrentConsumers,增加预取机制prefetchSize,优化ACK批量确认optimizeAcknowledge
  3. 针对生产者流控策略,可将默认阻塞配置为抛异常但非阻塞,conf/activemq.xml中systemUsage标签中sendFailIfNoSpace=true或sendFailIfNoSpaceAfterTimeout=3000,此方案下生产者不会被阻塞,但消息也投递不到broker。另外,若程序中使用spring的JmsTemplate发送消息,将无法捕获该JMS异常(本人亲测如此),因为被优化为非检查异常,与官方描述(即使异步也可抛出异常到客户端通知异常)的有所出入。

6、总结

        个人认为解决该问题,在确保DLQ中数据价值不大的前提下,可优先清除DLQ释放磁盘空间即可。流控策略不建议改,因为解决了服务可用不阻塞却也会带来新的问题:无感知的消息丢失。生产者服务看似正常,但消息都没发到broker中

7、生产者流控

7.1<systemUsage>

        用于设置整个ActiveMQ节点在进程级别的各种“容量”的设置情况,可在此配置sendFailIfNoSpace或sendFailIfNoSpaceAfterTimeout来改变流控策略,使mq在没有多余容量时拒绝消息并在生产者抛出ResourceAllocationException异常(上述的JMS异常)

7.2<memoryUsage>

设置整个ActiveMQ节点的“可用内存限制”

7.3<storeUsage>

用于存储“持久化消息”的“可用磁盘空间”

7.4<tempUsage>

用于存储“非持久化消息”的“可用磁盘的临时区域

注意:storeUsage和tempUsage并不是“最大可用空间”,而是一个阀值,达到阈值触发流控(阻塞或抛异常)

感兴趣的可查看官方描述:ActiveMQ