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

参考文章:

Enhanced JMS Scheduler in ActiveMQ

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值