rabbitmq——prefetch count

http://my.oschina.net/hncscwc/blog/195560?p=1

消费者在开启acknowledge的情况下,对接收到的消息可以根据业务的需要异步对消息进行确认。

然而在实际使用过程中,由于消费者自身处理能力有限,从rabbitmq获取一定数量的消息后,希望rabbitmq不再将队列中的消息推送过来,当对消息处理完后(即对消息进行了ack,并且有能力处理更多的消息)再接收来自队列的消息。在这种场景下,我们可以通过设置basic.qos信令中的prefetch_count来达到这种效果。

先直观的看看设置了prefetch_count的效果,:

1) 对比测试:两个消费者都订阅同一队列,no_ack均设置为false即开启acknowledge机制,且均未设置prefetch_count,向队列发布5条消息

结果:不管消息是否被ack,rabbitmq会轮流向两个消费者投递消息,第一个消费者收到"1","3","5"三条消息, 第二个消费者收到"2","4"两条消息。

2)prefetch_count设置测试:两个消费者都订阅同一队列,开启acknowledge机制,第一个消费者prefetch_count设置为1,另一个消费者未设置prefetch_count,同样向队列发布5条消息

结果:rabbitmq向第一个消费者投递了一条消息后,消费者未对该消息进行ack,rabbitmq不会再向该消费者投递消息,剩下的四条消息均投递给了第二个消费者

看完效果后,再来看看rabbitmq里的一些实现。

1. rabbitmq对basic.qos信令的处理

首先,basic.qos是针对channel进行设置的,也就是说只有在channel建立之后才能发送basic.qos信令。

在rabbitmq的实现中,每个channel都对应会有一个rabbit_limiter进程,当收到basic.qos信令后,在rabbit_limiter进程中记录信令中prefetch_count的值,同时记录的还有该channel未ack的消息个数。

注:其实basic.qos里还有另外两个参数可进行设置(global和prefetch_size),但rabbitmq没有相应的实现。

2. 队列中的消息投递给消费者时的处理

当rabbitmq要将队列中的一条消息投递给消费者时,会遍历该队列上的消费者列表,选一个合适的消费者,然后将消息投递出去。其中挑选消费者的一个依据就是看消费者对应的channel上未ack的消息数是否达到设置的prefetch_count个数,如果未ack的消息数达到了prefetch_count的个数,则不符合要求。当挑选到合适的消费者后,中断后续的遍历。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
rabbit_amqqueue_process.erl
 
deliver_msgs_to_consumers(_DeliverFun, true , State) ->
     { true , State};
deliver_msgs_to_consumers(DeliverFun, false ,
                           State = #q{active_consumers =
                                      ActiveConsumers}) ->
     case priority_queue:out_p(ActiveConsumers) of
         {empty, _} ->
             { false , State};
         {{value, QEntry, Priority}, Tail} ->
             {Stop, State1} =
                 deliver_msg_to_consumer(DeliverFun, QEntry,
                                         Priority,
                                         State#q{active_consumers =
                                                 Tail}),
             %%如果处理结果为 false ,遍历下一个消费者
             deliver_msgs_to_consumers(DeliverFun, Stop, State1)
     end.
 
deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer},
                         Priority, State) ->
     ...
     %%判断是否可以将消息投递给该消费者
     case rabbit_limiter:can_send(C#cr.limiter,
                                  Consumer#consumer.ack_required,
                                  Consumer#consumer.tag) of
         %%可以投递,再将该消费者放到队列的尾部
         { continue , Limiter} ->
             AC1 = priority_queue:in(E, Priority,
                                     State#q.active_consumers),
             %%将消息投递给消费者
             deliver_msg_to_consumer0(DeliverFun, Consumer,
                                      C#cr{limiter = Limiter},
                                      State#q{active_consumers = AC1})
     ...
 
rabbit_limiter.erl
 
handle_call({can_send, QPid, AckRequired}, _From,
             State = #lim{volume = Volume}) ->
     case prefetch_limit_reached(State) of
         %%未ack的消息数达到prefetch_count设置的个数
         true  -> {reply, false , limit_queue(QPid, State)};
         false -> {reply, true ,
                   %%消息需要被ACK, volume加 1
                   State#lim{volume = if AckRequired -> Volume + 1 ;
                                         true        -> Volume
                                      end}}
     end
 
prefetch_limit_reached(#lim{prefetch_count = Limit,
                             volume = Volume}) ->
     Limit =/= 0 andalso Volume >= Limit.



3. 消费者对消息ack后的处理

当消费者对消息进行ack后,最终会修改该消费者对应channel中未ack的消息数,这样队列又可以将消息投递给该消费者。

?
1
2
3
4
5
6
7
rabbit_limiter.erl
 
handle_cast({ack, Count}, State = #lim{volume = Volume}) ->
     NewVolume = if Volume == 0 -> 0 ;
                    true        -> Volume - Count
                 end,
     {noreply, maybe_notify(State, State#lim{volume = NewVolume})};



4. 扩展

在AMQP协议(0-9-1)中,有这么一段话

对于rabbitmq来说,最后一句话其实说的就是使用了acknowledge机制情况下,使用prefetch_count进行流量控制。另外在实际研究过程中发现还有channel.flow以及basic.credit(应该属于AMQP 1.0协议)可以进行一些控制,这里没有展开,有时间会研究下相应的机制以及可能使用的场景。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值