spring-rabbit消费过程解析及AcknowledgeMode选择

说明:本文内容来源于对amqp-clientspring-rabbit包源码的解读及debug,尽可能保证内容的准确性。

rabbitmq消费过程示意如下:
在这里插入图片描述
图中首字母大写的看上去像类名的,如ConsumerWorkServiceMainLoopWorkPoolRunnable等,没错就是类名,可自行根据类名去查看相关源码。

下面解释上图的含义。

1. 启动流程

  • 通过BeanPostProcessor扫描所有的bean中存在的@RabbitListener注解及相应的Method
  • RabbitListenerContainerFactory根据配置为每一个@RabbitListener注解创建一个MessageListenerContainer,持有@RabbitListener注解及Method信息;
  • 初始化MessageListenerContainer,主要是循环依次创建consumerAsyncMessageProcessingConsumer类),启动consumer
  • 创建consumer,过程包括:创建AMQConnection(仅第一次创建),创建AMQChannel(每个consumer都会创建),发送消费queue的请求(basic.consume),接收并处理消息;
  • AMQConnection持有连接到rabbitmq serverSocket,创建完成后启动MainLoop循环从Socket流中读取Frame,此时流中没有消息,因为channel还没创建完成;
  • 创建AMQChannel(一个AMQConnection中持有多个AMQChannel),并将创建完成的channel注册到AMQConnection持有的ConsumerWorkService,实际就是添加到WorkPool类的Map里面去,此时Socket流中也没有消息,因为channel还没有与queue绑定;
  • 创建完成的AMQChannel的代理返回给consumerconsumer通过channel发送消费queue的请求到rabbitmq server(绑定成功),此时还没开始处理消息,但Socket流中已经有消息,并且已经被connection读取到内存(即BlockingQueue<Runnable>)中,并且已经开始向BlockingQueue<Delivery>分发;
  • consumer启动循环,从BlockingQueue<Delivery>中取消息,利用MessageListenerContainer中持有的Method反射调用@RabbitListener注解方法处理消息。

2. 消费流程

  • rabbitmq serverSocket流中写入字节。
  • AMQConnection启动一个main loop thread来跑MainLoop,不断从Socket流中读取字节转换成Frame对象,这是每个connection唯一的数据来源。

Frame对象结构如下:
在这里插入图片描述
type:指定当前Frame的类型,如method(1)message header(2)message body(3)heartbeat(8)等;

channelchannel的编号,从0~n排列,指定当前Frame需要交给哪个channel处理。channel-0为一类,channel-n为一类。channel-0是一个匿名类,用来处理特殊Frame,如connection.startchannel-n都是ChannelN类,由ChannelManager类统一管理。

payload:当前Frame的具体内容。

consumer启动后,connection读取到的Frame如上图所示(一个consumer的情况,多个consumerFrame可能会交替)。从basic.deliver开始是消息的内容,每条消息分成三个Frame:第一个是methodbasic.deliver代表这是一个消息,后面一定会再跟着两个Frame;第二个是message header;第三个是message bodybody读取之后将三个Frame整合到一起转换成一条完整的deliver命令。

  • AMQConnection根据读取到的Frame中的type决定要怎么处理这个Frameheartbeat(8) do nothing;其它的根据channel编号交给相应的AMQChannel去处理,(编号为0的是特殊的channel,消息相关的用的都是编号非0channel),消息都会拿着这个编号到ChannelManager找对应的ChannelN处理。
  • ChannelN经过一系列中间过程由Frame(消息是三个Frame)得到了Runnable,将(ChannelN, Runnable) putConsumerWorkService持有的WorkPool里面的一个Map<Channel, BlockingQueue<Runnable>>里面去。这样这个Runnable就进入了与ChannelN对应的BlockingQueue<Runnable>(写死的size=1000)里面了。
  • execute一个WorkPoolRunnable,执行的任务是:从WorkPool中找出一个ready状态的ChannelN,把这个ChannelN设为inProgress状态,从对应的BlockingQueue<Runnable>中取最多16(写死的)个RunnableWorkPoolRunnable的线程里依次执行(注意:此处不再另开线程,所以可能会堵塞当前线程,导致这个ChannelN长时间处于inProgress状态),执行完后将当前ChannelN状态改为ready,并在当前线程execute另一个WorkPoolRunnable
  • BlockingQueue<Runnable>里面的Runnable执行的逻辑是:构造一个Delivery put到与ChannelN对应的AsyncMessageProcessingConsumer持有的BlockingQueue<Delivery>size=prefetchCount可配置)里面去(如果消息处理速度太慢,BlockingQueue<Delivery>已满,此处会堵塞)。
  • 每个AsyncMessageProcessingConsumer都有一个独立的线程在循环从BlockingQueue<Delivery>一次读取一个Delivery转换成Message反射调用@RabbitListener注解方法来处理。

3. 无ack消费模式与有ack消费模式对比

根据以上对消费过程的分析,将无ack模式与ack模式进行对比。

ack模式(AcknowledgeMode.NONE
server端行为
  • rabbitmq server默认推送的所有消息都已经消费成功,会不断地向消费端推送消息。
  • 因为rabbitmq server认为推送的消息已被成功消费,所以推送出去的消息不会暂存在server端。
消息丢失的风险

BlockingQueue<Runnable>堆满时(BlockingQueue<Delivery>一定会先满),server端推送消息会失败,然后断开connection。消费端从Socket读取Frame将会抛出SocketException,触发异常处理,shutdownconnection和所有的channelchannel shutdownWorkPool中的channel信息(包括channel inProgress,channel ready以及Map)全部清空,所以BlockingQueue<Runnable>中的数据会全部丢失。

此外,服务重启时也需对内存中未处理完的消息做必要的处理,以免丢失。

而在rabbitmq serverconnection断掉后就没有消费者去消费这个queue,因此在server端会看到消息堆积的现象。

ack模式(AcknowledgeMode.AUTOAcknowledgeMode.MANUAL

AcknowledgeMode.MANUAL模式需要人为地获取到channel之后调用方法向server发送ack(或消费失败时的nack)信息。

AcknowledgeMode.AUTO模式下,由spring-rabbit依据消息处理逻辑是否抛出异常自动发送ack(无异常)或nack(异常)到server端。

server端行为
  • rabbitmq server推送给每个channel的消息数量有限制,会保证每个channel没有收到ack的消息数量不会超过prefetchCount
  • server端会暂存没有收到ack的消息,等消费端ack后才会丢掉;如果收到消费端的nack(消费失败的标识)或connection断开没收到反馈,会将消息放回到原队列头部。

这种模式不会丢消息,但效率较低,因为server端需要等收到消费端的答复之后才会继续推送消息,当然,推送消息和等待答复是异步的,可适当增大prefetchCount提高效率。

注意,有ack的模式下,需要考虑setDefaultRequeueRejected(false),否则当消费消息抛出异常没有catch住时,这条消息会被rabbitmq放回到queue头部,再被推送过来,然后再抛异常再放回…死循环了。设置false的作用是抛异常时不放回,而是直接丢弃,所以可能需要对这条消息做处理,以免丢失。更详细的配置参考这里

对比
  • ack模式:效率高,存在丢失大量消息的风险。
  • ack模式:效率低,不会丢消息。
  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值