RabbitMQ工作队列阻止了消费者

时间:2015-11-27 09:50:42

标签: erlang rabbitmq

我跟随这里的例子

https://www.rabbitmq.com/tutorials/tutorial-two-python.html

仅在带有amqp_client

的Erlang中

完整的代码在这里

https://github.com/jhw/rabbit_worker

据我了解,要获得工作队列样式行为,您需要按如下方式定义RabbitMQ拓扑 -

  • '直接'交换
  • 多个使用者(工作者)绑定的单个队列
  • 路由密钥等于队列名称
  • 工人到ack'每条消息
  • 预取计数为1

现在我的工作正常,可以看到交换机将传递信息循环传递给工人

唯一的问题是没有并行性;一次只调用一名工人;它好像是对一个工作人员的请求阻止交换机向其他任何人发送消息

所以我想我的RabbitMQ拓扑可能设置不正确;但问题是我不知道在哪里。

有什么想法吗?

TIA。

以下核心代码 -

-module(pool_router).

-behaviour(gen_server).

-include_lib("amqp_client/include/amqp_client.hrl").

%% API.
-export([start_link/0]).
-export([subscribe/1]).
-export([unsubscribe/1]).
-export([publish/2]).
-export([acknowledge/1]).

%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).

-record(state, {rabbit_conn, rabbit_chan, queues}).

-define(RABBIT_USERNAME, <<"guest">>).
-define(RABBIT_PASSWORD, <<"Hufton123">>).

-define(EXCHANGE_NAME, <<"worker_exchange">>).
-define(EXCHANGE_TYPE, <<"direct">>).

-define(QUEUE_NAMES, [<<"worker_queue">>]).

%% API.

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%% [stak_worker_sup:spawn_worker() || _ <- lists:seq(1, 3)].
%% [pool_router:publish(<<"worker_queue">>, {<<"fibonacci">>, <<"fibonacci">>, [40]}) || _ <- lists:seq(1, 9)].

publish(QueueName, MFA) ->
    gen_server:call(?MODULE, {publish, {QueueName, MFA}}).

acknowledge(Tag) ->
    gen_server:call(?MODULE, {acknowledge, Tag}).

subscribe(QueueName) ->
    gen_server:call(?MODULE, {subscribe, QueueName}).

unsubscribe(Tag) ->
    gen_server:call(?MODULE, {unsubscribe, Tag}).


%% gen_server.

init([]) ->
    RabbitParams=#amqp_params_network{username=?RABBIT_USERNAME,
                      password=?RABBIT_PASSWORD},    
    {ok, RabbitConn}=amqp_connection:start(RabbitParams),
    {amqp_channel, {ok, RabbitChan}}={amqp_channel, 
                      amqp_connection:open_channel(RabbitConn)},
    Exchange=#'exchange.declare'{exchange=?EXCHANGE_NAME,
                 type=?EXCHANGE_TYPE,
                 auto_delete=false}, 
    #'exchange.declare_ok'{}=amqp_channel:call(RabbitChan, Exchange),
    InitQueueFn=fun(QueueName) ->
            Queue=#'queue.declare'{queue=QueueName},
            #'queue.declare_ok'{}=amqp_channel:call(RabbitChan, Queue), 
            Binding=#'queue.bind'{queue=Queue#'queue.declare'.queue,
                          exchange=?EXCHANGE_NAME,
                          routing_key=QueueName}, 
            #'queue.bind_ok'{}=amqp_channel:call(RabbitChan, Binding),
            Queue
        end,
    Queues=[{QueueName, InitQueueFn(QueueName)} || QueueName <- ?QUEUE_NAMES],
    #'basic.qos_ok'{}=amqp_channel:call(RabbitChan, #'basic.qos'{prefetch_count=1}),
    io:format("router started~n"),     
    {ok, #state{rabbit_conn=RabbitConn,
        rabbit_chan=RabbitChan,
        queues=Queues}}.

handle_call({publish, {QueueName, {Mod, Fn, Args}=MFA}}, {_From, _}, #state{rabbit_chan=RabbitChan}=State) ->
    io:format("Publishing ~p~n", [MFA]),
    Payload=jsx:encode([{<<"M">>, Mod},
            {<<"F">>, Fn},
            {<<"A">>, Args}]),
    Publish=#'basic.publish'{exchange=?EXCHANGE_NAME,
                 routing_key=QueueName},
    ok=amqp_channel:cast(RabbitChan, Publish, #amqp_msg{payload=Payload}),
    {reply, ok, State};
handle_call({acknowledge, Tag}, {From, _}, #state{rabbit_chan=RabbitChan}=State) ->
    ok=amqp_channel:cast(RabbitChan, #'basic.ack'{delivery_tag=Tag}),
    io:format("~p [~p] acknowledged~n", [From, Tag]),
    {reply, ok, State};
handle_call({subscribe, QueueName}, {From, _}, #state{queues=Queues, rabbit_chan=RabbitChan}=State) ->
    io:format("~p subscribed~n", [From]),
    {_, Queue}=proplists:lookup(QueueName, Queues), %% NB no error checking
    #'basic.consume_ok'{consumer_tag=Tag}=amqp_channel:subscribe(RabbitChan, #'basic.consume'{queue=Queue#'queue.declare'.queue}, From),
    {reply, {ok, Tag}, State};
handle_call({unsubscribe, Tag}, {From, _}, #state{rabbit_chan=RabbitChan}=State) ->
    io:format("~p [~p] unsubscribed~n", [From, Tag]),
    #'basic.cancel_ok'{}=amqp_channel:call(RabbitChan, #'basic.cancel'{consumer_tag=Tag}),
    {reply, ok, State};
handle_call(_Request, _From, State) ->
    {reply, ignored, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, State) ->
    ok=amqp_channel:close(State#state.rabbit_chan),
    ok=amqp_connection:close(State#state.rabbit_conn),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% internal functions
-module(stak_worker).

-behaviour(gen_server).

-include_lib("amqp_client/include/amqp_client.hrl").

%% API.

-export([start_link/0]).
-export([stop/1]).

%% gen_server.

-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).

-record(state, {rabbit_tag}).

-define(QUEUE_NAME, <<"worker_queue">>).

%% API.

%% {ok, Pid}=stak_worker:start_link().
%% stak_worker:stop(Pid).

start_link() ->
    gen_server:start_link(?MODULE, [], []).

stop(Pid) ->
    gen_server:cast(Pid, stop).

%% gen_server.

%% don't run request automatically
%% workers should subscribe to router on startup and unsubscribe on termination
%% router then routes messages to workers

init([]) ->
    io:format("starting worker ~p~n", [self()]),    
    {ok, #state{}, 0}.

handle_call(_Request, _From, State) ->
    {reply, ignored, State}.

handle_cast(stop, State) ->
    {stop, normal, State};
handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info({#'basic.deliver'{delivery_tag=Tag}, #amqp_msg{payload=Payload}=_Content}, State) ->
    %% io:format("~p received ~p~n", [self(), Payload]),
    Struct=jsx:decode(Payload),
    {_, ModBin}=proplists:lookup(<<"M">>, Struct),
    {_, FnBin}=proplists:lookup(<<"F">>, Struct),
    {_, Args}=proplists:lookup(<<"A">>, Struct),
    Mod=list_to_atom(binary_to_list(ModBin)),
    Fn=list_to_atom(binary_to_list(FnBin)),
    Mod:Fn(Args),
    ok=pool_router:acknowledge(Tag),
    {noreply, State};
handle_info(timeout, State) ->
    %% io:format("~p subscribing~n", [self()]),
    {ok, RabbitTag}=pool_router:subscribe(?QUEUE_NAME),
    {noreply, State#state{rabbit_tag=RabbitTag}};
handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, State) ->
    io:format("~p shutting down~n", [self()]),
    ok=pool_router:unsubscribe(State#state.rabbit_tag),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

1 个答案:

答案 0 :(得分:0)

避免使用prefetch_count;它将单个通道上的未确认消息数量限制为一个;因此,如果有一个工作池,那么将使它按顺序操作而不是并行操作(因为一旦工人完成,发送给工人的每个请求都只会被确认;即你有多个未完成的请求给定时间点的消息)