消费者本地事务

来源:互联网 发布:淘宝卖家骂人扣几分 编辑:程序博客网 时间:2024/06/06 17:10
之前在《实现应用内分布式事务管理(生产者)》章节已经重点介绍了生产者如何实现应用内的本地事务、分布式事务,对于消费者同样有事务管理的需求,本章节将重点介绍springboot下目前消费者本地事务。

本章概要
1、构建消费者基础工程;
2、事务验证;
3、尝试多种消息Response方式;
4、切换监听来源--由队列至发布订阅;
5、同步消息消费浅析;

构建消费者基础工程

1、POM依赖:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional><!-- optional=true,依赖不会传递,该项目依赖devtools;之后依赖SpringBoot1项目的项目如果想要使用devtools,需要重新引入 -->
</dependency>
<!-- end -->
<!-- activeMq support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!-- activeMq end -->
</dependencies>

2、设置链接地址:
# ACTIVEMQ
spring.activemq.broker-url=tcp://localhost:61626

3、启动工程类,并预注册一个队列bean实例:
package com.shf.activemq;import javax.jms.Queue;import org.apache.activemq.command.ActiveMQQueue;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.jms.annotation.EnableJms;@SpringBootApplication@EnableJmspublic class AppConsumer{public static void main(String[] args) throws Exception {        SpringApplication.run(AppConsumer.class, args);      }/** * 定义点对点队列 * @return */@Beanpublic Queue queue() {return new ActiveMQQueue("my.queue");}}
事务验证

首先来看看我们队列中已有的数据情况:

1、监听my.consuqueue队列,并在消费消息后生成my.queue中消息:
@Autowiredprivate JmsMessagingTemplate jmsMessagingTemplate;@Autowiredprivate Queue queue;/** * 1、队列中数据要么重试指定次数后出列进入DLQ死信队列要么不出列 * 2、如果在同一个@JmsListener注解监听方法体内,我们有不仅接受消息,同时也进行消息的发送,则一旦出现异常事务回滚,则无论是消费还是生产均能够事务回滚 * @param text * @throws Exception */@JmsListener(destination = "my.consuqueue")public void receiveQueue(String text) throws Exception {System.out.println("消费者:来源于生产者的消息:"+text);this.jmsMessagingTemplate.convertAndSend(this.queue, "生产者2辛苦生产的点对点消息成果");System.out.println("生产者2:辛苦生产的点对点消息成果");throw new Exception("出现异常了");}

预期:如果存在异常,则不会在my.queue生产消息也不会消费my.consuqueue中消息,如果没有异常则正常消费&生成:
1.1、通过throw 抛出异常,数据没有变化:

监听时间再长一点,其会在尝试6次失败后加入死信队列

1.2、取消异常,

小结,预期验证成功,本地事务生效:
a、队列中数据要么重试指定次数后出列进入DLQ死信队列要么不出列
b、如果在同一个@JmsListener注解监听方法体内,我们有不仅接受消息,同时也进行消息的发送,则一旦出现异常事务回滚,则无论是消费还是生产均能够事务回滚
c、在一定程度上我们通过自定义的响应队列实现了应答模式

2、在官方看到了一个@SendTo注解,其能够很好的实现异步应答模式,在1的数据基础上,我们继续验证:
/** * 通过@SendTo实现监听队列消息后同步发送其他消息,事务生效(监听消费、jmsMessagingTemplate发送、@SendTo3者在同一事务下) * @SendTo 发布的消息也为队列消息 * @param text * @return */@JmsListener(destination = "my.queue")@SendTo("status")public String listenQueue(String text) {System.out.println("消费者:来源于生产者2的点对点消息:"+text);this.jmsMessagingTemplate.convertAndSend(this.queue, "生产者辛苦生产的点对点消息成果");System.out.println("生产者:辛苦生产的点对点消息成果");System.out.println(1/0);    return "send status";}

预期:没有异常的情况下,my.queue被消费完,my.consuqueue队列生成3条消息,并且在status中也生产了3条消息;有异常的情况下,3个队列数据没有变化,3个队列处理在一个事务下。
2.1、抛出异常时,数据没有变化:

2.2、无异常,队列数据:

小结:
a、事务生效,@JmsListener、jmsMessagingTemplate、@SendTo 三者在同一事务下;
b、@SendTo能够方便的实现应答模式;

3、下面我们尝试下实体对象生产,首先定义一个User对象
class User implements Serializable{private static final long serialVersionUID = 4375692247237084682L;private String name;private int age;public User(String name,int age){this.name=name;this.age=age;}@Overridepublic String toString() {return "User [name=" + name + ", age=" + age + "]";}}
然后定义监听
/** * 监听到字符串状态消息后发出User对象消息,事务生效 * 对象实体一定需要序列化,默认仅Supported message payloads are: String, byte array, Map<String,?>, Serializable object. * @param text * @return */@JmsListener(destination = "status")@SendTo("user")public User listenStatus(String text) {System.out.println("消费者:来源于生产者的消息:"+text);User user=new User("帅帅",11);//System.out.println(1/0);    return user;}

小结:
a、我们生产的实体对象消息必须序列化,JMS默认Supported message payloads(消息体) are:String, byte array, Map<String,?>, Serializable object;
b、事务仍然生效;

4、消费对象实体
编写监听
/** * 监听直接获取User对象实体,出现异常通过控制台提示http://activemq.apache.org/objectmessage.html查看官方 * 需要设置spring.activemq.packages.trust-all(默认值为false)为true,也可以设置spring.activemq.packages.trusted指定包路径 * 如果自定义实现ActiveMQConnectionFactory则可以设置其trustAllPackages属性为true * @param text * @return */@JmsListener(destination = "user")@SendTo("user2")public User listenUser(User user) {System.out.println("消费者:来源于生产者的用户对象消息:"+user.toString());User user2=new User("帅帅",22);return user2;}

添加配置
# default value s false
spring.activemq.packages.trust-all=true

控制台打印消费的User对象

小结:
a、监听直接获取User对象实体,出现异常通过控制台提示http://activemq.apache.org/objectmessage.html查看官方需要设置spring.activemq.packages.trust-all(默认值为false)为true,也可以设置spring.activemq.packages.trusted指定包路径,如果自定义实现ActiveMQConnectionFactory则可以设置其trustAllPackages属性为true。

5、通过JmsResponse可以在运行中同比实现@sendTo功能:
/** * 通过JmsResponse可以在运行中同比实现@sendTo功能,如果我们需要在header中携带部分属性,则可以通过Message实现 * @param user * @return */@JmsListener(destination = "user2")public JmsResponse<Message<User>> JmsResponseUser(User user) {System.out.println("消费者:来源于生产者的用户对象消息:"+user.toString());    Message<User> response = MessageBuilder            .withPayload(user)//消息体            .setHeader("code", 1234)//消息头            .build();    return JmsResponse.forQueue(response, "headeruser");}

控制台

小结
a、通过JmsResponse可以实现@sendTo功能,并且可以通过Message携带Header信息;

6、监听读取header中的信息以及用户信息
/** * 监听读取header中的信息以及用户信息 * @param message */@JmsListener(destination = "headeruser")public void listenMessageHeader(Message<User> message) {System.out.println("消费者:来源于生产者的用户对象消息:"+message.getPayload().toString()+";header中的code信息:"+message.getHeaders().get("code"));}
控制台

小结:
a、header信息的读取同样通过Message即可。

7、之前所有的消息处理全部基于队列,在生产者模式下通过自定义JmsTemlate可以设置一个队列模板一个发布订阅模板,那么在消费者下,我们默认也是仅可监听队列消息,故我们需要调整配置:
# default value s false,just control queue message
spring.jms.pub-sub-domain=true
消费者监听:
/** * 需要设置spring.jms.pub-sub-domain为true方可监听发布订阅类消息 * 通过@SendTo实现监听消息后同步发送其他消息,事务生效(N次重试) * @SendTo 发布的消息也为发布订阅消息 * @param text * @return */@JmsListener(destination = "my.topic")@SendTo("replyTopic")public String listenTopic(String text) {System.out.println("消费者:来源于生产者的发布订阅消息:"+text);//System.out.println(1/0);    return "reply replyTopic";}

控制台

小结:
a、通过spring.jms.pub-sub-domain配置可以切换当前工程默认的监听类型,但同时却仅能监听一个类型消费。后续章节将通过自定义配置实现同时监听队列和发布订阅两个类型消息。
b、事务生效;

备注:通过JmsTemlate我们也可以很方便的实现应答模式,主要应用在同步场景下,通过Message的setJMSReplyTo方法传递接收应答的目的地,消费者获取到此目的地发出响应信息,生产者接收处理;

附录

本章节主要验证均采用了@JmsListener实现异步消息消费,通过JmsTemlate可以实现消息的同步消费处理,其并不在本章的范畴。
大致配置如下:
@Configurationpublic class JmsTemplateConfiguration {private final JmsProperties properties;private final ObjectProvider<DestinationResolver> destinationResolver;private final ObjectProvider<MessageConverter> messageConverter;public JmsTemplateConfiguration(JmsProperties properties,ObjectProvider<DestinationResolver> destinationResolver,ObjectProvider<MessageConverter> messageConverter) {this.properties = properties;this.destinationResolver = destinationResolver;this.messageConverter = messageConverter;}@Beanpublic JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);jmsTemplate.setPubSubDomain(this.properties.isPubSubDomain());DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();if (destinationResolver != null) {jmsTemplate.setDestinationResolver(destinationResolver);}MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();if (messageConverter != null) {jmsTemplate.setMessageConverter(messageConverter);}//deliveryMode, priority, timeToLive 的开关,要生效,必须配置为true,默认falsejmsTemplate.setExplicitQosEnabled(true);//DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);//默认不开启事务System.out.println("是否开启事务"+jmsTemplate.isSessionTransacted());//如果session带有事务,并且事务成功提交,则消息被自动签收。如果事务回滚,则消息会被再次传送。//jmsTemplate.setSessionTransacted(true);//不带事务的session的签收方式,取决于session的配置。//默认消息确认方式为1,即AUTO_ACKNOWLEDGESystem.out.println("是否消息确认方式"+jmsTemplate.getSessionAcknowledgeMode());//消息的应答方式,需要手动确认,此时SessionTransacted必须被设置为false,且为Session.CLIENT_ACKNOWLEDGE模式//Session.AUTO_ACKNOWLEDGE  消息自动签收           //Session.CLIENT_ACKNOWLEDGE  客户端调用acknowledge方法手动签收           //Session.DUPS_OK_ACKNOWLEDGE 不必必须签收,消息可能会重复发送  jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);return jmsTemplate;}}
此时如果我们配置的消息签收模式为客户端手动确认,则简单看了下源码,可能存在一定的问题

如果session支持事务则提交事务,反之如果是客户端确认其message不为空则签收确认,但此时可能会有异常发生,不能被默认签收,如果需要更好的显示执行手动签收,则需要继承JmsTemplate对其doReceive改造并新增显示acknowledge方法。



0 0
原创粉丝点击