源于蚂蚁课堂的学习,点击这里查看(老余很给力)
MQ背景
对于例如发送邮件或短信等行为,传统做法往往是自上而下执行,这样一来,增加用户等待响应时间,严重影响用户体验。
之后,开始将这些延时操作放入异步线程去处理,但是这样会增加CPU的开销。
故消息中间件横空出世,很好地解决了这一痛点,实现异步、解耦、流量削峰等功能
市面主流的MQ
ActiveMQ
历史悠久的开源项目,是Apache下的一个子项目。
已经在很多产品中得到应用,实现了JMS1.1规范,可以和spring-jms轻松融合,实现了多种协议,不够轻巧(源代码比RocketMQ多),
支持持久化到数据库,对队列数较多的情况支持不好。
RabbitMQ
结合erlang语言本身的并发优势,支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它变的非常重量级,
更适合于企业级的开发。
RocketMQ
阿里系下开源的一款分布式、队列模型的消息中间件,原名Metaq,3.0版本名称改为RocketMQ,是阿里参照kafka设计思想使用java
实现的一套mq。同时将阿里系内部多款mq产品(Notify、metaq)进行整合,只维护核心功能,去除了所有其他运行时依赖,保证核心功能
最简化,在此基础上配合阿里上述其他开源产品实现不同场景下mq的架构,目前主要多用于订单交易系统。
Kafka
Apache下的一个子项目,使用scala实现的一个高性能分布式Publish/Subscribe消息队列系统,具有以下特性:
高吞吐:在一台普通的服务器上既可以达到10W/s的吞吐速率;
高堆积:支持topic下消费者较长时间离线,消息堆积量大;
RabitMQ环境的基本安装 (windows)
1.下载并安装erlang,下载地址:http://www.erlang.org/download
2.配置erlang环境变量信息
新增环境变量ERLANG_HOME=erlang的安装地址
将%ERLANG_HOME%\bin加入到path中
3.下载并安装RabbitMQ,下载地址:http://www.rabbitmq.com/download.html
注意: RabbitMQ 它依赖于Erlang,需要先安装Erlang。
RabitMQ管理平台中心
RabbitMQ 管理平台地址 http://127.0.0.1:15672
默认账号:guest/guest 用户可以自己创建新的账号
Virtual Hosts:
每个VirtualHost相当一个相对独立的RabbitMQ服务器,VirtualHost之间是相互隔离的。exchange、queue、message不能互通。
默认的端口15672:rabbitmq管理平台端口号
默认的端口5672: rabbitmq消息中间内部通讯的端口
默认的端口号25672 rabbitmq集群的端口号
快速入门RabbitMQ简单队列
Maven依赖
com.rabbitmq
amqp-client
3.6.5
获取连接
public class RabitMQConnection {
/**
* 获取连接
*
* @return
*/
public static Connection getConnection() throws IOException, TimeoutException {
// 1.创建连接
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接地址
connectionFactory.setHost("127.0.0.1");
// 3.设置端口号:
connectionFactory.setPort(5672);
// 4.设置账号和密码
connectionFactory.setUsername("yanxiaohui");
connectionFactory.setPassword("yanxiaohui");
// 5.设置VirtualHost
connectionFactory.setVirtualHost("/yxh");
return connectionFactory.newConnection();
}
}
生产者
public class Producer {
private static final String QUEUE_NAME = "rabbit_mq_demo";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
// 1.创建我们的连接
Connection connection = RabitMQConnection.getConnection();
// 2.创建我们通道
Channel channel = connection.createChannel();
// 开启了确认消息机制
channel.confirmSelect();
String msg = "这是一个mq的入门案例";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
if (channel.waitForConfirms()) {
System.out.println("发送消息成功");
} else {
System.out.println("发送消息失败");
}
channel.close();
connection.close();
}
}
消费者
public class Consumer {
private static final String QUEUE_NAME = "rabbit_mq_demo";
private static int serviceTimeOut = 1000;
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建我们的连接
Connection connection = RabitMQConnection.getConnection();
// 2.创建我们通道
final Channel channel = connection.createChannel();
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("消费消息msg:" + msg);
// 手动ack应答模式
channel.basicAck(envelope.getDeliveryTag(), false);
try {
Thread.sleep(serviceTimeOut);
} catch (Exception e) {
e.printStackTrace();
}
}
};
// 3.创建我们的监听的消息
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
}
}
RabbitMQ如何保证消息不丢失
生产者:投递消息时采用消息确认机制confirm,确保消息投递至消息中间件中,否则进行重新投递
消费者:采用手动ack的方式通知消息中间件删除消息,即消息成功消费后再将其删除
中间件:采用持久化的方式将消息持久化至硬盘中
RabitMQ五种消息模式
点对点
消费者自动确认机制,即生成一个消息,推送一个消息至消费者,不管其是否消费成功,默认自动删除消息
工作模式
消费者手动ack通知消息的服务器删除消息,然后推送下一个。
在有多个消费者时,默认轮询推送,但基于前一个消息删除后才会对其推送下个消息,故手动ack可以实现能者多劳的工作模式
发布订阅(fanout)
引入交换机的概念,多个队列绑定至同一交换机上,生产者只需要将消息投放至交换机,交换机就会通过发布订阅的方式将
消息广播至其上队列。
交换机类型
Fanout exchange(扇型交换机)默认
Direct exchange(直连交换机)
Topic exchange(主题交换机)
Headers exchange(头交换机)
生产者
public class ProducerFanout {
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建Connection
Connection connection = RabitMQConnection.getConnection();
// 创建Channel
Channel channel = connection.createChannel();
// 通道关联交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout", true);
String msg = "发布订阅模式";
channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
channel.close();
connection.close();
}
}
消费者1
public class Consumer1 {
private static final String QUEUE_NAME = "consumerFanout_1";
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
System.out.println("消费者1...");
// 创建我们的连接
Connection connection = RabitMQConnection.getConnection();
// 创建我们通道
final Channel channel = connection.createChannel();
// 关联队列消费者关联队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("消费者1获取消息:" + msg);
}
};
// 开始监听消息 自动签收
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
}
消费者2
public class Consumer2 {
private static final String QUEUE_NAME = "consumerFanout_2";
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
System.out.println("消费者2...");
// 创建我们的连接
Connection connection = RabitMQConnection.getConnection();
// 创建我们通道
final Channel channel = connection.createChannel();
// 关联队列消费者关联队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("消费者2获取消息:" + msg);
}
};
// 开始监听消息 自动签收
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
}
路由(direct)
在发布订阅的基础上,引入路由key,每个队列都可以绑定多个路由key,生产者投递消息时,可以指定路由key,
交换机根据路由key
查找对应的队列进行消息投放。
即路由key类似于队列的一种属性,方便生成者投放消息时做查询过滤,又叫direct
生产者
/**
* 定义交换机的名称
*/
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
// 1.创建我们的连接
Connection connection = RabitMQConnection.getConnection();
// 2.创建我们通道
Channel channel = connection.createChannel();
// 不需要直接关心队列,只关心交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);
String msg = "路由直连的消息"
channel.basicPublish(EXCHANGE_NAME, "key1", null, msg.getBytes());
channel.close();
connection.close();
// 如果交换机没有绑定队列,消息可能会丢失
}
消费者1
public class Consumer1 {
private static final String QUEUE_NAME = "consumer_direct_1";
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建我们的连接
Connection connection = RabitMQConnection.getConnection();
// 创建我们通道
final Channel channel = connection.createChannel();
// 关联队列消费者关联队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key1");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("消费者1获取消息:" + msg);
}
};
// 开始监听消息 自动签收
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
}
消费者2
public class Consumer2 {
private static final String QUEUE_NAME = "consumer_direct_2";
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建我们的连接
Connection connection = RabitMQConnection.getConnection();
// 创建我们通道
final Channel channel = connection.createChannel();
// 关联队列消费者关联队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key2");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("消费者2获取消息:" + msg);
}
};
// 开始监听消息 自动签收
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
}
主题(topic)
在路由的基础上,实现通配符查询,即模糊查询,*代表一个单词,#代表多个单词
这样一来,队列不用去绑定一个又一个的路由key,直接绑定通配符的路由key即可
SpringBoot整合RabbitMQ
maven依赖
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
org.apache.commons
commons-lang3
com.alibaba
fastjson
1.2.49
application.yml
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: yanxiaohui
####密码
password: yanxiaohui
### 地址
virtual-host: /yxh
配置类
@Component
public class RabbitMQConfig {
/**
* 定义交换机
*/
private String EXCHANGE_SPRINGBOOT_NAME = "springboot_exchange";
/**
* 短信队列
*/
private String FANOUT_SMS_QUEUE = "fanout_sms_queue";
/**
* 邮件队列
*/
private String FANOUT_SMS_EMAIL = "fanout_email_queue";
/**
* 创建短信队列
*/
@Bean
public Queue smsQueue() {
return new Queue(FANOUT_SMS_QUEUE);
}
/**
* 创建邮件队列
*/
@Bean
public Queue emailQueue() {
return new Queue(FANOUT_SMS_EMAIL);
}
/**
* 创建交换机
*
* @return
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);
}
/**
* 定义短信队列绑定交换机
*/
@Bean
public Binding smsBindingExchange(Queue smsQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(smsQueue).to(fanoutExchange);
}
/**
* 定义邮件队列绑定交换机
*/
@Bean
public Binding emailBindingExchange(Queue emailQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(emailQueue).to(fanoutExchange);
}
}
生产者
@RestController
public class FanoutProducer {
@Autowired
private AmqpTemplate amqpTemplate;
@RequestMapping("/sendMsg")
public String sendMsg(String msg) {
// 参数1 交换机名称 、参数2路由key 参数3 消息
amqpTemplate.convertAndSend("springboot_exchange", "", msg);
return "success";
}
}
消费者
@Component
@RabbitListener(queues = "fanout_email_queue")
public class FanoutEmailConsumer {
@RabbitHandler
public void process(String msg) {
System.out.println("邮件消费者消息msg:" + msg);
}
}
死信队列
产生的背景
俗称备胎队列,用于存放消息多次消费失败、消费超时、超过队列最大长度被拒绝接收的消息。
消息中间件因为某种原因拒收该消息后,可以转移到死信队列中存放,死信队列也可以有交换机和路由key等。
SpringBoot整合死信队列
maven依赖
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
org.apache.commons
commons-lang3
com.alibaba
fastjson
1.2.49
application.yml
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: yanxiaohui
####密码
password: yanxiaohui
### 地址
virtual-host: /yxh
server:
port: 8080
###模拟演示死信队列
yxh:
dlx:
exchange: yxh_dlx_exchange
queue: yxh_order_dlx_queue
routingKey: dlx
###备胎交换机
order:
exchange: yxh_order_exchange
queue: yxh_order_queue
routingKey: yxh.order
死信队列配置
@Component
public class DeadLetterMQConfig {
/**
* 订单交换机
*/
@Value("${yxh.order.exchange}")
private String orderExchange;
/**
* 订单队列
*/
@Value("${yxh.order.queue}")
private String orderQueue;
/**
* 订单路由key
*/
@Value("${yxh.order.routingKey}")
private String orderRoutingKey;
/**
* 死信交换机
*/
@Value("${yxh.dlx.exchange}")
private String dlxExchange;
/**
* 死信队列
*/
@Value("${yxh.dlx.queue}")
private String dlxQueue;
/**
* 死信路由
*/
@Value("${yxh.dlx.routingKey}")
private String dlxRoutingKey;
/**
* 声明死信交换机
*
* @return DirectExchange
*/
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange(dlxExchange);
}
/**
* 声明死信队列
*
* @return Queue
*/
@Bean
public Queue dlxQueue() {
return new Queue(dlxQueue);
}
/**
* 声明订单业务交换机
*
* @return DirectExchange
*/
@Bean
public DirectExchange orderExchange() {
return new DirectExchange(orderExchange);
}
/**
* 绑定死信队列到死信交换机
*
* @return Binding
*/
@Bean
public Binding binding() {
return BindingBuilder.bind(dlxQueue())
.to(dlxExchange())
.with(dlxRoutingKey);
}
/**
* 声明订单队列
*
* @return Queue
*/
@Bean
public Queue orderQueue() {
// 订单队列绑定我们的死信交换机
Map arguments = new HashMap<>(2);
arguments.put("x-dead-letter-exchange", dlxExchange);
arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
return new Queue(orderQueue, true, false, false, arguments);
}
/**
* 绑定订单队列到订单交换机
*
* @return Binding
*/
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue())
.to(orderExchange())
.with(orderRoutingKey);
}
死信消费者
@Component
public class OrderDlxConsumer {
/**
* 死信队列监听队列回调的方法
*
* @param msg
*/
@RabbitListener(queues = "yxh_order_dlx_queue")
public void orderConsumer(String msg) {
System.out.println("死信队列消费订单消息" + msg);
}
}
订单消费者
@Component
public class OrderConsumer {
/**
* 监听队列回调的方法
*
* @param msg
*/
@RabbitListener(queues = "yxh_order_queue")
public void orderConsumer(String msg) {
System.out.println("正常订单消费者消息msg:" + msg);
}
}
生产者投递消息
@RestController
public class DeadLetterProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 订单交换机
*/
@Value("${yxh.order.exchange}")
private String orderExchange;
/**
* 订单路由key
*/
@Value("${yxh.order.routingKey}")
private String orderRoutingKey;
@RequestMapping("/sendOrder")
public String sendOrder() {
String msg = "死信队列的demo";
rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
return "succcess";
}
}
消息中间件如何获取消费结果
由于中间件的操作是异步的,所以想要获取其操作结果,只有通过主动查询的方式,才能知道消息是否已消费。
即通过业务生成全局的消息ID,使用消息ID去数据库查找对应的业务是否发生了预期的变化,进而得出消息是否成功消费。
此方法也是其解决消息幂等性的思路
RabbitMQ消息幂等问题
消息自动重试机制
在消费者消费消息的代码中出现异常后,默认是重复执行的,(默认无数次)。
如果代码本身就有问题,而非外部因素(网络抖动等)影响,那么这种重试本质无意义。
所以应该对重试机制设置重试次数和时间间隔,超过重试机制还是抛出异常的,我们可以将消息放入死信或者数据库将其记录,方便补偿。
SpringBoot开启重试策略
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: yanxiaohui
####密码
password: yanxiaohui
### 地址
virtual-host: /yxh
listener:
simple:
retry:
####开启消费者(程序出现异常的情况下会)进行重试
enabled: true
####最大重试次数
max-attempts: 5
####重试间隔次数
initial-interval: 3000
消费者开启重试策略
System.out.println("消费者消息msg:" + msg);
JSONObject msgJson = JSONObject.parseObject(msg);
String email = msgJson.getString("email");
String emailUrl = "http://127.0.0.1:8081/sendEmail?email=" + email;
JSONObject jsonObject = null;
try {
jsonObject = HttpClientUtils.httpGet(emailUrl);
} catch (Exception e) {
String errorMsg = email + ",调用第三方邮件接口失败:" + ",错误原因:" + e.getMessage();
throw new Exception(errorMsg);
}
System.out.println("邮件消费者调用第三方接口结果:" + jsonObject);
rabbitMQ如何解决消息幂等问题
采用消息全局id根据业务来定
生产者
@RequestMapping("/sendOrderMsg")
public String sendOrderMsg() {
// 1.生产订单id
String orderId = System.currentTimeMillis() + "";
String orderName = "生成消息幂等的订单";
OrderEntity orderEntity = new OrderEntity(orderName, orderId);
String msg = JSONObject.toJSONString(orderEntity);
sendMsg(msg, orderId);
return orderId;
// 后期客户端主动使用orderId调用服务器接口 查询该订单id是否在数据库中存在数据 消费成功 消费失败
}
@Async
public void sendMsg(String msg, String orderId) {
rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// message.getMessageProperties().setExpiration("10000");
message.getMessageProperties().setMessageId(orderId);
return message;
}
});
// 消息投递失败
}
消费者
String msg = new String(message.getBody());
System.out.println("订单队列获取消息:" + msg);
OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
if (orderEntity == null) {
return;
}
// messageId根据具体业务来定,如果已经在数据表中插入过数据,则不会插入
String orderId = message.getMessageProperties().getMessageId();
if (StringUtils.isEmpty(orderId)) {
// 开启消息确认机制
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}
OrderEntity dbOrderEntity = orderMapper.getOrder(orderId);
if (dbOrderEntity != null) {
// 说明已经处理过请求
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}
int result = orderMapper.addOrder(orderEntity);
if (result >= 0) {
// 开启消息确认机制
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
RabbitMQ如何解决分布式事务
什么是分布式事务
官方说法:在分布式系统中,因为跨服务调用接口,存在多个不同的事务,每个事务都互不影响。就存在分布式事务的问题。
说白了就是:在一个事务中,出现rpc远程调用,其中对数据的变更脱离当前事务的管理,导致当前事务回滚时,无法将远程事务一并回滚。
解决分布式事务核心思想
最终一致性。分布式领域不存在强一致性,对于短暂期间的不一致,可以允许通过补偿或延时使其最终数据保持一致。
RabbitMQ解决分布式事务的思路
1.通过消息确认机制confirm确保消息一定投递至消息中间中
2.消费者手动ack确定消息的消费成功
3.对于消费成功但事务回滚的操作,需要进行补充,即将整个业务操作(除去消息投递)都记录至补偿队列,然后补偿业务的数据缺失。
Maven依赖
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.1.1
mysql
mysql-connector-java
com.alibaba
druid
1.0.14
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
org.apache.commons
commons-lang3
com.alibaba
fastjson
1.2.49
org.projectlombok
lombok
application.yml
spring:
rabbitmq:
####连接地址
host: 127.0.0.1
####端口号
port: 5672
####账号
username: yanxiaohui
####密码
password: yanxiaohui
### 地址
virtual-host: /yxh
###开启消息确认机制 confirms
publisher-confirms: true
publisher-returns: true
listener:
simple:
retry:
####开启消费者(程序出现异常的情况下会)进行重试
enabled: true
####最大重试次数
max-attempts: 5
####重试间隔次数
initial-interval: 3000
###开启ack模式
acknowledge-mode: manual
datasource:
url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
server:
port: 8080
mq配置
@Component
public class OrderRabbitMQConfig {
/**
* 派单队列
*/
public static final String ORDER_DIC_QUEUE = "order_dic_queue";
/**
* 补单对接
*/
public static final String ORDER_CREATE_QUEUE = "order_create_queue";
/**
* 派单交换机
*/
private static final String ORDER_EXCHANGE_NAME = "order_exchange_name";
/**
* 定义派单队列
*
* @return
*/
@Bean
public Queue directOrderDicQueue() {
return new Queue(ORDER_DIC_QUEUE);
}
/**
* 定义补派单队列
*
* @return
*/
@Bean
public Queue directCreateOrderQueue() {
return new Queue(ORDER_CREATE_QUEUE);
}
/**
* 定义订单交换机
*
* @return
*/
@Bean
DirectExchange directOrderExchange() {
return new DirectExchange(ORDER_EXCHANGE_NAME);
}
/**
* 派单队列与交换机绑定
*
* @return
*/
@Bean
Binding bindingExchangeOrderDicQueue() {
return BindingBuilder.bind(directOrderDicQueue()).to(directOrderExchange()).with("orderRoutingKey");
}
/**
* 补单队列与交换机绑定
*
* @return
*/
@Bean
Binding bindingExchangeCreateOrder() {
return BindingBuilder.bind(directCreateOrderQueue()).to(directOrderExchange()).with("orderRoutingKey");
}
}
生产者
@Component
@Slf4j
public class OrderProducer implements RabbitTemplate.ConfirmCallback {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
@Transactional
public String send() {
// 1.创建订单
String orderId = System.currentTimeMillis() + "";
OrderEntity orderEntity = createOrder(orderId);
//2.将订单添加到数据库中(步骤一 先往数据库中添加一条数据)
int result = orderMapper.addOrder(orderEntity);
if (result <= 0) {
return orderId;
}
//3.使用消息中间件异步 ,分配订单
String sendMsgJson = JSONObject.toJSONString(orderEntity);
send(sendMsgJson);
int i = 1 / 0;
return orderId;
}
public OrderEntity createOrder(String orderId) {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setName("分布式事务");
orderEntity.setOrderCreatetime(new Date());
// 价格是300元
orderEntity.setOrderMoney(300d);
// 状态为 未支付
orderEntity.setOrderState(0);
Long commodityId = 30L;
// 商品id
orderEntity.setCommodityId(commodityId);
orderEntity.setOrderId(orderId);
return orderEntity;
}
private void send(String sendMsg) {
log.info(">>>生产者发送订单数据:" + sendMsg);
// 设置生产者消息确认机制
this.rabbitTemplate.setMandatory(true);
this.rabbitTemplate.setConfirmCallback(this);
// 构建回调返回参数
CorrelationData correlationData = new CorrelationData(sendMsg);
String orderExchange = "order_exchange_name";
String orderRoutingKey = "orderRoutingKey";
rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, sendMsg, correlationData);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
String sendMsg = correlationData.getId();
System.out.println("生产者开始消息确认orderId:" + sendMsg);
if (!ack) {
// 递归调用发送
send(sendMsg);
return;
}
System.out.println("生产者消息确认orderId:" + sendMsg);
}
}
消费者
@Component
public class DistriLeafleConsumer {
@Autowired
private DispatchMapper dispatchMapper;
@RabbitListener(queues = "order_dic_queue")
public void distriLeafleConsumer(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
System.out.println("派代服务平台msg:" + msg);
JSONObject jsonObject = JSONObject.parseObject(msg);
// 订单id
String orderId = jsonObject.getString("orderId");
// 假设派单userID 1234
Long userId = 1234L;
DispatchEntity dispatchEntity = new DispatchEntity(orderId, userId);
int result = dispatchMapper.insertDistribute(dispatchEntity);
if (result >= 0) {
// 手动ack 删除该消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
补单消费者(补偿队列的消费)
@Component
public class CreateOrderConsumer {
@Autowired
private OrderMapper orderMapper;
@RabbitListener(queues = "order_create_queue")
public void createOrderConsumer(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
String orderId = orderEntity.getOrderId();
// 根据订单号码查询该笔订单是否创建
OrderEntity dbOrderEntity = orderMapper.findOrderId(orderId);
if (dbOrderEntity != null) {
// 手动ack 删除该消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}
int result = orderMapper.addOrder(orderEntity);
if (result >= 0) {
// 手动ack 删除该消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!
公众号:帝都的雁