代码改变世界

RabbitMQ 初探

2019-03-27 14:19  掸尘  阅读(695)  评论(0编辑  收藏  举报

  有哪些优点

  •  可靠性:RabbitMQ 提供了多种技术可以让你在性能和可靠性之间进行权衡。这些技术包括持久性、投递确认、发布者证实和高可用性。

  • 灵活的路由:提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,甚至你可以写自己的交换机类型

  • 多协议和广泛的客户端:RabbitMQ 支持多种消息协议中的消息传递以及 你能想到的语言几乎都有与其相适配的 RabbitMQ 客户端。

  原理

 

  • Broker: 接收和分发消息的应用,RabbitMQ Server就是Message Broker。
  • Channel: 如果每一次访问都建立一个Connection,在消息量大的时候建立Connection的开销将是巨大的,效率也较低。Channel作为轻量级的Connection极大减少了操作系统建立connection的开销。线程池的思想。
  • Exchange: message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)。
  • Queue: 消息最终被送到这里等待consumer取走。一个message可以被同时拷贝到多个queue中。
  • Binding: exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到exchange中的查询表中,用于message的分发依据。其实可以理解为Exchange与Queue的关系对照表。

  工作流程

  • 消息被发布到exchanges,通常可将exchanges比作邮局或者邮箱
  • exchanges将消息副本分发到queues,按照bindings中的规则
  • AMQP brokers传递消息给与queues关联的consumers,或者consumers按照需求从queues拉取信息

以一个例子来解释Exchange、Queue、Binding三者之间的关系:乘客乘坐飞机到北京。
乘客是消息,飞机就是Exchange,Queue就是目的地北京,飞机可以通过不同的路线到达北京,这个路线就是Binding

  交换机(exchange)

$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->exchange_declare('direct_logs', 'direct', false, false, false);

类型

Name(交换机类型)

Default pre-declared names(预声明的默认名称)

Direct exchange(直连交换机)  (Empty string) and amq.direct
Fanout exchange(扇型交换机) amq.fanout 
Topic exchange(主题交换机) amq.topic
Headers exchange amq.match (and amq.headers in RabbitMQ)

 

 

 

 

 

除交换机类型外,在声明交换机时还有许多其他的属性,其中最重要的几个分别是:

  • Durability (exchanges survive broker restart) :持久化(消息代理重启后,交换机是否还存在)
  • Auto-delete (exchange is deleted when all queues have finished using it) : 自动删除(当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它)
  • Arguments (these are broker-dependent) :其他属性(依赖代理本身)

默认交换机

default exchange是一个没有名称的、被broker预先申明的direct exchange。它所拥有的一个特殊属性使它对于简单的应用程序很有作用:每个创建的queue会与它自动绑定,使用queue名称作为routing key。
举例说,当你申明一个名称为“search-indexing-online”的queue时,AMQP broker使用“search-indexing-online”作为routing key将它绑定到default exchange。因此,一条被发布到default exchange并且routing key为”search-indexing-online”将被路由到名称为”search-indexing-online”的queue

直连交换机

直连型交换机(direct exchange)是根据消息携带的路由键(routing key)来将消息投递给队列的。直连交换机用来处理消息的单播路由(unicast routing)(尽管它也可以处理多播路由)。下边介绍它是如何工作的:

  • 一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key)
  • 当一个携带着路由键为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列。

扇型交换机

扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列,而不理会绑定的路由键。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的拷贝分别发送给这所有的N个队列。扇型交换机处理消息的广播路由(broadcast routing)。

主题交换机(Topic Exchange)

主题交换机(topic exchanges)通过对消息的路由键和队列到交换机的绑定模式之间的匹配,将消息路由给一个或多个队列。主题交换机经常用来实现各种分发/订阅模式及其变种。主题交换机通常用来实现消息的多播路由(multicast routing)

头交换机(Headers Exchange)

对于消息的路由来说,有时使用多个属性来表示消息头比用路由键更方便,头交换机(headers exchange)就是为此而生的。头交换机使用多个消息属性来代替路由键建立路由规则。这个规则的建立是通过判断消息头的值是否与指定绑定相匹配而来的。

头交换机可以视为直连交换机的另一种表现形式。头交换机能够像直连交换机一样工作,不同之处在于头交换机的路由规则是建立在头属性值之上,而不是路由键。路由键必须是一个字符串,而头属性值则没有这个约束,它们甚至可以是整数或者哈希值(字典)等。

  路由

绑定(Binding)是交换机(exchange)将消息(message)路由给队列(queue)所需遵循的规则。如果要指示交换机-E将消息路由给队列-Q,那么Q就需要与E进行绑定。绑定操作需要定义一个可选的路由键(routing key)属性给某些类型的交换机。路由键的意义在于从发送给交换机的众多消息中选择出某些消息,将其路由给绑定的队列。

$channel->basic_publish($msg, 'direct_logs', 'error');

最后一个 参数 error 就是 routing_key,具体可参考文档的用法。

  消息确认

为了防止消息丢失,RabbitMQ 提供了消息响应(acknowledgments)。消费者会通过一个 ack(响应),告诉 RabbitMQ 已经收到并处理了某条消息,然后RabbitMQ 就会释放并删除这条消息。如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ 就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,及时工作者(workers)偶尔的挂掉,也不会丢失消息。

消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ 会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。消息响应默认是开启的。之前的例子中我们可以使用 no_ack=True 标识把它关闭。是时候移除这个标识了,当工作者(worker)完成了任务,就发送一个响应。

$callback = function ($msg) {

    $channel = $msg->delivery_info['channel'];
    $channel->basic_ack($msg->delivery_info['delivery_tag']);

    echo ' [x] Received ', $msg->body, "\n";
};


$channel->basic_consume('hello', '', false, false, false, false, $callback);
basic_consume 函数第 4 个参数 no_ack 设置为 false, 就是要消息确认。 通过 callback 回调处理。一个很容易犯的错误就是忘了 basic_ack,后果很严重。消息在你的程序退出之后就会重新发送,如果它不能够释放没响应的消息,RabbitMQ 就会占用越来越多的内存。

   为了排除这种错误,你可以使用 rabbitmqctl 命令,输出 messages_unacknowledged 字段:

$ sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged
Listing queues ...
hello    0       0
...done.

  消息持久化

如果你没有特意告诉 RabbitMQ,那么在它退出或者崩溃的时候,将会丢失所有队列和消息。为了确保信息不会丢失,有两个事情是需要注意的:我们必须把“队列”和“消息”设为持久化。

首先,为了不让队列消失,需要把队列声明为持久化(durable):

$channel->queue_declare('hello', false, true, false, false);

第 3 个参数就是持久化的设置. 

 

参考文献

AMQP 0-9-1 和 AMQP 模型高阶概述

消息队列协议AMQP的设计原理

RabbitMQ文档