Pulsar 元数据服务设计

在分布式系统中,元数据如同导航地图,指引着各个组件准确无误地工作。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/...

相关推荐
kcuwu.15 小时前
第 1 课 · Hello, World 与一个 Go 程序的诞生
开发语言·后端·golang
php@king15 小时前
golang入门到精通
开发语言·后端·golang
明月_清风15 小时前
Foundry Cheatcode 详解:prank、warp、deal、expectRevert、expectEmit
后端·web3·solidity
鹅城剑仙15 小时前
Spring Boot 3 全局异常处理与统一响应封装进阶实战
java·spring boot·后端
明月_清风15 小时前
Foundry Fuzz Testing:让测试自动寻找 Solidity Bug
后端·web3·solidity
IT_陈寒15 小时前
SpringBoot自动配置失效时我差点把电脑扔了
前端·人工智能·后端
两点王爷1 天前
SpringBoot 项目集成 GeoServer 源码,实现地图服务发布
java·spring boot·后端
leoZ2311 天前
2026-09-09-springboot-cloud-deploy-pitfalls
java·前端·javascript·vue.js·人工智能·spring boot·后端
海上彼尚1 天前
Cursor 模型的强度实测排行
前端·人工智能·后端
GoGeekBaird1 天前
从「跑完即销毁」到「用完再销毁」:云沙箱的一次进化
后端·github·ai编程