实现要点: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. mediacoder固定质量CRF

    视频编码:crf 与 bitrate 对照表 CRF(constant rate factor)就是x264/x265下压制视频的一种恒定量化值的编码方式,码率不恒定.其实就相当于vbr1pass.采 ...

  2. MiniUI表单验证总结

    原文地址:https://www.cnblogs.com/wllcs/p/5607890.html 1,页面效果图 2,代码实现   <!DOCTYPE html PUBLIC "-/ ...

  3. docker 安装redis 并配置外网可以访问

    1, docker 拉去最新版本的redis docker pull redis #后面可以带上tag号, 默认拉取最新版本 2, docker安装redis container 安装之前去定义我们的 ...

  4. 海思 Hi3516A Hi3518E V200 芯片介绍

    海康是生产监控摄像头和硬盘录像机的,海思是提供机器里芯片的,海思属于华为的. http://www.hisilicon.com/en/Products/ProductList/Surveillance ...

  5. Typescript 介绍和安装编译

    一. Typescript 介绍 1. TypeScript 是由微软开发的一款开源的编程语言. 2. TypeScript 是 Javascript 的超级,遵循最新的 ES6.Es5 规范.Typ ...

  6. 算法习题---5.4反片语(Uva156)

    一:题目 输入一些单词,找出所有满足以下条件的单词:该单词不能通过字母重排得到输入文本中的另外一个单词.在判断是否满足条件时,字母不区分大小写,但在输出时应该保留输入中的大小写,按字典序进行排列 将输 ...

  7. 一行命令学会全基因组关联分析(GWAS)的meta分析

    为什么需要做meta分析 群体分层是GWAS研究中一个比较常见的假阳性来源. 也就是说,如果数据存在群体分层,却不加以控制,那么很容易得到一堆假阳性位点. 当群体出现分层时,常规手段就是将分层的群体独 ...

  8. matplot中的对象

    figure:图表,可以理解为一个空间,二维情况下是一个平面 axes:坐标系,空间中的坐标系,一个空间可以有多个坐标系 axis:坐标轴,坐标系中的一个坐标轴,一个坐标轴只属于一个坐标系 画点:sc ...

  9. Swift细节记录<一>

    1.全局变量记录: import UIKit class HHTSwitchGlobalData: NSObject { var isWaiterAutoPop: Bool = true privat ...

  10. 使用sql语句创建和删除约束示例代码

    使用sql语句创建和删除约束  约束类型 主键约束(Primary Key constraint) --:要求主键列数据唯一,并且不允许为空.  唯一约束(Unique constraint) --: ...