Spring Boot整合RabbitMQ相关博客,前面已经整合过发布与订阅模式、路由模式,使用注解实现路由模式。开发中还有一种模式比较常见,就是主题模式(Topic)。本文我们来进行主题模式的整合,整合分两种方式实现,配置类绑定交换机和队列还有通过注解的方式实现。话不多说,进入正题。
1. 生产者配置信息
消息生产者配置文件和其他集中模式的配置文件相同,详情如下
server:
port: 10001
spring:
application:
name: springboot-rabbitmq-s1
rabbitmq:
host: 127.0.0.1
port: 5672
virtual-host: /
username: admin
password: admin
# 发送者开启 return 确认机制
publisher-returns: true
# 发送者开启 confirm 确认机制
publisher-confirm-type: correlated
2. 生产者配置类
配置类RabbitMQConfig中注入主题模式交换机TopicExchange,一样是声明三个队列,并将三个队列绑定到交换机上,绑定时需要声明routingKey,routingKey是通过*和#模糊匹配。详情如下
package com.study.rabbitmq.config;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author alen
* @DATE 2022/6/7 23:50
*/
@Slf4j
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "topic-order-exchange";
public static final String SMS_QUEUE = "sms-topic-queue";
public static final String EMAIL_QUEUE = "email-topic-queue";
public static final String WECHAT_QUEUE = "wechat-topic-queue";
/**
* 1.
* 声明交换机
* @return
*/
@Bean
public TopicExchange topicExchange() {
/**
* topicExchange的参数说明:
* 1. 交换机名称
* 2. 是否持久化 true:持久化,交换机一直保留 false:不持久化,用完就删除
* 3. 是否自动删除 false:不自动删除 true:自动删除
*/
return new TopicExchange(EXCHANGE_NAME, true, false);
}
/**
* 2.
* 声明队列
* @return
*/
@Bean
public Queue smsQueue() {
/**
* Queue构造函数参数说明
* 1. 队列名
* 2. 是否持久化 true:持久化 false:不持久化
*/
return new Queue(SMS_QUEUE, true);
}
@Bean
public Queue emailQueue() {
return new Queue(EMAIL_QUEUE, true);
}
@Bean
public Queue wechatQueue() {
return new Queue(WECHAT_QUEUE, true);
}
/**
* 3.
* 队列与交换机绑定
*/
@Bean
public Binding smsBinding() {
return BindingBuilder.bind(smsQueue()).to(topicExchange()).with("*.sms.#");
}
@Bean
public Binding emailBinding() {
return BindingBuilder.bind(emailQueue()).to(topicExchange()).with("#.email.*");
}
@Bean
public Binding wechatBinding() {
return BindingBuilder.bind(wechatQueue()).to(topicExchange()).with("*.wechat.#");
}
/**
* 将自定义的RabbitTemplate对象注入bean容器
*
* @param connectionFactory
* @return
*/
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//设置开启消息推送结果回调
rabbitTemplate.setMandatory(true);
//设置ConfirmCallback回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("==============ConfirmCallback start ===============");
log.info("回调数据:{}", correlationData);
log.info("确认结果:{}", ack);
log.info("返回原因:{}", cause);
log.info("==============ConfirmCallback end =================");
}
});
//设置ReturnCallback回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("==============ReturnCallback start ===============");
log.info("发送消息:{}", JSONUtil.toJsonStr(message));
log.info("结果状态码:{}", replyCode);
log.info("结果状态信息:{}", replyText);
log.info("交换机:{}", exchange);
log.info("路由key:{}", routingKey);
log.info("==============ReturnCallback end =================");
}
});
return rabbitTemplate;
}
}
3. 发送消息
使用测试类像交换机中发送消息,发送消息的代码详情如下
package com.study.rabbitmq;
import com.study.rabbitmq.entity.Order;
import com.study.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.UUID;
@SpringBootTest
class SpringbootRabbitmqS1ApplicationTests {
@Autowired
private OrderService orderService;
@Test
void contextLoads() {
for (long i = 1; i < 2; i++) {
//交换机名称
String exchangeName = "topic-order-exchange";
//路由key
String routingKey = "com.sms.email.message";
Order order = buildOrder(i);
orderService.createOrder(order, routingKey, exchangeName);
}
}
private Order buildOrder(long id) {
Order order = new Order();
order.setRequestId(id);
order.setUserId(id);
order.setOrderNo(UUID.randomUUID().toString());
order.setAmount(10L);
order.setGoodsNum(1);
order.setTotalAmount(10L);
return order;
}
}
结合配置文件设置的匹配规则,三个队列中的sms和email两个队列可以接收到消息,并正常消费。交换机再将消息发送到队列时,进行通配符匹配,通配符有 * 和 # 两种。匹配规则如下:
- * :有且仅有一个
- #:匹配0个或者多个
设置的routingKey是com.sms.email.message,根据匹配规则可以知道sms队列和email队列可以接收到消息,消息发送和消费的效果展示如下
4. 消费配置信息
配置文件中主要是rabbitmq的连接信息和开启手动确认的配置,配置详情如下
server:
port: 10002
spring:
application:
name: springboot-rabbitmq-s2
rabbitmq:
host: 127.0.0.1
port: 5672
virtual-host: /
username: admin
password: admin
listener:
simple:
# 表示消费者消费成功消息以后需要手工的进行签收(ack确认),默认为 auto
acknowledge-mode: manual
5. 创建消费者
创建三个消费者,分别用来消费三个匹配的队列消息,三个消费者分别是TopicEmailConsumer、TopicSmsConsumer、TopicWechatConsumer。对应的源码详情如下
5.1 TopicEmailConsumer
package com.study.rabbitmq.service.topic;
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.stereotype.Service;
import java.io.IOException;
/**
* @Author alen
* @DATE 2022/6/12 17:17
*/
@Slf4j
@Service
//监听队列
@RabbitListener(queues = {"email-topic-queue"})
public class TopicEmailConsumer {
@RabbitHandler
public void emailMessage(String msg, Channel channel, Message message) throws IOException {
try {
log.info("Email topic --接收到消息:{}", msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
if (message.getMessageProperties().getRedelivered()) {
log.error("消息已重复处理失败,拒绝再次接收...");
//basicReject: 拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似 false表示消息不再重新进入队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
} else {
log.error("消息即将再次返回队列处理...");
// basicNack:表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
}
5.2 TopicSmsConsumer
package com.study.rabbitmq.service.topic;
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.stereotype.Service;
import java.io.IOException;
/**
* @Author alen
* @DATE 2022/6/12 17:17
*/
@Slf4j
@Service
//监听队列
@RabbitListener(queues = {"sms-topic-queue"})
public class TopicSmsConsumer {
@RabbitHandler
public void smsMessage(String msg, Channel channel, Message message) throws IOException {
try {
log.info("sms topic --接收到消息:{}", msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
if (message.getMessageProperties().getRedelivered()) {
log.error("消息已重复处理失败,拒绝再次接收...");
//basicReject: 拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似 false表示消息不再重新进入队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
} else {
log.error("消息即将再次返回队列处理...");
// basicNack:表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
}
5.3 TopicWechatConsumer
package com.study.rabbitmq.service.topic;
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.stereotype.Service;
import java.io.IOException;
/**
* @Author alen
* @DATE 2022/6/12 17:17
*/
@Slf4j
@Service
//监听队列
@RabbitListener(queues = {"wechat-topic-queue"})
public class TopicWechatConsumer {
@RabbitHandler
public void wechatMessage(String msg, Channel channel, Message message) throws IOException {
try {
log.info("wechat topic --接收到消息:{}", msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
if (message.getMessageProperties().getRedelivered()) {
log.error("消息已重复处理失败,拒绝再次接收...");
//basicReject: 拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似 false表示消息不再重新进入队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
} else {
log.error("消息即将再次返回队列处理...");
// basicNack:表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
}
针对队列中的消息消费情况如下:
以上可知,消息的投送和消费均正常。除了这种通过配置文件绑定交换机和队列的方式,还有一种是通过注解的形式,和配置文件不一样的地方是消费者的@RabbitListener注解中进行队列和交换机的绑定,绑定配置信息如下
package com.study.rabbitmq.service.topic;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @Author alen
* @DATE 2022/6/12 17:17
*/
@Slf4j
@Service
//监听队列 email-topic-queue
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "email-topic-queue", durable = "true"),
exchange = @Exchange(name = "topic-order-exchange", durable = "true", type = ExchangeTypes.TOPIC),
key = "#.email.*"
))
public class TopicEmailConsumer {
@RabbitHandler
public void emailMessage(String msg, Channel channel, Message message) throws IOException {
try {
log.info("Email topic --接收到消息:{}", msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
if (message.getMessageProperties().getRedelivered()) {
log.error("消息已重复处理失败,拒绝再次接收...");
//basicReject: 拒绝消息,与basicNack区别在于不能进行批量操作,其他用法很相似 false表示消息不再重新进入队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
} else {
log.error("消息即将再次返回队列处理...");
// basicNack:表示失败确认,一般在消费消息业务异常时用到此方法,可以将消息重新投递入队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
}
可以参考这个改一下其他两个消费者。然后自测一下消费的情况。由于没有本质上的区别,就不再测试了,感兴趣的可以尝试一下。
Spring Boot整合RabbitMQ的常用的三种模式就全部整合完成了。其他的模式由于使用的场景较少,整合方式大同小异,后续就不再整合其他模式了。感兴趣的可以自行尝试。