实现要点: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. Mybatis自定义控制台打印sql的日志工具

    调试mybatis源码时,想要更改日志的的实现工具,首先需要了解其原理. 源码包里有这部分的解释,翻译如下: Mybatis 的内置日志工厂提供日志功能,内置日志工厂将日志交给以下其中一种工具作代理: ...

  2. Javascript自定义事件功能与用法实例分析

    原文地址:https://www.jb51.net/article/127776.htm 本文实例讲述了javascript自定义事件功能与用法.分享给大家供大家参考,具体如下: 概述 自定义事件很难 ...

  3. layuiadmin(iframe)常用问题集锦

    1. 弹出窗口中传值给父层表单 table.on('tool(tableList)', function(obj){ var selected = obj.data; var layEvent = o ...

  4. python中修改列表元素的方法

    一.在for循环中直接更改列表中元素的值不会起作用: 如: l = list(range(10)[::2]) print (l) for n in l: n = 0 print (l) 运行结果: [ ...

  5. 设置pycharm文件默认换行符onfiguring Line Separators

    http://www.jetbrains.com/help/pycharm/2016.2/configuring-line-separators.html PyCharm makes it possi ...

  6. 教孩子学编程 Python

    教孩子学编程   Python 目录 第1 章 Python 基础:认识环境 111 认识Python 312 用Python 编写程序 513 运行Python 程序 514 本章小结 615 编程 ...

  7. word xml 各个标签含义

    @参考文章 <w:p> <!--表示一个段落--> <w:val > <!--表示一个值--> <w:r> <!--表示一个样式串,指 ...

  8. [Log4j使用教程] JavaSE/JavaEE/SpringMVC中使用Log4j

    要想使用Log4j, 首先需要下载到Log4j的jar, Download: http://www.apache.org/dyn/closer.cgi/logging/log4j/1.2.17/log ...

  9. Angular cookies

    参考地址:https://stackoverflow.com/questions/34298133/angular-cookies/36093800#36093800 @Component({ sel ...

  10. consul服务注册与服务发现的巨坑

    最近使用consul作为项目的服务注册与服务发现的基础功能.在塔建集群使用中遇到一些坑,下面一个个的记录下来. consul集群多node consul集群的node也就是我们所说的consul实例. ...