使用事务虽然可以保证消息的准确达到,但它极大地牺牲了性能,因此我们为了性能上的要求,可以通过使用Confirm模式来保证消息的准确性。

这里的Confirm模式可以分为两个方面来讲解,一是消息的生产者(Producer),另一个是消息的消费者(Consumer)。

一、生产者(Producer)

通过生产者的确认模式我们是要保证消息准确达到Broker端,而与AMQP事务不同的是Confirm是针对一条消息的,而事务是可以针对多条消息的。

为了使用Confirm模式,client会发送confirm.select方法帧。通过是否设置了no-wait属性,来决定Broker端是否会以confirm.select-ok来进行应答。一旦在channel上使用confirm.select方法,channel就将处于Confirm模式。处于 transactional模式的channel不能再被设置成Confirm模式,反之亦然。即发布确认和事务两者不可同时引入。

在生产者将信道设置成Confirm模式,一旦信道进入Confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(以confirm.select为基础从1开始计数),一旦消息被投递到所有匹配的队列之后,Broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,Broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外Broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。

Confirm模式最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条basic.nack来代替basic.ack的消息,在这个情形下,basic.nack中各域值的含义与basic.ack中相应各域含义是相同的,同时requeue域的值应该被忽略。通过nack一条或多条消息, Broker表明自身无法对相应消息完成处理,并拒绝为这些消息的处理负责。在这种情况下,client可以选择将消息re-publish。

在channel 被设置成Confirm模式之后,所有被publish的后续消息都将被Confirm(即 ack)或者被nack一次。但是没有对消息被Confirm的快慢做任何保证,并且同一条消息不会既被Confirm又被nack。

编程模式

对于固定消息体大小和线程数,如果消息持久化,生产者Confirm(或者采用事务机制),消费者ack那么对性能有很大的影响.

消息持久化的优化没有太好方法,用更好的物理存储(SAS, SSD, RAID卡)总会带来改善。生产者confirm这一环节的优化则主要在于客户端程序的优化之上。归纳起来,客户端实现生产者confirm有三种编程方式:

  • 普通Confirm模式:每发送一条消息后,调用waitForConfirms()方法,等待服务器端Confirm。实际上是一种串行Confirm了,每publish一条消息之后就等待服务端Confirm,如果服务端返回false或者超时时间内未返回,客户端进行消息重传;
  • 批量Confirm模式:批量Confirm模式,每发送一批消息之后,调用waitForConfirms()方法,等待服务端Confirm,这种批量确认的模式极大的提高了Confirm效率,但是如果一旦出现Confirm返回false或者超时的情况,客户端需要将这一批次的消息全部重发,这会带来明显的重复消息,如果这种情况频繁发生的话,效率也会不升反降;
  • 异步Confirm模式:提供一个回调方法,服务端Confirm了一条或者多条消息后Client端会回调这个方法。

二、消费者(Consumer)

为了保证消息从队列可靠地到达消费者,RabbitMQ提供消息确认机制(message acknowledgment)。消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。

采用消息确认机制后,只要令noAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。

在Consumer中Confirm模式中分为手动确认和自动确认。

手动确认主要并使用以下方法:

  • basic.ack: 用于肯定确认,multiple参数用于多个消息确认。
  • basic.recover:是路由不成功的消息可以使用recovery重新发送到队列中。
  • basic.reject:是接收端告诉服务器这个消息我拒绝接收,不处理,可以设置是否放回到队列中还是丢掉,而且只能一次拒绝一个消息,官网中有明确说明不能批量拒绝消息,为解决批量拒绝消息才有了basicNack。
  • basic.nack:可以一次拒绝N条消息,客户端可以设置basicNack方法的multiple参数为true,服务器会拒绝指定了tag的所有未确认的消息。

肯定的确认只是指导RabbitMQ将一个消息记录为已投递。basic.reject的否定确认具有相同的效果。 两者的差别在于:肯定的确认假设一个消息已经成功处理,而对立面则表示投递没有被处理,但仍然应该被删除。


柠檬时间
63 声望5 粉丝

遵循内心


引用和评论

0 条评论