首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Apache Pulsar实战揭秘:关于我的直播分享引爆用户狂问,这些答案你不可错过!

Apache Pulsar实战揭秘:关于我的直播分享引爆用户狂问,这些答案你不可错过!

作者头像
老周聊架构
发布2025-12-29 11:35:23
发布2025-12-29 11:35:23
1030
举报

一、前言

前些天受邀针对《Pulsar存储计算分离架构设计》做了一次技术分享,下班急急忙忙赶到家,饭也没吃给大家做分享,搞得前面还有点小紧张,不过还好后面越讲越顺了。后面自己回放看自己讲的,整体还算满意吧。分享完后有些观众提了好些个问题交流,在此记录下用户的提的问题并且自己再次总结下以便多年后的回顾。

二、用户提问环节

2.1 Pulsar的读取流程的架构为啥采用三层缓存的设计?

在这里插入图片描述
在这里插入图片描述

用户是针对这张图的读取Entry时要经过三层缓存,也就是broker cache、writeCache、readCache

用户提的这个问题真的跟Spring循环依赖三级缓存有点像,就这种问题基本上没人完完全全的答得上来。

(1)broker cache(Broker层缓存)

‌定位‌Broker端的“轻量级缓存”,用于‌快速响应读请求‌

‌价值‌:

  • 减少对Bookie的直接访问:Broker收到读请求后,优先从本地broker cache命中数据,避免跨节点(Broker到Bookie)的网络开销;
  • 降低Broker到Bookie的流量压力:高频读请求在Broker端完成,减少Bookie的读取负载。

(2)writeCache(Bookie层写缓存)

定位‌Bookie端的“写入缓冲区”,用于‌批量写入优化‌

价值‌:

  • 聚合写请求:将Broker发来的消息批量写入writeCache,减少磁盘IO频率(磁盘IO是性能瓶颈,批量写可显著提升写入效率);
  • 异步化写操作:writeCache作为“缓冲层”,让Broker的写请求无需等待磁盘写完成,提升Broker端的响应速度。

(3)readCache(Bookie层读缓存)

‌定位‌Bookie端的“读取缓冲区”,用于‌缓存热点数据‌

价值‌:

  • 减少磁盘读取:将writeCache中已写入但未落盘的数据,或磁盘中高频读取的数据缓存到readCache,读请求直接从readCache命中,避免重复访问磁盘;
  • 提升读取性能:readCache作为“中间层”,将磁盘IO转化为内存IO,大幅降低读取延迟。

三层缓存的协同逻辑

  • 写链路‌:Broker写请求 → writeCache(批量写) → writeCacheBeingFlushed(数据落盘前的“待刷”状态) → Disk(最终持久化);
  • 读链路‌:Broker读请求 → broker cache(Broker端命中);若未命中 → Bookie的readCache(Bookie端命中);若仍未命中 → Disk(磁盘读取)。

通过“Broker端轻量缓存+Bookie端写/读缓存”的分层设计,Pulsar实现了‌读写性能的均衡优化‌:Broker端快速响应读请求,Bookie端通过批量写+缓存读降低磁盘IO压力,最终保障消息队列的高吞吐、低延迟特性。

2.2 RocksDB‌需要调优吗?哪些经典的一些场景需要去调整?

RocksDB作为高性能键值存储引擎,虽默认配置已覆盖多数通用场景,但‌不同业务场景对“性能、资源、可靠性”的优先级存在差异‌(如高并发写入、低延迟读取、高可用性等),需通过调优适配特定需求。

2.2.1 高并发写入场景(如日志系统、实时数据写入)

  • ‌调优目标‌:最大化写入吞吐量,减少磁盘IO压力。
  • 关键参数‌:
    • write_buffer_size增大写缓冲区大小,减少磁盘写频率(默认16MB,可调至64MB+);
    • max_write_buffer_number增加写缓冲区数量,避免频繁触发Compaction(默认2个,可调至4+);
    • disable_auto_compactions:在写入高峰期临时禁用Compaction,避免写放大(需结合业务高峰周期灵活配置)。

2.2.2 低延迟读取场景(如缓存系统、高频查询服务)

  • 调优目标‌:降低读取延迟,提升读取效率。
  • 关键参数‌:
    • block_size调整读取块大小,平衡内存占用与读取效率(默认4KB,可根据数据块大小优化);
    • block_cache_size增大块缓存大小,减少磁盘读取(默认128MB,可调至512MB+);
    • bloom_filter_bits_per_key:启用/优化布隆过滤器,减少无效磁盘读取(默认0,可调至10+)。

2.2.3 高可用性场景(如分布式系统、关键业务存储)

  • ‌调优目标‌:保障数据可靠性,减少故障影响。
  • ‌关键参数‌:
    • max_open_files增大文件句柄数,避免文件句柄耗尽(默认1000,可调至2000+);
    • max_background_compactions增加后台Compaction线程数,加速数据合并(默认1个,可调至2+);
    • max_background_flushes增加后台Flush线程数,提升写缓冲区刷新效率(默认1个,可调至2+)。

2.2.4 资源受限场景(如嵌入式设备、内存有限环境)

  • ‌调优目标‌:降低内存/磁盘占用,适配资源约束。
  • ‌关键参数‌:
    • write_buffer_size减小写缓冲区大小,减少内存占用(默认16MB,可调至8MB);
    • max_open_files减小文件句柄数,降低系统资源消耗(默认1000,可调至500);
    • disable_wal:在可接受数据丢失风险的场景下,关闭WAL(Write-Ahead Logging),减少磁盘IO

2.2.5 大数据量场景(如PB级数据存储、历史数据归档)

  • ‌调优目标‌:优化深层查询性能,减少Compaction开销。
  • ‌关键参数‌:
    • level0_file_num_compaction_trigger调整0层文件触发Compaction的阈值,避免过多文件导致线性查找(默认4个,可调至8+);
    • max_bytes_for_level_base调整各层数据大小,平衡Compaction频率与磁盘空间(默认104857600字节,可按业务数据量调整);
    • target_file_size_base调整SST文件大小,减少深层文件数量(默认10485760字节,可调至20971520字节+)。

2.3 现在有一股风潮,就是去ZK,Pulsar也有其它的一些元数据管理组件,你是怎么去看待这股风潮的?

用户提的这个问题挺好,早在五年前我就有在思考这个问题,像主流的消息中间件Kafka、RocketMQ、Pulsar都在去ZK。

这里老周提一嘴,像Kafka移除了ZK后,是使用了内部的核心元数据管理组件KRaft,Kafka Broker自身承担元数据管理职责,这不仅仅让运维省心省力,因为不需要再单独部署一套ZK集群了,降本增效。

像RocketMQ的元数据管理由‌NameServer‌负责了,核心组件为RouteInfoManager,RocketMQ通过‌Broker与NameServer的心跳机制‌实现路由注册,元数据管理聚焦于“路由信息+Broker状态”的高效维护。

Pulsar的话,我在直播内容也有讲,它采用‌可插拔的元数据管理架构‌,Pulsar允许用户根据需求选择元数据存储(如RocksDB、本地存储、Etcd、Zookeeper),灵活适配不同业务场景。

那么老周是如何看到这股去ZK风潮的呢?首先你得知道ZK的适用场景,ZK是CAP理论中的CP,是强一致性的。你可以想一想,对于分布式消息引擎来说,优先保障啥?那肯定是AP(可用性)的,一个分布式消息引擎不能因为某个元数据管理组件(ZK)挂了而导致整个消息引擎瘫痪,这个是非常危险的。

还有一个点,ZK在高并发大量元数据写入场景,性能有瓶颈,有可能导致选主异常,造成脑裂的现象,这也是非常危险的。

老周举的这两点场景足以让你去ZK了,但老周更想说的是,好的架构不是一蹴而就的,而是慢慢迭代演讲的。这也说明了为啥很多组件开始就是用的ZK,因为刚开始够用,当自身系统慢慢出现了瓶颈才慢慢换掉的。

2.4 怎么理解Bundle,有没有更通俗的介绍?

当时直播的时候,我感觉我给用户解释的还不够通俗,还是有点专业术语在,这里我重新梳理了下,尝试更通俗的写一下。

你可以先简单理解下,Bundle的“工作逻辑”:分组→分配→动态调整。

2.4.1 Bundle的“身份”:Topic的“分组管理员”

Pulsar的Topic是‌“消息的收发通道”‌(比如电商系统里“订单消息”“库存消息”就是不同Topic)。但当Topic数量爆炸式增长(比如百万级)时,Broker要给每个Topic分配“谁负责接收消息、谁负责存储消息”,就会像“给百万个快递员分配包裹”一样混乱。

Bundle就是‌把Topic“分组打包”‌,让Broker按“组”分配任务,减少管理压力。

2.4.2 Bundle的“工作逻辑”:分组→分配→动态调整

2.4.2.1 分组:把Topic“打包”成Bundle

Pulsar会把‌Namespace(命名空间,类似“业务分组”)下的所有Topic‌,按‌“哈希算法”‌分成若干个Bundle。

举个栗子:

假设一个Namespace里有6个Topic(Topic0 ~ Topic5),Pulsar会把它们分成4个Bundle(Bundle0 ~ Bundle3),每个Bundle里装1~2个Topic。

  • Topic0的哈希值落在Bundle3,Topic1落在Bundle0,Topic2落在Bundle1,Topic3落在Bundle2,Topic4落在Bundle0,Topic5落在Bundle1。
  • 最终结果:Bundle0装Topic1、Topic4;Bundle1装Topic2、Topic5;Bundle2装Topic3;Bundle3装Topic0。

2.4.2.2 分配:Broker“认领”Bundle

Broker(消息服务器)启动后,会主动“认领”Bundle:

  • Broker1认领Bundle0(负责Topic1、Topic4的收发);
  • Broker2认领Bundle1(负责Topic2、Topic5的收发);
  • Broker3认领Bundle2(负责Topic3的收发);
  • Broker4认领Bundle3(负责Topic0的收发)。

2.4.2.3 动态调整:负载不均时“搬家”

如果某个Broker压力太大(比如Broker1要处理的Topic消息量暴增),Pulsar会触发‌“负载均衡”‌:

  • 把Broker1认领的Bundle(比如Bundle0)“迁移到”压力小的Broker(比如Broker4);
  • 这样Broker1的负载就减轻了,Broker4的负载也更均衡。

2.4.3 Bundle的“价值”:解决“百万级Topic下的负载难题”

Pulsar的Topic数量可能达到百万级,如果直接让Broker“一个一个管理Topic”,效率会极低。Bundle通过‌“分组管理”‌,把“百万个Topic”变成“几十个Bundle”,让Broker按“组”分配任务,既减少了管理压力,又保证了负载均衡。

2.5 跨地域复制同步消息之后,客户端切换集群有没有最佳实践?比如说什么时候开始切换?或者说如何避免减少漏消息或者重复消费?

这个问题老周没有回答的很好,因为切换集群我们有专门的运维来处理,我倒是给用户分享了下如何避免减少漏消息或者重复消费的方案。

2.5.1 切换时机

2.5.1.1 ‌同步完成度验证

  • 若采用‌强一致性同步‌(如Pulsar元数据级复制、OSS强一致CRR),需等待‌所有消息/元数据同步完成‌(可通过集群间同步状态API查询);
  • 若采用‌最终一致性同步‌(如OSS自建同步工具、异步消息复制),需等待‌消息延迟稳定在业务可接受范围‌(如金融场景RPO趋近于0,需等待延迟<1秒)。

2.5.1.2 业务优先级触发

  • 容灾场景‌:当主地域发生故障(如光缆中断、机房断电),需‌立即切换‌(依赖监控系统实时告警,如OSS方案中“监控系统检测主Bucket异常,触发人工切换”);
  • 日常维护‌:如主地域集群升级,需‌提前规划切换窗口‌(如业务低峰期,结合同步延迟评估切换时间)。

2.5.2 减少漏消息/重复消费:技术手段+流程设计

漏消息与重复消费是跨地域复制的核心痛点,需从‌“技术保障”‌与‌“流程规范”‌双维度解决:

2.5.2.1 技术层面:依赖平台原生能力

  • 消息级同步保障‌:
    • Pulsar消息级复制:同步‌消息内容、顺序、标签‌等,确保目标集群消息与源集群一致;
    • OSS CRR:同步‌对象级别‌(非增量字节),最小单位为1个文件,避免部分消息丢失。
  • 消费进度同步‌:
    • Pulsar元数据级复制:同步‌订阅、角色‌等元数据,保障消费状态一致性。

2.5.2.2 流程层面:规范切换操作

  • ‌切换前准备
    • 确认目标集群‌同步延迟稳定‌(如OSS方案中“实时检测主Bucket异常后,触发切换”);
    • 预先配置‌DNS切换规则‌(如OSS方案中“客户端通过DNS切换指向新Bucket”),减少客户端配置调整成本。
  • ‌切换中保障‌:
    • 采用‌灰度切换‌:先将部分客户端切换至目标集群,验证消息同步完整性后再全量切换;
    • 启用‌消息重试机制‌:切换后若发现漏消息,通过消息重试补发。
  • 切换后验证‌:
    • 对比源集群与目标集群‌消息完整性‌(如通过消息ID、时间戳核对);
    • 监控‌消费进度一致性‌。

2.5.3 总结:切换时机与风险规避的核心逻辑

  • 切换时机‌:强一致性场景等“同步完成”,最终一致性场景等“延迟稳定”,容灾场景“故障即切”,日常维护“低峰期规划”;
  • 风险规避‌:依赖平台原生同步能力(如消息级/元数据级复制、消费进度同步),结合灰度切换、DNS预配置、消息重试等流程手段,从技术与流程双维度保障切换质量。

2.6 BK里面的Qurnumn一致性协议与常见的Raft有啥本质的区别?为啥BK不用Raft?

这个用户问的这个问题也问的很好,这个得从Quorum与Raft的本质区别说起了。

2.6.1 Quorum与Raft的本质区别

2.6.1.1 ‌设计目标

  • Quorum:聚焦‌“读写操作的简单多数派验证”‌,通过“读取数(R)+ 写入数(W)> 副本总数(N)”的数学逻辑,保障读写操作的‌基本一致性‌(如分布式数据库的读写隔离)。
  • Raft:聚焦‌“分布式日志的强一致性与高可用”‌,通过“选举+日志复制+提交”的流程,保障多节点日志的‌严格顺序性、一致性与容错性‌(如分布式存储、配置中心的元数据管理)。

2.6.1.2 ‌实现逻辑

  • Quorum:基于‌“多数派投票”‌的数学规则,通过“读取W个副本、写入R个副本”实现数据一致性,但未解决‌“数据版本冲突、故障恢复”‌等复杂场景。
  • Raft:基于‌“共识算法”‌的流程化设计,通过“选举阶段(选Leader)→ 日志复制阶段(Leader同步日志)→ 提交阶段(多数节点确认后提交)”的三阶段流程,保障日志的‌强一致性与高可用‌,且内置故障恢复机制。

2.6.1.3 ‌适用场景

  • Quorum:适合‌“读写操作简单、数据版本冲突少”‌的场景(如分布式缓存、轻量级数据库),对“强一致性”的要求相对宽松。
  • Raft:适合‌“强一致性要求高、日志顺序性关键”‌的场景(如分布式存储的元数据管理、配置中心),需严格保障数据的“最终一致性”与“故障后快速恢复”。

2.6.2 BK(分布式系统,如分布式存储、数据库)不采用Raft的原因

BK选择Quorum而非Raft,核心是‌“业务场景匹配度、技术复杂度、性能权衡”‌的综合考量:

  • 业务场景匹配度
    • BK的业务场景(如分布式存储的读写操作、数据分片管理)更契合Quorum的“简单多数派验证”逻辑——通过R+W>N的规则,快速完成读写操作的一致性验证,无需Raft复杂的“选举+日志复制”流程。
  • 技术复杂度与性能
    • Quorum的实现‌更轻量、简单‌:仅需通过数学规则验证读写操作,无需维护复杂的“Leader选举、日志同步”状态机,技术复杂度低。
    • Raft的实现‌更复杂‌:需维护“选举超时、日志索引、任期号”等状态,且“选举阶段”存在潜在的“选举碰撞”风险(如多个节点同时发起选举,导致时间浪费),对性能和资源消耗要求更高。
  • 容错性与一致性权衡
    • Quorum的“弱一致性”(仅保证读写操作的简单多数派验证)在BK的业务场景中‌足够满足需求‌(如分布式缓存的“最终一致性”要求)。
    • Raft的“强一致性”(严格保障日志顺序性与故障后恢复)对BK的业务场景‌并非刚需‌,反而会因复杂流程引入额外开销。

2.7 你前面提到的Oxia项目,对比ZK的话各自的优劣?

在这里插入图片描述
在这里插入图片描述

Oxia 提供了一种分片架构,旨在高效管理分布式元数据。在云原生应用领域,可扩展性和高可用性至关重要。传统的无分片架构系统虽然在数据一致性方面表现出色,但在处理超大型数据集或高吞吐量场景时往往会面临诸多限制。而这正是 Oxia 的优势所在。Oxia 的优势不就刚好弥补了 ZK 了劣势了呀!

借助 Oxia,您可以获得一个可扩展、稳健且灵活的分布式系统元数据管理解决方案,从而充分发挥现代云原生架构的潜力。

Zookeeper

Oxia

架构设计

采用‌强一致性+单点主节点‌架构,数据存储在主节点,副本节点仅用于故障恢复,架构更偏向“集中式强一致性”。

采用分布式架构,数据分散存储于多节点,支持水平扩展,适合大规模元数据存储与高并发场景;

性能表现

强一致性机制(如ZAB协议)保障数据强一致性,但写入性能受主节点处理能力限制,高并发写入时性能瓶颈更明显。

分布式架构下,写入与读取性能可通过节点扩容线性提升,适合高并发、大规模数据场景;

扩展性

强一致性+单点主节点的架构,扩容需谨慎(主节点切换复杂),扩展性弱于分布式架构。

分布式架构天然支持‌水平扩容‌(增加节点提升性能),适合业务规模快速扩张的场景;

生态与场景适配

在‌分布式协调、配置中心、服务注册‌等场景中生态成熟(如Kafka、Hadoop等广泛集成),强一致性特性更适配“数据一致性优先”的业务场景。

作为分布式元数据存储系统,更适配‌大规模分布式系统‌(如云原生环境、大数据平台)的元数据管理需求;

2.8 Pulsar中Bookkeeper磁盘满导致丢消息,这种场景有补救措施推荐吗?有一个订阅一直消费不过来导致磁盘打满,然后没办法去接收这个producer的消息了,生产环境遇到过这种情况。

2.8.1 紧急恢复措施

2.8.1.1 临时扩容‌

通过pulsar-admin namespaces set-message-ttl设置1秒TTL(仅限紧急情况),强制触发过期数据清理‌。

代码语言:javascript
复制
bin/pulsar-admin namespaces set-message-ttl <namespace> --messageTTL 1

2.8.1.2 ‌手动清理‌

定位积压的订阅者,通过pulsar-admin topics重置消费位点或删除旧订阅者释放资源‌

2.8.2 根本解决方案

  • 监控与告警‌:配置Pulsar的BookKeeper指标监控(如磁盘使用率、消息堆积量),设置阈值告警‌
  • 消费优化‌,增加消费者并行度(分区数需≥消费者数)

2.8.3 存储调优‌

  • 分离Journal(NVMe SSD)与Entry Log(SSD)目录‌
  • 定期运行Auditor检查数据一致性‌

2.9 ZK主要用来放哪些数据?

2.9.1 ‌元数据存储

  • 集群配置与节点信息‌:存放Pulsar集群的基本配置参数、集群名称,同时记录Broker节点、BookKeeper节点等服务节点的状态和地址,助力集群中各节点间通信协作。
  • 主题及分区信息‌:涵盖主题的创建、删除状态与属性设置,对于分区主题,明确分区分配情况,保证消息准确路由至对应分区。

2.9.2 ‌运行状态与权限数据

  • Broker运行数据‌:包含Broker负载信息,如CPU、内存使用情况,用于任务分配调度;还有Broker与ZooKeeper的会话信息,以便检测Broker健康状态。
  • 权限认证信息‌:记录用户对主题、命名空间等资源的访问权限,存储认证配置信息,保障集群操作安全与合法性。

2.9.3 ‌协调同步信息

  • 分布式锁‌:在同步协调操作中提供分布式锁功能,防止数据冲突与不一致。
  • 协调决策辅助‌:协助集群节点进行协调决策,例如在Broker选举等场景确保过程公平可靠。

三、总结

这次的直播分享有点仓促,下完班饭也没吃赶紧赶回家给大家直播,虽然前面有点紧张,但无伤大雅,因为肚子里长期的积累,无非就是把知道的知识回忆出来给大家分享而已。

老周想说的是,如果你想做一件事,不需要等到万事俱备了才上,准备个七七八八了就可以上,因为等你等到准备的非常完美的时候,机会已经不在了,或者说人家早就赚到盆满钵满了你才入场,汤你都喝不到。

最后老周再说一个点,有些技术你自以为掌握了,但当你写出来发现不知道从何写起;当你写了一篇又一篇的技术博文的时候,但当让你分享给大家听发现你有些逻辑还理不顺。这就是王阳明先生心学中的“知行合一”。

通过这次的直播分享,直播中与大家的互动交流,有些问题也触发到了老周的知识边界,从中我也学习到了很多。感谢自己的这次分享,不仅让技术知识传播给了广大技术人,同时自己也学习到了某些知识边界。


欢迎大家关注我的公众号【老周聊架构】,AI、大数据、云原生、物联网等相关领域的技术知识分享。


欢迎大家添加我的个人微信号,有关AI、大数据、云原生、物联网、消息队列的任何问题都可以问我。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老周聊架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、用户提问环节
  • 三、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档