RocketMQ Compaction Topic的设计与实现
本文作者:刘涛,阿里云智能技术专家。
01 Compaction Topic介绍
一般来说,消息队列提供的数据过期机制有如下几种,比如有基于时间的过期机制——数据保存多长时间后即进行清理,也有基于数据总量的过期机制——数据分区数据量达到一定值后进行清理。
而 Compaction Topic 是一种基于 key 的数据过期机制,即对于相同 key 的数据只保留最新值。
该特性的应用场景主要为维护状态信息,或者在需要用到 KV 结构时,可以通过 Compaction Topic 将 key-value 信息直接保存到 MQ,从而解除对外部数据库的依赖。比如维护消费位点,可以将消费组加分区作为 key ,将消费位点做 offset ,以消息形式发送到 MQ ,压缩之后,消费时获取最新 offset 信息即可。另外,像 connect 里的 source 信息比如 Binlog 解析位点或其他 source 处理的位点信息均可存到 Compaction Topic。同时 Compaction Topic 也支持 存储 RSQLDB 与 RStreams 的 checkpoint 信息。
02 需要解决的问题
Compaction 过程中,需要解决如下几个问题:
第一,数据写入过程中,数据如何从生产者发送到 broker 并且最终落盘,数据主备之间的 HA 如何保证?
第二,整个 compaction 的流程包括哪几个步骤?如果数据量太大,如何优化?
第三,数据消费时如何索引消息?如果找不到消息指定的 offset 消息,如何处理?
第四,如果有机器故障,如何恢复老数据?
03 方案设计与实现
第一,数据如何写入。
首先写入到 CommitLog,主要为复用 CommitLog 本身的 HA 能力。然后通过 reput线程将 CommitLog 消息按照 Topic 加 partition 的维度拆分到不同文件里,按分区整理消息,同时生成索引。这样最终消息就按 Topic 加 partition的粒度做了规整。
在 compaction 过程中,为什么不在原先的 commitLog 上做规整,而是再额外按分区做规整?原因如下:
- 所有数据都会写到 CommitLog ,因此单个 Topic 的数据不连续。如果要遍历单个 topic 的所有数据,可能需要跳着读,这样就会导致大量冷读,对磁盘 IO 影响比较大。
- CommitLog 数据有自动过期机制,会将老数据删除,因此不能将数据直接写到 CommitLog,而 CompactionLog 里的老数据为按 key 过期,不一定会删除。
- compact 以分区为维度进行。如果多个分区同时做 compact ,效率较低。因为很多分区的 key 同时在一个结构里,会导致同一个分区能够 compact 的数据比较少, 并且 compact 之后也需要重新写一份么,因此,索性就在 compact 之前将消息通过 reput service 重新归整一遍。
Compact 流程如下:
第一步,确定需要做 compaction 的数据文件列表。一般大于两个文件,需要排除当前正在写的文件。
第二步,将上一步筛选出的文件做遍历,得到 key 到 offset 的映射关系。
第三步,根据映射关系将需要保留的数据重新写到新文件。
第四步,用新文件替换老文件,将老文件删除。
第二步的构建 OffsetMap 主要目的在于可以知道哪文件需要被保留、哪文件需要被删除,以及文件的前后关系,这样就可以确定写入的布局,确定布局之后,就可以按照append 的方式将需要保留的数据写到新文件。
此处记录的并非 key 到 value 的信息,而是 key 到 Offset 的信息。因为 value 的数据 body 可能较长,比较占空间,而 offset 是固定长度,且通过 offset 信息也可以明确消息的先后顺序。另外,key 的长度也不固定,直接在 map 存储原始 key 并不合适。因此我们将 MD5 作为新 key ,如果 MD5 相同 key 认为也相同。
做 compaction 时会遍历所有消息,将相同 key 且 offset 小于 OffsetMap 的值删除。最终通过原始数据与 map 结构得到压缩之后的数据文件。
上图为目录结构展示。写入时上面为数据文件,下面为索引,要 compact 的是标红两个文件。压缩后的文件存储于子目录,需要将老文件先标记为删除,将子目录文件与 CQ 同时移到老的根目录。注意,文件与 CQ 文件名一一对应,可以一起删除。
随着数据量越来越大,构建的 OffsetMap 也会越来越大,导致无法容纳。
因此不能使用全量构建方式,不能将所有要 compact 的文件的 OffsetMap 一次性构建,需要将全量构建改为增量构建,构建逻辑也会有小的变化。
第一轮构建:如上图,先构建上面部分的 OffsetMap ,然后遍历文件,如果 offset 小于 OffsetMap 中对应 key 的 offset 则删除,如果等于则保留。而下面部分的消息的offset 肯定大于 OffsetMap 内的 offset ,因此也需要保留。
第二轮构建:从上一次结束的点开始构建。如果上一轮中的某个 key 在新一轮中不存在,则保留上一轮的值;如果存在,则依然按照小于删除、大于保留的原则进行构建。
将一轮构建变为两轮构建后, OffsetMap 的大小显著降低,构建的数据量也显著降低。
原先的索引为 CommitLog Position、Message Size 和 Tag Hush,而现在我们复用了bcq 结构。由于 Compact 之后数据不连续,无法按照先前的方式直接查找数据所在物理位置。由于 queueOffset 依然为单调增排列,因此可以通过二分查找方式将索引找出。
二分查找需要 queueoffset 信息,索引结构也会发生变化,而 bcq 带有 queueoffse 信息,因此可以复用 bcq 的结构。
Queueoffset 在 compact 前后保持不变。如果 queueoffset 不存在,则获取第一个大于 queueoffset 的消息,然后从头开始将所有全量数据发送给客户端。
机器故障导致消息丢失时,需要做备机的重建。因为 CommitLog 只能恢复最新数据,而 CompactionLog 需要老数据。之前的 HA 方式下,数据文件可能在 compact 过程中被被删除,因此也不能基于复制文件的方式做主备间同步。
因此,我们实现了基于 message 的复制。即模拟消费请求从 master 上拉取消息。拉取位点一般从 0 开始,大于等于 commitLog 最小offset 时结束。拉取结束之后,再做一次 force compaction 将 CommitLog 数据与恢复时的数据做一次 compaction ,以保证保留的数据是被压缩之后的数据。后续流程不变。
04 使用说明
生产者侧使用现有生产者接口,因为要按分区做 compact ,因此需要将相同 key 路由到相同的 MessageQueue,需要自己实现相关算法。
消费者侧使用现有消费者接口,消费到消息后,存入本地类 Map 结构中再进行使用。我们的场景大多为从头开始拉数据,因此需要在开始时将消费位点重置到0。拉取完以后,将消息 key 与 value 传入本地 kv 结构,使用时直接从该结构拿取即可。
RocketMQ Compaction Topic的设计与实现的更多相关文章
- Kafka vs RocketMQ——多Topic对性能稳定性的影响-转自阿里中间件
引言 上期我们对比了RocketMQ和Kafka在多Topic场景下,收发消息的对比测试,RocketMQ表现稳定,而Kafka的TPS在64个Topic时可以保持13万,到了128个Topic就跌至 ...
- Kafka vs RocketMQ——多Topic对性能稳定性的影响
引言 上期我们对比了RocketMQ和Kafka在多Topic场景下,收发消息的对比测试,RocketMQ表现稳定,而Kafka的TPS在64个Topic时可以保持13万,到了128个Topic就跌至 ...
- RocketMQ补偿方案架构设计
RocketMQ作为消息中间件,在系统异步化架构中,应用非常广泛.但是我们在享用RocketMQ的同时,也不能百分百完全信赖它.一旦RocketMQ崩溃了,给我们业务带来的也将是毁灭性打击. 因此,我 ...
- RocketMQ消息轨迹-设计篇
目录 1.消息轨迹数据格式 2.记录消息轨迹 3.如何存储消息轨迹数据 @(本节目录) RocketMQ消息轨迹主要包含两篇文章:设计篇与源码分析篇,本节将详细介绍RocketMQ消息轨迹-设计相关. ...
- Kafka vs RocketMQ—— Topic数量对单机性能的影响-转自阿里中间件
引言 上一期我们对比了三类消息产品(Kafka.RabbitMQ.RocketMQ)单纯发送小消息的性能,受到了程序猿们的广泛关注,其中大家对这种单纯的发送场景感到并不过瘾,因为没有任何一个网站的业务 ...
- Topic Model的分类和设计原则
Topic Model的分类和设计原则 http://blog.csdn.net/xianlingmao/article/details/7065318 topic model的介绍性文章已经很多,在 ...
- Kafka vs RocketMQ—— Topic数量对单机性能的影响
引言 上一期我们对比了三类消息产品(Kafka.RabbitMQ.RocketMQ)单纯发送小消息的性能,受到了程序猿们的广泛关注,其中大家对这种单纯的发送场景感到并不过瘾,因为没有任何一个网站的业务 ...
- RocketMQ 创建和删除 topic,以及 broker 和 nameserver 之间的心跳
命令行主类:org.apache.rocketmq.tools.command.MQAdminStartup 客户端创建 topic 程序参数:updateTopic -n localhost:987 ...
- rocketmq(1)
参考: 开源社区:https://github.com/alibaba/RocketMQ rocketmq入门: http://www.cnblogs.com/LifeOnCode/p/4805953 ...
- RocketMQ 事务消息
RocketMQ 事务消息在实现上充分利用了 RocketMQ 本身机制,在实现零依赖的基础上,同样实现了高性能.可扩展.全异步等一系列特性. 在具体实现上,RocketMQ 通过使用 Half To ...
随机推荐
- python-D2-计算机与编程语言
计算机五大核心 控制器 计算机的指挥系统,可以控制计算机硬件的整体运行 运算器 实现算术运算和逻辑运算 控制器和运算器结合起来就是cpu,也称为中央处理器,是整个电脑的核心. 存储器 分为两类,非永久 ...
- 死锁与Lock锁
死锁1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁 2.说明: 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞 ...
- html点击a标签弹窗QQ聊天界面
以为很难.以为要第三方.谁知道不用.一句话的事情. 1 <a hfer="tencent://message/?uin=12345&Site=&Menu-=yes&qu ...
- Windows活动目录_票据——敬请期待!
票据:域控&域机子之间的信任密钥 [缺省40天更换一次] 域用户登录过程 域用户的账户密码(用信任密钥加密的)传递至域控: 域控验证账户密码成功后,构造域用户SID和组SID(用信任密钥加密的 ...
- 人生苦短,我用 python 之入门篇
Python 是一种跨平台的,开源的,免费的,解释型的高级编程语言,它具有丰富和强大的库,其应用领域也非常广泛,在 web 编程/图形处理/黑客编程/大数据处理/网络爬虫和科学计算等领域都能找到其身影 ...
- etcd定时任务脚本执行失败
etcd定时任务脚本执行失败 报错如下:etcdctl命令不存在 是因为在脚本中etcdctl命令没有写绝对路径,修改标记部分改成绝对路径 可以先获取路径然后改成绝对路径问题解决 which etcd ...
- vue中动态引入图片为什么要是require, 你不知道的那些事
相信用过vue的小伙伴,肯定被面试官问过这样一个问题:在vue中动态的引入图片为什么要使用require 有些小伙伴,可能会轻蔑一笑:呵,就这,因为动态添加src被当做静态资源处理了,没有进行编译,所 ...
- 怎么实现无痛刷新token
最近遇到这个需求,前端登录后,后端返回 access_token 和 refresh_token ,当token 过期时用旧的 refresh_token 去获取新的token,前端要不痛去刷新to ...
- Golang 实现时间戳和时间的转化
何为时间戳: 时间戳是使用数字签名技术产生的数据,签名的对象包括了原始文件信息.签名参数.签名时间等信息.时间戳系统用来产生和管理时间戳,对签名对象进行数字签名产生时间戳,以证明原始文件在签名时间之前 ...
- C#之GCHandle
转载 略谈GCHandle C# - Marshal.StructureToPtr方法简介 Marshal类 两个方法StructureToPtr和PtrToStructure实现序列化 字节 数组 ...