1、什么是 rabbitmq
采 用 AMQP 高 级 消 息 队 列 协 议 的 一 种 消 息 队 列 技 术 ,最 大 的 特 点 就 是 消 费 并 不 需 要 确 保 提 供 方 存 在 , 实 现 了 服 务 之 间 的 高 度 解 耦。
2、为什么要使用 rabbitmq
- 在 分 布 式 系 统 下 具 备 异 步 , 削 峰 , 负 载 均 衡 等 一 系 列 高 级 功 能 ;
- 拥 有 持 久 化 的 机 制, 进 程 消 息, 队 列 中 的 信 息 也 可 以 保 存 下 来 ;
- 实 现 消 费 者 和 生 产 者 之 间 的 解 耦 ;
- 对 于 高 并 发 场 景 下, 利 用 消 息 队 列 可 以 使 得 同 步 访 问 变 为 串 行 访 问 达 到 一 定 量 的 限 流, 利 于 数 据 库 的 操 作 ;
- 可 以 使 用 消 息 队 列 达 到 异 步 下 单 的 效 果, 排 队 中, 后 台 进 行 逻 辑 下 单 。
3、使用 rabbitmq 的场景
1、 服 务 间 异 步 通 信
2、 顺 序 消 费
3、 定 时 任 务
4、 请 求 削 峰
4、如何确保消息正确地发送至 RabbitMQ? 如何确保消息接收方消费了消息?
发送方确认模式
将 信 道 设 置 成 confirm 模 式 (发 送 方 确 认 模 式) , 则 所 有 在 信 道 上 发 布 的 消 息 都 会 被 指 派 一 个 唯 一 的 ID。
一 旦 消 息 被 投 递 到 目 的 队 列 后, 或 者 消 息 被 写 入 磁 盘 后 (可 持 久 化 的 消 息) , 信 道 会 发 送 一 个 确 认 给 生 产 者 (包 含 消 息 唯 一 ID) 。
如 果 RabbitMQ 发 生 内 部 错 误 从 而 导 致 消 息 丢 失, 会 发 送 一 条 nack (not acknowledged, 未 确 认) 消 息 。
发送 方确 认模 式是 异步 的, 生产 者应 用程 序在 等待 确认 的同 时, 可以 继续 发送 消 息。 当确 认消 息到 达生 产者 应用 程序, 生 产者 应用 程序 的回 调方 法就 会被 触发 来 处理 确认 消息 。
接收 方确 认机 制
接收 方消 息确 认机 制
消费 者接 收每 一条 消息 后都 必须 进行 确认 (消 息接 收和 消息 确认 是两 个不 同操 作) 。只 有消 费者 确认 了消 息, RabbitMQ 才能 安全 地把 消息 从队 列中 删除 。
这里 并没 有用 到超 时机 制, RabbitMQ 仅通 过 Consumer 的连 接中 断来 确认 是否 需要 重新 发送 消息 。也 就是 说, 只要 连接 不中 断, RabbitMQ 给了 Consumer 足 够长 的时 间来 处理 消息 。保 证数 据的 最终 一致 性;
下面 罗列 几种 特殊 情况
如果 消费 者接 收到 消息, 在 确认 之前 断开 了连 接或 取消 订阅, RabbitMQ 会认 为 消息 没有 被分 发, 然后 重新 分发 给下 一个 订阅 的消 费者 。(可能 存在 消息 重复 消 费的 隐患, 需 要去 重)
如果 消费 者接 收到 消息 却没 有确 认消 息, 连接 也未 断开, 则 RabbitMQ 认为 该消 费者 繁忙, 将 不会 给该 消费 者分 发更 多的 消息 。
5、如何避免消息重复投递或重复消费?
在消 息生 产时, MQ 内部 针对 每条 生产 者发 送的 消息 生成 一个 inner-msg-id, 作 为去 重的 依据 (消 息投 递失 败并 重传) , 避免 重复 的消 息进 入队 列;
在消 息消 费时, 要求 消息 体中 必须 要有 一个 bizId (对于 同一 业务 全局 唯一, 如支 付 ID、订 单 ID、帖 子 ID 等) 作为 去重 的依 据, 避免 同一 条消 息被 重复 消费 。
6、消息基于什么传输?
由于 TCP 连接 的创 建和 销毁 开销 较大, 且 并发 数受 系统 资源 限制, 会 造成 性能 瓶 颈。 RabbitMQ 使用 信道 的方 式来 传输 数据 。信 道是 建立 在真 实的 TCP 连接 内的 虚拟 连接, 且 每条 TCP 连接 上的 信道 数量 没有 限制 。
7、消息如何分发?
若该 队列 至少 有一 个消 费者 订阅, 消息 将以 循环 (round-robin) 的方 式发 送给 消 费者 。每 条消 息只 会分 发给 一个 订阅 的消 费者 (前 提是 消费 者能 够正 常处 理消 息 并进 行确 认) 。
通过 路由 可实 现多 消费 的功 能
8、消息怎么路由?
消息提供方->路由->一至多个队列
消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。
通过队列路由键,可以把队列绑定到交换器上。
消息到达交换器后,RabbitMQ 会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则);
常用的交换器主要分为以下三种:
- fanout: 如果交换器收到消息,将会广播到所有绑定的队列上
- direct: 如果路由键完全匹配,消息就被投递到相应的队列
- topic: 可以使来自不同源头的消息能够到达同一个队列。使用 topic 交换器时,可以使用通配 符
9、如何确保消息不丢失?
消息持久化, 当然前提是队列必须持久化
RabbitMQ 确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件, 当发布一条持久性消息到持久交换器上时,Rabbit 会在消息提交到日志文件后才发送响应。一旦消费者从持久队列中消费了一条持久化消息,RabbitMQ 会在持久化日志中把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前 RabbitMQ 重启, 那么 Rabbit 会自动重建交换器和队列(以及绑定), 并重新发布持久化日志文件中的消息到合适的队列。
10、使用 RabbitMQ 有什么好处?
1、服务间高度解耦
2、异步通信性能高
3、流量削峰
11、 RabbitMQ 的集群
镜像集群模式
你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,然后每次你写消息到 queue 的时候,都会自动把消息到多个实例的 queue 里进行消息同步。
好处在于,你任何一个机器宕机了, 没事儿, 别的机器都可以用。
坏处在于, 第一, 这个性能开销也太大了吧,消息同步所有机器,导致网络带宽压力和消耗很重! 第二,这么玩儿,就没有扩展性可言了,如果某个 queue 负载很重,你加机器,新增的机器也包含了这个 queue 的所有数据,并没有办法线性扩展你的 queue。
12、 mq 的缺点
系统可用性降低
系统引入的外部依赖越多, 越容易挂掉,本来你就是 A 系统调用 BCD 三个系统的接口就好了,人家ABCD 四个系统好好的,没啥问题, 你偏加个 MQ 进来, 万一 MQ 挂了咋整? MQ挂了,整套系统崩溃了, 你不就完了么。
系统复杂性提高
硬生生加个 MQ 进来,你怎么保证消息没有重复消费? 怎么处理消息丢失的情况? 怎么保证消息传递的顺序性? 头大头大, 问题一大堆, 痛苦不已。
一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了; 但是问题是, 要是 BCD 三个系统 那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整? 你这数据就不一致了。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处, 但是也得针对它带来的坏处做各 种额外的技术方案和架构来规避掉, 最好之后, 你会发现,妈呀, 系统复杂度提升了一个数量级,也许是复杂了 10 倍。 但是关键时刻, 用, 还是得用的。