1、实施可靠生产和推送确认的必要
假设有订单系统A和派单系统B,A和B系统都有自己的单独模块、数据库。
小明在A下单后,A逻辑处理完后,保存相关数据到数据库,并发送消息给B系统通知其派单,B系统也保存相关数据到数据库。
一般都会在A和B中设置事务,但是因为某种原因,B系统延迟,A系统迟迟得不到B系统处理完成的反馈就当失败处理进行事务回滚,所以此时A系统并没有存储相关订单信息到数据库。而B系统尽管延迟但最后还是执行成功了,此处B系统存储成功了。就造成了两边数据的不一致。
利用rabbitmq的一些功能可以避免大部分此类事件
2、具体实现
- 生产者建立消息冗余表,每发送一条数据到MQ都存一条道冗余表
- 设置推送确认,确认返回后将冗余表状态变更
- 设置定时器,重新发送冗余表中发送失败消息,重发次数大于2,说明消息异常需要进行人工确认
3、实现目标
生产者发送消息到MQ,并存一份到冗余表。MQ确认接收返回,修改冗余表状态
创建生产者工程
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.yml配置
server:
port: 8082
spring:
rabbitmq:
host: 110.2=14.218.10
port: 5672
username: admin
password: admin
virtual-host: /
# 发布确认属性配置 correlated: 发布消息成功到交换器后会触发回调方法
publisher-confirm-type: correlated
Config配置
package com.example.service;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class Config {
@Bean
public DirectExchange directExchange(){
return new DirectExchange("order_exchange",true,false);
}
@Bean
public Queue orderQueue(){
return new Queue("order.queue",true);
}
@Bean
public Binding orderBinding(){
return BindingBuilder.bind(orderQueue()).to(directExchange()).with("order");
}
}
OrderService
package com.example.service;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
// PostConstruct 很多人以为是spring提供的,其实是java自己的注解
// 被PostConstruct修饰的方法会在服务器加载servlet的时候运行,并且只会被服务器执行一次
// PostConstruct在构造函数之后执行,init()方法之前执行
// 简单来说就是在下面send方法调用rabbitTemplate之前,将rabbitTemplate的确认方法重载好
@PostConstruct
public void regCallback(){
// 消息发送成功后,给予生产者的消息回执,来确保生产者的可靠性
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("cause:------------>"+cause);
String orderId = correlationData.getId();
// 如果ack为true代表消息已经收到
if (!ack){
// 这里需要其他的方式进行失败后的处理逻辑
System.out.println("MQ队列应答失败,orderId是:"+orderId);
return;
}
// 消息收到,则更改冗余表中状态
try {
String sql = "update xxx_table set status ='1' where order_id = 'xxxxxx'";
// int count = jdbcTemplet.update(sql,orderId);
// if (count == 1){
// 这边没连接数据库,就模拟已经执行update操作后
System.out.println("本地消息状态修改成功,消息成功投递到消息队列中...");
// }
}catch (Exception e){
System.out.println("本地消息状态修改失败,出现异常:"+e.getMessage());
}
}
});
}
public void sendMsg(String msg){
System.out.println("开始发送订单信息到MQ");
rabbitTemplate.convertAndSend("order_exchange","order",msg,new CorrelationData("2021922"));
}
public void saveOrder(){
// ...这边执行订单的处理流程
System.out.println("创建保存订单信息");
// 保存订单信息到本地冗余表
saveLocalOrder();
}
public void saveLocalOrder(){
// ...这边执行订单信息的冗余保存
System.out.println("保存订单信息冗余表");
}
}
测试类
package com.example.rabbitmqdirectproducer;
import com.example.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitmqDirectProducerApplicationTests {
@Autowired
private OrderService orderService;
@Test
void contextLoads() {
// 下单-保存订单
orderService.saveOrder();
// 发送订单信息到MQ,让派单系统接收订单信息实行派单
orderService.sendMsg("订单111201210");
}
}