在分布式系统中,元数据如同导航地图,指引着各个组件准确无误地工作。Pulsar 作为一款采用存算分离架构的分布式消息队列,其核心组件包括负责存储服务的 BookKeeper 和承担计算任务的 Broker。为了确保这些组件的高效协同运作,一个稳定可靠的元数据存储服务显得尤为重要。本文将深入探讨 Pulsar 元数据服务的设计理念与实现细节。
BookKeeper 元数据设计
BookKeeper 的定位是一个可用于实时场景下的高扩展性、强容错、低延迟的存储服务。适合作为日志、流式数据、Topic 这类服务的持久化服务。
BookKeeper 中的基本概念
Ledger:它是 BK 的一个基本存储单元,BK Client 的读写操作也都是以 Ledger 为粒度的;类似文件系统中的一个文件,ledgerID 就是个唯一的文件名。 Fragment:是 Ledger 的组成单位,一个 Ledger 可能由1个或多个 Fragment 组成;当前 Fragment 使用的 Bookies 发生写入错误或超时,系统会在剩下的 Bookie 中新建 Fragment。 Entry:每条日志都是一个 Entry,它代表一个 record,每条 record 都会有一个对应的 entry id; 关于 Fragment,它是 Ledger 的物理组成单元,也是最小的物理存储单元,在以下两种情况下会创建新的 Fragment:
BookKeeper 元数据
Bookie集群的元数据在 ZK里有以下4个目录: 1、00,保存所有ledger元数据,根据ledgerID可以找到数据在哪个Bookie节点上存着。为了防止某个目录下node过多,通过多级目录打散。
shell
└── 00
├── 0000
│ ├── L0001
│ ├── L0002
│ ├── L0003
├── 0001
│ ├── L0011
│ ├── L0012
│ └── L0013
└── 0002
└── L0397
bookKeeper 使用了一种固定算法,将 long 类型的 ledger ID 转化成 00/0000/L0001 这样的字符串,作为zookeeper 中的path。 一个ledger的元数据如下,解码后的内容为:
json
{
"23632": {
"ledgerId": 23632, //ledgerId
"metadataFormatVersion": 3,
"ensembleSize": 3,//将数据分布到几个bookie节点
"writeQuorumSize": 3,//数据存储的副本数
"ackQuorumSize": 3,//返回给客户端前,要求写成功的副本数
"state": "OPEN",//ledger 当前状态,有:CLOSED、IN_RECOVERY、OPEN
"length": 0,
"lastEntryId": -1,
"ctime": 1739245083147,
"digestType": "CRC32C",
"password": "",
"customMetadata": {
"pulsar/managed-ledger": "YmVuY2htYXJrL3RuL3BlcnNpc3RlbnQvdHAtcGFydGl0aW9uLTA=", //the name of the ledger
"component": "bWFuYWdlZC1sZWRnZXI=",
"application": "cHVsc2Fy"
},
"closed": false,
"allEnsembles": {//Fragment集合,这里有2个Fragment
"0": [//Fragment1,Entry0~167的数据所在的bookie节点
{
"id": "10.224.129.239:3181"
},
{
"id": "10.224.130.217:3181"
},
{
"id": "10.224.130.218:3181"
}
],
"168": [//Fragment2,Entry168以后的数据所在的bookie节点
{
"id": "10.224.129.253:3181"
},
{
"id": "10.224.130.217:3181"
},
{
"id": "10.224.130.218:3181"
}
]
},
"ctoken": 0
}
}
2、available,保存集群中的bookie节点,包括可用和只读的 3、cookies,保存bookie节点数据目录配置和集群ID
shell
bookieHost: "10.224.130.218:3181" journalDir: "/data/bookkeeper/journal" ledgerDirs: "1\t/data/bookkeeper/ledgers" instanceId: "2a7890d3-1c66-40f0-b271-1a051b0682e6"
4、underreplication,用于autorecovery恢复数据的所有元信息 auditorelection:AutoRecovery 的 leader 节点,leader节点扫描缺副本的ledger,将结果写到下面的 ledgers 目录下。 ledgers:缺副本的ledger locks:AutoRecovery 的 Worker 节点各自分配一些缺副本的 ledger,进行修复。
Broker 元数据设计
Broker作为计算层,实现了MQ的topic模型,比如 topic 信息、数据持久化(保存和过期)、消费进度记录。 在 Pulsar 内部,一个topic使用多个 BookKeeper 的 Ledgers 来存储数据,当 Ledger 中的 Entry 条数超过客户端配置的数量时,将滚动创建新Ledger,当 Ledger 中的所有游标都消费完了 Ledger 中的消息时,Ledger 可以被删除。
topic 与ledger 的映射关系就记录在元数据服务里。当新打开一个ledger 时,才会更新zk里的元信息,每次插入数据不会更新元数据,因此元数据服务压力较小。
元数据路径: /managed-ledgers/[tenant]/[namespace]/persistent/[test-topic-partition-0]
json
ledgerInfo {
ledgerId: 20420
entries: 522
size: 32758
timestamp: 1737290581468
}
ledgerInfo {
ledgerId: 20426
timestamp: 0
}
消费进度也保存在Bookie中,元数据路径: /managed-ledgers/[tenant]/[namespace]/persistent/[test-topic-partition-0]/[subscription-name]
bash
cursorsLedgerId: 20432
markDeleteLedgerId: 20419
markDeleteEntryId: 11
lastActive: 1737276571526
Message对应的schema,由客户端注册到broker上,数据存储到bookie中,这里保存了schema存在哪个ledger上 元数据路径: /schemas/[tenant]/[namespace]/[test-topic]
bash
info {
version: 0
position {
ledgerId: 20421
entryId: 0
}
hash: "\343\260\304B\230\374\034\024\232\373\364\310\231o\271$\'\256A\344d\233\223L\244\225\231\033xR\270U"
}
index {
version: 0
position {
ledgerId: 20421
entryId: 0
}
hash: "\343\260\304B\230\374\034\024\232\373\364\310\231o\271$\'\256A\344d\233\223L\244\225\231\033xR\270U"
}
参考
[1]. BookKeeper 原理浅谈 matt33.com/2019/01/28/...
[2]. Bookkeeper AutoRecovery bookkeeper.apache.org/docs/admin/...