🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
⭐ RabbitMQ ⭐
🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
前言
RabbitMQ 7种工作模式详解及应用场景_rabbitmq 广播模式应用场景-CSDN博客
之前简单介绍了RabbitMQ的七种工作模式,但是没有具体的代码实现。
这里复习的时候,把代码实现补上。
RabbitMQ 7种工作模式
RabbitMQ 是一个广泛使用的开源消息代理和队列服务器,支持多种消息传递模式。以下是RabbitMQ提供的7种主要的工作模式及其应用场景的详细介绍。
1. Simple(简单模式)
角色:
- P (Producer): 生产者,负责发送消息。
- C (Consumer): 消费者,负责接收消息。
- Queue (消息队列): 类似于邮箱,可以缓存消息;生产者向其中投递消息,消费者从中取出消息。
特点:一个生产者,一个消费者,消息只能被消费一次。也称为点对点 (Point-to-Point) 模式。
适用场景:消息只能被单个消费者处理的情况。
生产者
java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitProducer {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("110.41.51.65");//ip 默认值localhost
factory.setPort(15673); //默认值5672
factory.setVirtualHost("bite");//虚拟机名称, 默认 /
factory.setUsername("study");//用户名,默认guest
factory.setPassword("study");//密码, 默认guest
//3. 创建连接Connection
Connection connection = factory.newConnection();
//4. 创建channel通道
Channel channel = connection.createChannel();
//5. 声明队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments)
1.queue: 队列名称
2.durable: 是否持久化, 当mq重启之后, 消息还在
3.exclusive:
* 是否独占, 只能有一个消费者监听队列
* 当Connection关闭时, 是否删除队列
4.autoDelete: 是否自动删除, 当没有Consumer时, 自动删除掉
5.arguments: 一些参数
*/
//如果没有一个hello这样的一个队列, 会自动创建, 如果有, 则不创建
channel.queueDeclare("hello", true, false, false, null);
//6. 通过channel发送消息到队列中
/*
basicPublish(String exchange, String routingKey, AMQP.BasicProperties
props, byte[] body)
1. exchange: 交换机名称, 简单模式下, 交换机会使用默认的""
2.routingKey: 路由名称, routingKey = 队列名称
3.props: 配置信息
4.body: 发送消息的数据
*/
String msg = "Hello World";
//使用的是内置交换机. 使用内置交换机时, routingKey要和队列名称一样, 才可以路由
//到对应的队列上去
channel.basicPublish("", "hello", null, msg.getBytes());
//7.释放资源
System.out.println(msg + "消息发送成功");
channel.close();
connection.close();
}
}
- 创建连接工厂并设置参数 :首先创建
ConnectionFactory
对象,它是与 RabbitMQ 建立连接的工厂类。通过设置host
、port
、virtualHost
、username
和password
等参数,指定要连接的 RabbitMQ 服务器的地址、端口、虚拟机、用户名和密码。这里连接的是110.41.51.65:15673
,使用bite
虚拟机,用户名和密码都是study
。- 创建连接和通道 :利用
ConnectionFactory
创建Connection
连接对象,再通过连接创建Channel
通道。通道是进行消息读写操作的载体,通过它来执行各种 RabbitMQ 的操作。- 声明队列 :调用
channel.queueDeclare
方法声明一个队列。这里声明了名为hello
的队列,设置为持久化(durable = true
),这样在 RabbitMQ 重启后队列依然存在;不独占(exclusive = false
),允许多个消费者监听;不自动删除(autoDelete = false
),即使没有消费者时队列也不会被删除。- 发送消息 :使用
channel.basicPublish
方法向队列发送消息。在简单模式下,使用默认交换机(exchange = ""
),routingKey
设置为队列名称hello
,消息内容为"Hello World"
,并将其转换为字节数组发送。- 释放资源 :消息发送完成后,关闭
channel
和connection
,释放与 RabbitMQ 服务器的连接资源。
消费者
java
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class RabbitmqConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("110.41.51.65");//ip 默认值localhost
factory.setPort(15673); //默认值5672
factory.setVirtualHost("bite");//虚拟机名称, 默认 /
factory.setUsername("study");//用户名,默认guest
factory.setPassword("study");//密码, 默认guest
//3. 创建连接Connection
Connection connection = factory.newConnection();
//4. 创建channel通道
Channel channel = connection.createChannel();
//5. 声明队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments)
1.queue: 队列名称
2.durable: 是否持久化, 当mq重启之后, 消息还在
3.exclusive:
* 是否独占, 只能有一个消费者监听队列
* 当Connection关闭时, 是否删除队列
4.autoDelete: 是否自动删除, 当没有Consumer时, 自动删除掉
5.arguments: 一些参数
*/
//如果没有一个hello这样的一个队列, 会自动创建, 如果有, 则不创建
channel.queueDeclare("hello", true, false, false, null);
//6. 接收消息, 并消费
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1. queue: 队列名称
2. autoAck: 是否自动确认, 消费者收到消息之后,自动和MQ确认
3. callback: 回调对象
*/
DefaultConsumer consumer = new DefaultConsumer(channel) {
/*
回调方法, 当收到消息后, 会自动执行该方法
1. consumerTag: 标识
2. envelope: 获取一些信息, 交换机, 路由key
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume("hello", true, consumer);
//等待回调函数执行完毕之后, 关闭资源
TimeUnit.SECONDS.sleep(5);
//7. 释放资源 消费者相当于是一个监听程序, 不需要关闭资源
//顺序不可改变
// channel.close();
// connection.close();
}
}
- 连接设置与队列声明 :与生产者类似,先创建连接工厂并设置参数,建立连接和通道,然后声明
hello
队列,保证消费者与生产者操作的是同一个队列。- 接收并消费消息 :通过
channel.basicConsume
方法接收消息。创建一个DefaultConsumer
回调对象,重写handleDelivery
方法,当消费者接收到消息时,该方法会自动执行,将接收到的消息字节数组转换为字符串并打印输出。这里设置autoAck = true
,表示消费者收到消息后自动向 RabbitMQ 确认,这样 RabbitMQ 就会认为消息已被成功处理并从队列中移除。- 资源处理 :消费者通常作为一个监听程序持续运行,这里使用
TimeUnit.SECONDS.sleep(5)
让程序等待 5 秒,模拟回调函数执行的过程,之后如果需要关闭资源,可以按照channel.close()
和connection.close()
的顺序关闭,但一般情况下消费者不会轻易关闭连接,而是持续监听队列获取消息。
2. Work Queue(工作队列)
角色:
- P (Producer): 生产者。
- C1, C2 (Consumers): 多个消费者。
- Queue (消息队列): 用于缓存消息。
特点:一个生产者,多个消费者。在多个消息的情况下,Work Queue 会将消息分派给不同的消费者,每个消费者都会接收到不同的消息。
适用场景:集群环境中做异步处理。例如,12306 短信通知服务,订单成功后,订单消息会被发送到 RabbitMQ,短信服务从 RabbitMQ 中获取订单信息,并发送通知信息。
1. 常量类
为了方便管理和维护 RabbitMQ 的连接信息,我们创建一个常量类 Constants
,用于存储连接所需的主机地址、端口、虚拟主机、用户名、密码以及队列名称。
java
public class Constants {
public static final String HOST = "110.41.51.65";
public static final Integer PORT = 15673;
public static final String VIRTUAL_HOST = "bite";
public static final String USER_NAME = "study";
public static final String PASSWORD = "study";
public static final String WORK_QUEUE_NAME = "work_queues";
}
2. 生产者
生产者类的主要职责是创建与 RabbitMQ 的连接,声明队列,并向队列中发送 10 条消息。以下是具体的代码实现:
java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import constant.Constants;
public class WorkRabbitProducer {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 声明队列
// 如果队列不存在,会自动创建;如果存在,则不创建
channel.queueDeclare(Constants.WORK_QUEUE_NAME, true, false, false, null);
// 5. 发送 10 条消息
for (int i = 0; i < 10; i++) {
String msg = "Hello World" + i;
// 向队列中发送消息
channel.basicPublish("", Constants.WORK_QUEUE_NAME, null, msg.getBytes());
}
// 6. 释放资源
channel.close();
connection.close();
}
}
代码解释:
- 连接工厂和连接创建 :使用
ConnectionFactory
创建连接工厂,并设置连接所需的参数,如主机地址、端口、虚拟主机、用户名和密码。然后通过factory.newConnection()
方法创建连接。- 通道创建 :在连接的基础上,使用
connection.createChannel()
方法创建通道,通道是进行消息读写操作的主要对象。- 队列声明 :使用
channel.queueDeclare
方法声明队列,设置队列的持久化、独占性和自动删除等属性。- 消息发送 :通过
for
循环发送 10 条消息,使用channel.basicPublish
方法将消息发送到指定的队列中。- 资源释放:消息发送完成后,关闭通道和连接,释放资源。
3. 消费者
消费者类的主要功能是创建与 RabbitMQ 的连接,声明队列,并从队列中消费消息。我们创建两个消费者类来模拟多个消费者竞争消费消息的场景。
WorkRabbitmqConsumer1
类
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class WorkRabbitmqConsumer1 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 声明队列
// 如果队列不存在,会自动创建;如果存在,则不创建
channel.queueDeclare(Constants.WORK_QUEUE_NAME, true, false, false, null);
// 5. 定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("WorkRabbitmqConsumer1 接收到消息: " + new String(body));
}
};
// 6. 开始消费消息
channel.basicConsume(Constants.WORK_QUEUE_NAME, true, consumer);
}
}
WorkRabbitmqConsumer2
类与 1一样,这里就不多赘述
代码解释:
- 连接和通道创建:与生产者类类似,创建连接工厂、连接和通道。
- 队列声明:确保消费者使用的队列与生产者声明的队列一致。
- 消费者定义 :创建
DefaultConsumer
对象,并重写handleDelivery
方法,该方法在接收到消息时会被自动调用,用于处理接收到的消息。- 消息消费 :使用
channel.basicConsume
方法开始消费队列中的消息,设置自动确认模式。
运行步骤:
- 先启动两个消费者程序
WorkRabbitmqConsumer1
和WorkRabbitmqConsumer2
。- 再启动生产者程序
WorkRabbitProducer
。
结果分析:
由于工作队列模式采用轮询的方式将消息分发给多个消费者,因此两个消费者会交替消费生产者发送的 10 条消息。通常情况下,
WorkRabbitmqConsumer1
会消费到消息Hello World0
、Hello World2
、Hello World4
、Hello World6
、Hello World8
,而WorkRabbitmqConsumer2
会消费到消息Hello World1
、Hello World3
、Hello World5
、Hello World7
、Hello World9
。这体现了多个消费者竞争消费消息的特点,提高了系统的处理能力和效率。
3. Publish/Subscribe(发布/订阅)
角色:
特点:生产者将消息发送到 Ex
- P (Producer): 生产者。
- X (Exchange, 交换机): 负责将消息按一定规则路由到一个或多个队列。
- C1, C2 (Consumers): 多个消费者。
- Queue (消息队列): 用于缓存消息。
概念介绍
Exchange: 交换机 (X).
作⽤: ⽣产者将消息发送到Exchange, 由交换机将消息按⼀定规则路由到⼀个或多个队列中(上图中⽣产 者将消息投递到队列中, 实际上这个在RabbitMQ中不会发⽣. )
RabbitMQ交换机有四种类型: fanout,direct, topic, headers, 不同类型有着不同的路由策略.
AMQP协 议⾥还有另外两种类型, System和⾃定义, 此处不再描述.
- Fanout:⼴播,将消息交给所有绑定到交换机的队列(Publish/Subscribe模式)
- Direct:定向,把消息交给符合指定routing key的队列(Routing模式)
- Topic:通配符,把消息交给符合routing pattern(路由模式)的队列(Topics模式)
- headers类型的交换器不依赖于路由键的匹配规则来路由消息, ⽽是根据发送的消息内容中的
headers属性进⾏匹配. headers类型的交换器性能会很差,⽽且也不实⽤,基本上不会看到它的存在.
Exchange(交换机)只负责转发消息, 不具备存储消息的能⼒, 因此如果没有任何队 Exchange绑定,或者没有符合路由规则的队列,那么消息就会丢失
RoutingKey: 路由键.⽣产者将消息发给交换器时, 指定的⼀个字符串, ⽤来告诉交换机应该如何处理这个消息.
Binding Key:绑定. RabbitMQ中通过Binding(绑定)将交换器与队列关联起来, 在绑定的时候⼀般会指 定⼀个Binding Key, 这样RabbitMQ就知道如何正确地将消息路由到队列了.
change,由交换机将消息广播到所有绑定的队列。每个消费者都会接收到相同的消息。
⽐如下图: 如果在发送消息时, 设置了RoutingKey 为orange, 消息就会路由到Q1

当消息的Routing key与队列绑定的Bindingkey相匹配时,消息才会被路由到这个队列.
BindingKey其实也属于路由键中的⼀种, 官⽅解释为:the routingkey to use for the binding.
可以翻译为:在绑定的时候使⽤的路由键. ⼤多数时候,包括官⽅⽂档和RabbitMQJava API 中都把
BindingKey和RoutingKey看作RoutingKey, 为了避免混淆,可以这么理解:
在使⽤绑定的时候,需要的路由键是BindingKey.
在发送消息的时候,需要的路由键是RoutingKey
适用场景:消息需要被多个消费者同时接收的场景。例如,中国气象局发布天气预报的消息,新浪、百度、搜狐等门户网站通过队列绑定到该交换机,自动获取气象局推送的气象数据。
1. 常量类
首先,我们定义一些常量,用于存储 RabbitMQ 连接信息以及交换器和队列的名称。这些常量可以方便地在生产者和消费者代码中复用。
java
public class Constants {
public static final String HOST = "110.41.51.65";
public static final Integer PORT = 15673;
public static final String VIRTUAL_HOST = "bite";
public static final String USER_NAME = "study";
public static final String PASSWORD = "study";
public static final String FANOUT_EXCHANGE_NAME = "test_fanout";
public static final String FANOUT_QUEUE_NAME1 = "fanout_queue1";
public static final String FANOUT_QUEUE_NAME2 = "fanout_queue2";
}
2. 生产者
生产者的主要任务是创建与 RabbitMQ 的连接,声明扇形交换器、队列,并将队列与交换器绑定,最后发送消息。以下是详细的生产者代码:
java
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import constant.Constants;
public class FanoutRabbitProducer {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 创建交换机
channel.exchangeDeclare(Constants.FANOUT_EXCHANGE_NAME,
BuiltinExchangeType.FANOUT, true, false, false, null);
// 5. 声明队列
channel.queueDeclare(Constants.FANOUT_QUEUE_NAME1, true, false, false, null);
channel.queueDeclare(Constants.FANOUT_QUEUE_NAME2, true, false, false, null);
// 6. 绑定队列和交换机
channel.queueBind(Constants.FANOUT_QUEUE_NAME1, Constants.FANOUT_EXCHANGE_NAME, "");
channel.queueBind(Constants.FANOUT_QUEUE_NAME2, Constants.FANOUT_EXCHANGE_NAME, "");
// 7. 发送消息
String msg = "hello fanout";
channel.basicPublish(Constants.FANOUT_EXCHANGE_NAME, "", null, msg.getBytes());
// 8. 释放资源
channel.close();
connection.close();
}
}
代码解释:
- 连接设置 :使用
ConnectionFactory
创建连接工厂,并设置 RabbitMQ 服务器的主机地址、端口、虚拟主机、用户名和密码。然后通过factory.newConnection()
创建连接。- 通道创建 :通过
connection.createChannel()
创建通道,通道是进行消息操作的载体。- 交换器声明 :使用
channel.exchangeDeclare
方法声明一个扇形交换器,设置交换器名称为test_fanout
,类型为FANOUT
,并设置为持久化(durable = true
),不自动删除(autoDelete = false
),不是内部使用(internal = false
)。- 队列声明 :使用
channel.queueDeclare
方法声明两个队列fanout_queue1
和fanout_queue2
,设置为持久化,不独占,不自动删除。- 绑定操作 :使用
channel.queueBind
方法将两个队列分别与扇形交换器绑定,由于是扇形交换器,路由键设置为空字符串(""
),表示广播消息。- 消息发送 :使用
channel.basicPublish
方法发送消息,指定交换器名称,路由键为空字符串,消息内容为hello fanout
。- 资源释放:发送完消息后,关闭通道和连接,释放资源。
3. 消费者
消费者的主要任务是创建与 RabbitMQ 的连接,接收并处理从队列中获取的消息。由于交换器和队列的绑定关系及声明已经在生产者代码中完成,消费者代码中不需要重复这些操作。以下是两个消费者的代码示例:
消费者 1
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class FanoutRabbitmqConsumer1 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.FANOUT_QUEUE_NAME1, true, consumer);
}
}
消费者 2
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class FanoutRabbitmqConsumer2 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.FANOUT_QUEUE_NAME2, true, consumer);
}
}
代码解释:
- 连接和通道创建:与生产者类似,创建连接工厂、连接和通道。
- 消息消费 :创建
DefaultConsumer
对象,并重写handleDelivery
方法,该方法在接收到消息时会被调用,用于处理接收到的消息。然后使用channel.basicConsume
方法开始消费指定队列(fanout_queue1
或fanout_queue2
)中的消息,设置自动确认模式(autoAck = true
)。
- 运行结果
当我们先启动两个消费者,再启动生产者时,生产者发送的消息
hello fanout
会被广播到两个队列中,两个消费者都会接收到消息并打印出相应的内容。这体现了扇形交换器模式的广播特性,即消息会被发送到所有与之绑定的队列,实现了消息的多播功能。
通过以上代码和步骤,我们成功实现了 RabbitMQ 的扇形交换器模式,并且可以观察到消息广播的效果。在实际应用中,扇形交换器模式适用于需要将消息同时发送给多个消费者的场景,如日志记录、通知系统等。
4. Routing(路由模式)
角色:
- P (Producer): 生产者。
- X (Exchange, 交换机): 根据 RoutingKey 的规则将消息路由到对应的队列。
- C1, C2 (Consumers): 多个消费者。
- Queue (消息队列): 用于缓存消息。
特点:交换机根据 RoutingKey 将消息筛选后发给对应的消费者队列。
适用场景:需要根据特定规则分发消息的场景。例如,系统打印日志,日志等级分为 error, warning, info, debug,可以通过这种模式将不同的日志发送到不同的队列,最终输出到不同的文件。
1. 常量类
定义一些常量,用于存储 RabbitMQ 连接信息以及直连交换器和队列的名称。
java
public class Constants {
public static final String HOST = "110.41.51.65";
public static final Integer PORT = 15673;
public static final String VIRTUAL_HOST = "bite";
public static final String USER_NAME = "study";
public static final String PASSWORD = "study";
public static final String DIRECT_EXCHANGE_NAME = "test_direct";
public static final String DIRECT_QUEUE_NAME1 = "direct_queue1";
public static final String DIRECT_QUEUE_NAME2 = "direct_queue2";
}
2. 生产者
生产者的主要任务是创建与 RabbitMQ 的连接,声明直连交换器和队列,将队列与交换器通过不同的路由键进行绑定,最后根据不同的路由键发送消息。
java
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import constant.Constants;
public class DirectRabbitProducer {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 创建直连交换器
// 注意:原代码此处使用BuiltinExchangeType.FANOUT有误,应改为BuiltinExchangeType.DIRECT
channel.exchangeDeclare(Constants.DIRECT_EXCHANGE_NAME,
BuiltinExchangeType.DIRECT, true, false, false, null);
// 5. 声明队列
channel.queueDeclare(Constants.DIRECT_QUEUE_NAME1, true, false, false, null);
channel.queueDeclare(Constants.DIRECT_QUEUE_NAME2, true, false, false, null);
// 6. 绑定队列和交换器
// 队列1绑定到路由键 "orange"
channel.queueBind(Constants.DIRECT_QUEUE_NAME1, Constants.DIRECT_EXCHANGE_NAME, "orange");
// 队列2绑定到路由键 "black" 和 "green"
channel.queueBind(Constants.DIRECT_QUEUE_NAME2, Constants.DIRECT_EXCHANGE_NAME, "black");
channel.queueBind(Constants.DIRECT_QUEUE_NAME2, Constants.DIRECT_EXCHANGE_NAME, "green");
// 7. 发送消息
String msg = "hello direct, I am orange";
channel.basicPublish(Constants.DIRECT_EXCHANGE_NAME, "orange", null, msg.getBytes());
String msg_black = "hello direct, I am black";
channel.basicPublish(Constants.DIRECT_EXCHANGE_NAME, "black", null, msg_black.getBytes());
String msg_green = "hello direct, I am green";
channel.basicPublish(Constants.DIRECT_EXCHANGE_NAME, "green", null, msg_green.getBytes());
// 8. 释放资源
channel.close();
connection.close();
}
}
代码解释:
- 连接设置 :使用
ConnectionFactory
创建连接工厂,并设置 RabbitMQ 服务器的主机地址、端口、虚拟主机、用户名和密码,然后创建连接和通道。- 交换器声明 :使用
channel.exchangeDeclare
方法声明一个直连交换器,指定交换器名称、类型为DIRECT
,并设置为持久化。- 队列声明 :使用
channel.queueDeclare
方法声明两个队列,同样设置为持久化。- 绑定操作 :使用
channel.queueBind
方法将队列 1 绑定到路由键"orange"
,队列 2 绑定到路由键"black"
和"green"
。- 消息发送 :使用
channel.basicPublish
方法根据不同的路由键发送不同的消息。- 资源释放:发送完消息后,关闭通道和连接。
3. 消费者
消费者的主要任务是创建与 RabbitMQ 的连接,从指定的队列中接收并处理消息。
消费者 1:DirectRabbitmqConsumer1
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class DirectRabbitmqConsumer1 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.DIRECT_QUEUE_NAME1, true, consumer);
}
}
消费者 2:DirectRabbitmqConsumer2
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class DirectRabbitmqConsumer2 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.DIRECT_QUEUE_NAME2, true, consumer);
}
}
代码解释:
- 连接和通道创建:与生产者类似,创建连接工厂、连接和通道。
- 消息消费 :创建
DefaultConsumer
对象,并重写handleDelivery
方法用于处理接收到的消息。使用channel.basicConsume
方法从指定队列中消费消息,设置自动确认模式。
- 运行结果及分析
运行步骤:
- 先运行生产者程序,生产者会根据不同的路由键将消息发送到相应的队列。
- 再运行两个消费者程序,消费者会从各自绑定的队列中获取消息。
结果分析:
DirectRabbitmqConsumer1
会接收到路由键为"orange"
的消息,即"hello direct, I am orange"
。DirectRabbitmqConsumer2
会接收到路由键为"black"
和"green"
的消息,即"hello direct, I am black"
和"hello direct, I am green"
。通过这种方式,我们可以看到直连交换器模式根据路由键精确分发消息的特性,不同的队列可以根据绑定的路由键接收不同的消息,实现了消息的定向分发。
5. Topics(通配符模式)
角色:
- P (Producer): 生产者。
- X (Exchange, 交换机): 根据 RoutingKey 的通配符匹配规则将消息路由到对应的队列。
- C1, C2 (Consumers): 多个消费者。
- Queue (消息队列): 用于缓存消息。
特点:类似于 Routing 模式,但使用通配符来定义 RoutingKey 的模式,使得匹配更加灵活。
适用场景:需要灵活匹配和过滤消息的场景。
示例图:展示交换机根据通配符匹配规则将消息路由到不同的队列。
1. 常量类
定义一些常量,用于存储 RabbitMQ 连接信息以及主题交换器和队列的名称。
java
public class Constants {
public static final String HOST = "110.41.51.65";
public static final Integer PORT = 15673;
public static final String VIRTUAL_HOST = "bite";
public static final String USER_NAME = "study";
public static final String PASSWORD = "study";
public static final String TOPIC_EXCHANGE_NAME = "test_topic";
public static final String TOPIC_QUEUE_NAME1 = "topic_queue1";
public static final String TOPIC_QUEUE_NAME2 = "topic_queue2";
}
2. 生产者
生产者的主要任务是创建与 RabbitMQ 的连接,声明主题交换器和队列,将队列与交换器通过不同的主题模式进行绑定,最后根据不同的主题发送消息。
java
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import constant.Constants;
public class TopicRabbitProducer {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 创建主题交换器
channel.exchangeDeclare(Constants.TOPIC_EXCHANGE_NAME,
BuiltinExchangeType.TOPIC, true, false, false, null);
// 5. 声明队列
channel.queueDeclare(Constants.TOPIC_QUEUE_NAME1, true, false, false, null);
channel.queueDeclare(Constants.TOPIC_QUEUE_NAME2, true, false, false, null);
// 6. 绑定队列和交换器
// 队列 1 绑定 *.error,仅接收 error 信息
channel.queueBind(Constants.TOPIC_QUEUE_NAME1, Constants.TOPIC_EXCHANGE_NAME, "*.error");
// 队列 2 绑定 #.info 和 *.error,接收 info 和 error 信息
channel.queueBind(Constants.TOPIC_QUEUE_NAME2, Constants.TOPIC_EXCHANGE_NAME, "#.info");
channel.queueBind(Constants.TOPIC_QUEUE_NAME2, Constants.TOPIC_EXCHANGE_NAME, "*.error");
// 7. 发送消息
String msg = "hello topic, I'm order.error";
channel.basicPublish(Constants.TOPIC_EXCHANGE_NAME, "order.error", null, msg.getBytes());
String msg_black = "hello topic, I'm order.pay.info";
channel.basicPublish(Constants.TOPIC_EXCHANGE_NAME, "order.pay.info", null, msg_black.getBytes());
String msg_green = "hello topic, I'm pay.error";
channel.basicPublish(Constants.TOPIC_EXCHANGE_NAME, "pay.error", null, msg_green.getBytes());
// 8. 释放资源
channel.close();
connection.close();
}
}
代码解释:
- 连接设置 :使用
ConnectionFactory
创建连接工厂,并设置 RabbitMQ 服务器的主机地址、端口、虚拟主机、用户名和密码,然后创建连接和通道。- 交换器声明 :使用
channel.exchangeDeclare
方法声明一个主题交换器,指定交换器名称、类型为TOPIC
,并设置为持久化。- 队列声明 :使用
channel.queueDeclare
方法声明两个队列,同样设置为持久化。- 绑定操作 :使用
channel.queueBind
方法将队列 1 绑定到主题模式"*.error"
,队列 2 绑定到主题模式"#.info"
和"*.error"
。其中,*
表示匹配一个单词,#
表示匹配零个或多个单词。- 消息发送 :使用
channel.basicPublish
方法根据不同的主题发送不同的消息。- 资源释放:发送完消息后,关闭通道和连接。
3. 消费者代码
消费者的主要任务是创建与 RabbitMQ 的连接,从指定的队列中接收并处理消息。
消费者 1:TopicRabbitmqConsumer1
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class TopicRabbitmqConsumer1 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.TOPIC_QUEUE_NAME1, true, consumer);
}
}
消费者 2:TopicRabbitmqConsumer2
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class TopicRabbitmqConsumer2 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.TOPIC_QUEUE_NAME2, true, consumer);
}
}
代码解释:
- 连接和通道创建:与生产者类似,创建连接工厂、连接和通道。
- 消息消费 :创建
DefaultConsumer
对象,并重写handleDelivery
方法用于处理接收到的消息。使用channel.basicConsume
方法从指定队列中消费消息,设置自动确认模式。
- 运行结果及分析
运行步骤:
- 先运行生产者程序,生产者会根据不同的主题将消息发送到主题交换器。
- 再运行两个消费者程序,消费者会从各自绑定的队列中获取消息。
结果分析:
TopicRabbitmqConsumer1
会接收到主题匹配"*.error"
的消息,即"hello topic, I'm order.error"
和"hello topic, I'm pay.error"
。TopicRabbitmqConsumer2
会接收到主题匹配"#.info"
和"*.error"
的消息,即"hello topic, I'm order.pay.info"
、"hello topic, I'm order.error"
和"hello topic, I'm pay.error"
。通过这种方式,我们可以看到主题交换器模式根据主题模式灵活分发消息的特性,不同的队列可以根据绑定的主题模式接收不同的消息,实现了消息的灵活订阅和分发。
6. RPC (Remote Procedure Call, 远程过程调用)
角色:
- 客户端 (Client): 发送请求消息并等待响应。
- 服务端 (Server): 接收请求消息并发送响应消息。
特点:客户端发送消息到指定的队列,并设置 replyTo 字段指定回调队列。服务端处理请求并发送响应消息到回调队列。客户端在回调队列上等待响应消息,并检查 correlationId 属性以确保它是所期望的响应。
适用场景:需要实现远程调用的过程,例如微服务间的通信。
1. 常量
首先,我们定义一些常量,用于存储 RabbitMQ 连接信息以及主题交换器和队列的名称。
java
public class Constants {
public static final String HOST = "110.41.51.65";
public static final Integer PORT = 15673;
public static final String VIRTUAL_HOST = "bite";
public static final String USER_NAME = "study";
public static final String PASSWORD = "study";
public static final String TOPIC_EXCHANGE_NAME = "test_topic";
public static final String TOPIC_QUEUE_NAME1 = "topic_queue1";
public static final String TOPIC_QUEUE_NAME2 = "topic_queue2";
}
2. 生产者
生产者的主要任务是创建与 RabbitMQ 的连接,声明主题交换器和队列,将队列与交换器通过不同的主题模式进行绑定,最后根据不同的主题发送消息。
java
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import constant.Constants;
public class TopicRabbitProducer {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 创建主题交换器
channel.exchangeDeclare(Constants.TOPIC_EXCHANGE_NAME,
BuiltinExchangeType.TOPIC, true, false, false, null);
// 5. 声明队列
channel.queueDeclare(Constants.TOPIC_QUEUE_NAME1, true, false, false, null);
channel.queueDeclare(Constants.TOPIC_QUEUE_NAME2, true, false, false, null);
// 6. 绑定队列和交换器
// 队列 1 绑定 *.error,仅接收 error 信息
channel.queueBind(Constants.TOPIC_QUEUE_NAME1, Constants.TOPIC_EXCHANGE_NAME, "*.error");
// 队列 2 绑定 #.info 和 *.error,接收 info 和 error 信息
channel.queueBind(Constants.TOPIC_QUEUE_NAME2, Constants.TOPIC_EXCHANGE_NAME, "#.info");
channel.queueBind(Constants.TOPIC_QUEUE_NAME2, Constants.TOPIC_EXCHANGE_NAME, "*.error");
// 7. 发送消息
String msg = "hello topic, I'm order.error";
channel.basicPublish(Constants.TOPIC_EXCHANGE_NAME, "order.error", null, msg.getBytes());
String msg_black = "hello topic, I'm order.pay.info";
channel.basicPublish(Constants.TOPIC_EXCHANGE_NAME, "order.pay.info", null, msg_black.getBytes());
String msg_green = "hello topic, I'm pay.error";
channel.basicPublish(Constants.TOPIC_EXCHANGE_NAME, "pay.error", null, msg_green.getBytes());
// 8. 释放资源
channel.close();
connection.close();
}
}
代码解释:
- 连接设置 :使用
ConnectionFactory
创建连接工厂,并设置 RabbitMQ 服务器的主机地址、端口、虚拟主机、用户名和密码,然后创建连接和通道。- 交换器声明 :使用
channel.exchangeDeclare
方法声明一个主题交换器,指定交换器名称、类型为TOPIC
,并设置为持久化。- 队列声明 :使用
channel.queueDeclare
方法声明两个队列,同样设置为持久化。- 绑定操作 :使用
channel.queueBind
方法将队列 1 绑定到主题模式"*.error"
,队列 2 绑定到主题模式"#.info"
和"*.error"
。其中,*
表示匹配一个单词,#
表示匹配零个或多个单词。- 消息发送 :使用
channel.basicPublish
方法根据不同的主题发送不同的消息。- 资源释放:发送完消息后,关闭通道和连接。
3. 消费者
消费者的主要任务是创建与 RabbitMQ 的连接,从指定的队列中接收并处理消息。
消费者 1:TopicRabbitmqConsumer1
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class TopicRabbitmqConsumer1 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.TOPIC_QUEUE_NAME1, true, consumer);
}
}
消费者 2:TopicRabbitmqConsumer2
java
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
public class TopicRabbitmqConsumer2 {
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接参数
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setVirtualHost(Constants.VIRTUAL_HOST);
factory.setUsername(Constants.USER_NAME);
factory.setPassword(Constants.PASSWORD);
// 2. 创建连接
Connection connection = factory.newConnection();
// 3. 创建通道
Channel channel = connection.createChannel();
// 4. 接收并消费消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume(Constants.TOPIC_QUEUE_NAME2, true, consumer);
}
}
代码解释:
- 连接和通道创建:与生产者类似,创建连接工厂、连接和通道。
- 消息消费 :创建
DefaultConsumer
对象,并重写handleDelivery
方法用于处理接收到的消息。使用channel.basicConsume
方法从指定队列中消费消息,设置自动确认模式。
- 运行结果及分析
运行步骤:
- 先运行生产者程序,生产者会根据不同的主题将消息发送到主题交换器。
- 再运行两个消费者程序,消费者会从各自绑定的队列中获取消息。
结果分析:
TopicRabbitmqConsumer1
会接收到主题匹配"*.error"
的消息,即"hello topic, I'm order.error"
和"hello topic, I'm pay.error"
。TopicRabbitmqConsumer2
会接收到主题匹配"#.info"
和"*.error"
的消息,即"hello topic, I'm order.pay.info"
、"hello topic, I'm order.error"
和"hello topic, I'm pay.error"
。通过这种方式,我们可以看到主题交换器模式根据主题模式灵活分发消息的特性,不同的队列可以根据绑定的主题模式接收不同的消息,实现了消息的灵活订阅和分发。
7. Publisher Confirms(发布确认)
角色:
- P (Producer): 生产者。
- RabbitMQ Server: 消息服务器.
特点:生产者将 Channel 设置为 confirm 模式,发布的每一条消息都会获得一个唯一的 ID。当消息被 RabbitMQ 服务器接收并处理后,服务器会异步地向生产者发送一个确认 (ACK),表明消息已经送达。
适用场景:对数据安全性要求较高的场景,例如金融交易、订单处理。
1. 常量类
首先,我们需要定义一些常量,包括 RabbitMQ 的连接信息和队列名称,以及要发送的消息数量。假设我们已经有一个 Constants
类来存储 RabbitMQ 的连接信息。
java
import constant.Constants;
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class PublisherConfirms {
private static final int MESSAGE_COUNT = 500;
private static final String PUBLISHER_CONFIRMS_QUEUE_NAME1 = "publisher_confirms_queue1";
private static final String PUBLISHER_CONFIRMS_QUEUE_NAME2 = "publisher_confirms_queue2";
private static final String PUBLISHER_CONFIRMS_QUEUE_NAME3 = "publisher_confirms_queue3";
// 创建 RabbitMQ 连接
static Connection createConnection() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Constants.HOST); // ip 默认值 localhost
factory.setPort(Constants.PORT); // 默认值 5672
factory.setVirtualHost(Constants.VIRTUAL_HOST); // 虚拟机名称, 默认 /
factory.setUsername(Constants.USER_NAME); // ⽤⼾名, 默认 guest
factory.setPassword(Constants.PASSWORD); // 密码, 默认 guest
return factory.newConnection();
}
public static void main(String[] args) throws Exception {
publishMessagesIndividually();
publishMessagesInBatch();
handlePublishConfirmsAsynchronously();
}
2. 单独确认模式(Individual Confirms)
在单独确认模式下,生产者每发送一条消息后,会等待 Broker 的确认信号,只有在收到确认后才会继续发送下一条消息。这种模式的优点是简单直观,但性能较低,因为每次发送消息都需要等待确认,会引入额外的延迟。
java
static void publishMessagesIndividually() throws Exception {
try (Connection connection = createConnection()) {
// 创建 channel
Channel ch = connection.createChannel();
// 开启信道确认模式
ch.confirmSelect();
// 声明队列
ch.queueDeclare(PUBLISHER_CONFIRMS_QUEUE_NAME1, true, false, true, null);
long start = System.currentTimeMillis();
// 循环发送消息
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = "消息" + i;
// 发布消息
ch.basicPublish("", PUBLISHER_CONFIRMS_QUEUE_NAME1, null, body.getBytes());
// 等待确认消息。只要消息被确认,这个方法就会返回
// 如果超时过期, 则抛出 TimeoutException。如果任何消息被 nack(丢失), waitForConfirmsOrDie 将抛出 IOException。
ch.waitForConfirmsOrDie(5_000);
}
long end = System.currentTimeMillis();
System.out.format("Published %d messages individually in %d ms%n", MESSAGE_COUNT, end - start);
}
}
3. 批量确认模式(Batch Confirms)
批量确认模式是将多条消息作为一个批次发送,然后等待整个批次的消息都被确认。这种模式的性能比单独确认模式要好,因为减少了等待确认的次数,但如果其中一条消息失败,需要重新发送整个批次的消息。
java
static void publishMessagesInBatch() throws Exception {
try (Connection connection = createConnection()) {
// 创建信道
Channel ch = connection.createChannel();
// 信道设置为 confirm 模式
ch.confirmSelect();
// 声明队列
ch.queueDeclare(PUBLISHER_CONFIRMS_QUEUE_NAME2, true, false, true, null);
int batchSize = 100;
int outstandingMessageCount = 0;
long start = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = "消息" + i;
// 发送消息
ch.basicPublish("", PUBLISHER_CONFIRMS_QUEUE_NAME2, null, body.getBytes());
outstandingMessageCount++;
// 批量确认消息
if (outstandingMessageCount == batchSize) {
ch.waitForConfirmsOrDie(5_000);
outstandingMessageCount = 0;
}
}
// 消息发送完,还有未确认的消息,进行确认
if (outstandingMessageCount > 0) {
ch.waitForConfirmsOrDie(5_000);
}
long end = System.currentTimeMillis();
System.out.format("Published %d messages in batch in %d ms%n", MESSAGE_COUNT, end - start);
}
}
4. 异步确认模式(Asynchronous Confirms)
异步确认模式是在发送消息的同时,通过注册一个确认监听器来处理 Broker 的确认信号。这种模式的性能最高,因为生产者可以在发送消息后继续发送其他消息,而不需要等待确认,确认信号会在后台异步处理。
java
static void handlePublishConfirmsAsynchronously() throws Exception {
try (Connection connection = createConnection()) {
Channel ch = connection.createChannel();
ch.queueDeclare(PUBLISHER_CONFIRMS_QUEUE_NAME3, false, false, true, null);
ch.confirmSelect();
// 有序集合,元素按照自然顺序进行排序,存储未 confirm 消息序号
SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<>());
ch.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// multiple 批量
// confirmSet.headSet(n) 方法返回当前集合中小于 n 的集合
if (multiple) {
// 批量确认:将集合中小于等于当前序号 deliveryTag 元素的集合清除,表示这批序号的消息都已经被 ack 了
confirmSet.headSet(deliveryTag + 1).clear();
} else {
// 单条确认:将当前的 deliveryTag 从集合中移除
confirmSet.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.err.format("deliveryTag: %d, multiple: %b%n", deliveryTag, multiple);
if (multiple) {
// 批量确认:将集合中小于等于当前序号 deliveryTag 元素的集合清除,表示这批序号的消息都已经被 ack 了
confirmSet.headSet(deliveryTag + 1).clear();
} else {
// 单条确认:将当前的 deliveryTag 从集合中移除
confirmSet.remove(deliveryTag);
}
// 如果处理失败,这里需要添加处理消息重发的场景。此处代码省略
}
});
// 循环发送消息
long start = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = "消息" + i;
// 得到下次发送消息的序号,从 1 开始
long nextPublishSeqNo = ch.getNextPublishSeqNo();
ch.basicPublish("", PUBLISHER_CONFIRMS_QUEUE_NAME3, null, message.getBytes());
// 将序号存入集合中
confirmSet.add(nextPublishSeqNo);
}
// 消息确认完毕
while (!confirmSet.isEmpty()) {
Thread.sleep(10);
}
long end = System.currentTimeMillis();
System.out.format("Published %d messages and handled confirms asynchronously in %d ms%n", MESSAGE_COUNT, end - start);
}
}
}
5. 性能对比
消息数越多,异步确认的优势越明显。以下是不同消息数量下三种策略的性能对比:
200 条消息的结果对比
Published 200 messages individually in 6931 ms
Published 200 messages in batch in 137 ms
Published 200 messages and handled confirms asynchronously in 73 ms
500 条消息结果对比
Published 500 messages individually in 15805 ms
Published 500 messages in batch in 246 ms
Published 500 messages and handled confirms asynchronously in 107 ms
- 从上述结果可以看出,单独确认模式的性能最差,因为它需要为每条消息等待确认,引入了大量的延迟。
- 批量确认模式通过减少等待确认的次数,提高了性能。
- 而异步确认模式则进一步优化了性能,因为它允许生产者在发送消息的同时继续处理其他任务,确认信号在后台异步处理。
因此,在需要高吞吐量的场景中,建议使用异步确认模式。
总结
画了这么久的饼终于实现了哈哈哈,不过现在大部分都用 springboot 封装的来实现了。以前肯定还有很多坑,以后会慢慢补全的,感谢阅览!!