发布订阅模式
RabbitMQ 支持发布-订阅模式(Publish-Subscribe Pattern),它是一种消息传递模式,用于将消息广播到多个消费者。在发布-订阅模式中,消息的发送方称为发布者(Publisher),而消息的接收方称为订阅者(Subscriber)。发布者将消息发送到交换机(Exchange),而交换机负责将消息广播到与之绑定的多个队列(Queues)。每个订阅者都可以独立地创建一个队列,并将其绑定到交换机上,以接收消息。
具体的步骤如下:
- 发布者将消息发送到交换机,而不是直接发送到队列。消息可以具有一个特定的路由键(Routing Key)。
- 交换机根据其类型和消息的路由键,将消息广播到与之绑定的多个队列。
- 每个订阅者创建一个独立的队列,并将其绑定到交换机上,以接收消息。
- 每个订阅者独立地消费队列中的消息。
这样,每个订阅者都能够独立地接收到发布者发送的消息,实现了消息的广播和多消费者的并行处理。
一、Fanout交换机
Fanout Exchange(扇形交换机): 扇形交换机将消息广播到与之绑定的所有队列,无论路由键是否匹配。它是实现发布-订阅模式最常用的交换机类型。
package com.example.springamqp.发布订阅.fanout;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfig {
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanout");
}
// 声明第一个队列
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
// 声明第二个队列
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
// 绑定队列1和交换机
@Bean
public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
// 绑定队列2和交换机
@Bean
public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
package com.example.springamqp.发布订阅.fanout;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class FanoutRabbitMQManager {
private final RabbitTemplate rabbitTemplate;
@Autowired
public FanoutRabbitMQManager(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void sendMessage(String exchangeName, String message) {
rabbitTemplate.convertAndSend(exchangeName, null, message);
}
}
package com.example.springamqp.发布订阅.fanout;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class FanoutCommandLineRunner implements CommandLineRunner {
private final FanoutRabbitMQManager rabbitMQManager;
@Autowired
public FanoutCommandLineRunner(FanoutRabbitMQManager rabbitMQManager) {
this.rabbitMQManager = rabbitMQManager;
}
@Override
public void run(String... args) {
rabbitMQManager.sendMessage("fanout", "Hello, RabbitMQ!");
}
@RabbitListener(queues = "fanout.queue1")
public void receiveMessage1(String message){
System.out.println("Received1 receive fanout message: " + message);
}
@RabbitListener(queues = "fanout.queue2")
public void receiveMessage2(String message){
System.out.println("Received2 receive fanout message: " + message);
}
}
二、direct交换机
Direct Exchange(直连交换机): 直连交换机根据消息的路由键将消息发送到与之匹配的队列。在发布-订阅模式中,通常将直连交换机和具有相同路由键的队列一起使用,以实现消息的广播。
package com.example.springamqp.发布订阅.direct;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DirectRabbitMQManager {
private final RabbitTemplate rabbitTemplate;
@Autowired
public DirectRabbitMQManager(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void sendMessage(String exchangeName, String routingKey, String message) {
rabbitTemplate.convertAndSend(exchangeName, routingKey, message);
}
}
package com.example.springamqp.发布订阅.direct;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class DirectCommandLineRunner implements CommandLineRunner {
private final DirectRabbitMQManager rabbitMQManager;
@Autowired
public DirectCommandLineRunner(DirectRabbitMQManager rabbitMQManager) {
this.rabbitMQManager = rabbitMQManager;
}
@Override
public void run(String... args) {
rabbitMQManager.sendMessage("direct", "yellow", "Hello, RabbitMQ!");
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "direct"),
key = {"red", "yellow"}
))
public void listenDirectQueue1(String message){
System.out.println("Received1 receive direct message: " + message);
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "direct"),
key = {"red", "blue"}
))
public void listenDirectQueue2(String message){
System.out.println("Received2 receive direct message: " + message);
}
}
三、topic交换机
Topic Exchange(主题交换机): 主题交换机根据消息的主题(Topic)进行消息路由。订阅者可以使用通配符匹配来接收特定主题的消息。
package com.example.springamqp.发布订阅.topic;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TopicRabbitMQManager {
private final RabbitTemplate rabbitTemplate;
@Autowired
public TopicRabbitMQManager(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void sendMessage(String exchangeName, String routingKey, String message) {
rabbitTemplate.convertAndSend(exchangeName, routingKey, message);
}
}
package com.example.springamqp.发布订阅.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class TopicCommandLineRunner implements CommandLineRunner {
private final TopicRabbitMQManager rabbitMQManager;
@Autowired
public TopicCommandLineRunner(TopicRabbitMQManager rabbitMQManager) {
this.rabbitMQManager = rabbitMQManager;
}
@Override
public void run(String... args) {
rabbitMQManager.sendMessage("topic", "china.news", "Hello, RabbitMQ!");
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
key = "china.#"
))
public void listenTopicQueue1(String message){
System.out.println("Received1 receive topic message: " + message);
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String message){
System.out.println("Received2 receive topic message: " + message);
}
}
@Override
public void run(String... args) {
rabbitMQManager.sendMessage("topic", "America.news", "Hello, RabbitMQ!");
}
@Override
public void run(String... args) {
rabbitMQManager.sendMessage("topic", "America.qq", "Hello, RabbitMQ!");
}
@Override
public void run(String... args) {
rabbitMQManager.sendMessage("topic", "chinachina.news", "Hello, RabbitMQ!");
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
key = "china.*"
))
public void listenTopicQueue1(String message){
System.out.println("Received1 receive topic message: " + message);
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),
key = "*.news"
))
public void listenTopicQueue2(String message){
System.out.println("Received2 receive topic message: " + message);
}