ActiveMQ的延时消息是一个让人又爱又恨的功能,具体使用可参考上篇ActiveMQ笔记(6):消息延时投递,在很多需要消息延时投递的业务场景十分有用,但是也有一个缺陷,在一些大访问量的场景,如果瞬间向MQ发送海量的延时消息,超过MQ的调度能力,就会造成很多消息到了该投递的时刻,却没有投递出去,形成积压,一直停留在ActiveMQ web控制台的Scheduled面板中。

下面的代码演示了,如何清理activemq中的延时消息(包括:全部清空及清空指定时间段的延时消息),这也是目前唯一可行的办法。

为了演示方便,先封装一个小工具类:

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发送大量延时消息:

    /**
* 发送延时消息
*
* @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中应该堆积着了很多消息了:

下面的代码可以清空所有延时消息:

    /**
* 删除所有延时消息
*
* @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点还没投递出去)

    /**
* 删除过期的延时消息
*
* @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函数入口

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL"
value="failover:(tcp://localhost:61616,tcp://localhost:61626)?randomize=false&amp;backup=true"/>
<property name="maxThreadPoolSize" value="100"/>
</bean>
</property>
</bean> <bean id="messageUtil" class="cn.mwee.utils.mq.MessageUtil">
<constructor-arg index="0" ref="jmsFactory"/>
<constructor-arg index="1" value="10000"/>
<constructor-arg index="2">
<list>
<value>dest1</value>
<value>dest2</value>
</list>
</constructor-arg>
<property name="autoBackup" value="true"/>
</bean> </beans>

main函数:

    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);
}

参考文章:

Enhanced JMS Scheduler in ActiveMQ

ActiveMQ笔记(7):如何清理无效的延时消息?的更多相关文章

  1. ActiveMQ笔记(6):消息延时投递

    在开发业务系统时,某些业务场景需要消息定时发送或延时发送(类似:飞信的短信定时发送需求),这时候就需要用到activemq的消息延时投递,详细的文档可参考官网说明,本文只介绍二种常用的用法: 注:本文 ...

  2. Git常用命令及常见报错:You have not concluded your merge (MERGE_HEAD exists)、清理无效的远程追踪分支

    一.常用命令 切换到master分支:git checkout master 查看已有本地及远程分支:git branch -a(先git pull拉下全部数据) 查看远程分支:git branch ...

  3. activeMq笔记

    安装 下载地址:http://activemq.apache.org/download.html 安装教程: http://gerrard-ok.iteye.com/blog/1766203 解压缩: ...

  4. ActiveMq笔记2-消息持久化

    为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一般都会采用持久化机制. ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB, 无论使用哪种持久化方 ...

  5. ActiveMQ 笔记(六)ActiveMQ的消息存储和持久化

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.持久化机制 1.Activemq持久化 1.1 什么是持久化: 持久化就是高可用的机制,即使服务器宕 ...

  6. ActiveMQ 笔记(五)ActiveMQ的传输协议

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 面试思考题: 默认的61616端口如何更改 你生产上的连接协议如何配置的?使用tcp吗? 一.Activ ...

  7. spring boot:用rocketmq发送延时消息用来取消订单(spring boot 2.3.3)

    一,为什么要用延时消息来取消订单? 1,为什么要取消订单 在电商的下单过程中,需要在生成订单时扣减库存, 但有可能发生这种情况:用户下了单,临时改变主意不再支付, 则订单不能无限期的保留,因为还要把占 ...

  8. RocketMQ源码 — 九、 RocketMQ延时消息

    上一节消息重试里面提到了重试的消息可以被延时消费,其实除此之外,用户发送的消息也可以指定延时时间(更准确的说是延时等级),然后在指定延时时间之后投递消息,然后被consumer消费.阿里云的ons还支 ...

  9. rocketmq延时消息

    rocketmq提供一种延时消息的解决方案,就是在特定的时间到了,消息才会被投递出去供consumer消费. 总体来是简单的场景是满足了,但是需要注意的是延时的时间是需要按照默认配置的延时级别去配置的 ...

随机推荐

  1. Oracle AWR报告提取方法

    本文旨在用来指导项目人员自行提取Oracle数据库的AWR报告. 1.当前连接实例的AWR报告提取:@?/rdbms/admin/awrrpt 2.RAC的其他实例AWR报告提取:@?/rdbms/a ...

  2. Oracle 11g DG配置简明版

    环境: 主库A机:在线生产环境,RHEL 6.4 + Oracle 11.2.0.3 备库B机:新增备机,RHEL 6.4 需求: 对生产环境最小影响前提下配置DG备库. 目录: 一.B机安装相同版本 ...

  3. iOS 相机

    本章节主要为之前项目 JXHomepwner 添加照片功能(项目地址).具体任务就是显示一个 UIImagePickerController 对象,使用户能够为 JXItem 对象拍照并保存.拍摄的照 ...

  4. JSTL标签 参考手册

    前言 ========================================================================= JSTL标签库,是日常开发经常使用的,也是众多 ...

  5. java中异常抛出后代码还会继续执行吗

    今天遇到一个问题,在下面的代码中,当抛出运行时异常后,后面的代码还会执行吗,是否需要在异常后面加上return语句呢? public void add(int index, E element){ i ...

  6. 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 4 - 验证

    验证: 快要完成我们程序的界面部分了.剩下的事情就是在用户点击 "保存" 的时候管理验证问题了.验证是主要需求,今天就是最无知的应用也不会忽视它.通过正确的验证,用户可以知道应该输 ...

  7. mvc过滤器学习(1)

    mvc 过滤器结构图 AuthorizeAttribute AuthorizeAttribute是IAuthorizationFilter的默认实现,添加了Authorize特性的Action将对用户 ...

  8. C#开发微信门户及应用(36)--微信卡劵管理的封装操作

    前面几篇介绍了微信支付方面的内容,本篇继续微信接口的一些其他方面的内容:卡劵管理.卡劵管理是微信接口里面非常复杂的一个部分,里面的接口非常多,我花了不少时间对它进行了封装处理,重构优化等等工作,卡劵在 ...

  9. TypeSDK总体设计思路和架构

    引言:本文旨在提供读者制作一个自己的聚合SDK的思路,抛砖引玉,让更多的读者对聚合SDK有好的理解. 这是最好的时代,这是最坏的时代,这是智慧的时代,这是愚蠢的时代:这是信仰的时期,这是怀疑的时期:这 ...

  10. javascript浏览器检测

    <script type="text/javascript">   /**  * 获取浏览器类型以及版本号  * 支持国产浏览器:猎豹浏览器.搜狗浏览器.傲游浏览器.3 ...