持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
Publisher Confirms
发送者消息确认
RabbitMQ
的消息可靠性是非常高的,但以往的机制都是保证消息发送到MQ
之后,可以推送到消费者消费,不会丢失消息。但是发送者发送消息是否成功是没有保证的。
Porducer.basicPublish
这个方法是没有返回值的,也就是说,一次发送消息是否成功,应用是不知道的,在业务上就容易造成消息丢失。而这个模式就是通过给生产者提供一个确认的机制,来保证这个消息发送的过程是成功的。
这个消息确认机制跟RocketMQ
的事务消息机制比较类似,而对于这个机制,RocketMQ
的支持明显更优。
确认模式是默认不开启的,所以如果需要开启,需要手动在channel
中进行声明:
channel.confirmSelect();
复制代码
官方提供了三种策略
发布单条消息
发布一条消息就确认一条消息。
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
channel.basicPublish("", queue, null, body.getBytes());
channel.waitForConfirmsOrDie(5_000);
}
复制代码
channel.waitForConfirmsOrDie(5_000);
这个方法就会在channel
端等待RabbitMQ
给出一个响应,用来表明这个消息已经正确发送到了RabbitMQ
服务端。但是要注意,这个方法会同步阻塞channel
,在等待确认期间,channel
将不能再继续发送消息,也就是说会明显降低集群的发送速度即吞吐量。
注意:
- 官方说明,其实
channel
底层是异步工作的,会将channel
阻塞住,然后异步等待服务端发送一个确认消息,才解除阻塞。但是我们在使用时,可以当做一个同步工具来看。 - 如果到达了超时时间,还没有收到服务端的确认机制,那就会抛出异常。然后通常处理这个异常的方式是记录错误日志或者尝试重发消息,但是尝试重发时一定注意不要是程序陷入死循环。
发送批量消息
单条确认的机制会对系统的吞吐量造成很大的影响,所以稍微中和一点的方式就是发送一批消息后,再一起确认。
int batchSize = 100;
int outstandingMessageCount = 0;
long start = System.nanoTime();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String body = String.valueOf(i);
ch.basicPublish("", queue, null, body.getBytes());
outstandingMessageCount++;
if (outstandingMessageCount == batchSize) {
ch.waitForConfirmsOrDie(5_000);
outstandingMessageCount = 0;
}
}
if (outstandingMessageCount > 0) {
ch.waitForConfirmsOrDie(5_000);
}
复制代码
这种方式可以稍微缓解下发送者确认模式对吞吐量的影响。但是也引入另一个问题,当确认出现异常时,我们只能知道是这一批消息出问题了, 而无法确认具体是哪一条消息出了问题。所以接下来就需要增加一个机制能够具体对每一条发送出错的消息进行处理。
异步消息确认
生产者在channel
中注册监听器来对消息进行确认。
channel.addConfirmListener(ConfirmCallback var1, ConfirmCallback var2);
复制代码
按说监听只要注册一个就可以了,那为什么这里要注册两个呢?如果对比RocketMQ
的事务消息机制,这就很容易理解了。发送者在发送完消息后,就会执行第一个监听器callback1
,然后等服务端发过来的反馈后,再执行第二个监听器callback2
。
关于这个ConfirmCallback
,这是个监听器接口,里面只有一个方法: void handle(long sequenceNumber, boolean multiple) throws IOException;
这方法中的两个参数:
sequenceNumer
:这个是一个唯一的序列号,代表一个唯一的消息。在RabbitMQ
中,他的消息体只是一个二进制数组,并不像RocketMQ
一样有一个封装的对象,所以默认消息是没有序列号的。而RabbitMQ
提供了一个方法intsequenceNumber = channel.getNextPublishSeqNo());
来生成一个全局递增的序列号。然后应用程序需要自己来将这个序列号与消息对应起来(需要客户端自己去做对应)。multiple
:这个是一个Boolean
型的参数。如果是true
,就表示这一次只确认了当前一条消息。如果是false
,就表示RabbitMQ
这一次确认了一批消息,在sequenceNumber
之前的所有消息都已经确认完成了。