九、消息的可靠性
1、生产者确认
9.1.1、Confirm模式简介

可能因为网络或者Broker的问题导致①失败,而此时应该让生产者知道消息是否正确发送到了Broker的exchange中;
有两种解决方案:
第一种是开启Confirm(确认)模式;(异步)
第二种是开启Transaction(事务)模式;(性能低,实际项目中很少用)
消息的confirm确认机制,是指生产者投递消息后,到达了消息服务器Broker里面的exchange交换机,则会给生产者一个应答
生产者接收到应答,用来确定这条消息是否正常的发送到Broker的exchange中,这也是消息可靠性投递的重要保障;

9.1.2、Confirm模式实现
-
开启生产者确认模式
ymlpublisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
-
实现RabbitTemplate.ConfirmCallback交界口,重写confirm()方法
判断成功和失败的ack结果,可以根据具体的结果,如果ack为false,对消息进行重新发送或记录日志等处理
-
设置rabbitTemplate的确认回调方法
javarabbitTemplate.setConfirmCallback(messageConfirmCallBack);
(1)、外部类实现
测试模块:rabbitmq-09-confirm-01
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
配置MQ
yml
server:
port: 8080
spring:
application:
name: confirm-learn01
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
生产者
设置回调方法
java
package com.longdidi.service;
import com.longdidi.config.MyConfirmCallBack;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
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.Date;
@Service
@Slf4j
public class SendMessageService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Resource
private MyConfirmCallBack confirmCallBack;
//构造方法执行后会调用一次该方法,只调用一次,起到初始化的作用
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(confirmCallBack);
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes()).build();
//关联数据对象
CorrelationData correlationData = new CorrelationData(); //关联数据
//比如设置一个订单ID,到时候在confirm回调里面就可以知道是哪个订单没有发送到交换机上去
correlationData.setId("order_123456"); //发送订单信息
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.ROUTING_NAME1, message, correlationData);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.confirm.normal.1";
// 队列
public static final String QUEUE_NAME1 = "queue.confirm.normal.1";
// 路由key
public static final String ROUTING_NAME1 = "key.confirm.normal.1";
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
//Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
Map<String, Object> arguments = new HashMap<>();
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.withArguments(arguments)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
实现接口
java
package com.longdidi.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MyConfirmCallBack implements RabbitTemplate.ConfirmCallback {
/**
* 交换机收到消息后,会回调该方法
*
* @param correlationData 相关联的数据
* @param ack 有两个取值,true和false,true表示成功:消息正确地到达交换机,反之false就是消息没有正确地到达交换机
* @param cause 消息没有正确地到达交换机的原因是什么
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("关联id为:{}", correlationData.getId() + "");
if (ack) {
log.info("消息正确的达到交换机");
return;
} else {
//ack =false 没有到达交换机
log.error("消息没有到达交换机,原因为:{}", cause);
}
}
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Confirm01Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Confirm01Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试
写错交换机的名字,查看日志输出

(2)、生产者实现
测试模块:rabbitmq-09-confirm-02
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
配置MQ
yml
server:
port: 8080
spring:
application:
name: confirm-learn02
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
生产者
生产者直接实现RabbitTemplate.ConfirmCallback接口,重写confirm()方法并设置回调函数
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
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.Date;
@Service
@Slf4j
public class SendMessageService implements RabbitTemplate.ConfirmCallback {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct //构造方法后执行它,相当于初始化作用
public void init() {
rabbitTemplate.setConfirmCallback(this);
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes()).build();
CorrelationData correlationData = new CorrelationData(); //关联数据
correlationData.setId("order_123456"); //发送订单信息
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.QUEUE_NAME1, message, correlationData);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("关联id为:{}", correlationData.getId() + "");
if (ack) {
log.info("消息正确的达到交换机");
return;
}
//ack =false 没有到达交换机
log.error("消息没有到达交换机,原因为:{}", cause);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.confirm.normal.2";
// 队列
public static final String QUEUE_NAME1 = "queue.confirm.normal.2";
// 路由key
public static final String ROUTING_NAME1 = "key.confirm.normal.2";
}
定义MQ队列
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
//Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
Map<String, Object> arguments = new HashMap<>();
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.withArguments(arguments)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Confirm02Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Confirm02Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

(3)、匿名内部类实现
测试模块:rabbitmq-09-confirm-03
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
配置MQ
yml
server:
port: 8080
spring:
application:
name: confirm-learn03
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
生产者
生产者使用匿名内部类实现RabbitTemplate.ConfirmCallback接口,重写confirm()方法并设置回调函数
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct //构造方法后执行它,相当于初始化作用
public void init() {
rabbitTemplate.setConfirmCallback(
//匿名内部类
new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("关联id为:{}", correlationData.getId() + "");
if (ack) {
log.info("消息正确的达到交换机");
return;
}
//ack =false 没有到达交换机
log.error("消息没有到达交换机,原因为:{}", cause);
}
}
);
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes()).build();
CorrelationData correlationData = new CorrelationData(); //关联数据
correlationData.setId("order_123456"); //发送订单信息
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.QUEUE_NAME1, message, correlationData);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
定义MQ队列
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
//Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
Map<String, Object> arguments = new HashMap<>();
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.withArguments(arguments)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.confirm.normal.3";
// 队列
public static final String QUEUE_NAME1 = "queue.confirm.normal.3";
// 路由key
public static final String ROUTING_NAME1 = "key.confirm.normal.3";
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Confirm03Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Confirm03Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

(4)、Lambad实现
测试模块:rabbitmq-09-confirm-04
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
配置MQ
yml
server:
port: 8080
spring:
application:
name: confirm-learn04
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
生产者
生产者使用lambda实现RabbitTemplate.ConfirmCallback接口,重写confirm()方法并设置回调函数
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
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.Date;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct //构造方法后执行它,相当于初始化作用
public void init() {
rabbitTemplate.setConfirmCallback(
//lambda 表达式
(correlationData, ack, cause) -> {
log.info("关联id为:{}", correlationData.getId() + "");
if (ack) {
log.info("消息正确的达到交换机");
return;
}
//ack =false 没有到达交换机
log.error("消息没有到达交换机,原因为:{}", cause);
}
);
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes()).build();
CorrelationData correlationData = new CorrelationData(); //关联数据
correlationData.setId("order_123456"); //发送订单信息
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME + "error", RabbitMQConstant.ROUTING_NAME1, message, correlationData);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
定义MQ队列
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
//Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments)
Map<String, Object> arguments = new HashMap<>();
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.withArguments(arguments)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.confirm.normal.4";
// 队列
public static final String QUEUE_NAME1 = "queue.confirm.normal.4";
// 路由key
public static final String ROUTING_NAME1 = "key.confirm.normal.4";
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Confirm04Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Confirm04Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

2、交换机确认

可能因为路由关键字错误,或者队列不存在,或者队列名称错误导致②失败
使用return模式可以实现消息无法路由的时候返回给生产者
消息从 exchange --> queue 投递失败则会返回一个 returnCallback;
利用这个callback控制消息的可靠性投递;
9.2.1、return模式实现
实现步骤
-
开启return确认模式
ymlpublisher-returns: true #开启return模式
-
实现RabbitTemplate.ReturnsCallback接口并重写returnedMessage()方法
-
设置回调
使用rabbitTemplate.setReturnCallback设置退回函数
当消息从exchange路由到queue失败后,则会将消息退回给producer,并执行回调函数returnedMessage;
(1)、外部类实现
测试模块:rabbitmq-09-return-01
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: return-learn01
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-returns: true #开启return模式
实现回调接口
java
package com.longdidi.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
/**
* 写一个类实现RabbitTemplate.ReturnsCallback接口
*/
@Component
@Slf4j
public class MyReturnCallBack implements RabbitTemplate.ReturnsCallback {
/**
* 当消息从交换机 没有正确地 到达队列,则会触发该方法
* 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
*
* @param returnedMessage
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", returnedMessage.getReplyText());
}
}
生产者
设置回调函数
java
package com.longdidi.service;
import com.longdidi.config.MyReturnCallBack;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private MyReturnCallBack myReturnCallBack;
@PostConstruct
public void init() {
rabbitTemplate.setReturnsCallback(myReturnCallBack); //设置回调
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes())
.build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.return.normal.1";
// 队列
public static final String QUEUE_NAME1 = "queue.return.normal.1";
// 路由key
public static final String ROUTING_NAME1 = "key.return.normal.1";
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Return01Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Return01Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

(2)、生产者实现
测试模块:rabbitmq-09-return-02
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: return-learn02
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-returns: true #开启return模式
生产者
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@Slf4j
public class SendMessageService implements RabbitTemplate.ReturnsCallback {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setReturnsCallback(this); //设置回调
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes())
.build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
/**
* 当消息从交换机 没有正确地 到达队列,则会触发该方法
* 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
*
* @param returnedMessage
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", returnedMessage.getReplyText());
}
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.return.normal.2";
// 队列
public static final String QUEUE_NAME1 = "queue.return.normal.2";
// 路由key
public static final String ROUTING_NAME1 = "key.return.normal.2";
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Return02Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Return02Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

(3)、匿名内部类实现
测试模块:rabbitmq-09-return-03
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: return-learn03
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-returns: true #开启return模式
生产者
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
/**
* 当消息从交换机 没有正确地 到达队列,则会触发该方法
* 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
*/
rabbitTemplate.setReturnsCallback(
//匿名内部类 实现了接口
new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", returnedMessage.getReplyText());
}
}
); //设置回调
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes())
.build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.return.normal.3";
// 队列
public static final String QUEUE_NAME1 = "queue.return.normal.3";
// 路由key
public static final String ROUTING_NAME1 = "key.return.normal.3";
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Return03Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Return03Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

(4)、lambda实现
测试模块:rabbitmq-09-return-04
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: return-learn04
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-returns: true #开启return模式
生产者
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
/**
* 当消息从交换机 没有正确地 到达队列,则会触发该方法
* 如果消息从交换机 正确地 到达队列了,那么就不会触发该方法
*/
rabbitTemplate.setReturnsCallback(
//使用lambda表达式
message -> {
log.error("消息从交换机没有正确的路由到(投递到)队列,原因为:{}", message.getReplyText());
}
); //设置回调
}
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes())
.build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1 + "error", message);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).build();
}
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.return.normal.4";
// 队列
public static final String QUEUE_NAME1 = "queue.return.normal.4";
// 路由key
public static final String ROUTING_NAME1 = "key.return.normal.4";
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Return04Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Return04Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

9.2.2、使用备用交换机
使用备份交换机(alternate-exchange),无法路由的消息会发送到这个备用交换机上;

测试模块:rabbitmq-09-backup-01
【示例】
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: backup-learn01
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-returns: true #开启return模式
生产者
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
public void sendMsg() {
Message message = MessageBuilder.withBody("hello world".getBytes())
.build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NORMAL_NAME, RabbitMQConstant.QUEUE_NORMAL_NAME + "error", message);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NORMAL_NAME = "exchange.normal.backup.5";
//备用交换机
public static final String EXCHANGE_BACKUP_NAME = "exchange.backup.5";
//正常队列
public static final String QUEUE_NORMAL_NAME = "queue.normal.backup.5";
//备用队列
public static final String QUEUE_BACKUP_NAME = "queue.backup.5";
public static final String ROUTING_WARNING_KEY = "info5";
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitConfig {
/**
* 定义正常交换机
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder // 默认为持久化的,默认不自动删除
.directExchange(RabbitMQConstant.EXCHANGE_NORMAL_NAME) // 交换机的名字
.alternate(RabbitMQConstant.EXCHANGE_BACKUP_NAME) //设置备用交换机 alternate-exchange
.build();
}
/**
* 定义正常队列
*
* @return
*/
@Bean
public Queue queueNormal() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NORMAL_NAME).build();
}
/**
* 绑定正常交换机和队列
*
* @param normalExchange
* @param queueNormal
* @return
*/
@Bean
public Binding binding(DirectExchange normalExchange, Queue queueNormal) {
return BindingBuilder.bind(queueNormal).to(normalExchange).with(RabbitMQConstant.ROUTING_WARNING_KEY);
}
/**
* 定义备用交换机
*
* @return
*/
@Bean
public FanoutExchange alternateExchange() {
return ExchangeBuilder.fanoutExchange(RabbitMQConstant.EXCHANGE_BACKUP_NAME).build();
}
/**
* 定义备用队列
*
* @return
*/
@Bean
public Queue alternateQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_BACKUP_NAME).build();
}
/**
* 绑定备用交换机和队列
*
* @param alternateExchange
* @param alternateQueue
* @return
*/
@Bean
public Binding bindingAlternate(FanoutExchange alternateExchange, Queue alternateQueue) {
return BindingBuilder.bind(alternateQueue).to(alternateExchange);
}
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Backup01Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Backup01Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

3、消息持久化存储
消息的可靠性投递就是要保证消息投递过程中每一个环节都要成功,那么这肯定会牺牲一些性能,性能与可靠性是无法兼得的;
如果业务实时一致性要求不是特别高的场景,可以牺牲一些可靠性来换取性能

- 代表消息从生产者发送到Exchange;
- 代表消息从Exchange路由到Queue;
- 代表消息在Queue中存储;
- 代表消费者监听Queue并消费消息;
可能因为系统宕机、重启、关闭等等情况导致存储在队列的消息丢失,即③出现问题;
解决方案:
-
队列持久化
javaQueueBuilder.durable("队列名称").build();
-
交换机持久化
javaExchangeBuilder.directExchange("交换机名称").durable(true).build();
-
消息持久化
默认就是持久化的
javaMessageProperties messageProperties = new MessageProperties(); //设置消息持久化,当然它默认就是持久化,所以可以不用设置,可以查看源码 messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
【示例】
测试模块:rabbitmq-09-durable-01
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: durable-learn01
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
publisher-returns: true #开启return模式
生产者
发送消息时设置消息持久化
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
public void sendMsg() {
MessageProperties messageProperties = new MessageProperties();
//设置消息持久化,当然它默认就是持久化,所以可以不用设置,可以查看源码
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = MessageBuilder.withBody("hello world 1".getBytes())
.andProperties(messageProperties)
.build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1, message);
}
}
定义MQ
定义队列和交换机持久化
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
* 使用durable()方法设置持久化
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).durable(true).build();
}
/**
* 正常队列
* durable()方法就是持久化
*
* @return
*/
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.durable.normal.1";
// 队列
public static final String QUEUE_NAME1 = "queue.durable.normal.1";
// 路由key
public static final String ROUTING_NAME1 = "key.durable.normal.1";
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Durable01Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Durable01Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试

4、消费者手动确认
9.4.1、手动确认消息

采用消息消费时的手动ack确认机制来保证;
如果消费者收到消息后未来得及处理即发生异常,或者处理过程中发生异常,会导致④失败。
为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement);
properties
#开启手动ack消息消费确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
消费者在订阅队列时,通过上面的配置,不自动确认,采用手动确认,RabbitMQ会等待消费者显式地回复确认信号后才从队列中删除消息;
如果消息消费失败,也可以调用basicReject()或者basicNack()来拒绝当前消息而不是确认。如果requeue参数设置为true,可以把这条消息重新存入队列,以便发给下一个消费者(当然只有一个消费者的时候,这种方式可能会出现无限循环重复消费的情况,可以投递到新的队列中,或者只打印异常日志);
【示例】
测试模块:rabbitmq-09-ack-01
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: ack-learn01
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
publisher-returns: true #开启return模式
# 开启消费者手动确认
listener:
simple:
acknowledge-mode: manual
生产者
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
/**
* 构造方法执行后自动执行
*/
@PostConstruct
public void init() {
//开启生产者的确定模式
rabbitTemplate.setConfirmCallback(
(correlationData, ack, cause) -> {
if (!ack) {
log.error("消息没有到达交换机,原因为:{}", cause);
//TODO 重发消息或者记录错误日志
}
}
);
// 开启交换机确认模式
rabbitTemplate.setReturnsCallback(
returnedMessage -> {
log.error("消息没有从交换机正确的投递(路由)到队列,原因为:{}", returnedMessage.getReplyText());
//TODO 记录错误日志,给程序员发短信或者或者邮件
}
);
}
public void sendMsg() {
MessageProperties messageProperties = new MessageProperties();
//设置单条消息的持久化,默认就是持久化
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = MessageBuilder.withBody("hello world".getBytes())
.andProperties(messageProperties).build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1+"1", message);
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
消费者
java
package com.longdidi.service;
import com.longdidi.constants.RabbitMQConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import com.rabbitmq.client.Channel;
@Component
@Slf4j
public class ReceiveMessageService {
@RabbitListener(queues = {RabbitMQConstant.QUEUE_NAME1})
public void receiveMsg(Message message, Channel channel) {
//获取消息的唯一标识
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info("接收到的消息为:{}", new String(message.getBody()));
// TODO 插入订单等
//int a=10/0;
//手动确认
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
log.error("消息处理出现问题");
try {
channel.basicNack(deliveryTag, false, true);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
}
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.ack.normal.1";
// 队列
public static final String QUEUE_NAME1 = "queue.ack.normal.1";
// 路由key
public static final String ROUTING_NAME1 = "key.ack.normal.1";
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
* 使用durable()方法设置持久化
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).durable(true).build();
}
/**
* 正常队列
* durable()方法就是持久化
*
* @return
*/
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Ack01Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Ack01Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
9.4.2、消息的幂等性
消息消费时的幂等性(消息不被重复消费)
同一个消息,第一次接收,正常处理业务,如果该消息第二次再接收,那就不能再处理业务,否则就处理重复了;
幂等性是:对于一个资源,不管你请求一次还是请求多次,对该资源本身造成的影响应该是相同的,不能因为重复的请求而对该资源重复造成影响;
以接口幂等性举例:
接口幂等性是指:一个接口用同样的参数反复调用,不会造成业务错误,那么这个接口就是具有幂等性的;
比如同一个订单我支付两次,但是只会扣款一次,第二次支付不会扣款,这说明这个支付接口是具有幂等性的;
如何避免消息的重复消费问题?(消息消费时的幂等性)
全局唯一ID + Redis
生产者在发送消息时,为每条消息设置一个全局唯一的messageId,消费者拿到消息后,使用setnx命令,将messageId作为key放到redis中:setnx(messageId, 1),若返回1,说明之前没有消费过,正常消费;若返回0,说明这条消息之前已消费过,抛弃;
【示例】
测试模块:rabbitmq-09-idempotent-01
引入依赖
xml
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
配置MQ
yml
server:
port: 8080
spring:
application:
name: idempotent-learn01
rabbitmq:
host: 192.168.1.101
port: 5672
username: admin
password: 123456
virtual-host: longdidi
publisher-confirm-type: correlated # 开启生产者的确认模式,设置关联模式
publisher-returns: true #开启return模式
# 开启消费者手动确认
listener:
simple:
acknowledge-mode: manual
data:
redis:
host: 192.168.1.4
port: 6379
#password: 123456
database: 0 # 0号数据库
生产者
java
package com.longdidi.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.longdidi.constants.RabbitMQConstant;
import com.longdidi.vo.Orders;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
@Service
@Slf4j
public class SendMessageService {
@Resource
private RabbitTemplate rabbitTemplate;
//这个对象可以进行序列化和反序列化(json格式)
@Resource
private ObjectMapper objectMapper;
/**
* 构造方法执行后自动执行
*/
@PostConstruct
public void init() {
//开启生产者的确定模式
rabbitTemplate.setConfirmCallback(
(correlationData, ack, cause) -> {
if (!ack) {
log.error("消息没有到达交换机,原因为:{}", cause);
//TODO 重发消息或者记录错误日志
}
}
);
rabbitTemplate.setReturnsCallback(
returnedMessage -> {
log.error("消息没有从交换机正确的投递(路由)到队列,原因为:{}", returnedMessage.getReplyText());
//TODO 记录错误日志,给程序员发短信或者或者邮件
}
);
}
public void sendMsg() throws JsonProcessingException {
{
//创建订单
Orders orders1 = Orders.builder()
.orderId("order_12345")
.orderName("买的手机")
.orderMoney(new BigDecimal(2356))
.orderTime(new Date())
.build();
//转成json
String strOrders1 = objectMapper.writeValueAsString(orders1);
MessageProperties messageProperties = new MessageProperties();
//设置单条消息的持久化,默认就是持久化
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = MessageBuilder.withBody(strOrders1.getBytes())
.andProperties(messageProperties).build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1, message);
}
{
Orders orders2 = Orders.builder()
.orderId("order_12345")
.orderName("买的手机")
.orderMoney(new BigDecimal(2356))
.orderTime(new Date())
.build();
String strOrders2 = objectMapper.writeValueAsString(orders2);
MessageProperties messageProperties = new MessageProperties();
//设置单条消息的持久化,默认就是持久化
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = MessageBuilder.withBody(strOrders2.getBytes())
.andProperties(messageProperties).build();
rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_NAME, RabbitMQConstant.ROUTING_NAME1, message);
}
log.info("消息发送完毕,发送时间为:{}", new Date());
}
}
消费者
java
package com.longdidi.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.longdidi.constants.RabbitMQConstant;
import com.longdidi.vo.Orders;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.io.IOException;
import com.rabbitmq.client.Channel;
@Component
@Slf4j
public class ReceiveMessageService {
@Resource
private ObjectMapper objectMapper;
@Resource
private StringRedisTemplate stringRedisTemplate;
@RabbitListener(queues = {RabbitMQConstant.QUEUE_NAME1})
public void receiveMsg(Message message, Channel channel) throws IOException {
//获取消息的唯一标识
long deliveryTag = message.getMessageProperties().getDeliveryTag();
//使用objectmapper把字节数组反序列化成对象
Orders orders = objectMapper.readValue(message.getBody(), Orders.class);
try {
log.info("接收到的消息为:{}", orders.toString());
//如果不存在就在redis中存储
Boolean setResult = stringRedisTemplate.opsForValue().setIfAbsent("idempotent:" + orders.getOrderId(), orders.getOrderId());
if (setResult) {
// TODO 向数据库插入订单等
log.info("向数据库插入订单");
}
//手动确认
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
log.error("消息处理出现问题");
try {
channel.basicNack(deliveryTag, false, true);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
}
}
}
定义常量
java
package com.longdidi.constants;
public class RabbitMQConstant {
// 正常交换机
public static final String EXCHANGE_NAME = "exchange.idempotent.normal.1";
// 队列
public static final String QUEUE_NAME1 = "queue.idempotent.normal.1";
// 路由key
public static final String ROUTING_NAME1 = "key.idempotent.normal.1";
}
定义MQ
java
package com.longdidi.config;
import com.longdidi.constants.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
/**
* 正常交换机
* 使用durable()方法设置持久化
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return ExchangeBuilder.directExchange(RabbitMQConstant.EXCHANGE_NAME).durable(true).build();
}
/**
* 正常队列
* durable()方法就是持久化
*
* @return
*/
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(RabbitMQConstant.QUEUE_NAME1)
.build();
}
/**
* 正常交换机和正常队列绑定
*
* @param normalExchange
* @param normalQueue
* @return
*/
@Bean
public Binding bindingNormal(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(RabbitMQConstant.ROUTING_NAME1);
}
}
实体类
java
package com.longdidi.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Orders implements Serializable {
private String orderId;
private String orderName;
private BigDecimal orderMoney;
private Date orderTime; //下单时间
}
发送消息
java
package com.longdidi;
import com.longdidi.service.SendMessageService;
import jakarta.annotation.Resource;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Rabbitmq09Idempotent01Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Rabbitmq09Idempotent01Application.class, args);
}
@Resource
private SendMessageService sendMessageService;
/**
* 程序一启动就会运行该方法
*
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sendMessageService.sendMsg();
}
}
测试
