类型
1、事务机制。
2、confirm与return机制。
事务机制性能比较低,这里不做描述,主要描述第二种,而且描述的不是同步类型,而是异步类型,因为异步性能比较好,比较常用。
原理:
rabbitmq的消息传递过程是 producer-->broker-->exchange-->queue-->consumer。
- confirm机制是当producer将消息发送给broker之后,broker会返回该消息是否发送成功给producer。
- return机制是当producer成功将消息发送给broker之后,broker会将消息发送到exchange,exchange再发送到queue,若没有成功发送到queue,那么就会返回一个returnCallback。
也就是说,若消息在发送到broker这就失败了,那么肯定不会返回returnCallback;而如果返回了returnCallback,那么说明消息肯定是成功投递给了broker,只是broker没有成功投递到queue。
springboot代码实现
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rabbitmqtest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmqtest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties文件
spring.application.name=spring-boot-rabbitmq
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
#开启发送失败退回
spring.rabbitmq.publisher-returns=true
#开启发送确认
spring.rabbitmq.publisher-confirms=true
config文件
package com.example.rabbitmqtest;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Configuration
public class RabbitConfig {
@Resource
private RabbitTemplate rabbitTemplate;
/**
* 注入队列
* @return
*/
@Bean
public Queue testQueue(){
/**
* 创建队列时可以指定下列参数,也可以只指定主题名
* durable,持久化,默认true
* exclusive,消息是否只在当前connection生效,默认false
* autoDelete,队列没有使用时将被自动删除,默认false
*/
Queue queue = new Queue("test-queue",true,false,false);
//Queue queue = new Queue("test-queue");
return queue;
}
/**
* 注入主题
* @return
*/
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("test-topic");
}
//这里不进行绑定,就可以测试return机制了
// /**
// * 绑定队列和交换机,参数名称需要跟上边注入队列和注入交换机的方法名称相同。
// * @param testQueue
// * @param topicExchange
// * @return
// */
// @Bean
// public Binding bindingTopicExchange(Queue testQueue,TopicExchange topicExchange){
// return BindingBuilder.bind(testQueue).to(topicExchange).with("test-key");
// }
}
producer文件
package com.example.rabbitmqtest;
import org.springframework.amqp.core.Message;
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 java.util.UUID;
@Service
public class OrderProducer implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
//唯一id
private String correlationId = UUID.randomUUID().toString();
public void send(){
//每个发送的消息都需要配备一个CorrelationData对象,这对象内部只有一个id属性,用来表示消息的唯一性。
CorrelationData correlationData = new CorrelationData();
correlationData.setId(correlationId);
//设置confirm
rabbitTemplate.setConfirmCallback(this);
//设置return
rabbitTemplate.setReturnCallback(this);
//发送消息
rabbitTemplate.convertAndSend("test-topic","test-key","this is message",correlationData);
}
/**
* confirm机制
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
System.out.println("消息发送到服务器成功");
}else {
System.out.println("消息发送到服务器失败");
if (correlationData != null) {
if (correlationData.getId().equals(correlationId)) {
//自动重发消息或者记录日志之后设置定时任务定时发送,超过重试次数之后人工处理
//这里是直接自动重发消息
//需要注意幂等性!!!!
try{
Thread.sleep(2000);
send();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
/**
* return机制
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.err.println("message: " + message);
System.err.println("replyCode: " + replyCode);
System.err.println("replyText: " + replyText);
System.err.println("exchange: " + exchange);
System.err.println("routingKey: " + routingKey);
System.out.println("在这里可以进行记录日志,或者及时去跟踪记录,有可能重新设置一下就好了。");
}
}
controller文件
package com.example.rabbitmqtest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/producer")
public class ProducerController{
@Autowired
private OrderProducer orderProducer;
@RequestMapping("/send")
public String send(){
orderProducer.send();
return "ok";
}
}
测试
return机制
在config配置文件中,queue和exchange绑定的代码是注释掉的,即没有绑定,那么消息肯定是发送不到queue里的。
运行项目,在浏览器访问接口,就可以发现触发了return机制。
confirm机制
将生产者发送消息指定的交换机名称改成错误的,例如
rabbitTemplate.convertAndSend("test-topic2","test-key","this is message",correlationData);
test-topic2是不存在的,运行项目
在浏览器访问接口,就可以发现触发了confirm机制。
扩展
例子:订单服务下单,用rabbitmq发送消息给库存服务消费,库存服务减库存,然后订单服务继续执行业务逻辑
那么会出现一种情况,消息发送成功了,然后订单服务执行的业务逻辑出现异常,接着事务回滚了,那么就造成了数据不一致问题。
伪代码如下:
解决方案
可以使用编程式事务,让本地事务执行完之后,再发送mq,如果本地事务执行失败了,那么就不发mq了。
对系统数据一致性要求比较高的情况下,可以在本地事务中记录一条日志,这个日志有一个mq的状态字段,在mq发送前是未发送,发送成功之后就变成已发送,然后后台写一个定时任务,如果这个状态值超过了一定的时间阈值,还是未发送,那么有可能是mq发送异常,那么就可以重试发送了,超过一定的次数就不发送了,进行人工处理。