在使用Message Queue的过程中,总会由于种种原因而导致消息失败。一个经典的场景是一个生成者向Queue中发消息,里面包含了一组邮件地址和邮件内容。而消费者从Queue中将消息一条条读出来,向指定邮件地址发送邮件。消费者在发送消息的过程中由于种种原因会导致失败,比如网络超时、当前邮件服务器不可用等。这样我们就希望建立一种机制,对于未发送成功的邮件再重新发送,也就是重新处理。重新处理超过一定次数还不成功,就放弃对该消息的处理,记录下来,继续对剩余消息进行处理。

ActiveMQ为我们实现了这一功能,叫做ReDelivery(重新投递)。当消费者在处理消息时有异常发生,会将消息重新放回Queue里,进行下一次处理。当超过重试次数时,消息会被放置到一个特殊的Queue中,即Dead Letter Queue,简称DLQ,用于进行后续分析。

废话不多说,一起来实现吧。(该示例中的全部代码已放置到GitHub上,请自行下载。)

还是接着本系列中的示例代码来进行。要实现ReDelivery功能,要给LinsterContainer加上事务处理。设置SimpleMessageListenerContainer的sessionTransacted属性为true。

activeMQConnection.xml
1
2
3
4
5
6
7
8
9
    <!-- Message Receiver Definition -->
<bean id="messageReceiver" class="huangbowen.net.jms.retry.MessageReceiver">
</bean>
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" value="${jms.queue.name}"/>
<property name="messageListener" ref="messageReceiver"/>
<property name="sessionTransacted" value="true" />
</bean>

然后创建一个ReDeliveryPolicy,来定义ReDelivery的机制。

activeMQConnection.xml
1
    <amq:redeliveryPolicy id="activeMQRedeliveryPolicy" destination="#defaultDestination" redeliveryDelay="100" maximumRedeliveries="4" />

这里设置ReDelivery的时间间隔是100毫秒,最大重发次数是4次。

在ActiveMQ的Connection Factory中应用这个Policy。就是给Connection Factory设置属性redeliveryPolicy为我们刚刚创建的Policy。

activeMQConnection.xml
1
2
3
4
5
6
    <!-- Activemq connection factory -->
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="${jms.broker.url}?"/>
<property name="useAsyncSend" value="true"/>
<property name="redeliveryPolicy" ref="activeMQRedeliveryPolicy" />
</bean>

这样ReDelivery机制就设置好了。那么怎么能证明我不是在忽悠你们那?当然最好的办法是写自动化测试来测试这个功能了。

首先修改下broker的配置,将其对消息的持久化设置为false,这样每次运行测试时Queue中消息都为0,用于还原现场。然后设置一个Destination Policy,当消息超过重试次数仍未被正确处理时,就把它放入到以DLQ.为前缀的Queue中。由于ActiveMQ默认对非持久化的Message不放入DLQ中的,所以手动设置processNonPersistent为true。

activeMQConnection.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    <amq:broker id="activeMQBroker" persistent="false">
<amq:transportConnectors>
<amq:transportConnector uri="${jms.broker.url}"/>
</amq:transportConnectors>
<amq:destinationPolicy>
<amq:policyMap>
<amq:policyEntries>
<amq:policyEntry queue=">">
<amq:deadLetterStrategy>
<amq:individualDeadLetterStrategy
queuePrefix="DLQ." useQueueForQueueMessages="true" processExpired="true"
processNonPersistent="true" />
</amq:deadLetterStrategy>
</amq:policyEntry>
</amq:policyEntries>
</amq:policyMap>
</amq:destinationPolicy>
</amq:broker>

然后新建一个MessageListener,当接收到消息就抛出一个异常,这样用以启动ReDelivery机制。

retry/MessageReceiver
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
package huangbowen.net.jms.retry;

import org.springframework.jms.support.JmsUtils;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage; public class MessageReceiver implements MessageListener { public void onMessage(Message message) {
if(message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
String text = textMessage.getText();
System.out.println(String.format("Received: %s",text));
throw new JMSException("process failed");
} catch (JMSException e) {
System.out.println("there is JMS exception: " + e.getMessage() );
throw JmsUtils.convertJmsAccessException(e);
}
}
}
}

最后新建一个集成测试类。

ReDeliveryFunctionIntegrationTest.java
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
package huangbowen.net;

import huangbowen.net.jms.MessageSender;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.BrowserCallback;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import javax.jms.JMSException;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import java.util.Enumeration; import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat; @ContextConfiguration(locations = {"/retry/activeMQConnection.xml"})
@DirtiesContext
public class ReDeliveryFunctionIntegrationTest extends AbstractJUnit4SpringContextTests { private final static String DLQ = "DLQ.bar";
@Autowired
public JmsTemplate jmsTemplate; @Autowired
public MessageSender messageSender; private int getMessagesInDLQ() {
return jmsTemplate.browse(DLQ, new BrowserCallback<Integer>() {
@Override
public Integer doInJms(Session session, QueueBrowser browser) throws JMSException {
Enumeration messages = browser.getEnumeration();
int total = 0;
while(messages.hasMoreElements()) {
messages.nextElement();
total++;
} return total;
}
});
} @Test
public void shouldRetryIfExceptionHappened() throws Exception { assertThat(getMessagesInDLQ(), is(0)); messageSender.send("this is a message");
Thread.sleep(5000); assertThat(getMessagesInDLQ(), is(1));
}
}

我们通过Spring的Autowired功能拿到配置中的JmsTemplate和MessageSender。使用JmsTemplate的brower方法来读取当前DLQ.bar Queue中有多少剩余的消息。用MessageSender来发送一条消息,这样即使我们有Listener来处理这条消息,但是由于每次都会抛出异常,超过限定次数后,被放置到了DLQ.bar中。我们检测DLQ.bar中的消息数量就可以知道ReDelivery功能是否正确。

运行测试,成功通过。这是日志信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
send: this is a message
Received: this is a message
there is JMS exception: process failed
Received: this is a message
there is JMS exception: process failed
Received: this is a message
there is JMS exception: process failed
Received: this is a message
there is JMS exception: process failed
Received: this is a message
there is JMS exception: process failed Process finished with exit code 0

本系列的全部示例代码请在https://github.com/huangbowen521/SpringJMSSample下载。

ActiveMQ第五弹:增加ReDelivery功能的更多相关文章

  1. 学习ASP.NET Core Razor 编程系列九——增加查询功能

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  2. 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(五)——实现注册功能

    使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(一)——创建应用 使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(二)——使用蓝图功能进行模块化 使用 Flask 框架写用 ...

  3. Delphi APP 開發入門(五)GPS 定位功能

    Delphi APP 開發入門(五)GPS 定位功能 分享: Share on facebookShare on twitterShare on google_plusone_share   閲讀次數 ...

  4. 关于『进击的Markdown』:第五弹

    关于『进击的Markdown』:第五弹 建议缩放90%食用 路漫漫其修远兮,吾将上下而求索.  我们要接受Mermaid的考验了呢  Markdown 语法真香(一如既往地安利) ( 进击吧!Mark ...

  5. 前端学习 第五弹: CSS (一)

    前端学习 第五弹: CSS (一) 创建css: <link rel="stylesheet" type="text/css" href="my ...

  6. 如何Windows分页控件中增加统计功能

    在我的博客里面,很多Winform程序里面都用到了分页处理,这样可以不管是在直接访问数据库的场景还是使用网络方式访问WCF服务获取数据,都能获得较好的效率,因此WInform程序里面的分页控件的使用是 ...

  7. 为ecshop红包增加”转赠”功能

    ecshop促销中使用红包激励用户购物,要想炒热活动,红包就需要有物以稀为贵的感觉.有人求有人送,这样红包之间的转赠有助于拉动第二梯队的顾客.但是如果已经把红包添加到自己的账户了怎么办?如果ecsho ...

  8. 给ecshop后台增加管理功能页面

    给ecshop后台增加管理功能页面 比如我们增加一个统计报表叫做 物流费用统计报表 放在后台“报表统计”栏目中 具体操作步骤: 第一步,我们要添加一个菜单到后台,然后设置语言项,最后设置权限,这样,后 ...

  9. [个人网站搭建]·Django增加评论功能(Python3)

    [个人网站搭建]·Django增加评论功能 个人主页--> https://xiaosongshine.github.io/ 个人网站搭建github地址:https://github.com/ ...

随机推荐

  1. 十步图解CSS的position

    CSS的positon,我想做为一个Web制作者来说都有碰到过,但至于对其是否真正的了解呢?那我就不也说了,至少我自己并不非常的了解其内核的运行.今天在Learn CSS Positioning in ...

  2. HDU 5673 Robot ——(卡特兰数)

    先推荐一个关于卡特兰数的博客:http://blog.csdn.net/hackbuteer1/article/details/7450250. 卡特兰数一个应用就是,卡特兰数的第n项表示,现在进栈和 ...

  3. Aborting commit: 'XXX' remains in conflict

    Aborting commit: 'XXX' remains in conflict 错误   本地也删除了一个文件夹,用svn 的repo-brower 删除了服务器上的对应的文件夹 ,再次comm ...

  4. POJ 1661 Help Jimmy LIS DP

    http://poj.org/problem?id=1661 对板按高度排序后. dp[i][0]表示现在站在第i块板上,向左跑了,的状态,记录下时间和其他信息. O(n^2)LIS: 唯一的麻烦就是 ...

  5. POJ 1113 - Wall 凸包

    此题为凸包问题模板题,题目中所给点均为整点,考虑到数据范围问题求norm()时先转换成double了,把norm()那句改成<vector>压栈即可求得凸包. 初次提交被坑得很惨,在GDB ...

  6. [原创]Matlab之按位操作

    在硬件语言Verilog中按位操作是相对容易的,在C语言中一样的用好逻辑符号“|”.“!”.“&”.“>>”等即可.但是在Matlab中一些类似的操作是判断或者逻辑用法,不能用在按 ...

  7. mormot THttpApiServer使用例子

    mormot THttpApiServer使用例子 THttpApiServer封装了WINDOWS的HTTPS.SYS. unit Unit1; interface uses Winapi.Wind ...

  8. <大学祭>

    我将20岁青涩的身影留在教室前的花坛边,远赴他乡,这些年过去了,他总是对我说别忘记我的梦: 我将那个沉静娇小的眼镜女孩印象留在第一排靠窗的座位,带着淡淡离愁而去,这些年过去了,她对我说,永恒的爱是心中 ...

  9. Dojo注意

    关于插件的加载: 回调函数中的顺序,是按照插件的顺序填写的,否则就很可能会插件加载出错.

  10. ThinkPHP 模板判断输出--Switch 标签

    ThinkPHP 模板引擎支持 switch 判断,根据不同情况输出不同的值,格式如下:<switch name="变量名" >    <case value=& ...