spring jms 事务_Spring JMS:处理事务中的消息

spring jms 事务

1.引言

这篇文章将向您展示使用JMS异步接收消息期间使用者执行过程中的错误如何导致消息丢失。 然后,我将解释如何使用本地事务解决此问题。

您还将看到这种解决方案在某些情况下可能导致消息重复(例如,当它将消息保存到数据库中,然后侦听器执行失败时)。 发生这种情况的原因是因为JMS事务独立于其他事务资源(如DB)。 如果您的处理不是幂等的,或者您的应用程序不支持重复消息检测,那么您将不得不使用分布式事务。

分布式事务超出了本文的范围。 如果您对处理分布式事务感兴趣,可以阅读这篇有趣的文章。

我已经实现了一个再现以下情况的测试应用程序:

  1. 发送和接收消息:消费者将处理收到的消息,并将其存储到数据库中。

    生产者将消息发送到队列:

    发送1

    使用者从队列中检索消息并进行处理:

    发送2

  2. 消息处理之前发生错误:使用者检索消息,但是在将消息存储到DB之前执行失败。

    发送3

  3. 处理消息后发生错误:使用者检索消息,将其存储到DB,然后执行失败。

    发送4

  • 该应用程序的源代码可以在github上找到。

2.测试应用

测试应用程序执行两个测试类TestNotTransactedMessagingTestTransactedMessaging 。 这些类都将执行上述三种情况。

让我们看看在没有事务的情况下执行应用程序时的配置。

app-config.xml

应用程序配置。 基本上,它会在指定的包中进行检查以自动检测应用Bean:生产者和使用者。 它还配置了将在其中存储处理后的通知的内存数据库。

<context:component-scan base-package="xpadro.spring.jms.producer, xpadro.spring.jms.receiver"/><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg ref="dataSource"/>
</bean><jdbc:embedded-database id="dataSource"><jdbc:script location="classpath:db/schema.sql" />
</jdbc:embedded-database>

notx-jms-config.xml

配置JMS基础结构,该基础结构是:

  • 经纪人联系
  • JmsTemplate
  • 将通知发送到的队列
  • 侦听器容器,它将发送通知给侦听器以处理它们
<!-- Infrastructure -->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"><property name="brokerURL" value="vm://embedded?broker.persistent=false"/>
</bean><bean id="cachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory"><property name="targetConnectionFactory" ref="connectionFactory"/>
</bean><bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"><property name="connectionFactory" ref="cachingConnectionFactory"/><property name="defaultDestination" ref="incomingQueue"/>
</bean><!-- Destinations -->
<bean id="incomingQueue" class="org.apache.activemq.command.ActiveMQQueue"><constructor-arg value="incoming.queue"/>
</bean><!-- Listeners -->
<jms:listener-container connection-factory="connectionFactory"><jms:listener ref="notificationProcessor" destination="incoming.queue"/>
</jms:listener-container>

生产者仅使用jmsTemplate发送通知。

@Component("producer")
public class Producer {private static Logger logger = LoggerFactory.getLogger(Producer.class);@Autowiredprivate JmsTemplate jmsTemplate;public void convertAndSendMessage(String destination, Notification notification) {jmsTemplate.convertAndSend(destination, notification);logger.info("Sending notification | Id: "+notification.getId());}
}

侦听器负责从队列中检索通知,并将其存储到数据库中。

@Component("notificationProcessor")
public class NotificationProcessor implements MessageListener {private static Logger logger = LoggerFactory.getLogger(NotificationProcessor.class);@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic void onMessage(Message message) {try {Notification notification = (Notification) ((ObjectMessage) message).getObject();logger.info("Received notification | Id: "+notification.getId()+" | Redelivery: "+getDeliveryNumber(message));checkPreprocessException(notification);saveToBD(notification);checkPostprocessException(message, notification);} catch (JMSException e) {throw JmsUtils.convertJmsAccessException(e);}}	...
}

当id = 1的通知到达时, checkPreprocessException方法将抛出运行时异常。 这样,在将消息存储到数据库之前,我们将导致错误。

如果到达id = 2的通知, checkPostprocessException方法将引发异常,从而在将其存储到数据库后立即引起错误。

getDeliveryNumber方法返回消息已发送的次数。 这仅适用于事务,因为在侦听器处理失败导致回滚之后,代理将尝试重新发送消息。

最后, saveToDB方法非常明显。 它将通知存储到数据库。

您始终可以通过单击本文开头的链接来检查此应用程序的源代码。

3.测试没有交易的消息接收

我将启动两个测试类,一个不包含事务,另一个在本地事务中。 这两个类都扩展了一个基类,该基类加载了公共应用程序上下文并包含一些实用程序方法:

@ContextConfiguration(locations = {"/xpadro/spring/jms/config/app-config.xml"})
@DirtiesContext
public class TestBaseMessaging {protected static final String QUEUE_INCOMING = "incoming.queue";protected static final String QUEUE_DLQ = "ActiveMQ.DLQ";@Autowiredprotected JdbcTemplate jdbcTemplate;@Autowiredprotected JmsTemplate jmsTemplate;@Autowiredprotected Producer producer;@Beforepublic void prepareTest() {jdbcTemplate.update("delete from Notifications");}protected int getSavedNotifications() {return jdbcTemplate.queryForObject("select count(*) from Notifications", Integer.class);}protected int getMessagesInQueue(String queueName) {return jmsTemplate.browse(queueName, new BrowserCallback<Integer>() {@Overridepublic Integer doInJms(Session session, QueueBrowser browser) throws JMSException {Enumeration<?> messages = browser.getEnumeration();int total = 0;while (messages.hasMoreElements()) {messages.nextElement();total++;}return total;}});}
}

实用方法说明如下:

  • getSavedNotifications :返回存储到数据库的通知数。 我使用了queryForObject方法,因为自版本3.2.2开始建议使用该方法。 queryForInt方法已被弃用。
  • getMessagesInQueue :允许您检查指定队列中哪些消息仍在等待处理。 对于此测试,我们有兴趣知道仍有多少通知等待处理。

现在,让我向您展示第一个测试的代码( TestNotTransactedMessaging )。 此测试启动本文开头指示的3种情况。

@Test
public void testCorrectMessage() throws InterruptedException {Notification notification = new Notification(0, "notification to deliver correctly");producer.convertAndSendMessage(QUEUE_INCOMING, notification);Thread.sleep(6000);printResults();assertEquals(1, getSavedNotifications());assertEquals(0, getMessagesInQueue(QUEUE_INCOMING));
}@Test
public void testFailedAfterReceiveMessage() throws InterruptedException {Notification notification = new Notification(1, "notification to fail after receiving");producer.convertAndSendMessage(QUEUE_INCOMING, notification);Thread.sleep(6000);printResults();assertEquals(0, getSavedNotifications());assertEquals(0, getMessagesInQueue(QUEUE_INCOMING));
}@Test
public void testFailedAfterProcessingMessage() throws InterruptedException {Notification notification = new Notification(2, "notification to fail after processing");producer.convertAndSendMessage(QUEUE_INCOMING, notification);Thread.sleep(6000);printResults();assertEquals(1, getSavedNotifications());assertEquals(0, getMessagesInQueue(QUEUE_INCOMING));
}private void printResults() {logger.info("Total items in \"incoming\" queue: "+getMessagesInQueue(QUEUE_INCOMING));logger.info("Total items in DB: "+getSavedNotifications());
}

4,执行测试

好的,让我们执行测试,看看结果是什么:

testCorrectMessage输出:

Producer|Sending notification | Id: 0
NotificationProcessor|Received notification | Id: 0 | Redelivery: 1
TestNotTransactedMessaging|Total items in "incoming" queue: 0
TestNotTransactedMessaging|Total items in DB: 1

此处没有问题,因为消息已正确接收并存储到数据库,所以队列为空。

testFailedAfterReceiveMessage输出:

Producer|Sending notification | Id: 1
NotificationProcessor|Received notification | Id: 1 | Redelivery: 1
AbstractMessageListenerContainer|Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.RuntimeException: error after receiving message
TestNotTransactedMessaging|Total items in "incoming" queue: 0
TestNotTransactedMessaging|Total items in DB: 0

由于它在事务外部执行,因此使用确认模式(默认为自动)。 这意味着一旦调用onMessage方法并因此将其从队列中删除,就认为该消息已成功传递。 因为侦听器在将消息存储到DB之前失败,所以我们丢失了消息!

testFailedAfterProcessingMessage输出:

2013-08-22 18:39:09,906|Producer|Sending notification | Id: 2
2013-08-22 18:39:09,906|NotificationProcessor|Received notification | Id: 2 | Redelivery: 1
2013-08-22 18:39:09,906|AbstractMessageListenerContainer|Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.RuntimeException: error after processing message
2013-08-22 18:39:15,921|TestNotTransactedMessaging|Total items in "incoming" queue: 0
2013-08-22 18:39:15,921|TestNotTransactedMessaging|Total items in DB: 1

在这种情况下,在执行失败之前,已从队列(AUTO_ACKNOWLEDGE)中删除了该消息并将其存储到DB。

5,添加本地交易

通常,我们不允许像测试的第二种情况那样丢失消息,因此我们要做的是在本地事务中调用侦听器。 所做的更改非常简单,并不意味着从我们的应用程序中修改一行代码。 我们只需要更改配置文件。

为了测试这3种事务的情况,我将以下配置文件notx-jms-config.xml替换为:

tx-jms-config.xml

首先,我添加了在发生回滚的情况下进行的重新传递的数量(由于侦听器执行中的错误导致):

<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"><property name="brokerURL" value="vm://embedded?broker.persistent=false"/><property name="redeliveryPolicy"><bean class="org.apache.activemq.RedeliveryPolicy"><property name="maximumRedeliveries" value="4"/></bean></property>
</bean>

接下来,我指示侦听器将在事务内执行。 这可以通过修改侦听器容器定义来完成:

<jms:listener-container connection-factory="connectionFactory" acknowledge="transacted"><jms:listener ref="notificationProcessor" destination="incoming.queue"/>
</jms:listener-container>

这将导致在本地JMS事务中执行对侦听器的每次调用。 收到消息后,事务将开始。 如果侦听器执行失败,则消息接收将回滚。

这就是我们要做的一切。 让我们使用此配置启动测试。

6,测试交易中的消息接收

来自TestTransactedMessaging类的代码实际上与先前的测试相同。 唯一的区别是,它向DLQ(死信队列)添加了查询。 在事务内执行时,如果回退消息接收,则代理会将消息发送到此队列(在所有重新传递失败之后)。

我跳过了成功接收的输出,因为它不会带来任何新的变化。

testFailedAfterReceiveMessage输出:

Producer|Sending notification | Id: 1
NotificationProcessor|Received notification | Id: 1 | Redelivery: 1
AbstractMessageListenerContainer|Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.RuntimeException: error after receiving message
NotificationProcessor|Received notification | Id: 1 | Redelivery: 2
AbstractMessageListenerContainer|Execution of JMS message listener failed, and no ErrorHandler has been set.
...
java.lang.RuntimeException: error after receiving message
NotificationProcessor|Received notification | Id: 1 | Redelivery: 5
AbstractMessageListenerContainer|Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.RuntimeException: error after receiving message
TestTransactedMessaging|Total items in "incoming" queue: 0
TestTransactedMessaging|Total items in "dead letter" queue: 1
TestTransactedMessaging|Total items in DB: 0

如您所见,第一次接收失败,并且代理尝试将其重新发送四次(如maximumRedeliveries属性中所示)。 由于情况持续存在,因此消息已发送到特殊DLQ队列。 这样,我们不会丢失消息。

testFailedAfterProcessingMessage输出:

Producer|Sending notification | Id: 2
NotificationProcessor|Received notification | Id: 2 | Redelivery: 1
AbstractMessageListenerContainer|Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.RuntimeException: error after processing message
NotificationProcessor|Received notification | Id: 2 | Redelivery: 2
TestTransactedMessaging|Total items in "incoming" queue: 0
TestTransactedMessaging|Total items in "dead letter" queue: 0
TestTransactedMessaging|Total items in DB: 2

在这种情况下,这是发生的情况:

  1. 侦听器检索到消息
  2. 它将消息存储到数据库
  3. 侦听器执行失败
  4. 代理重新发送消息。 由于情况已解决,因此侦听器将消息再次存储到DB。 该消息已重复。

7,结论

将本地事务添加到消息接收中可避免丢失消息。 我们必须考虑的是,可能会出现重复的消息,因此我们的侦听器将必须检测到它,否则我们的处理必须是幂等的才能再次进行处理。 如果这不可能,我们将不得不进行分布式事务,因为它们支持涉及不同资源的事务。

参考: Spring JMS:在XavierPadró的Blog博客上处理来自JCG合作伙伴 Xavier Padro的事务内的消息 。

翻译自: https://www.javacodegeeks.com/2014/02/spring-jms-processing-messages-within-transactions.html

spring jms 事务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/343866.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用AWS Lambda,S3和AWS CloudFront进行动态内容缓存

快速提供内容对于任何网站或应用程序具有更好的客户体验至关重要。 如果您将网站或应用程序托管在AWS Cloud中&#xff0c;那么无论从何处访问应用程序&#xff0c;都可以以较低的延迟快速提供内容。 AWS提供了CloudFront服务&#xff0c;用于将内容缓存在每个用户地理位置本地…

数据光端机设备性能指标介绍

作为安防监控工程&#xff0c;设备的可靠性应该是第一考虑要素。而数据光端机设备的可靠性是设备厂商在产品设计时就必需考虑的&#xff0c;但是&#xff0c;有些厂商可能会因为某些原因而不愿做或不知道怎么做这方面的工作&#xff0c;在这里着重从工程的角度简单地讨论以下问…

光电转换器有什么作用?光纤收发器如何保养?

光电转换器可以使原来的快速以太网平滑升级&#xff0c;并能充分保护用户原来的网络资源&#xff0c;它也可以称为光纤收发器。光电转换器可以实现交换机和计算机之间的互联&#xff0c;也可以作为传输中继&#xff0c;还可以进行单多模转换。光纤收发器在应用过程中&#xff0…

html中输出PHP的下拉列表,html中关于下拉列表select的图文代码详解

HTML中的下拉列表&#xff1a;Html代码VolvoSaabOpelAudi其中select是显示一个下拉列表(drop down list)出来&#xff0c;option是下拉列表中的项目(item)&#xff0c;而option的文本内容(text content)是下拉列表项目中显示到页面上的值&#xff0c;value是真正需要提交到服务…

光纤收发器tx和rx的区别?

光纤收发器&#xff0c;是一种将短距离的双绞线电信号和长距离的光信号进行互换的以太网传输媒体转换单元&#xff0c;在很多地方也被称之为光电转换器&#xff08;Fiber Converter&#xff09;。产品一般应用在以太网电缆无法覆盖、必须使用光纤来延长传输距离的实际网络环境中…

光纤收发器元件级和整机测试内容介绍

对光纤收发器的测试可分为元件级和整机测试&#xff0c;元件级测试主要包括对光纤收发器内部关键器件在电工作的电性能测试。整机测试主要指将光纤收发器接入到以太局域网中&#xff0c;测试整机的功能、性能和特性。那么&#xff0c;具体要怎样测试光纤收发器才是一次完整的测…

如何判断光纤收发器是否有问题?

一般情况下&#xff0c;光纤收发器或光模块的发光功率如下&#xff1a;多模在10db--18db之间&#xff1b;单模20公里在-8db--15db之间&#xff1b;而单模60公里则在-5db--12db之间。但如是光纤收发器的发光功率出现在-30db--45db之间&#xff0c;那么&#xff0c;很有可能这个光…

activemq 实例_在一台计算机上运行多个ActiveMQ实例

activemq 实例几周前&#xff0c;我再次通过Mule ESB解决方案将Apache ActiveMQ用作JMS提供程序。 由于使用ActiveMQ已经有几年了&#xff0c;所以我认为最好检查一些&#xff08;新&#xff09;功能&#xff0c;例如故障转移传输和其他群集功能 。 为了能够测试这些最后的东西…

什么是光纤收发器?光纤收发器作用是什么?

许多朋友一听到光纤收发器这五个大字总会有困惑&#xff0c;比如说什么是光纤收发器&#xff0c;光纤收发器又有什么作用等等疑问。那么&#xff0c;什么是光纤收发器呢&#xff1f;光纤收发器又有什么作用呢&#xff1f;接下来我们就跟随飞畅科技的小编一起来详细了解下吧&…

在15分钟内使用Spring Boot和Spring Security构建一个Web应用程序

“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕&#xff1f; 尝试使用Okta API进行托管身份验证&#xff0c;授权和多因素身份验证。 开发人员知道保护Web应用程序安全可能会很麻烦。 正确地做是很难的。 最糟糕的是&…

音频光端机的必备要素有哪些?

音频光端机就是发射端把传统的音频模拟信号转换成光信号&#xff0c;通过光纤传输到接收端&#xff0c;在接收端再转换成模拟信号的一种音频设备。那么&#xff0c;音频光端机有哪些必备要素呢&#xff1f;音频光端机的原理是什么呢&#xff1f;接下来我们就一起来详细了解下吧…

16路开关量光端机产品功能及接线图

飞畅科技生产的16路开关量光端机&#xff0c;可以使开关量信号通过光缆在光纤上传输双向控制。全数字光传输通道&#xff0c;确保高质量的信号传输。面板上有电源指示灯、光信号指示灯数据信号指示灯&#xff0c;可以直观的检测电源、光信号、数据信号状态。单向系列开关量光端…

matlab查找替换指令,使用matlab GUI在.txt文件中编辑特定数字使用搜索/替换

使用matlab GUI在.txt文件中编辑特定数字使用搜索/替换 我有一堆不同的模板.txt文件&#xff0c;我想由Matlab中的用户访问。这些模板看起来像这样&#xff0c;但是有一些主要的区别&#xff0c;我将解释&#xff1a;LOAD BOX 1 SUBJ M1_299633_D295158_JUN191910_Aut_ERROR2 E…

电话光端机安装步骤详解

光端机是光信号传输的终端设备&#xff0c;其主要作用是把传统的电话信号转换成光信号并在光纤上传输的设备&#xff0c;是随着光线技术的发展而出现的光端机产品。那么&#xff0c;电话光端机是怎么安装的呢&#xff1f;今天就由飞畅科技的小编来为大家介绍下电话光端机的详细…

php 站内搜索引擎,淘特站内搜索引擎For PhoCmsV9.4

一、产品特点&#xff1a;该产品是淘特站内搜索引擎For PhpCmsV9特别定制版&#xff0c;系统安装后&#xff0c;就可以索引、模糊查询PhpCmsV9的文章数据了(无需连接数据库哦)二、产品下载&#xff1a;三、使用说明&#xff1a;提示&#xff1a;本系统已和tomcat7打包&#xff…

交换机组网常见九大故障问题

交换机是一种用于电信号转发的网络设备。它可以为接入交换机的任意两个网络节点提供独享的电信号通路。最常见的交换机是以太网交换机。其他常见的还有电话语音交换机、光纤交换机等。那么&#xff0c;交换机组网常见九大故障问题有哪些呢&#xff1f;接下来我们就跟随飞畅科技…

python qtdesigner安装,PyCharm+Qt Designer+PyUIC安装配置教程详解

Qt Designer用于像VC的MFC一样拖放、设计控件PyUIC用于将Qt Designer生成的.ui文件转换成.py文件Qt Designer和PyUIC都包含在PyQt5中&#xff0c;所以我们只需要安装PyQt5塻块然后再指定Qt Designer和PyUIC即可为了避免篇幅过长&#xff0c;本文只讲安装配置&#xff0c;使用可…

可网管交换机的三种管理方式介绍

交换机的按是否可网管&#xff0c;分为可网管交换机和不可网管交换机&#xff0c;可网管交换机可以通过以下几种途径进行管理&#xff1a;通过RS-232串行口&#xff08;或并行口&#xff09;管理、通过网络浏览器管理和通过网络管理软件管理。接下来就由杭州飞畅科技来为大家详…

ping cat.flag.php,关于2020年强网杯-强网先锋-主动的赛题解析

原标题&#xff1a;关于2020年强网杯-强网先锋-主动的赛题解析一、基本信息(总概述)本题涉及知识点&#xff1a;命令执行正则匹配linux命令绕过二、基本环境和工具Linux系统PHPApacheFirefoxIndex.phpFlag.php三、Writeup1、根据题目要求还原实验环境首先在Linux虚拟机上安装Ap…

使用Prometheus发现在Kubernetes上运行的应用程序

Prometheus支持抓取应用程序的多个实例。 由于其IP地址会发生变化&#xff0c;因此需要动态发现在协调环境中运行的应用程序。 可以将Prometheus配置为使用Kubernetes API动态地发现正在运行的实例列表中的更改。 下面显示了一个简单的Prometheus示例&#xff0c;该示例对具有…