zoukankan      html  css  js  c++  java
  • rabbitmq之消息重入队列

    说起消息重入队列还得从队列注册消费者说起,客户端在向队列注册消费者之后,创建的channel也会被主队列进程monitor,当channel挂掉后,主队列进程(rabbit_amqqueue_process)收到'DOWN'通知,将未ack的消息重入队列,并根据消息的deliver tag,也就是消费入队列的顺序,将消息重入队列中

    主要代码如下:

    1.注册消费者

    handle_method(#'basic.consume'{queue        = QueueNameBin,
                                   consumer_tag = ConsumerTag,
                                   no_local     = _, % FIXME: implement
                                   no_ack       = NoAck,
                                   exclusive    = ExclusiveConsume,
                                   nowait       = NoWait,
                                   arguments    = Args},
                  _, State = #ch{consumer_prefetch = ConsumerPrefetch,
                                 consumer_mapping  = ConsumerMapping}) ->
        case dict:find(ConsumerTag, ConsumerMapping) of
            error ->
                QueueName = qbin_to_resource(QueueNameBin, State),
                check_read_permitted(QueueName, State),
                ActualConsumerTag =
                    case ConsumerTag of
                        <<>>  -> rabbit_guid:binary(rabbit_guid:gen_secure(),
                                                    "amq.ctag");
                        Other -> Other
                    end,
                case basic_consume(
                       QueueName, NoAck, ConsumerPrefetch, ActualConsumerTag,
                       ExclusiveConsume, Args, NoWait, State) of
                    {ok, State1} ->
                        {noreply, State1};
                    {error, exclusive_consume_unavailable} ->
                        rabbit_misc:protocol_error(
                          access_refused, "~s in exclusive use",
                          [rabbit_misc:rs(QueueName)])
                end;
            {ok, _} ->
                %% Attempted reuse of consumer tag.
                rabbit_misc:protocol_error(
                  not_allowed, "attempt to reuse consumer tag '~s'", [ConsumerTag])
        end;
    

    2.主队列进程增加消费者,并对channel进程监控

    handle_call({basic_consume, NoAck, ChPid, LimiterPid, LimiterActive,
                 PrefetchCount, ConsumerTag, ExclusiveConsume, Args, OkMsg},
                _From, State = #q{consumers          = Consumers,
                                  exclusive_consumer = Holder}) ->
        case check_exclusive_access(Holder, ExclusiveConsume, State) of
            in_use -> reply({error, exclusive_consume_unavailable}, State);
            ok     -> Consumers1 = rabbit_queue_consumers:add(
                                     ChPid, ConsumerTag, NoAck,
                                     LimiterPid, LimiterActive,
                                     PrefetchCount, Args, is_empty(State),
                                     Consumers),
        end;
    
    ch_record(ChPid, LimiterPid) ->
        Key = {ch, ChPid},
        case get(Key) of
            undefined -> MonitorRef = erlang:monitor(process, ChPid),
                         Limiter = rabbit_limiter:client(LimiterPid),
                         C = #cr{ch_pid               = ChPid,
                                 monitor_ref          = MonitorRef,
                                 acktags              = queue:new(),
                                 consumer_count       = 0,
                                 blocked_consumers    = priority_queue:new(),
                                 limiter              = Limiter,
                                 unsent_message_count = 0},
                         put(Key, C),
                         C;
            C = #cr{} -> C
        end.
    

    3.主队列进程收到channel 'DOWN'的消息后,删除消费者,获取此被此channel ack的消息

    handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) ->
        case handle_ch_down(DownPid, State) of
            {ok, State1}   -> noreply(State1);
            {stop, State1} -> stop(State1)
        end;
    
    handle_ch_down(DownPid, State = #q{consumers          = Consumers,
                                       exclusive_consumer = Holder,
                                       senders            = Senders}) ->
        State1 = State#q{senders = case pmon:is_monitored(DownPid, Senders) of
                                       false -> Senders;
                                       true  -> credit_flow:peer_down(DownPid),
                                                pmon:demonitor(DownPid, Senders)
                                   end},
        case rabbit_queue_consumers:erase_ch(DownPid, Consumers) of
            not_found ->
                {ok, State1};
            {ChAckTags, ChCTags, Consumers1} ->
    
                case should_auto_delete(State2) of
                    true  -> {stop, State2};
                    false -> {ok, requeue_and_run(ChAckTags,
                                                  ensure_expiry_timer(State2))}
                end
        end.
    

    4.涉及重入队列时,需要了解backing queue,即消息是如何在镜像队列之间内部以及消息如何在本地内存和磁盘资源之间按需切换,或此部分涉及内容较多,后序会专门列出一个专题来分析此实现。rabbit_amqqueue_process主队列进程的backing_queue是rabbit_mirror_queue_master(镜像队列消息同步),后者因为需要将消息按需放置,所以也有backing_queueu,即rabbit_variable_queue。

    根据sequeue_id来判断消息在队列中的位置,从当前队列中pop出队头的消息(最早入队列的消息),若未ack的消息较晚(seqid相对大),则将pop队头的队列再与未ack的消息比较,将消息pop出的消息放置在front队列中,直到符合,插入队列,并将front队列与此队列合入。

    queue_merge(SeqIds, Q, MsgIds, Limit, PubFun, State) ->
        queue_merge(SeqIds, Q, ?QUEUE:new(), MsgIds,
                    Limit, PubFun, State).
    
    queue_merge([SeqId | Rest] = SeqIds, Q, Front, MsgIds,
                Limit, PubFun, State)
      when Limit == undefined orelse SeqId < Limit ->
        case ?QUEUE:out(Q) of
            {{value, #msg_status { seq_id = SeqIdQ } = MsgStatus}, Q1}
              when SeqIdQ < SeqId ->
                %% enqueue from the remaining queue
                queue_merge(SeqIds, Q1, ?QUEUE:in(MsgStatus, Front), MsgIds,
                            Limit, PubFun, State);
            {_, _Q1} ->
                %% enqueue from the remaining list of sequence ids
                {MsgStatus, State1} = msg_from_pending_ack(SeqId, State),
                {#msg_status { msg_id = MsgId } = MsgStatus1, State2} =
                    PubFun(MsgStatus, State1),
                queue_merge(Rest, Q, ?QUEUE:in(MsgStatus1, Front), [MsgId | MsgIds],
                            Limit, PubFun, State2)
        end;
    queue_merge(SeqIds, Q, Front, MsgIds,
                _Limit, _PubFun, State) ->
        {SeqIds, ?QUEUE:join(Front, Q), MsgIds, State}.
    
  • 相关阅读:
    SDK Hello world(直接使用SDK封装)
    一个类有多个基类的内存布局
    写个测试程序看看磁盘映像文件中哪个扇区可以使用?
    在服务中以当前用户身份启动一个程序
    在类有成员变量的场景下, 按照虚表原理, 模拟虚函数实现
    pipe----管道
    Qt序列化格式分析(qint,QString)(非常简单好用)
    模拟QQ系统设置面板实现功能
    Qt持久性对象进行序列化(同时比较了MFC与Java的方法)
    使用srvany.exe将任何程序作为Windows服务运行
  • 原文地址:https://www.cnblogs.com/haoqingchuan/p/4777560.html
Copyright © 2011-2022 走看看