在前文中,我们通过RabbitMQ原生态的API方式了解到了RabbitMQ 是怎样实现消息的发送、接收、手动签收配置、各种模式的具体实现方式等。接下来我们将以与SpringBoot注解的方式来理解RabbitMQ。
Ps:注解的方式简化了实现代码,但是不利于理解。若朋友们觉得不好理解注解方式的话不妨先看一下前一篇文章。
RabbitMQ 消息中间件之案例分析Hello、Work 、Publish/Subscribe、Routing、Topics
愿为最亮星,公众号:华星详谈RabbitMQ 消息中间件之案例分析Hello、Work 、Publish/Subscribe、Routing、Topics
引入Maven 依赖
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1、简单的hello 模式
1.1、hello 模式 application.properties 文件配置
server.port=8080
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
1.2、hello 模式 生产者代码
packagecom.huaxing.rabbitmq._06springboot;
importorg.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.ResponseBody;
importorg.springframework.web.bind.annotation.RestController;
/**
* @Description SpringBoot 集成RabbitMQ --生产者
* @author: 姚广星
* @time: 2020/11/26 21:25
*/
@RestController
public classSendToSpringBootController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/queue")
@ResponseBody
public String sendMsg(String msg){
rabbitTemplate.convertAndSend("","boot_queue",msg);
return "发送成功";
}
}
1.3、消费者代码
packagecom.huaxing.rabbitmq._06springboot;
importcom.rabbitmq.client.Channel;
importlombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Queue;
importorg.springframework.amqp.rabbit.annotation.RabbitListener;
importorg.springframework.amqp.support.AmqpHeaders;
importorg.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
/**
* @Description
* @author: 姚广星
* @time: 2020/11/26 21:52
*/
@Slf4j
@Component
public classRecvToSpringBoot {
@RabbitListener(
queuesToDeclare =@Queue("boot_queue")//使用默认的交换机 @Queue:队列消息配置
)
publicvoid receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag,Channel channel) {
log.info("收到的消息-> deliveryTag:{}",deliveryTag);
log.info("收到的消息-> channel:{}",channel.toString());
log.info("收到的消息-> msg:{}",msg);
}
}
2、集成SpringBoot-手动签收实现方式
2.1 application.properties 文件配置
server.port=8080
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
2.2 生产者代码
/**
* 手动签收实现方式发送端
*
* @param msg
* @return
*/
@RequestMapping("/sendBootQueueManuallySigned")
@ResponseBody
public StringsendBootQueueManuallySigned(String msg) {
rabbitTemplate.convertAndSend("","boot_queue_ManuallySigned", msg);
return "发送成功";
}
2.3 消费者代码
/**
* 手动签收接受者
*
* @param msg 消息
* @param deliveryTag 消息唯一标识
* @param channel 通道
* @throws IOException
*/
@RabbitListener(
queuesToDeclare =@Queue("boot_queue_ManuallySigned")//使用默认的交换机 @Queue:队列消息配置
)
public voidreceiveMsgToManuallySigned(String msg, @Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, Channel channel) throws IOException {
try {
//模拟出现异常
//int a = 1 / 0;
log.info("收到的消息-> msg:{}", msg);
log.info("收到的消息标识-> deliveryTag:{}", deliveryTag);
log.info("收到的消息-> channel:{}", channel.toString());
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
一般我们在实际的使用中都会把签收方式配置为手动签收的方式。这样更方便于在消息出现异常时进行特殊的处理,如(报警机制、日志记录等)。
3、集成SpringBoot-work模式实现方式
Work模式与最基础的hello模式基本一样,他们区别在于work模式支持多个消费者同时接收消息,通过spring.rabbitmq.listener.simple.prefetch配置消息的预读数量控制消费者一次性从队列中读取多少条消息,做到能者多劳的配置(因为在实际的生产环境中每个服务器的配置不可能完全相同,带来的处理消息的时间也不一样)。
3.1 application.properties 文件配置
在原本的基础上添加消息预读数量配置。
完全的配置文件如下:
server.port=8080
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1
3.2 生产者代码
/**
* work 模式发送端
*
* @param msg
* @return
*/
@RequestMapping("/sendBootQueueToWork")
@ResponseBody
public String sendBootQueueToWork(Stringmsg) {
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("", "boot_queue_work",i + ":" + msg);
}
return "发送成功";
}
3.3 消费者代码
/**
* work 模式消费者1
*
* @param msg 消息
* @param deliveryTag 消息唯一标识
* @param channel 通道
* @throws IOException
*/
@RabbitListener(
queuesToDeclare =@Queue("boot_queue_work")//使用默认的交换机 @Queue:队列消息配置
)
publicvoid receiveMsgToToWork(String msg, @Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, Channel channel) throws IOException {
try {
//模拟服务器性能
TimeUnit.SECONDS.sleep(1);
log.info("01 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
/**
* work 模式消费者2
*
* @param msg 消息
* @param deliveryTag 消息唯一标识
* @param channel 通道
* @throws IOException
*/
@RabbitListener(
queuesToDeclare =@Queue("boot_queue_work")//使用默认的交换机 @Queue:队列消息配置
)
public void receiveMsgToToWork2(String msg,@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throwsIOException {
try {
//模拟服务器性能
TimeUnit.SECONDS.sleep(2);
log.info("02 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
3.4 运行结果
从运行结果可以看出,01消费者处理消息的速度快一些,处理的数据也就多一些。
4、集成SpringBoot-发布订阅(Publish/Subscribe)模式实现方式
我们将消息传达给多个消费者。这种模式称为“发布/订阅”。
4.1 application.properties 文件配置
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1
4.2 生产者代码
/**
* 发布/订阅模式发送端
*
* @param msg
* @return
*/
@RequestMapping("/sendBootQueueToPubSub")
@ResponseBody
public String sendBootQueueToPubSub(Stringmsg) {
rabbitTemplate.convertAndSend("boot_queue_pub_sub","", msg);
return "发送成功";
}
4.3 消费者代码
/**
* 发布/订阅模式接收者01
*
* @param msg
* @param deliveryTag
* @param channel
* @throws IOException
*/
@RabbitListener(//RabbitMQ 监听器
bindings = @QueueBinding(//创建队列和交换机的绑定关系
value = @Queue(),//创建个匿名队列
exchange = @Exchange(name ="boot_queue_pub_sub", type = "fanout")//定义交换机,指定交换机名称和类型
)
)
public void receiveMsgToPubSub1(String msg,
@Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, //@Header 获取消息体header中的信息
Channelchannel) throws IOException {
try {
log.info("01 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
/**
* 发布/订阅模式接收者02
*
* @param msg
* @param deliveryTag
* @param channel
* @throws IOException
*/
@RabbitListener(//RabbitMQ 监听器
bindings = @QueueBinding(//创建队列和交换机的绑定关系
value = @Queue(),//创建个匿名队列
exchange = @Exchange(name ="boot_queue_pub_sub", type = "fanout")//定义交换机,指定交换机名称和类型
)
)
public void receiveMsgToPubSub2(String msg,
@Header(AmqpHeaders.DELIVERY_TAG) longdeliveryTag, //@Header 获取消息体header中的信息
Channelchannel) throws IOException {
try {
log.info("02 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
5.4 运行结果
6 集成SpringBoot-Routing模式
在同一个消息,根据定义的消息路由key,指定消息只发送给固定的路由key对应的消息队列。达到分别的控制。
6.1 application.properties 文件配置
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1
6.2 生产者代码
/**
* routing 模式生产者
*
* @param msg
* @param routingKey 对应的routingKey
* @return
*/
@RequestMapping("/sendBootQueueToRouting")
@ResponseBody
public String sendBootQueueToRouting(Stringmsg,String routingKey) {
rabbitTemplate.convertAndSend("boot_queue_routing",routingKey, msg);
return "发送成功";
}
6.3 消费者代码
/**
* routing 模式消费者01
*
* @param msg
* @param deliveryTag
* @param channel
* @throws IOException
*/
@RabbitListener(//RabbitMQ 监听器
bindings = @QueueBinding(//创建队列和交换机的绑定关系
value = @Queue(),//创建个匿名队列
exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
key ={"log.error", "log.info", "log.warning"}//定义routingKey
)
)
public void receiveMsgToRouting01(Stringmsg,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, //@Header 获取消息体header中的信息
Channelchannel) throws IOException {
try {
log.info("01 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
/**
* routing 模式消费者02
*
* @param msg
* @param deliveryTag
* @param channel
* @throws IOException
*/
@RabbitListener(//RabbitMQ 监听器
bindings = @QueueBinding(//创建队列和交换机的绑定关系
value = @Queue(),//创建个匿名队列
exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
key ={"log.error"}//定义routingKey
)
)
public void receiveMsgToRouting02(Stringmsg,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, //@Header 获取消息体header中的信息
Channelchannel) throws IOException {
try {
log.info("02 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
当routingkey为log.error时:
从结果中我们可以看出两个消费者都收到了消息。
当routingKey为log.info时:
从结果中我们可以看出只有消费者01收到了消息,而消费者02并没有收到消息。
我们修改消费者02的代码,测试routing模式是否支持通配符配置:
当我们传入的routingKey为log.error 时,若是支持通配符配置则应该两个消费者都能够收到信息,若是不支持通配符则只有消费者01支持通配符。重新运行后,结果为
发现02消费者并没有收到消息,则可以得出结论:RoutingKey 模式不支持通配符设置。若我们想使用通配符则必须使用topics模式。
7 集成SpringBoot-Topic模式实现方式
Topics模式与Routing模式基本一样,他们两个的区别在于Topics模式支持通配,而Routing模式不支持通配。
7.1 application.properties 文件配置
#RabbitMQ服务器
spring.rabbitmq.host=localhost
#默认端口5672
spring.rabbitmq.port=45672
#默认guest,可不写
spring.rabbitmq.password=guest
#默认guest用户,可不写
spring.rabbitmq.username=guest
#配置手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息预读数量 1表示每次从队列中读取一条消息
spring.rabbitmq.listener.simple.prefetch=1
7.2 生产者代码
/**
* topics 模式生产者
*
* @param msg
* @param routingKey 对应的routingKey
* @return
*/
@RequestMapping("/sendBootQueueToTopics")
@ResponseBody
public String sendBootQueueToTopics(Stringmsg,String routingKey) {
rabbitTemplate.convertAndSend("boot_queue_routing",routingKey, msg);
return "发送成功";
}
7.3 消费者代码
/**
* Topics 模式消费者01
*
* @param msg
* @param deliveryTag
* @param channel
* @throws IOException
*/
@RabbitListener(//RabbitMQ 监听器
bindings = @QueueBinding(//创建队列和交换机的绑定关系
value = @Queue(),//创建个匿名队列
exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
key = {"log.*"}//定义routingKey 使用通配符接收routingKey
)
)
public void receiveMsgToTopics01(Stringmsg,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, //@Header 获取消息体header中的信息
Channel channel) throws IOException {
try {
log.info("01 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
/**
* Topics 模式消费者02
*
* @param msg
* @param deliveryTag
* @param channel
* @throws IOException
*/
@RabbitListener(//RabbitMQ 监听器
bindings = @QueueBinding(//创建队列和交换机的绑定关系
value = @Queue(),//创建个匿名队列
exchange = @Exchange(name ="boot_queue_routing", type = "direct"),//定义交换机,指定交换机名称和类型
key ={"log.error"}//定义routingKey
)
)
public void receiveMsgToTopics02(Stringmsg,
@Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, //@Header 获取消息体header中的信息
Channelchannel) throws IOException {
try {
log.info("02 消费者收到的消息 -> msg:{}", msg);
//手动签收 channel.basicAck(消息唯一标识,是否批量签收);
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
e.printStackTrace();
//channel.basicNack(deliveryTag:消息的唯一标识,multiple:是否批量处理,requeue:是否重新放入队列);
//消息出现异常时,若requeue=false,则该消息会被放入死信队列,若没有配置死信队列则该消息会丢失。
channel.basicNack(deliveryTag,false, false);
}
}
由以下运行结果可以得出Topics模式是支持通配符设置的。
本文涉及到的文献
官方文档:https://www.rabbitmq.com/
Git 代码地址:https://github.com/17666555910/HuaXing-SpringCloudDemo.git
CSDN 博客地址:https://blog.csdn.net/a767815662/category_10598332.html