消息队列 RabbitMQ 入门基础

32 阅读6分钟

1 为什么要用 MQ?MQ 的优缺点?

优点:

1.1 提高单个请求的响应速度

对于一个请求,可以在其完成核心业务后,先将结果响应给客户端,同时发送一条消息到 MQ 中,来慢慢处理其余的非核心业务。
例如:当一个用户注册成功后,可在完成初始化用户基础信息后,快速将注册结果响应给用户。同时在 MQ 中插入一条“用户注册成功”的消息,交给监听该消息队列的邮件业务处理模块,根据该消息携带的详细内容,发送激活邮件给用户。而不是等到邮件发送成功后,才将注册结果响应给用户。

1.2 提高大量并发请求时的系统稳定性

当系统中的并发请求量较大时,可先将所有并发请求入队到 MQ 中,并按照系统的处理能力,来按顺序慢慢处理这些请求,以避免系统崩溃。

1.3 降低业务或系统间的耦合

在 RabbitMQ 中 可实现发布订阅模式,即消息的发送者无需知道谁会接收处理这个消息,而消息的接收者也无需知道消息是谁发送的,这样一来就可以降低系统之间的耦合。

缺点:

1.4 增加系统复杂度

对于业务简单的应用,若引入 MQ 反而会增加系统复杂度,使整个代码调用链路变长,导致代码难以调试、测试。

1.5 系统可用性降低

根据奥卡姆剃刀原理的“如无必要,勿增实体”,增加了 MQ 后系统中就多了一个可能发生故障的点,导致系统可用性降低。

1.6 难保证数据一致性

若在核心业务处理完后,其发送到 MQ 中的消息处理失败,则会导致数据不一致,且难以回滚已经完成的业务操作。

2 核心概念模型

RabbitMQ 是一种消息队列的实现方案,主要基于 AMQP 0-9-1 协议,当然 RabbitMQ 还支持其他协议,如 STOMP、MQTT、HTTP 等。

hello-world-example.png

AMQP 0-9-1 模型的主要流程:首先将一条 message 发布到某个 exchange,exchange 再根据预先定义的路由规则将 message 的副本分发到指定的 queue 中。再由订阅此 queue 的某个 consumer 消费该条 message。

complex_example.png

当然也可以定义更复杂的 MQ 拓扑结构,如上图所示:可以由多个 producer 发布不同的 message 到某些 exchange ,exchange 又可以同时与每个 queue 定义(绑定)多个路由关系,即 exchange 与 queue 之间的路由绑定关系是多对多的。当 message 到达 queue 后,会通知某个 consumer 来消费该条消息,或者由 consumer 主动去拿取 message。

3 基础概念

3.1 Connection

  1. 客户端需先连接到 RabbitMQ 服务端,方可进行发布和消费消息。
  2. 客户端不再需要连接时,需要主动断开连接,避免消耗 RabbitMQ 服务端资源。
  3. 建议为发布者和消费者使用单独的连接,以便在对发布者进行流量控制时,避免影响消费者的手动确认速度。
  4. 因为操作系统对单个进程可以同时打开多少个 TCP 连接有限制,所以在生产环境中必须配置足够数量的并发客户端连接。

3.2 Channel

  1. 相当于一个轻量的 Connection
  2. 一个 Channel 只属于某一个 Connection,多个 Channel 可共享一个 Connection
  3. Connection 关闭,则其所有的 Channel 也会被关闭
  4. 不同 Channel 间的通信是完全隔离的
  5. 客户端 Channel 数量受限于服务端
  6. 对于多线/进程应用,通常会为每个线/进程创建一个新的 Channel
  7. 建议对于大多数应用程序,可以在每个 Connection 中使用个位数量的 Channel
  8. Channel 的打开或关闭速率超过100次/s左右时,则表明 Channel 的使用方式很可能有问题

3.3 Exchange

  1. 默认 exchange :系统默认声明的 exchange 为 direct 类型,名称由""表示,每个创建的 queue 都会用与 queue 同名的 binding key 自动绑定到默认 exchange
  2. fanout 类型 exchange : 该类型的 exchange 会忽略 routing key 并将消息广播到所有与其绑定的 queue 中
  3. direct 类型 exchange: 该类型的 exchange 会根据 routing key 精确匹配 binding key ,即 routing key 与binding key 完全相等时才进行路由
  4. topic 类型 exchange: 该类型的 exchange 会根据 routing key 模糊匹配 binding key。
    *(星号):用于匹配一个单词,例如 routing key 为 www.hxstrive.com 时将匹配 binding key 为 *.hxstrive.com;routing key 为 www.hxstrive.com 也将匹配 binding key为 *.hxstrive.*。
    #(井号):用于匹配零个或多个单词,例如 routing key 为 www.hxstrive.com、h1.demo.hxstrive.com 可以匹配 binding key 为 #.hxstrive.com 。
  5. headers 类型 exchange: 该类型的 exchange 会忽略 routing key 并从 headers 中的值进行匹配。
    all:完全匹配,如果消息头与绑定队列和交换器中指定的键值对完全匹配,则将消息路由到该队列。例如:绑定队列和交换器时,指定了 key1=val1,key2=val2,key3=val3 三个键值对,而发送的消息指定了 key1=val1,key2=val2 两个键值对,则不会将消息路由到该队列。如果消息指定了 key1=val1,key2=val2,key3=val3 三个键值对,则将消息路由到该队列。
    any:部分匹配,如果消息头与绑定队列和交换器中指定的键值对任意一个匹配,则将消息路由到该队列。例如:绑定队列和交换器时,指定了 key1=val1,key2=val2,key3=val3 三个键值对,而发送的消息指定了 key2=val2 两个键值对,则将消息路由到该队列。
  6. 当一个源 exchange 被设置 auto_delete 为 true 时, 且其绑定的目标 exchange/queue 都被删除时,则此源 exchange 就会被自动删除
  7. 可自定义 exchange 类型或使用插件提供的 exchange 类型

3.4 Queue

  1. 同一 queue 可与同一 exchange 使用不同的 binding_key 进行多次绑定
  2. 重复创建相同的 queue 没有影响,仍然返回最初创建的那个 queue
  3. 不可使用不同的参数重新定义现有 queue,否则会抛出一个异常

3.5 Publisher

  1. 不能向 queue 直接发送消息,只能发送给 exchange,由 exchange 进行转发到某个 exchange 或 queue
  2. 当发生资源告警时,所有 publisher 的连接都将被阻塞,导致无法发布消息,直到告警解除。
  3. RabbitMQ 客户端库不支持共享 channel 上的并发发布消息

3.6 Message

  1. 对于所有路由最终不匹配的 message 默认会被丢弃
  2. message 的持久化只与 message 本身的 persistence mode 有关
  3. 临时 queue 中的 message 将在 queue 消失后被丢弃
  4. 发布为临时 message 将会在 queue 恢复时被丢弃,即使是在持久化的 queue 中

3.7 Consumer

  1. 多个 consumer 可一起消费某个 queue 上的 message
  2. 可实现 channel 级别的公平调度,这告诉 RabbitMQ 一次不要给一个 consumer 多条消息。即在 consumer 处理并确认消息之前,不要向 consumer 发送新的消息。相反,它将把它分派给下一个不忙的 consumer。