RabbitMQ erlang "Publish/Subscribe"

官网地址: http://www.rabbitmq.com/tutorials/tutorial-three-python.html

        在 work queue 例子中,每个消息只会发送给一个消费者,本例将演示完全不一样的例子,

就是一个消息被发送给多个消费者消费,这就是我们常说的发布/订阅模式。

        为了演示这个模式,我们将建立一个简单的log系统,它有两个程序组成,一个程序用来发布log消息,另一个程序接收log信息并且把log信息打印出来。

        在这个log系统中,所有正在运行的消费者都可以接收到发布者发布的log信息。通过这种方式我们可以启动一个消费者用来把收到的log信息写入磁盘,同时运行另外一个消费者,把收到的log信息显示在屏幕上。

        实际上,发布的log信息会被广播给所有的消费者。

Exchanges

        在前面的例子中我们从一个队列发送和接收消息。现在我们将介绍Rabbit的整个消息模型。

        RabbitMQ里消息模型的核心思想是生产者从不直接发送任何消息到队列。实际上,通常生产者甚至不知道消息是否发送到了任意一个消息队列中去。

    取而代之的是,生产者仅仅发送一个消息到exchang.  exchange 是一个很简单的东西。Exchange一方面从生产者接收消息,另一方面把消息放到相应的队列中去。 exchange必须真实的知道对于收到的消息做什么操作。这个消息是需要被发到特定的消息队列里面去?这个消息是被放到多个消息队列里面去?或者这个消息应该被丢弃?操作的原则在echange Type中定义。

        RabbitMQ erlang "Publish/Subscribe"_第1张图片

        Exchange type 有以下几种类型: direct, topic, headers, fanout。我们现在关注最后一个fanout.我们先创建一个fanout类型的交换,我们把它取名为"logs":

amqp_channel:call(Channel, #'exchange.declare'{exchange = <<"logs">>,
                                                   type = <<"fanout">>}),

RabbitMQ erlang "Publish/Subscribe"_第2张图片

RabbitMQ erlang "Publish/Subscribe"_第3张图片

    现在我们可以像下面这样声明我们的有名echange:

 amqp_channel:call(Channel, #'exchange.declare'{exchange = <<"logs">>,
                                                   type = <<"fanout">>}),

Temporary queues(临时队列)

    前面的例子中我们使用的队列都是有名字的,比如说hello和task_queue。可以为队列命名对我们来说非常的重要,特别是当我们需要在生产者和消费者直接共享队列的时候。

但是现在我们的这个log系统不需要这样。消费者需要获取所有的log消息,我们也只对当前的消息感兴趣。为了解决这个问题,我们必须做两件事情。

首先,不管我们什么时候连接到了Rabbit,我们需要一个新的,空的消息队列。我们可以创建一个有随机名字的队列,或者让RabbitMQ为我们选择一个随机的队列名字。我们可以在声明队列的时候不指定queue参数来实现这个目的。

其次,一旦消费者断链,这个消费者使用的消息队列必须被删除,我们可以配置参数exclusive = true来实现这个目的。所以队列的声明看起来像下面这样:

#'queue.declare_ok'{queue = Queue} =
        amqp_channel:call(Channel, #'queue.declare'{exclusive = true}),

Bindings (绑定)

    

    我们已经创建了一个 fanout exchange 和一个队列。现在我们需要告诉exchange 发送消息给我们的queue。exchange和 queue之间的关系我们称为binding(绑定)。

amqp_channel:call(Channel, #'queue.bind'{exchange = <<"logs">>,
                                             queue = Queue}),

    从现在起我们的 log exchange 就会添加消息到我们队列。


Putting it all together

    RabbitMQ erlang "Publish/Subscribe"_第4张图片

完整代码:

    emit_log.erl

-moudle(emit_log).
-compile([export_all]).
-include_lib("amqp_client/include/amqp_client.hrl").

main(Argv) ->
    {ok, Connection} =
        amqp_connection:start(#amqp_params_network{host = "localhost"}),
    {ok, Channel} = amqp_connection:open_channel(Connection),

    amqp_channel:call(Channel, #'exchange.declare'{exchange = <<"logs">>,
                                                   type = <<"fanout">>}),

    Message = case Argv of
          [] -> <<"info: Hello World!">>;
          Msg -> list_to_binary(string:join(Msg, " "))
          end,
    amqp_channel:cast(Channel,
                      #'basic.publish'{exchange = <<"logs">>},
                      #amqp_msg{payload = Message}),
    io:format(" [x] Sent ~p~n", [Message]),
    ok = amqp_channel:close(Channel),
    ok = amqp_connection:close(Connection),
    ok.

receive_logs.erl

-module(receive_logs).
-compile([export_all]).
-include_lib("amqp_client/include/amqp_client.hrl").

main(_) ->
    {ok, Connection} =
        amqp_connection:start(#amqp_params_network{host = "localhost"}),
    {ok, Channel} = amqp_connection:open_channel(Connection),

    amqp_channel:call(Channel, #'exchange.declare'{exchange = <<"logs">>,
                                                   type = <<"fanout">>}),

    %% exclusive=true : 消费者断链,这个消费者使用的消息队列必须被删除
    #'queue.declare_ok'{queue = Queue} =
        amqp_channel:call(Channel, #'queue.declare'{exclusive = true}),

    %% 绑定exchange和queue
    amqp_channel:call(Channel, #'queue.bind'{exchange = <<"logs">>,
                                             queue = Queue}),

    io:format(" [*] Waiting for logs. To exit press CTRL+C~n"),

    amqp_channel:subscribe(Channel, #'basic.consume'{queue = Queue,
                                                     no_ack = true}, self()),
    receive
        #'basic.consume_ok'{} -> ok
    end,
    loop(Channel).

loop(Channel) ->
    receive
        {#'basic.deliver'{}, #amqp_msg{payload = Body}} ->
            io:format(" [x] ~p~n", [Body]),
            loop(Channel)
    end.

发出日志消息:

RabbitMQ erlang "Publish/Subscribe"_第5张图片

同时打开两个接收日志进程,可以看到,这两个接收消息的进程都接收到了日志消息:

RabbitMQ erlang "Publish/Subscribe"_第6张图片

RabbitMQ erlang "Publish/Subscribe"_第7张图片


你可能感兴趣的:(rabbitmq,erlang)