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/...

相关推荐
兮动人4 小时前
SpringBoot加载配置文件的优先级
java·spring boot·后端·springboot加载配置
m0_748254665 小时前
Spring Boot 热部署
java·spring boot·后端
Seven975 小时前
SpringCloud带你走进微服务的世界
java·后端·spring cloud
夕颜1116 小时前
排查问题的知识记录
后端
zhuyasen6 小时前
Go语言Viper配置详解:conf库优雅解析实战
后端·golang
佳佳_6 小时前
Spring Boot SSE 示例
spring boot·后端
Seven977 小时前
【设计模式】使用解释器模式简化复杂的语法规则
java·后端·设计模式
李长渊哦7 小时前
Spring Boot 接口延迟响应的实现与应用场景
spring boot·后端·php
Seven978 小时前
【设计模式】通过访问者模式实现分离算法与对象结构
java·后端·设计模式
Seven978 小时前
【设计模式】遍历集合的艺术:深入探索迭代器模式的无限可能
java·后端·设计模式