说明 通过之前的两篇博文《RabbitMQ学习(十六):消息确认之消费者确认模式 I》和《RabbitMQ学习(十七):消息确认之消费者确认模式 II》,对消费者确认模式进行了翻译学习,本篇博文将继续翻译学习文档的最后一部分,有关发送者确认模式的内容。内容包含了服务器如何对生产者发送的消息进行确认,消息确认的时机,确认消息的顺序等内容。
正文
发送者确认 网络可能一些不明显的原因发生故障,并且检测故障需要时间。因此当客户端向套接字写入一个协议帧或一组帧(比如发送一个消息)时,不能假定消息已经到达服务器并且被成功处理。消息可能在传输中丢失或者传输有很大的延迟。
使用标准的AMQP 0-9-1协议,唯一可以保证消息不会丢失的方式就是通过事务,对于每个消息,每组消息使用通道事务进行发送,提交。但是这种情况,事务是十分重量级的,也不是必须的,而且它会使得吞吐量降低250倍。为了避免使用事务,RabbitMQ引入了一种确认机制,它和之前介绍的消费者确认模式十分相似。
为了实现发送者确认模式,发送者客户端使用confirm.select方法发送消息,服务器根据no-wait参数是否被设置,响应一个confirm.select-ok消息。一旦confirm.select方法被用于一个通道,那么就可以说该通道已经处于确认模式中。注意,事务和确认模式不能同时作用于一个通道。
一旦通道处于确认模式,服务器和发送者客户端都会对消息进行计数(在第一次发送接收fconfirn.select时,从1开始计数)。服务器在处理完消息后,在同一个通道上发送basic.ack消息进行确认。消息的delivery-tag字段包含了确认消息的序列号。同时,服务器也有可能在basic.ok方法中设置了multiple字段值为true,表明了截至指定序列号之前所有的消息(包含该序列号代表的消息)都已经被处理。
发送者的消极确认
在异常情况服务器不能成功处理消息时,服务器将发送basic.nack消息。在该情况下,basic.nack方法中的字段与basic.ack方法中对应字段有相同的意义,并且requeue字段应该被忽略。通过消极确认一个或多个消息,表明服务器无法处理这些消息。对此,发送者客户端应该选择重新发送这些消息。
当一个通道被设置为确认模式后,随后发送的所有消息当将被积极确认或消极确认一次。但是无法保证消息确认将需要多长时间。注意,一个消息不可能既被积极确认又被消极确认。
basic.nack消息只有在负责队列的erlang进程出现内部错误时被发送。
服务器何时对消息进行确认
对于不能路由的消息,服务器将在交换机证实消息无法被路由到任何队列(返回一个空的队列列表)时,发送确认消息。如果消息发送时设置了mandatory参数,basic.return消息将在basic.ack消息之前发送给生产者客户端。消息的消极确认(basic.nack)也是一样的。
对于可以被路由的消息,服务器在消息被所有队列都接收后发送basic.ack消息进行确认。对于被路由到持久化队列的持久化消息,则意味着持久到磁盘后发送。对于镜像队列,意味着所有的镜像都接收到该消息后发送。
持久化消息的确认延迟
对于路由到持久化队列的持久化消息,确认消息basic.ack将在消息被持久化到磁盘后发送。RabbitMQ使用批量的方式将持久化消息写入磁盘,为了减少对fsync方法的调用,每次刷盘都间隔几百毫秒,又或者是在队列空闲时写入。
这意味着在常规压力下,basic.ack确认消息的延迟可以达到几百毫秒。为了提高吞吐量,强烈建议应用程序异步处理确认消息(作为一个流)或者批量发送消息并等待确认。不同客户端的api是不同的。
确认消息的顺序
在大多数情况下,服务器可以按照接收到的消息顺序(对于在同一个通道上的消息)进行确认。然而,发送者的确认消息是被异步发送并且可以是确认一个消息或者多个消息。同时,确认消息被发送的时间也取决于消息的发送模式(持久态/瞬时态)和消息被路由到的队列的属性。所以说不同的消息被认为可以准备确认的时机是不同的。这意味着,确认消息的到达顺序可能与它们所代表的的消息发送顺序是不同的。所以,应用程序应该尽量避免依赖于确认消息的顺序。
发送者确认和传输保证
一个服务器节点可能在消息被写入磁盘前崩溃,造成消息丢失。例如,考虑以下场景:
- 发送者客户端发送一个持久化消息到一个持久化的队列
- 消费者客户端从队列消费消息(注意 队列和消息都是持久化的),但是未确认消息
- 服务器节点崩溃后重启
- 消费者客户端重置连接并消费消息
传输标签的最大值 【RabbitMQ学习(十八)(消息确认之发送者确认模式)】传输标签是一个64位的long类型的值。它的最大值是9223372036854775807。因为传输标签作用范围是每个通道,所以生产者或消费者在实际使用中都不太可能超过这个限制。