摘要:本文简单介绍rabbitmq的几种消息模式的使用方式,让开发同学能够快速接入rabbitmq进行消息开发。案例采用的是springboot+rabbitmq方式。
简介
RabbitMQ是一个开源的消息代理软件,也被称为消息队列。它支持多种消息协议,并且能够为分布式系统提供异步消息传递功能。RabbitMQ使用高级消息队列协议(AMQP)0-9-1,同时也支持其他协议,如STOMP、MQTT等。
RabbitMQ的主要特点包括:
- 可靠性:确保消息能够被正确地发送和接收。
- 灵活性:支持多种消息协议和多种编程语言的客户端。
- 可扩展性:可以水平扩展以处理大量消息。
- 支持多种交换类型:如直接交换、主题交换、扇出交换和头交换等。
- 管理界面:提供了一个基于Web的简单管理界面,方便监控和管理。
RabbitMQ广泛应用于微服务架构中,用于解耦服务之间的通信,提高系统的可维护性和可扩展性。
基本概念
RabbitMQ是一个流行的开源消息代理,它基于AMQP协议,提供了一个可靠的消息队列服务。以下是RabbitMQ中的一些基本概念:
-
生产者(Producer):消息的发送方,负责将消息发送到RabbitMQ。
-
消费者(Consumer):消息的接收方,从RabbitMQ接收消息并进行处理。
-
消息(Message):在RabbitMQ中传输的数据,它由消息头和消息体组成。
-
队列(Queue):消息的存储地,生产者发送的消息会被放入队列中,消费者从队列中获取消息。
-
交换器(Exchange):接收来自生产者的消息,并将消息根据路由规则发送到一个或多个队列。RabbitMQ支持多种类型的交换器,例如:
- 直接交换器(Direct Exchange):根据路由键将消息路由到具有匹配路由键的队列。
- 主题交换器(Topic Exchange):根据路由键和模式匹配将消息路由到队列。
- 扇出交换器(Fanout Exchange):将消息发送到所有绑定的队列,不考虑路由键。
- 头交换器(Headers Exchange):根据消息的头属性进行路由。
-
绑定(Binding):交换器和队列之间的虚拟连接,它定义了消息如何从交换器路由到队列。
-
虚拟主机(Vhost):RabbitMQ服务器的命名空间,拥有自己的队列、交换器和绑定,可以看作是消息的隔离环境。
-
连接(Connection):客户端与RabbitMQ服务器之间的TCP连接。
-
通道(Channel):在客户端和RabbitMQ之间的虚拟连接,大多数RabbitMQ操作都在通道上进行,如声明队列、发送和接收消息等。
-
持久性(Durability):消息或队列的持久性设置,确保消息或队列在RabbitMQ服务器重启后依然存在。
-
确认(Acknowledgement):消费者接收并处理消息后,向RabbitMQ发送的确认信号,表示消息已被成功处理。
-
死信队列(Dead Letter Exchange):当消息无法被正常处理时,会被发送到死信队列,(现在用得很少了)。
常用队列使用案例
项目准备
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
RabbitmqApplication
@SpringBootApplication
public class RabbitmqApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitmqApplication.class, args);
}
}
点对点(单队列方式)
该方式只需要我们定义一个队列,然后向队列中发送消息即可,然后消费者监听该队列。
SampleConfig
该类定义队列名称
@Configuration
public class SampleConfig {
public static final String QUEUE = "SAMPLE_QUEUE";
@Autowired
private AmqpAdmin amqpAdmin;
@Bean(QUEUE)
public Queue sampleQueue(){
return new Queue(QUEUE);
}
public static final String QUEUE2 = "SAMPLE_QUEUE2";
@Bean(QUEUE2)
public Queue sampleQueue2(){
return new Queue(QUEUE2);
}
}
SampleProducer
生产者
@Component
public class SampleProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String message){
rabbitTemplate.convertAndSend(SampleConfig.QUEUE, message);
rabbitTemplate.convertAndSend(SampleConfig.QUEUE2, message);
}
}
SampleConsumer
消费者,我们也可以定义多个消费者,监听同一个队列,这样就进行了负载均衡,消息会按照顺序消费,生产过程中一般也是这样使用的(只不过是利用的线程池来消费)。
@Slf4j
@Component
public class SampleConsumer {
@RabbitListener(queues = SampleConfig.QUEUE)
public void fire(String message){
log.info("收到消息" + message);
}
@RabbitListener(queues = SampleConfig.QUEUE)
public void fire1(String message){
log.info("收到消息1" + message);
}
}
TestController
测试调用者
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private SampleProducer sampleProducer;
@GetMapping(value = "sampleProducer")
public Object sampleProducer(){
sampleProducer.send("sampleProducer" + System.currentTimeMillis());
return "SUCCESS";
}
}
发布订阅模式(fanout)
该模式就像是订阅新闻一样,我喜欢这个新闻,我订阅它,当它更新的时候就会给你推送最新的新闻;该模式需要引入交换机概念。
FanoutConfig配置类
@Configuration
public class FanoutConfig {
public static final String QUEUE_ZS = "collect_zs";
public static final String QUEUE_LS = "collect_ls";
public static final String EXCHANGE_COLLECT = "exchange_collect";
/**
* 定义交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchangeCollect(){
return new FanoutExchange(EXCHANGE_COLLECT);
}
/**
* 定义队列
* @return
*/
@Bean
public Queue queueCollectZs(){
return new Queue(QUEUE_ZS);
}
/**
* 定义队列
* @return
*/
@Bean
public Queue queueCollectLs(){
return new Queue(QUEUE_LS);
}
/**
* 绑定队列到交换机
* @param fanoutExchangeCollect
* @param queueCollectZs
* @return
*/
@Bean
public Binding bindingZs(FanoutExchange fanoutExchangeCollect, Queue queueCollectZs){
return BindingBuilder.bind(queueCollectZs).to(fanoutExchangeCollect);
}
/**
* 绑定队列到交换机
* @param fanoutExchangeCollect
* @param queueCollectLs
* @return
*/
@Bean
public Binding bindingLs(FanoutExchange fanoutExchangeCollect, Queue queueCollectLs){
return BindingBuilder.bind(queueCollectLs).to(fanoutExchangeCollect);
}
}
FanoutProducer生产者
@Service
public class FanoutProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* 路由键设置为空字符串
* @param message
*/
public void send(String message){
rabbitTemplate.convertAndSend(FanoutConfig.EXCHANGE_COLLECT, "", message);
}
}
FanoutConsumer消费者
@Slf4j
@Service
public class FanoutConsumer {
@RabbitListener(queues = FanoutConfig.QUEUE_ZS)
public void fireZs(String message){
log.info("张三收到消息" + message);
}
@RabbitListener(queues = FanoutConfig.QUEUE_LS)
public void fireLs(String message){
log.info("李四收到消息" + message);
}
}
TestController测试入口
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private FanoutProducer fanoutProducer;
@GetMapping(value = "fanoutProducer")
public Object fanoutProducer(String message){
fanoutProducer.send("fanoutProducer:" + message + System.currentTimeMillis());
return "SUCCESS";
}
}
结果
2024-07-11 09:25:19.192 INFO 1572 --- [ntContainer#0-1] c.h.rabbitmq.demo.fanout.FanoutConsumer : 张三收到消息fanoutProducer:你好1720661119173
2024-07-11 09:25:19.193 INFO 1572 --- [ntContainer#1-1] c.h.rabbitmq.demo.fanout.FanoutConsumer : 李四收到消息fanoutProducer:你好1720661119173
2024-07-11 09:25:24.485 INFO 1572 --- [ntContainer#0-1] c.h.rabbitmq.demo.fanout.FanoutConsumer : 张三收到消息fanoutProducer:你好a1720661124482
2024-07-11 09:25:24.492 INFO 1572 --- [ntContainer#1-1] c.h.rabbitmq.demo.fanout.FanoutConsumer : 李四收到消息fanoutProducer:你好a1720661124482
路由选择模式(direct)
该模式下我们只需要关注我们关心的消息,比如系统发送的日志,普通的日志收集器则需要关注
error、info、warning,但是报警服务则只需要关注error级别的日志,下面给出使用案例。
DirectConfig配置类
@Configuration
public class DirectConfig {
public static final String QUEUE_LOG_ALL = "queue_log_all";
public static final String QUEUE_LOG_ERROR = "queue_log_error";
public static final String EXCHANGE_LOG = "log";
public static final String ROUTING_KEY_INFO = "info";
public static final String ROUTING_KEY_ERROR = "error";
/**
* 定义交换机
* @return
*/
@Bean
public DirectExchange directExchangeLog(){
return new DirectExchange(EXCHANGE_LOG);
}
@Bean
public Queue queueLogAll(){
return new Queue(QUEUE_LOG_ALL);
}
@Bean
public Queue queueLogError(){
return new Queue(QUEUE_LOG_ERROR);
}
/**
* 绑定info的路由键到all队列
* @param queueLogAll
* @param directExchangeLog
* @return
*/
@Bean
public Binding bindingInfoToAll(Queue queueLogAll, DirectExchange directExchangeLog){
return BindingBuilder.bind(queueLogAll).to(directExchangeLog).with(ROUTING_KEY_INFO);
}
/**
* 绑定error的路由键到all队列
* @param queueLogAll
* @param directExchangeLog
* @return
*/
@Bean
public Binding bindingErrorToAll(Queue queueLogAll, DirectExchange directExchangeLog){
return BindingBuilder.bind(queueLogAll).to(directExchangeLog).with(ROUTING_KEY_ERROR);
}
/**
* 绑定error的路由键到error队列
* @param queueLogError
* @param directExchangeLog
* @return
*/
@Bean
public Binding bindingErrorToError(Queue queueLogError, DirectExchange directExchangeLog){
return BindingBuilder.bind(queueLogError).to(directExchangeLog).with(ROUTING_KEY_ERROR);
}
}
DirectProducer生产者
@Service
public class DirectProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param message
*/
public void send(String routingKey, String message){
rabbitTemplate.convertAndSend(DirectConfig.EXCHANGE_LOG, routingKey, message);
}
}
DirectConsumer消费者
@Slf4j
@Service
public class DirectConsumer {
@RabbitListener(queues = DirectConfig.QUEUE_LOG_ALL)
public void fireAll(String message){
log.info("接收所有日志:" + message);
}
@RabbitListener(queues = DirectConfig.QUEUE_LOG_ERROR)
public void fireError(String message){
log.info("接收ERROR日志:" + message);
}
}
TestController测试入口
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private DirectProducer directProducer;
@GetMapping(value = "directProducer")
public Object directProducer(String routingKey, String message){
directProducer.send(routingKey, "directProducer:" + message + System.currentTimeMillis());
return "SUCCESS";
}
}
测试结果
2024-07-11 10:32:48.856 INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer : 接收所有日志:directProducer:info日志1720665168842
2024-07-11 10:32:54.364 INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer : 接收所有日志:directProducer:info日志1720665174363
2024-07-11 10:33:05.009 INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer : 接收所有日志:directProducer:error日志1720665185007
2024-07-11 10:33:05.010 INFO 20856 --- [ntContainer#0-1] c.h.rabbitmq.demo.direct.DirectConsumer : 接收ERROR日志:directProducer:error日志1720665185007
2024-07-11 10:33:08.103 INFO 20856 --- [ntContainer#0-1] c.h.rabbitmq.demo.direct.DirectConsumer : 接收ERROR日志:directProducer:error日志1720665188102
2024-07-11 10:33:08.103 INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer : 接收所有日志:directProducer:error日志1720665188102
通配符模式Topics
该模式其实和
Direct模式差不多,只不过路由键可以配置为通配符,这样让我们配置更方便简单,在实际的业务场景中,我还没有遇到必须使用这种模式的。下面随便列了使用方式,匹配dog.*路由键和cat.*的路由键。
TopicsConfig 配置类
@Configuration
public class TopicsConfig {
public static final String QUEUE_DOG = "dog";
public static final String QUEUE_CAT = "cat";
public static final String EXCHANGE_ANIMAL = "animal";
public static final String ROUTING_KEY_DOG = "dog.*";
public static final String ROUTING_KEY_CAT = "cat.*";
@Bean
public Queue queueDog(){
return new Queue(QUEUE_DOG);
}
@Bean
public Queue queueCat(){
return new Queue(QUEUE_CAT);
}
@Bean
public TopicExchange topicExchangeAnimal(){
return new TopicExchange(EXCHANGE_ANIMAL);
}
@Bean
public Binding bindingDog(Queue queueDog, TopicExchange topicExchangeAnimal){
return BindingBuilder.bind(queueDog).to(topicExchangeAnimal).with(ROUTING_KEY_DOG);
}
@Bean
public Binding bindingCat(Queue queueCat, TopicExchange topicExchangeAnimal){
return BindingBuilder.bind(queueCat).to(topicExchangeAnimal).with(ROUTING_KEY_CAT);
}
}
TopicsProducer生产者
@Service
public class TopicsProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param message
*/
public void send(String routingKey, String message){
rabbitTemplate.convertAndSend(TopicsConfig.EXCHANGE_ANIMAL, routingKey, message);
}
}
TopicsConsumer 消费者
@Slf4j
@Service
public class TopicsConsumer {
@RabbitListener(queues = TopicsConfig.QUEUE_DOG)
public void fireDog(String message){
log.info("狗收到消息" + message);
}
@RabbitListener(queues = TopicsConfig.QUEUE_CAT)
public void fireCat(String message){
log.info("猫收到消息" + message);
}
}
TestController 测试发送者
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private TopicsProducer topicsProducer;
@GetMapping(value = "topicsProducer")
public Object topicsProducer(String routingKey, String message){
topicsProducer.send(routingKey, "topicsProducer:" + message + System.currentTimeMillis());
return "SUCCESS";
}
}
结果
2024-07-11 12:47:42.586 INFO 21200 --- [ntContainer#6-1] c.h.rabbitmq.demo.topics.TopicsConsumer : 狗收到消息topicsProducer:红色的狗1720673262558
2024-07-11 12:47:55.628 INFO 21200 --- [ntContainer#6-1] c.h.rabbitmq.demo.topics.TopicsConsumer : 狗收到消息topicsProducer:蓝色的狗1720673275621
2024-07-11 12:48:04.502 INFO 21200 --- [ntContainer#7-1] c.h.rabbitmq.demo.topics.TopicsConsumer : 猫收到消息topicsProducer:蓝色的猫1720673284499
延时消息,基于rabbitmq的延时插件
下面的案例模拟订单的取消延时。
docker镜像封装
Dockerfile文件内容
FROM rabbitmq:3-management
COPY rabbitmq_delayed_message_exchange-3.9.0.ez /plugins
RUN rabbitmq-plugins enable --offline rabbitmq_delayed_message_exchange
DelayConfig配置
@Configuration
public class DelayConfig {
public static final String QUEUE_ORDER_CANCEL = "order_cancel";
public static final String EXCHANGE_DELAY = "delayed";
public static final String ROUTING_KEY_ORDER_CANCEL = "routing_order_cancel";
@Bean
public Queue queueOrderCancel(){
return new Queue(QUEUE_ORDER_CANCEL);
}
@Bean
public CustomExchange customExchangeDelayed(){
Map<String, Object> args = new HashMap<>(1);
args.put("x-delayed-type", "direct");
return new CustomExchange(EXCHANGE_DELAY, "x-delayed-message", true, false, args);
}
@Bean
public Binding bindingOrderCancel(Queue queueOrderCancel, CustomExchange customExchangeDelayed){
return BindingBuilder.bind(queueOrderCancel).to(customExchangeDelayed).with(ROUTING_KEY_ORDER_CANCEL).noargs();
}
}
DelayProducer生产者
@Service
public class DelayProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param message
* @param delay 过期时间秒
*/
public void send(String message, long delay){
rabbitTemplate.convertAndSend(DelayConfig.EXCHANGE_DELAY, DelayConfig.ROUTING_KEY_ORDER_CANCEL, message, me -> {
me.getMessageProperties().setHeader("x-delay", delay * 1000);
return me;
});
}
}
DelayConsumer消费者
@Slf4j
@Service
public class DelayConsumer {
@RabbitListener(queues = DelayConfig.QUEUE_ORDER_CANCEL)
public void fireOrderCancel(String message){
log.info("取消订单消息 接收时间:" + System.currentTimeMillis() + "消息体:" + message);
}
}
TestController 测试发送
@Slf4j
@RestController
@RequestMapping(value = "test")
public class TestController {
@Autowired
private SampleProducer sampleProducer;
@Autowired
private FanoutProducer fanoutProducer;
@Autowired
private DirectProducer directProducer;
@Autowired
private TopicsProducer topicsProducer;
@Autowired
private DelayProducer delayProducer;
@GetMapping(value = "sampleProducer")
public Object sampleProducer(){
sampleProducer.send("sampleProducer" + System.currentTimeMillis());
return "SUCCESS";
}
@GetMapping(value = "fanoutProducer")
public Object fanoutProducer(String message){
fanoutProducer.send("fanoutProducer:" + message + System.currentTimeMillis());
return "SUCCESS";
}
@GetMapping(value = "directProducer")
public Object directProducer(String routingKey, String message){
directProducer.send(routingKey, "directProducer:" + message + System.currentTimeMillis());
return "SUCCESS";
}
@GetMapping(value = "topicsProducer")
public Object topicsProducer(String routingKey, String message){
topicsProducer.send(routingKey, "topicsProducer:" + message + System.currentTimeMillis());
return "SUCCESS";
}
@GetMapping(value = "delayProducer")
public Object delayProducer(String message, long delay){
log.info("延时消息发送时间:"+ System.currentTimeMillis() + "延迟秒数:"+ delay);
delayProducer.send("topicsProducer:" + message + System.currentTimeMillis(), delay);
return "SUCCESS";
}
}
测试结果
2024-07-11 15:46:29.140 INFO 21648 --- [nio-8080-exec-1] c.h.rabbitmq.controller.TestController : 延时消息发送时间:1720683989140延迟秒数:5
2024-07-11 15:46:34.373 INFO 21648 --- [ntContainer#0-1] c.h.rabbitmq.demo.delay.DelayConsumer : 取消订单消息 接收时间:1720683994373消息体:topicsProducer:这是订单11720683989141
2024-07-11 15:47:35.034 INFO 21648 --- [nio-8080-exec-5] c.h.rabbitmq.controller.TestController : 延时消息发送时间:1720684055034延迟秒数:0
2024-07-11 15:47:35.036 INFO 21648 --- [ntContainer#0-1] c.h.rabbitmq.demo.delay.DelayConsumer : 取消订单消息 接收时间:1720684055036消息体:topicsProducer:这是订单11720684055034
基于SpringBoot的高阶用法
该用法主要用于基础工具中,配置更加灵活精简。
@Configuration
public class ComplexConfig {
@Autowired
private AmqpAdmin amqpAdmin;
@PostConstruct
protected void init(){
/**
* 定义了一个goods-service的队列,通过direct交换机,路由键rk-goods-service
*/
Queue queue = new Queue("goods-service");
amqpAdmin.declareQueue(queue);
DirectExchange directExchange = new DirectExchange("center-direct-exchange");
amqpAdmin.declareExchange(directExchange);
amqpAdmin.declareBinding(BindingBuilder.bind(queue).to(directExchange).with("rk-goods-service"));
}
}
发送带header的消息
public void send(String headerValue, String message){
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties properties = message.getMessageProperties();
if (properties == null) {
properties = new MessageProperties();
}
properties.setHeader("class", headerValue);
return message;
}
};
rabbitTemplate.convertAndSend(SampleConfig.QUEUE, (Object) message, messagePostProcessor);
}
接收带header的消息
@RabbitListener(queues = SampleConfig.QUEUE)
public void fire2(Message message){
log.info("收到消息2 header:" + message.getMessageProperties().getHeaders().get("class"));
log.info("收到消息2 content:" + new String(message.getBody()));
}
附录
RabbitMq的Docker部署方式
version: '3'
services:
rabbitmq:
image: huzhihui/rabbitmq:3.9.11-management-delayed
container_name: rabbitmq
networks:
- default
environment:
- "TZ=Asia/Shanghai"
- "RABBITMQ_DEFAULT_USER=admin"
- "RABBITMQ_DEFAULT_PASS=admin"
volumes:
- "./data:/var/lib/rabbitmq"
ports:
- 5672:5672
- 15672:15672
- 1883:1883
- 15675:15675
networks:
default:
external:
name: huzhihui