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 等。
AMQP 0-9-1 模型的主要流程:首先将一条 message 发布到某个 exchange,exchange 再根据预先定义的路由规则将 message 的副本分发到指定的 queue 中。再由订阅此 queue 的某个 consumer 消费该条 message。
当然也可以定义更复杂的 MQ 拓扑结构,如上图所示:可以由多个 producer 发布不同的 message 到某些 exchange ,exchange 又可以同时与每个 queue 定义(绑定)多个路由关系,即 exchange 与 queue 之间的路由绑定关系是多对多的。当 message 到达 queue 后,会通知某个 consumer 来消费该条消息,或者由 consumer 主动去拿取 message。
3 基础概念
3.1 Connection
- 客户端需先连接到 RabbitMQ 服务端,方可进行发布和消费消息。
- 客户端不再需要连接时,需要主动断开连接,避免消耗 RabbitMQ 服务端资源。
- 建议为发布者和消费者使用单独的连接,以便在对发布者进行流量控制时,避免影响消费者的手动确认速度。
- 因为操作系统对单个进程可以同时打开多少个 TCP 连接有限制,所以在生产环境中必须配置足够数量的并发客户端连接。
3.2 Channel
- 相当于一个轻量的 Connection
- 一个 Channel 只属于某一个 Connection,多个 Channel 可共享一个 Connection
- Connection 关闭,则其所有的 Channel 也会被关闭
- 不同 Channel 间的通信是完全隔离的
- 客户端 Channel 数量受限于服务端
- 对于多线/进程应用,通常会为每个线/进程创建一个新的 Channel
- 建议对于大多数应用程序,可以在每个 Connection 中使用个位数量的 Channel
- Channel 的打开或关闭速率超过100次/s左右时,则表明 Channel 的使用方式很可能有问题
3.3 Exchange
- 默认 exchange :系统默认声明的 exchange 为 direct 类型,名称由""表示,每个创建的 queue 都会用与 queue 同名的 binding key 自动绑定到默认 exchange
- fanout 类型 exchange : 该类型的 exchange 会忽略 routing key 并将消息广播到所有与其绑定的 queue 中
- direct 类型 exchange: 该类型的 exchange 会根据 routing key 精确匹配 binding key ,即 routing key 与binding key 完全相等时才进行路由
- 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 。 - 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 两个键值对,则将消息路由到该队列。 - 当一个源 exchange 被设置 auto_delete 为 true 时, 且其绑定的目标 exchange/queue 都被删除时,则此源 exchange 就会被自动删除
- 可自定义 exchange 类型或使用插件提供的 exchange 类型
3.4 Queue
- 同一 queue 可与同一 exchange 使用不同的 binding_key 进行多次绑定
- 重复创建相同的 queue 没有影响,仍然返回最初创建的那个 queue
- 不可使用不同的参数重新定义现有 queue,否则会抛出一个异常
3.5 Publisher
- 不能向 queue 直接发送消息,只能发送给 exchange,由 exchange 进行转发到某个 exchange 或 queue
- 当发生资源告警时,所有 publisher 的连接都将被阻塞,导致无法发布消息,直到告警解除。
- RabbitMQ 客户端库不支持共享 channel 上的并发发布消息
3.6 Message
- 对于所有路由最终不匹配的 message 默认会被丢弃
- message 的持久化只与 message 本身的 persistence mode 有关
- 临时 queue 中的 message 将在 queue 消失后被丢弃
- 发布为临时 message 将会在 queue 恢复时被丢弃,即使是在持久化的 queue 中
3.7 Consumer
- 多个 consumer 可一起消费某个 queue 上的 message
- 可实现 channel 级别的公平调度,这告诉 RabbitMQ 一次不要给一个 consumer 多条消息。即在 consumer 处理并确认消息之前,不要向 consumer 发送新的消息。相反,它将把它分派给下一个不忙的 consumer。