消息中间件
设计中间件
假设让你设计一个消息中间件,你会怎么做呢?需要考虑些什么东西呢?
个人畅想
以下是笔者初看时的想法
从需求出发,思考有哪些是必备的功能点,需要考虑的点
需求项 | 描述 |
---|---|
消息的存储 | 保证消息存储不丢失,消息如何高效存储,且存储密度高 |
订阅 | 对 topic 的订阅,保证失败后重试,保证消息消费成功,对于广播消息的消费 |
灾备机制 | 保证宕机后消息存在,恢复后可以从断点进行消费 |
高性能 | 保证消息的发送、消费能保证高并发 |
高可用性 | 如何部署高可用的集群,同城、异地,分布式集群时投票机制等 |
消息中间件的特点
消息中间件的缺点
可靠消息最终一致性
如何上游服务对消息的 100%可靠投递
sequenceDiagram
participant 上游
participant 可靠消息服务
participant 下游MQ
上游->>可靠消息服务: 1️⃣如果上游开启事务失败<br/>由于这是同步调用,<br/>上游可自行决定回调or放弃<br/>对下游不会有影响
可靠消息服务->>可靠消息服务: 2️⃣将开启的事务存储到数据库<br/>记为“待确认”
上游->>可靠消息服务: 3️⃣如果上游操作完业务<br/>通知可靠消息ok时失败
loop Healthcheck
可靠消息服务->>可靠消息服务: 4️⃣可靠消息服务有一个线程<br/>定时检查“待确认”的订单
可靠消息服务->>上游: 5️⃣将待确认订单去上游回查<br/>查询订单实际状态
end
loop Healthcheck
可靠消息服务->>可靠消息服务: 1️⃣可靠消息服务有一个线程<br/>定时检查“已投递”的订单
可靠消息服务->>下游MQ: 2️⃣如果一直未处理<br/>尝试重新发送消息<br/>要求下游幂等性
end
高可用的降级方案
关于 KV 存储的设计要点
由于是消息队列的设计
第一点先把 KV 存储划分为几十个、几百个的线程 Key,这样子我们只需要往对应的 key 里面写,即可实现消息队列的功能。
注意点 | 补充项 |
---|---|
避免数据量过大,大 value | KV 数据库对于大 value、大 key 本身处理是很慢的,无论是带宽还是内存,假如因为太大,导致触发虚拟内存,写回磁盘的操作,那 redis 本身就拖垮了 |
避免热 key | 不要往少数的 key 持续写消息,应用一定的分发规则将消息进行打散下发,这样子能提高 TPS |
是否需要保证消息的顺序性 | 按照队列配置,同时消费端也固定消费指定线程,保证单线程取到数据 |
架构
数据结构
- 考虑点
- 存储地方
- 内存
- 磁盘
- both
- 持久化
- 内存
每隔几秒刷入 disk,刷入后生成的文件大小、数量
- 磁盘
切割文件规则,如何使用一些标识来标志数据信息,像 offset 偏移量 or 内置的唯一 id
- 内存
- 消费模型
- 均匀分配给消费者各实例
- etc
- 存储地方
海量数据
背景:需要支撑 GB 级、TB 级海量数据高并发高吞吐写场景
那么需要实现高并发、高吞吐、海量数据,要考虑些什么呢?首先要解决海量数据存储的问题,单点肯定无法支撑,那么就要考虑分布式存储
分布式存储
关于数据的切分,把数据分成 n 片,放在不同的机器上,这里可以参考一致性哈希模型
假如原先分了 15 分片,达到了上限,那么就需要进行扩容数据,那么中间件就要支持动态增删分片、分片数据自动迁移
灾备
对于生产环境的产品,都要考虑高可用性,极端宕机场景,确定数据是否不可丢失 RPO[^rpo],数据恢复时间指标是多少[^rto],通过多副本冗余机制实现高可用
多副本冗余机制:数据写入主分片后,还要把数据写入灾备数量的副本分片上,才算写入成功,确保发生宕机时,在副本分片上有备份数据,确保不丢失。
[^rpo]: Recovery point Objective 复原点目标,假设发生灾备,如果 RPO=0,代表恢复时数据不会丢失。
[^rto]: Recovery Time Objective 恢复时间目标。假如发生灾备,恢复时间需要多久
数据不丢失
作为消息中间件,确保发送的消息不丢失是第一点,其次是要确保消息能被准确的消费,才算完成了一笔完整的消息投递,而确保的关键点在于 ack 机制。
不丢失就需要先看看消息投递的全链路情况,将各个模块的投递情况做记录
全链路
sequenceDiagram
participant 造数模块
participant 消息中间件
造数模块->>消息中间件: 投递消息给中间件
Note left of 消息中间件: 如果接收消息后宕机<br/>那么就需要持久化<br/>恢复队列以及数据
loop checkDurable
消息中间件->>消息中间件: 持久化队列以及数据<br/>到磁盘中,再返回ack
end
消息中间件-->>造数模块: 接收消息成功ack<br/>也可以通过磁盘备份<br/>保证数据不丢失
Note left of 消息中间件: 依赖磁盘持久化<br/>可能数据会丢失
消息中间件->>数据库模块: 数据库模块消费消息
Note right of 消息中间件: 如果数据库消费一半宕机<br/>那这条消息消费失败了
loop checkConsume
数据库模块->>数据库模块: 如果已经消费成功<br/>向消息中间件发送ack
end
数据库模块->>消息中间件: 消费成功的ack消息
Note left of 数据库模块: 一条消息完整消费完
loop checkSend
消息中间件->>消息中间件: 投递的消息是否已消费<br/>等待ack消息
end
Note right of 消息中间件: 假如限时内未收到ack<br/>重新发送消息
消息中间件->>数据库模块: 重新消费对应消息
更新记录
更新时间 | 更新内容 |
---|---|
2021 年 5 月 31 日 08:17:15 | 消息中间件设计 |
2021 年 7 月 6 日 07:23:31 | 补充了最终一致分布式的原理内容 |