实现要点: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实现分布式事务的更多相关文章

  1. 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存

    原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...

  2. RabbitMQ解决分布式事务

    案例:经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. RabbitMQ解决分布式事务原理: 采用最终 ...

  3. 利用redis实现分布式事务锁,解决高并发环境下库存扣减

    利用redis实现分布式事务锁,解决高并发环境下库存扣减   问题描述: 某电商平台,首发一款新品手机,每人限购2台,预计会有10W的并发,在该情况下,如果扣减库存,保证不会超卖 解决方案一 利用数据 ...

  4. 使用RabbitMQ实现分布式事务

    RabbitMQ解决分布式事务思路: 案例: 经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. Rab ...

  5. 关于利用MQ实现分布式事务的想法【转】

    转自:https://www.jianshu.com/p/bafb09954f18 假设:消息服务不丢消息 场景 服务A 服务B 服务C 消息服务Q 伪代码 服务A中 transaction{ A本地 ...

  6. RabbitMq解决分布式事物

    一.RabbitMQ解决分布式事务思路: 案例: 经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. 二 ...

  7. springcloud分布式事务终极探讨

    2018阿里云全部产品优惠券(好东东,强烈推荐)领取地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userC ...

  8. Spring Cloud异步场景分布式事务怎样做?试试RocketMQ

    一.背景 在微服务架构中,我们常常使用异步化的手段来提升系统的 吞吐量 和 解耦 上下游,而构建异步架构最常用的手段就是使用 消息队列(MQ),那异步架构怎样才能实现数据一致性呢?本文主要介绍如何使用 ...

  9. Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案

    Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案 说明:Java生鲜电商平台中由于采用了微服务架构进行业务的处理,买家,卖家,配送,销售,供应商等进行服务化,但是不可避免存在 ...

随机推荐

  1. face-morpher过程函数分析

    01 dlib.get_frontal_face_detector #功能:人脸检测画框#参数:无#返回值:默认的人脸检测器 02 points1.astype 转换数组的数据类型 03 np.mea ...

  2. 在mac中安装tmux

    在mac 中安装Tmux: 在终端输入如下命令: brew  install tmux Tmux 的快捷键前缀(Prefix) 为了使自身的快捷键和其他软件的快捷键互不干扰,Tmux 提供了一个快捷键 ...

  3. leetcode 11. Container With Most Water 、42. Trapping Rain Water 、238. Product of Array Except Self 、407. Trapping Rain Water II

    11. Container With Most Water https://www.cnblogs.com/grandyang/p/4455109.html 用双指针向中间滑动,较小的高度就作为当前情 ...

  4. Vue非父子组件传值

    <template> <div id="app"> <v-home></v-home> <br> <hr> ...

  5. 六、postman做ui测试

    一.思路 发请求去拿到网站的响应——>html 解析html标签,判断一些元素是否显示 二.目的 快速检查ui是否正确 三.用到的库 Cheerio https://cheerios.js.or ...

  6. C# 使用 Dapper 实现 SQLite 增删改查

    Dapper 是一款非常不错的轻型 ORM 框架,使用起来非常方便,经常使用 EF 框架的人几乎感觉不到差别,下面是自己写的 Sqlite 通用帮助类: 数据连接类: public class SQL ...

  7. Java二维数组的应用

    package com.imooc; public class ArrayDemo5 { public static void main(String[] args) { //二维数组的声明和创建: ...

  8. jsp、freemarker、velocity、thymeleaf

    1.概述在java领域,表现层技术主要有三种, (1)jsp; (2)freemarker; (3)velocity; (4)thymeleaf; 2.jsp优点: 1.功能强大,可以写java代码 ...

  9. python 调用java脚本的加密(没试过,先记录在此)

    http://lemfix.com/topics/344 前言 自动化测试应用越来越多了,尤其是接口自动化测试. 在接口测试数据传递方面,很多公司都会选择对请求数据进行加密处理. 而目前为主,大部分公 ...

  10. shell脚本中执行mysql sql脚本文件并传递参数

    1 shell 文件内容替换 sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换.删除.新增.选取等特定工作,下面先了解一下sed的用法. 调用sed命 ...