ActiveMQ笔记(7):如何清理无效的延时消息?
ActiveMQ的延时消息是一个让人又爱又恨的功能,具体使用可参考上篇ActiveMQ笔记(6):消息延时投递,在很多需要消息延时投递的业务场景十分有用,但是也有一个缺陷,在一些大访问量的场景,如果瞬间向MQ发送海量的延时消息,超过MQ的调度能力,就会造成很多消息到了该投递的时刻,却没有投递出去,形成积压,一直停留在ActiveMQ web控制台的Scheduled面板中。
下面的代码演示了,如何清理activemq中的延时消息(包括:全部清空及清空指定时间段的延时消息),这也是目前唯一可行的办法。
为了演示方便,先封装一个小工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | package cn.mwee.utils.mq; import cn.mwee.utils.list.ListUtil; import cn.mwee.utils.log4j2.MwLogger; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessagePostProcessor; import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.TextMessage; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * Created by yangjunming on 6/20/16. */ public final class MessageUtil { private Logger logger = new MwLogger(MessageUtil. class ); //这里就是一个Log4j2的实例,大家可以换成原生的log4j2或类似工具 private ConnectionFactory connectionFactory; private long receiveTimeout; //接收超时时间 private JmsTemplate jmsTemplate; private List<String> destinationQueueNames; private final static String BACKUP_QUEUE_SUFFIX = "_B" ; private boolean autoBackup = false ; //是否自动将消息备份到_b的队列,方便调试 public MessageUtil( final ConnectionFactory connectionFactory, final long receiveTimeout, final List<String> destinationQueueNames) { this .connectionFactory = connectionFactory; this .receiveTimeout = receiveTimeout; this .destinationQueueNames = new ArrayList<>(); this .destinationQueueNames.addAll(destinationQueueNames.stream().collect(Collectors.toList())); jmsTemplate = new JmsTemplate( this .connectionFactory); jmsTemplate.setReceiveTimeout( this .receiveTimeout); } public MessageUtil(ConnectionFactory connectionFactory, List<String> destinationQueueNames) { this (connectionFactory, 10000 , destinationQueueNames); } public void convertAndSend(Object message) { if (ListUtil.isEmpty(destinationQueueNames)) { logger.error( "目标队列为空,无法发送,请检查配置!message => " + message.toString()); return ; } for (String dest : destinationQueueNames) { jmsTemplate.convertAndSend(dest, message); if (autoBackup) { jmsTemplate.convertAndSend(dest + BACKUP_QUEUE_SUFFIX, message); } } } public void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) { if (ListUtil.isEmpty(destinationQueueNames)) { logger.error( "目标队列为空,无法发送,请检查配置!message => " + message.toString()); return ; } for (String dest : destinationQueueNames) { jmsTemplate.convertAndSend(dest, message, messagePostProcessor); if (autoBackup) { jmsTemplate.convertAndSend(dest + BACKUP_QUEUE_SUFFIX, message, messagePostProcessor); } } } public void convertAndSend(String destinationName, Object message) { if (StringUtils.isBlank(destinationName)) { logger.error( "目标队列为空,无法发送,请检查配置!message => " + message.toString()); return ; } jmsTemplate.convertAndSend(destinationName, message); if (autoBackup) { jmsTemplate.convertAndSend(destinationName + BACKUP_QUEUE_SUFFIX, message); } } public void convertAndSend(String destinationName, Object message, MessagePostProcessor messagePostProcessor) { if (StringUtils.isBlank(destinationName)) { logger.error( "目标队列为空,无法发送,请检查配置!message => " + message.toString()); return ; } jmsTemplate.convertAndSend(destinationName, message, messagePostProcessor); if (autoBackup) { jmsTemplate.convertAndSend(destinationName + BACKUP_QUEUE_SUFFIX, message, messagePostProcessor); } } public static String getText(javax.jms.Message message) { if (message instanceof TextMessage) { try { return ((TextMessage) message).getText(); } catch (JMSException e) { return message.toString(); } } return message.toString(); } public String getFirstDestination() { if (ListUtil.isEmpty(destinationQueueNames)) { return null ; } return destinationQueueNames.get( 0 ); } public boolean isAutoBackup() { return autoBackup; } public void setAutoBackup( boolean autoBackup) { this .autoBackup = autoBackup; } } |
其中主要就用到了convertAndSend(Object message, MessagePostProcessor messagePostProcessor) 这个方法,其它代码可以无视。
先来模拟瞬间向MQ发送大量延时消息:
1 2 3 4 5 6 7 8 9 10 11 | /** * 发送延时消息 * * @param messageUtil */ private static void sendScheduleMessage(MessageUtil messageUtil) { for ( int i = 0 ; i < 10000 ; i++) { Object obj = "test:" + i; messageUtil.convertAndSend(obj, new ScheduleMessagePostProcessor( 1000 + i * 1000 )); } } |
这里向MQ发送了1w条延时消息,每条消息延时1秒*i,上面代码中的ScheduleMessagePostProcessor类可在上篇中找到。
运行完之后,MQ中应该堆积着了很多消息了:
下面的代码可以清空所有延时消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * 删除所有延时消息 * * @param connectionFactory * @throws JMSException */ private static void deleteAllScheduleMessage( final ConnectionFactory connectionFactory) throws JMSException { Connection conn = connectionFactory.createConnection(); Session session = conn.createSession( false , Session.AUTO_ACKNOWLEDGE); Destination management = session.createTopic(ScheduledMessage.AMQ_SCHEDULER_MANAGEMENT_DESTINATION); MessageProducer producer = session.createProducer(management); Message request = session.createMessage(); request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION, ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVEALL); producer.send(request); } |
清空所有延时消息,有些用力过猛了,很多时候,我们只需要清理掉过期的延时消息(即:本来计划是8:00投递出去的消息,结果过了8点还没投递出去)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * 删除过期的延时消息 * * @param connectionFactory * @throws JMSException */ private static void deleteExpiredScheduleMessage( final ConnectionFactory connectionFactory) throws JMSException { long start = System.currentTimeMillis() - TimeUnit.HOURS.toMillis( 12 ); //删除:当前时间前12小时范围的延时消息 long end = System.currentTimeMillis(); Connection conn = connectionFactory.createConnection(); Session session = conn.createSession( false , Session.AUTO_ACKNOWLEDGE); Destination management = session.createTopic(ScheduledMessage.AMQ_SCHEDULER_MANAGEMENT_DESTINATION); MessageProducer producer = session.createProducer(management); Message request = session.createMessage(); request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION, ScheduledMessage.AMQ_SCHEDULER_ACTION_REMOVEALL); request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION_START_TIME, Long.toString(start)); request.setStringProperty(ScheduledMessage.AMQ_SCHEDULER_ACTION_END_TIME, Long.toString(end)); producer.send(request); } |
与上一段代码基本相似,只是多指定了删除消息的起止时间段。
最后贴一段spring的配置文件及main函数入口

1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 5 6 <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"> 7 <property name="connectionFactory"> 8 <bean class="org.apache.activemq.ActiveMQConnectionFactory"> 9 <property name="brokerURL" 10 value="failover:(tcp://localhost:61616,tcp://localhost:61626)?randomize=false&backup=true"/> 11 <property name="maxThreadPoolSize" value="100"/> 12 </bean> 13 </property> 14 </bean> 15 16 <bean id="messageUtil" class="cn.mwee.utils.mq.MessageUtil"> 17 <constructor-arg index="0" ref="jmsFactory"/> 18 <constructor-arg index="1" value="10000"/> 19 <constructor-arg index="2"> 20 <list> 21 <value>dest1</value> 22 <value>dest2</value> 23 </list> 24 </constructor-arg> 25 <property name="autoBackup" value="true"/> 26 </bean> 27 28 </beans>
main函数:
1 2 3 4 5 6 7 8 | public static void main(String[] args) throws InterruptedException, JMSException { ApplicationContext context = new ClassPathXmlApplicationContext( "spring-sender.xml" ); ConnectionFactory connectionFactory = context.getBean(ConnectionFactory. class , "jmsFactory" ); MessageUtil messageUtil = context.getBean(MessageUtil. class ); // sendScheduleMessage(messageUtil); // deleteAllScheduleMessage(connectionFactory); deleteExpiredScheduleMessage(connectionFactory); } |
参考文章:
作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类:
09.Open Source
, 15.Java/Scala
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2013-09-04 航空货运:运价类别Rate Class
2010-09-04 FluorineFx:视频录制及回放(Flash/AS3环境)
2008-09-04 WCF Testing Tool(转)
2008-09-04 [转自雨痕]RESTful WCF
2008-09-04 [转]用HTTP/Post与WCF互操作
2008-09-04 [转载]利用SQL SERVER2005发送邮件
2008-09-04 [转贴]三种Ext提交数据的方法