可靠性和性能的权衡
消费者在做消息消费的时候主要有三种方式来做:拉取,事务,QoS(Quality of Service 服务质量)。如果业务场景不考虑可靠性那么总量QoS最佳,总量QoS速度最快。如果严格保证可靠性但是不怎么考虑速度的场景,那么拉取和事务最佳。拉取方式消费者会一直在循环的去队列里拿数据,如果有就拿,如果没有就阻塞在哪里,从这点就能看出效率肯定不搞。事务就是同步,一般不推荐使用。所以在大多数的业务场景中多数使用QoS方式,让RabbitMQ队列推送消息给消费者,使用QoS为了进一步提交效率呢,可以使用批量的方式。
以下是可靠性和性能对比图
消息消费时的方式
消息拉取Get
属于一种轮询模型,发送一次get请求,获得一个消息。如果此时RabbitMQ中没有消息,会获得一个表示空的回复。总的来说,这种方式性能比较差,很明显,每获得一条消息,都要和RabbitMQ进行网络通信发出请求。而且对RabbitMQ来说,RabbitMQ无法进行任何优化,因为它永远不知道应用程序何时会发出请求。对我们实现者来说,要在一个循环里,不断去服务器get消息。
注意:如果autoAck设置成false,那么就必须手动确认消息,否则会出现消息重复消费问题。
消息推送Consume
属于一种推送模型。注册一个消费者后,RabbitMQ会在消息可用时,自动将消息进行推送给消费者。
消息的应答
前面说过,消费者收到的每一条消息都必须进行确认。消息确认后,RabbitMQ才会从队列删除这条消息,RabbitMQ不会为未确认的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。
自动确认
消费者在声明队列时,可以指定autoAck参数,当autoAck=true时,一旦消费者接收到了消息,就视为自动确认了消息。如果消费者在处理消息的过程中,出了错,就没有什么办法重新处理这条消息,所以我们很多时候,需要在消息处理成功后,再确认消息,这就需要手动确认。
手动确认
当autoAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。
采用消息确认机制后,只要令autoAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。
当autoAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
通过运行程序,启动两个消费者A、B,都可以收到消息,但是其中有一个消费者A不会对消息进行确认,当把这个消费者A关闭后,消费者B又会收到本来发送给消费者A的消息。所以我们一般使用手动确认的方法是,将消息的处理放在try/catch语句块中,成功处理了,就给RabbitMQ一个确认应答,如果处理异常了,就在catch中,进行消息的拒绝。
QoS预取模式
在确认消息被接收之前,消费者可以预先要求接收一定数量的消息,在处理完一定数量的消息后,批量进行确认。如果消费者应用程序在确认消息之前崩溃,则所有未确认的消息将被重新发送给其他消费者。所以这里存在着一定程度上的可靠性风险。
这种机制一方面可以实现限速(将消息暂存到RabbitMQ内存中)的作用,一方面可以保证消息确认质量(比如确认了但是处理有异常的情况)。
注意:消费确认模式必须是非自动ACK机制(这个是使用baseQos的前提条件,否则会Qos不生效),然后设置basicQos的值;另外,还可以基于consume和channel的粒度进行设置(global)。
basicQos方法参数详细解释:
prefetchSize:最多传输的内容的大小的限制,0为不限制,但据说prefetchSize参数,rabbitmq没有实现。
prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack
global:true\false 是否将上面设置应用于channel,简单点说,就是上面限制是channel级别的还是consumer级别。
如果同时设置channel和消费者,会怎么样?AMQP规范没有解释如果使用不同的全局值多次调用basic.qos会发生什么。 RabbitMQ将此解释为意味着两个预取限制应该彼此独立地强制执行; 消费者只有在未达到未确认消息限制时才会收到新消息。
channel.basicQos(10, false); // Per consumer limit
channel.basicQos(15, true); // Per channel limit
channel.basicConsume(“my-queue1”, false, consumer1);
channel.basicConsume(“my-queue2”, false, consumer2);
也就是说,整个通道加起来最多允许15条未确认的消息,每个消费者则最多有10条消息。
QoS单条确认示例:
消费者中的事务
使用方法和生产者一致
假设消费者模式中使用了事务,并且在消息确认之后进行了事务回滚,会是什么样的结果?
结果分为两种情况:
- autoAck=false手动应对的时候是支持事务的,也就是说即使你已经手动确认了消息已经收到了,但RabbitMQ对消息的确认会等事务的返回结果,再做最终决定是确认消息还是重新放回队列,如果你手动确认之后,又回滚了事务,那么以事务回滚为准,此条消息会重新放回队列;
- autoAck=true如果自动确认为true的情况是不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了。