官网地址: 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中定义。
Exchange type 有以下几种类型: direct, topic, headers, fanout。我们现在关注最后一个fanout.我们先创建一个fanout类型的交换,我们把它取名为"logs":
amqp_channel:call(Channel, #'exchange.declare'{exchange = <<"logs">>, type = <<"fanout">>}),
现在我们可以像下面这样声明我们的有名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
完整代码:
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.
发出日志消息:
同时打开两个接收日志进程,可以看到,这两个接收消息的进程都接收到了日志消息: