RabbitMQ知识串联

RabbitMQ

RabbitMQ是一个消息代理,是一个通用的消息发送和接受平台,并保证消息传输的安全性。

AMQP协议

AMQP(advance message queue protocol)高级消费队列协议。是一个进程间传递异步消息的网络协议。
RabbitMQ的AMQP协议是由Erlang实现的。
AMQP的模型架构的主要角色,生产者、消费者、交换器、队列。

RabbitMQ的特性

可靠性
RabbitMQ提供多种技术保证性能和可靠性之间权衡。这些技术包括持久化、投递确认、发布者证实、高可用性。
灵活的路由
消息在到达队列前可以通过交换机进行路由,RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,
可以将这些交换机组合起来使用,甚至你可以写自己的交换机类型,并且当做RabbitMQ的插件来使用。
集群
在相同局域网中的多个RabbitMQ服务器可以被聚合在一起,作为一个独立的逻辑代理(broke)来使用。
高可用队列
在同一个集群中,队列可以被镜像到多个机器中,以确保当其中某些硬件出现事故后,你的消息仍然是安全的。
多协议
RabbitMQ 支持多种消息协议中的消息传递。
广泛的客户端
只要是你能想到的语言几乎都有与其相适配的RabbitMQ客户端。
可视化管理工具
RabbitMQ附带了一个易于使用的可视化管理工具,它可以帮助你监控消息代理的每一个环节。
追踪
如果你的消息系统有异常行为,RabbitMQ还提供了追踪的支持,让你能够发现问题所在。
插件系统
RabbitMQ附带了各种各样的插件来对自己进行扩展。甚至你也可以写自己的插件来使用。

AMQP模型简介

AMQP工作过程如下图: 消息(message)有生产者(producer)发送给交换机(exchange),交换机常常被比喻成邮局或者邮箱。
然后交换机把消息按照路由规则分发给绑定的队列(queue)。最后AMQP代理会将消息投递给订阅此队列的消费者,由消费者按需自取。
avatar

Producer:消息生产者。主要将消息投递到对应的交换机(exchange)。
Routing Key :路由关键字。Exchange根据Routing Key进行消息传递。定义绑定时指定的关键字称为Binding Key。
Broker:即RabbitMQ的实体服务器。提供一种传输服务,维护一条从生产者到消费者的传输线路,保证消息数据能按照指定的方式传输。
Exchange:消息交换机。指定消息按照什么规则路由到哪个队列Queue。
Binding:绑定。作用是将Exchange和Queue按照某种路由规则绑定起来。
Queue:消息队列。消息的载体,每条消息都会被投送到一个或多个队列中。
Consumer:消息消费者。消息的接收者。
Connection:Producer和Consumer与Broker之间的TCP长连接。
Channel:消息通道,也称信道。在客户端的每个连接里可以建立多个channel,每个Channel代表一个会话任务。

绑定

Rabbitmq中需要路由键和绑定键联合使用才能使生产者成功投递到队列中去。

  • RoutingKey 生产者发送给交换器绑定的Key
  • BindingKey 交换器和队列绑定的Key

交换机

RabbitMQ有四种交换机类型:

  • direct exchange 直连交换机
    路由规则:发现消息到直连类型的交换机时,只有routing key和binding key完全匹配时,绑定的队列才能收到消息。

  • fanout exchange 扇型交换机
    路由规则:当消息发送到扇型交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。

  • topic exchange 主题交换机
    路由规则:发送消息到主题类型的交换机时,routing key符合binding key的模式时,绑定的队列才能收到消息。
    通配符规则:

    • *代表匹配一个单词。
    • 代表匹配零个或者多个单词。单词与单词之间用.隔开。

  • header exchange 头交换机
    不依赖于RoutingKey而是通过消息体中的headers属性来进行匹配绑定,通过headers中的key和BindingKey完全匹配,由于性能较差一般用的比较少。

队列

AMQP中的队列(queue)跟其他消息队列或任务队列中的队列是很相似的:它们存储着即将被应用消费掉的消息。队列跟交换机共享某些属性,但是队列也有一些另外的属性。

  • Name
  • Durable(消息代理重启后,队列依旧存在)
  • Exclusive(只被一个连接(connection)使用,而且当连接关闭后队列即被删除)
  • Auto-delete(当最后一个消费者退订后即被删除)
  • Arguments(一些消息代理用他来完成类似与TTL的某些额外功能)
    队列在声明(declare)后才能被使用。如果一个队列尚不存在,声明一个队列会创建它。如果声明的队列已经存在,并且属性完全相同,那么此次声明不会对原有队列产生任何影响。
    如果声明中的属性与已存在队列的属性有差异,那么一个错误代码为406的通道级异常就会被抛出。

消费

消息如果只是存储在队列里是没有任何用处的。被应用消费掉,消息的价值才能够体现。在AMQP 0-9-1 模型中,有两种途径可以达到此目的:

  • 将消息投递给应用 ("push API")
  • 应用根据需要主动获取消息 ("pull API")
    使用push API,应用(application)需要明确表示出它在某个特定队列里所感兴趣的,想要消费的消息。如是,我们可以说应用注册了一个消费者,或者说订阅了一个队列。
    一个队列可以注册多个消费者,也可以注册一个独享的消费者(当独享消费者存在时,其他消费者即被排除在外)。
    每个消费者(订阅者)都有一个叫做消费者标签的标识符。它可以被用来退订消息。消费者标签实际上是一个字符串。

如何保证消息的可靠性投递

流程图如下:

  • 1代表消息从生产者发送到Exchange。
  • 2代表消息从Exchange路由到Queue。
  • 3代表消息在Queue中存储。
  • 4代表消费者订阅Queue并消费消息。

确保消息发送到RabbitMQ服务端

可能有网络波动或者Broker的原因导致消息发送到1失败,而生产者无法知道消息是否发送到Broker。
有两种方案解决:

  • Transaction(事务)模式
    通过\(channel->tx_select方法开启事务后,我们便可以发布消息到RabbitMQ了,如果事务提交成功,则消息一定到达了RabbitMQ中,如果在事务提交执行之前由于RabbitMQ异常崩溃或者其他原因抛出异常, 这个时候我们便可以将其捕获,进而通过执行\)channel->tx_rollback()方法来实现事务回滚。使用事务机制的话会吸干RabbitMQ的性能,一般不建议使用。
  • Confirm(确认)模式
    生产者通过调用$channel->confirm_select()方法将信道设置为confirm模式。一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(basic_ack())给生产者(包含消息的唯一ID),这就使得生产者知晓消息已经正确到达了目的地。

确保消息路由到正确的队列

可能由于路由关键字错误,或者队列不存在,或者队列名错误导致2失败。

  • 使用mandatory参数和ReturnListener,可以实现消息无法路由的时候返回给生产者。
  • 另一种方式就是使用备份交换机(alternate-exchange),无法路由的消息会发送到这个交换机上。
/**
 * Declares queue, creates if needed 声明队列,如果队列不存在创建之。此方法用于创建或检查队列。当新建一个队列时,客户端可以指定一系列属性用于控制队列的持久性及其内容,还有队列的分享等级。
 *
 * @param string $queue 队列名称
 * @param bool $passive
 * @param bool $durable 持久化
 * @param bool $exclusive 排它
排他队列只对首次创建它的连接可见,排他队列是基于连接(Connection)可见的,并且该连接内的所有信道(Channel)都可以访问这个排他队列,
在这个连接断开之后,该队列自动删除,由此可见这个队列可以说是绑到连接上的,对同一服务器的其他连接不可见。同一连接中不允许建立同名的排他队列的
这种排他优先于持久化,即使设置了队列持久化,在连接断开后,该队列也会自动删除。
非排他队列不依附于连接而存在,同一服务器上的多个连接都可以访问这个队列。
 * @param bool $auto_delete  自动删除 为true则设置队列为自动删除。
自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。
不能把这个参数错误地理解为:”当连接到此队列的所有客户端断开时,这个队列自动删除”,因为生产者客户端创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列。
 * @param bool $nowait 是否等待服务器确认
 * @param null $arguments
 * @param null $ticket
 * @return mixed|null
 */
$channel->queue_declare('hello', false, false, false, false);

/**
 * Binds queue to an exchange 将队列绑定到交换机
此方法用于绑定队列到交换机。队列绑定到交换机之前不会接收到任何消息。在经典消息模型中,存储转发队列绑定到直连交换机,订阅队列绑定到主题交换机。
 *
 * @param string $queue 队列名称
 * @param string $exchange 交换机名称
 * @param string $routing_key 绑定键
 * @param bool $nowait 是否等待服务器确认
 * @param null $arguments
 * @param null $ticket
 * @return mixed|null
 */
$channel->queue_bind(string $queue, string $exchange, string $routing_key,bool $nowait);

确保消息在队列正确的存储

可能因为系统宕机,重启,关闭等情况导致存储在队列的消息丢失,即3出现问题
解决方案:

  • 队列持久化(queue.declare)
  • 交换机持久化(exchange.declare)
  • 消息持久化(basic.publish)
  • 集群,镜像队列

确保消息正确的投递到消费者

如果消费者收到消息后未来得及处理即发生异常,或者处理过程中发生异常,会导致4失败。
为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement)。消费者在订阅队列(basic_consume)时,可以指定autoAck参数,
当autoAck等于false时,RabbitMQ会等待消费者显式的回复确认信号后才从队列中移去消息。如果消费失败,也可以调用basic.reject或者basic.nack来拒绝当前消息而不是确认。
如果requeue参数设置为true,可以把这条消息重新存入队列,以便发给下一个消费者(当然,只有一个消费者的时候,这种方式可能会出现无限重复消费的情况,可以投递到新的队列中,或者只打印异常日志)。

消息的幂等性

服务端是没有这种控制的,只能在消费端控制。

如何避免消息的重复消费?
消息重复可能会有两个原因:
1、生产者的问题,环节1重复发送消息,比如在开启了Confirm模式但未收到确认。
2、环节4出了问题,由于消费者未发送ACK或者其他原因,消息重复投递。 对于重复发送的消息,可以对每一条消息生成一个唯一的业务ID,通过日志或者建表来做重复控制

参考链接
RabbitMQ-PHP版
blog

posted @ 2020-05-20 16:43  phper-liunian  阅读(205)  评论(0编辑  收藏  举报