利用RabbitMQ实现分布式事务
实现要点:1、构建本地消息表及定时任务,确保消息可靠发送;2、RabbitMQ可靠消费;3、redis保证幂等
两个服务:订单服务和消息服务
订单服务消息可靠发送
使用springboot构建项目,相关代码如下
spring:
datasource:
druid:
url: jdbc:postgresql://127.0.0.1:5432/test01?characterEncoding=utf-8
username: admin
password: 123456
driver-class-name: org.postgresql.Driver
initial-size: 1
max-active: 20
max-wait: 6000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000
min-idle: 1
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
rabbitmq:
username: guest
password: guest
host: 127.0.0.1
virtual-host: /
port: 5672
publisher-confirms: true
server:
port: 8087
logging:
level:
org.springframework.jdbc.core.JdbcTemplate: DEBUG
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
package com.jlwj.mqtransaction.bean; import lombok.Data; import java.io.Serializable; /**
* @author hehang on 2019-06-27
* @descriptionsdf
*/ @Data
public class OrderBean implements Serializable { private String orderNo;
private String orderInfo;
}
package com.jlwj.mqtransaction.service; import com.alibaba.fastjson.JSON;
import com.jlwj.mqtransaction.bean.OrderBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; /**
* @author hehang on 2019-07-01
* @description发动消息到MQ
*/
@Service
@Slf4j
public class RabbitMQService { @Autowired
private RabbitTemplate rabbitTemplate; @Autowired
private JdbcTemplate jdbcTemplate; @PostConstruct
private void initRabbitTemplate(){
//设置消息发送确认回调,发送成功后更新消息表状态
rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> {
log.info(String.valueOf(ack));
if(ack){
String sql = "update t_confirm set send_status = ? where id = ?";
jdbcTemplate.update(sql,1,correlationData.getId());
}
});
}
public void sendMessage(OrderBean orderBean){
rabbitTemplate.convertAndSend("orderExchange","orderRoutingKey", JSON.toJSONString(orderBean),
message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);return message;},
new CorrelationData(orderBean.getOrderNo()));
} }
package com.jlwj.mqtransaction.service; import com.alibaba.fastjson.JSON;
import com.jlwj.mqtransaction.bean.OrderBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; /**
* @author hehang on 2019-06-27
* @description订单服务,简单期间不再写接口
*/ @Service
@Slf4j
public class OrderService { @Autowired
private JdbcTemplate jdbcTemplate; @Autowired
private RabbitMQService rabbitMQService; //注意加事务注解
@Transactional(propagation = Propagation.REQUIRED)
public void save(OrderBean orderBean){
String sql1 = "insert into t_order(order_no,order_info) values (?,?)";
String sql2 = "insert into t_confirm(id,message_info,send_status) values (?,?,?)";
jdbcTemplate.update(sql1,orderBean.getOrderNo(),orderBean.getOrderInfo());
jdbcTemplate.update(sql2,orderBean.getOrderNo(), JSON.toJSONString(orderBean),0);
rabbitMQService.sendMessage(orderBean);
} }
package com.jlwj.mqtransaction.service; import com.jlwj.mqtransaction.bean.OrderBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import java.util.List; /**
* @author hehang on 2019-07-01
* @description 定时扫描confirm表,重复发送未发送的消息
*/
@Service
@Slf4j
public class OrderScheduleService { @Autowired
private JdbcTemplate jdbcTemplate; @Autowired
private RabbitMQService rabbitMQService; //定时扫描记录表,将发送状态为0的消息再次发送,甚至可以记录重发次数,必要时人工干预,生产环境中需要单独部署定时任务
@Scheduled(cron ="30/30 * * * * ?" )
public void scanOrder(){
log.info("定时扫面confirm表");
String sql = "select o.* from t_order o join t_confirm c on o.order_no = c.id where c.send_status = 0";
List<OrderBean> orderBeanList = jdbcTemplate.queryForList(sql, OrderBean.class);
for (OrderBean orderBean : orderBeanList) {
rabbitMQService.sendMessage(orderBean);
}
}
}
消息服务相关代码
spring:
rabbitmq:
username: guest
password: guest
host: 127.0.0.1
virtual-host: /
port: 5672
listener:
simple:
acknowledge-mode: manual
redis:
host: 127.0.0.1
port: 6379
timeout: 5000
jedis:
pool:
max-idle: 8
min-idle: 0
max-active: 8
max-wait: 1
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
package com.jlwj.messageservice.config; import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; /**
* @author hehang on 2019-06-27
* @descriptionmq配置类
*/
@Configuration
public class RabbitConfig { /**
* 死信队列
* @return
*/
@Bean
public Queue dlQueue(){
return QueueBuilder.durable("dlQueue")
.build();
} @Bean
public DirectExchange dlExchange(){
return (DirectExchange) ExchangeBuilder.directExchange("dlExchange").build();
}
@Bean
public Binding dlMessageBinding(){
return BindingBuilder.bind(dlQueue()).to(dlExchange()).with("dlRoutingKey");
} @Bean
public DirectExchange messageDirectExchange() {
return (DirectExchange) ExchangeBuilder.directExchange("orderExchange")
.durable(true)
.build();
} @Bean
public Queue messageQueue() {
return QueueBuilder.durable("orderQueue")
//配置死信
.withArgument("x-dead-letter-exchange","dlExchange")
.withArgument("x-dead-letter-routing-key","dlRoutingKey")
.build();
} @Bean
public Binding messageBinding() {
return BindingBuilder.bind(messageQueue())
.to(messageDirectExchange())
.with("orderRoutingKey");
} }
package com.jlwj.messageservice.listener; import com.alibaba.fastjson.JSON;
import com.jlwj.messageservice.bean.OrderBean;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component; import java.io.IOException; /**
* @author hehang on 2019-06-28
* @description订单监听
*/
@Component
@Slf4j public class OrderListener { @Autowired
private StringRedisTemplate stringRedisTemplate; @RabbitListener(queues = "orderQueue")
public void HandlerMessage(Channel channel, @Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long tag,
@Header(AmqpHeaders.REDELIVERED) boolean reDelivered ) throws IOException {
log.info(message);
OrderBean orderBean = JSON.parseObject(message,OrderBean.class);
try {
log.info("收到的消息为{}",JSON.toJSONString(orderBean));
//保证幂等性
if(stringRedisTemplate.opsForValue().get(orderBean.getOrderNo())==null){
sendMessage(orderBean);
stringRedisTemplate.opsForValue().set(orderBean.getOrderNo(),"1");
}
channel.basicAck(tag,false);
} catch (Exception e) {
if(reDelivered){
log.info("消息已重复处理失败:{}",message);
channel.basicReject(tag,false);
}else{
log.error("消息处理失败",e);
//重新入队一次
channel.basicNack(tag,false,true);
} }
} private void sendMessage(OrderBean orderBean)throws Exception{
if(orderBean.getOrderNo().equals("0007")){
int a =3/0;
}
log.info("模拟发送短信");
}
}
package com.jlwj.messageservice.listener; import com.alibaba.fastjson.JSON;
import com.jlwj.messageservice.bean.OrderBean;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component; import java.io.IOException; /**
* @author hehang on 2019-06-28
* @description订单监听
*/
@Component
@Slf4j public class DlListener { @Autowired
private StringRedisTemplate stringRedisTemplate; @RabbitListener(queues = "dlQueue")
public void HandlerMessage(Channel channel, Message message, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
log.info(new String(message.getBody()));
//人工处理死信队列中的消息
handlerDl(new String(message.getBody()));
channel.basicAck(tag,false);
} private void handlerDl(String message){
log.info("发送邮件,请求人工干预:{}",message);
}
}
利用RabbitMQ实现分布式事务的更多相关文章
- 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存
原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...
- RabbitMQ解决分布式事务
案例:经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. RabbitMQ解决分布式事务原理: 采用最终 ...
- 利用redis实现分布式事务锁,解决高并发环境下库存扣减
利用redis实现分布式事务锁,解决高并发环境下库存扣减 问题描述: 某电商平台,首发一款新品手机,每人限购2台,预计会有10W的并发,在该情况下,如果扣减库存,保证不会超卖 解决方案一 利用数据 ...
- 使用RabbitMQ实现分布式事务
RabbitMQ解决分布式事务思路: 案例: 经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. Rab ...
- 关于利用MQ实现分布式事务的想法【转】
转自:https://www.jianshu.com/p/bafb09954f18 假设:消息服务不丢消息 场景 服务A 服务B 服务C 消息服务Q 伪代码 服务A中 transaction{ A本地 ...
- RabbitMq解决分布式事物
一.RabbitMQ解决分布式事务思路: 案例: 经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. 二 ...
- springcloud分布式事务终极探讨
2018阿里云全部产品优惠券(好东东,强烈推荐)领取地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userC ...
- Spring Cloud异步场景分布式事务怎样做?试试RocketMQ
一.背景 在微服务架构中,我们常常使用异步化的手段来提升系统的 吞吐量 和 解耦 上下游,而构建异步架构最常用的手段就是使用 消息队列(MQ),那异步架构怎样才能实现数据一致性呢?本文主要介绍如何使用 ...
- Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案
Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案 说明:Java生鲜电商平台中由于采用了微服务架构进行业务的处理,买家,卖家,配送,销售,供应商等进行服务化,但是不可避免存在 ...
随机推荐
- Mybatis自定义控制台打印sql的日志工具
调试mybatis源码时,想要更改日志的的实现工具,首先需要了解其原理. 源码包里有这部分的解释,翻译如下: Mybatis 的内置日志工厂提供日志功能,内置日志工厂将日志交给以下其中一种工具作代理: ...
- Javascript自定义事件功能与用法实例分析
原文地址:https://www.jb51.net/article/127776.htm 本文实例讲述了javascript自定义事件功能与用法.分享给大家供大家参考,具体如下: 概述 自定义事件很难 ...
- layuiadmin(iframe)常用问题集锦
1. 弹出窗口中传值给父层表单 table.on('tool(tableList)', function(obj){ var selected = obj.data; var layEvent = o ...
- python中修改列表元素的方法
一.在for循环中直接更改列表中元素的值不会起作用: 如: l = list(range(10)[::2]) print (l) for n in l: n = 0 print (l) 运行结果: [ ...
- 设置pycharm文件默认换行符onfiguring Line Separators
http://www.jetbrains.com/help/pycharm/2016.2/configuring-line-separators.html PyCharm makes it possi ...
- 教孩子学编程 Python
教孩子学编程 Python 目录 第1 章 Python 基础:认识环境 111 认识Python 312 用Python 编写程序 513 运行Python 程序 514 本章小结 615 编程 ...
- word xml 各个标签含义
@参考文章 <w:p> <!--表示一个段落--> <w:val > <!--表示一个值--> <w:r> <!--表示一个样式串,指 ...
- [Log4j使用教程] JavaSE/JavaEE/SpringMVC中使用Log4j
要想使用Log4j, 首先需要下载到Log4j的jar, Download: http://www.apache.org/dyn/closer.cgi/logging/log4j/1.2.17/log ...
- Angular cookies
参考地址:https://stackoverflow.com/questions/34298133/angular-cookies/36093800#36093800 @Component({ sel ...
- consul服务注册与服务发现的巨坑
最近使用consul作为项目的服务注册与服务发现的基础功能.在塔建集群使用中遇到一些坑,下面一个个的记录下来. consul集群多node consul集群的node也就是我们所说的consul实例. ...