Apache Pulsar 2.9.1 部署实战:单机、集群、多租户与避坑
发布时间:2026/11/2 23:44:25 锦皓数字建站

简介Apache Pulsar 2.9.1 的官方二进制发行包bin.tar.gz面向需要在分布式环境中搭建消息中间件的后端开发、数据平台与运维人员用于应对高吞吐实时消息传递、集群容错与云原生部署等场景适合已具备一定消息队列基础的中高级读者。压缩包约 321.53MB解压后即可获得启动服务端与客户端所需的二进制文件、脚本及辅助工具可按照官方文档完成配置与启动。内容围绕 Pulsar 的核心机制展开依托 ZooKeeper 管理主题分区、集群配置与租约等元数据借助 BookKeeper 实现低延迟、高可用的消息持久化并支持独占、共享与故障转移等多种订阅类型Pulsar Functions 提供轻量级流处理能力分层存储可将冷数据迁移至低成本介质。已有 152 人学习适合用来搭建本地实验集群、验证跨数据中心部署方案或作为对比 Kafka 等中间件时的实操参考。1. 从 bin.tar.gz 到一个能发消息的 Pulsar2.9.1 包里到底装了哪几层拿到 apache pulsar 2.9.1 bin.tar.gz 的人多半已经在架构图上见过它Broker 无状态、BookKeeper 存 Ledger、ZooKeeper 管元数据计算存储分离天生适合云原生环境里横向扩。可解压之后conf 目录里同时躺着 zookeeper.conf、bookkeeper.conf、broker.conf、standalone.conf 四套配置bin 目录下 pulsar、pulsar-admin、pulsar-client、pulsar-perf 又各有各的入口先动哪个很容易发懵。这份二进制包的实际价值在于不用编译、不用容器编排一台装了 JDK 的机器就能把单机全栈拉起来也能沿用同一套配置扩成三节点集群。它适合要验证消息模型选型、做吞吐压测、或者给内部跑一轮分布式消息 PoC 的从业者。下面按先跑通、再拆层、后治理、最后踩坑的顺序拆。2. 解包与单机起步standalone 五分钟跑通一条消息standalone 模式是这份包里最容易被低估的东西。它并不是一个阉割版玩具而是把 ZooKeeper、BookKeeper、Broker 三个角色塞进同一个 JVM 进程里用同一套 data 目录落盘topic 的创建、分区、订阅、Schema 全都能用。对刚接触 Pulsar 的人来说先用它把 produce/consume 闭环跑通比一上来就摆三台机器靠谱得多因为集群模式下任何一个配置写错日志都会含糊地指向 ZK 连接排查成本高好几倍。2.1 环境预检JDK、内存与端口三件事Pulsar 2.9.1 这一代对 JDK 比较挑。整套包在 JDK 8 和 JDK 11 上都能跑但 JDK 17 会因为模块系统反射限制直接炸掉报InaccessibleObjectException或者Unsupported class file major version 61——这类报错看起来像依赖缺失实际是运行时版本不对。血泪经验是先把java -version打出来确认是 11再往下走。内存方面standalone 默认给 JVM 的堆是 2G 起步BookKeeper 还要额外占堆外直接内存机器少于 8G 时压测很容易触发 OOM Killer。文件句柄也不能忽视Pulsar 每个 Ledger 都要占 fdulimit -n低于 65536 在几百个 topic 的场景下会开始报Too many open files。# 1. 确认 JDK 版本2.9.1 建议 118 也可 java -version # 2. 文件句柄与内存映射上限 ulimit -n sysctl vm.max_map_count # 3. 检查四个关键端口是否被占用 # 2181ZooKeeper 3181Bookie 6650Broker 8080WebService ss -lntp | grep -E 2181|3181|6650|8080代码里的三段检查各自对应一个失败面JDK 版本决定能不能启动ulimit -n决定能撑多少 topic 和消费者端口检查则是为了避免和机器上已有的 ZooKeeper、Kafka 之类服务撞车——2181 这个端口在很多大数据环境里已经有人占了启动时只会看到Address already in use不会告诉你用的是哪个进程。vm.max_map_count在默认 65530 的发行版上一般够用只有当 topic 数量上千、Ledger 频繁打开关闭时才需要调到 20 万以上。2.2 standalone 启动与必须改的三个配置解包之后不要急着敲启动命令。conf/standalone.conf里有几个值在单机场景下必须改否则后面客户端连不上会很莫名其妙。tar -xzf apache-pulsar-2.9.1-bin.tar.gz cd apache-pulsar-2.9.1 # 关键三项对外通告地址、数据目录、Web 端口 sed -i s/^advertisedAddress.*/advertisedAddress192.168.10.21/ conf/standalone.conf sed -i s#^dataDir.*#dataDir/data/pulsar/standalone# conf/standalone.conf sed -i s/^webServicePort.*/webServicePort8080/ conf/standalone.conf # 前台启动日志直接打屏第一次建议这样跑 bin/pulsar standaloneadvertisedAddress是这三个参数里最容易被忽略、后果也最严重的一个。它的作用不是绑定监听地址而是告诉客户端你应该用这个地址回来找我。默认值取的是机器 hostname如果 hostname 在客户端那台机器上解析不了或者解析到一个不可达的内网地址表现就是命令行pulsar-client produce能连上 6650 发出去但消费端一直TimeoutExceptionbroker 日志里干干净净。我第一次踩这个坑时盯了半天 broker 日志最后发现客户端拿到的是pulsar://myhost:6650而 myhost 在测试机上根本没配 hosts。dataDir改成独立磁盘目录是为了两件事一是默认的data/在安装目录里重装升级时容易被顺手删掉二是生产环境上 Ledger 写入对磁盘 IO 敏感放系统盘会和日志抢 IO。standalone 模式下 ZooKeeper 的 snapshot、BookKeeper 的 journal 和 ledgers 都会落到这个目录下一次压测就能写掉几十个 G。启动成功时日志里会出现messaging service is ready随后自动创建public/default这个默认租户和命名空间。这一步不需要手动建但要知道它存在因为后面所有不带前缀的 topic 名都会落到这里。2.3 用 pulsar-client 走完 produce/consume 闭环包里自带的pulsar-client是个轻量命令行客户端够用来做冒烟验证。整个闭环分三步发消息、起订阅消费、看统计。# 1. 往默认命名空间发 5 条消息 bin/pulsar-client produce persistent://public/default/smoke-test \ --messages hello-pulsar -n 5 # 2. 起一个订阅从最早位置开始消费 5 条 bin/pulsar-client consume persistent://public/default/smoke-test \ -s smoke-sub -n 5 -p Earliest # 3. 看这个 topic 的元数据与吞吐 bin/pulsar-admin topics stats persistent://public/default/smoke-test第一条命令里-n 5是发送条数--messages后面跟的是消息体多条消息用逗号分隔。第二条命令的-s指定订阅名-p Earliest表示从最早的消息开始读——这个参数如果不加默认是Latest在消息已经发完之后再起消费者是读不到任何东西的新手最容易在这里以为消息丢了。-n 5是消费够 5 条就退出不写会一直挂着。第三条命令返回的 JSON 值得逐字段看一遍尤其是msgRateIn、msgThroughputIn、storageSize和subscriptions下的msgBacklog。msgBacklog是积压量消费端卡住时这个数字会一直涨比看日志直观得多。顺序上有个反直觉的点先发消息后起订阅在Earliest模式下没问题但如果订阅是Exclusive且已经存在第二次用同一个订阅名起消费者会直接报ConsumerBusyException。这不是 bug是 Pulsar 的订阅语义在起作用验证时换个订阅名就行。3. 从单机到集群ZooKeeper、BookKeeper、Broker 三层怎么分工standalone 跑顺之后绝大多数人的下一个动作是我要上集群。Pulsar 2.9.1 的集群拓扑是固定的三段式中间没有简化余地。理解这三层各自存什么比背启动命令重要得多因为集群模式下的报错信息几乎全部指向某层连不上另一层不知道分工就只能盲猜。3.1 2.9.1 为什么还离不开 ZooKeeperPulsar 在 2.9 这一代用了两套元数据存储本地元数据存储local metadata store和配置存储configuration store默认两者都落在 ZooKeeper 上。前者管集群内的动态状态——哪些 broker 活着、topic 归属哪个 broker、订阅游标在哪后者管集群级别的配置——租户、命名空间、分区数、保留策略这些。BookKeeper 自己也依赖 ZooKeeper 来存 Ledger 的元数据也就是某个 Ledger 被哪些 bookie 持有这份映射。所以 2.9.1 的集群里ZooKeeper 是被两套系统共用的这一点在部署时经常被误判成Pulsar 的 ZK 和 BookKeeper 的 ZK 是两拨结果配了两套集群白多花资源。真正让 ZK 变成架构约束的是它的写路径所有元数据变更都要走 ZK 的 ZAB 协议节点数越多写越慢。这也是后续版本把 ZK 换成可插拔元数据接口、最终在 3.0 移除 ZK 的直接原因。用 2.9.1 就得接受这个前提ZK 三节点起步且必须放在低延迟网络上跨机房部署 ZK 是自找麻烦。云原生场景下更常见的做法是把 ZK 放同一可用区Broker 层再横向扩把弹性压在无状态的那一层。3.2 三节点集群的配置清单与启动顺序下面这套是三节点最小可用拓扑三台机器各跑一个 ZK、一个 bookie、一个 broker。角色混布是为了省机器代价是单机故障影响面大PoC 阶段够用生产环境建议拆开。先改 ZK 配置三台机器的conf/zookeeper.conf核心只有一段# conf/zookeeper.conf三台机器内容一致 dataDir/data/pulsar/zookeeper clientPort2181 server.1zk1:2888:3888 server.2zk2:2888:3888 server.3zk3:2888:3888server.N里的 N 必须和该机器dataDir/myid文件里的数字一致2888 是 ZK 节点间数据同步端口3888 是选举端口。这两个端口写错不会报错只会表现为集群一直选不出 leader日志反复刷LOOKING。BookKeeper 侧改conf/bookkeeper.conf# conf/bookkeeper.conf zkServerszk1:2181,zk2:2181,zk3:2181 advertisedAddressbookie1 bookiePort3181 journalDirectory/data/pulsar/journal ledgerDirectories/data/pulsar/ledgersjournalDirectory和ledgerDirectories分开放是 BookKeeper 的硬性建议journal 是顺序写、延迟敏感ledgers 是随机读、容量敏感混在一块盘上互相拖累。advertisedAddress和前面 standalone 的道理一样写 hostname 就要保证集群内所有机器都能解析。Broker 侧改conf/broker.conf# conf/broker.conf zookeeperServerszk1:2181,zk2:2181,zk3:2181 configurationStoreServerszk1:2181,zk2:2181,zk3:2181 clusterNamepulsar-cluster advertisedAddressbroker1 bookkeeperMetadataServiceUrizkhierarchical://zk1:2181;zk2:2181;zk3:2181/ledgers webServicePort8080 brokerServicePort6650bookkeeperMetadataServiceUri这一行是 2.9 版本里最容易写错的地方。注意它用的是分号分隔、前缀是zkhierarchical://和上面两行的逗号分隔格式完全不同。写成逗号会导致 broker 启动时报 URI 解析失败而这个报错在日志里埋得很深。启动顺序是固定的ZK 全部起来并确认有 leader → 初始化集群元数据 → bookie 起来 → broker 起来。初始化那条命令只需在整个集群生命周期里执行一次# 只在第一台 broker 上执行一次注册集群元数据 bin/pulsar initialize-cluster-metadata \ --cluster pulsar-cluster \ --zookeeper zk1:2181 \ --configuration-store zk1:2181 \ --web-service-url http://broker1:8080 \ --broker-service-url pulsar://broker1:6650 # 三台机器上分别按顺序起进程 bin/pulsar-daemon start zookeeper bin/pulsar-daemon start bookkeeper bin/pulsar-daemon start broker--web-service-url和--broker-service-url填的是客户端将来访问的入口可以指向 VIP 或负载均衡地址不一定非要是某台 broker 的真实 IP。这两个值写进去之后就存在 ZK 里了后面用bin/pulsar-admin clusters update改改错了客户端会连到不存在的地址。验证集群是否成形用三条命令逐层看bin/pulsar-admin clusters list bin/pulsar-admin brokers list pulsar-cluster bin/pulsar-admin broker-stats brokers第一条返回集群名第二条返回 broker 列表第三条能看到每个 broker 的 topic 数和吞吐。如果第二条返回空说明 broker 没注册上问题一定在zookeeperServers或clusterName上如果 broker 在列表里但客户端连不上问题在advertisedAddress。3.3 ensembleSize / writeQuorum / ackQuorum 怎么定这三个参数决定了消息写入的持久化强度和延迟是集群调优里最先要定的东西。它们的值写在broker.conf里作用于新创建的 Ledger。参数含义3 bookie 推荐值5 bookie 推荐值影响managedLedgerDefaultEnsembleSize一条 Ledger 横跨多少个 bookie35决定数据分布宽度越大越难写满managedLedgerDefaultWriteQuorum每条消息写几份副本33决定冗余度等于 ensemble 时最安全managedLedgerDefaultAckQuorum收到几份确认才算写成功22决定写延迟越小越快但越不安全配置片段# conf/broker.conf追加在文件末尾 managedLedgerDefaultEnsembleSize3 managedLedgerDefaultWriteQuorum3 managedLedgerDefaultAckQuorum2约束关系是ackQuorum writeQuorum ensembleSize。三副本写三确认二意味着任意一台 bookie 挂掉不影响写入两台同时挂才会拒绝写。这个组合在延迟和可靠性之间比较平衡是多数 PoC 的起点。把ackQuorum降到 1 能让写延迟明显下降代价是单副本确认就算成功bookie 进程崩溃时可能丢最近的一批消息。反过来把三者都设成 5写延迟会翻倍因为每次写要等五个 bookie 都落盘。我一般会在压测里把这两组值各跑一遍拿 P99 延迟数据去说服人比讲理论有用。需要注意的是这三个参数只影响之后新建的 Ledger。已经存在的 topic 要改得用bin/pulsar-admin topics set-persistence单独设置改配置文件对存量 topic 无效——这条边界踩过一次就记住了。4. 多租户与 Topic 治理tenant / namespace / topic 三级结构实操Pulsar 和多数消息队列最大的结构差异就在这三级命名空间上。Kafka 只有 topic 一层多业务共用集群时靠命名约定区分Pulsar 把租户和命名空间做成了一等公民配额、权限、保留策略全部挂在这一层。用得好一个集群能安全地服务多个业务线用不好就是给每个业务建一个public/default下的 topic白白浪费了这套设计。4.1 三级命名空间与命名规范完整 topic 名的格式是persistent://tenant/namespace/topic2.9 里还有non-persistent://前缀用于不需要落盘的消息。租户tenant是隔离的最高层命名空间namespace是策略的作用域topic 是实际的消息通道。落地时我习惯这么映射租户对应业务线或团队命名空间对应环境prod、stagingtopic 名用「实体.事件」的格式比如order.created、payment.refunded。这样从 topic 名就能反推出归属和用途排查时不用翻文档。# 建租户--admin-roles 指定谁能管理这个租户 bin/pulsar-admin tenants create app-a --admin-roles ops-team # 建命名空间生产环境一个、预发一个 bin/pulsar-admin namespaces create app-a/prod bin/pulsar-admin namespaces create app-a/staging # 看命名空间下有哪些 topic bin/pulsar-admin topics list app-a/prod--admin-roles需要配合认证插件才有实际效果。2.9.1 默认是不开认证的生产环境要么在前面挡一层网关要么开authenticationEnabledtrue配 JWT 或 mTLS这部分配置量不小PoC 阶段可以先跳过。4.2 用 pulsar-admin 设定保留与配额策略命名空间这一层的价值全在策略上。默认策略对生产环境基本不适用消息既没有保留上限也没有积压上限一个卡住的消费者能把磁盘吃满。# 保留策略最多留 10G 或 7 天先到先删 bin/pulsar-admin namespaces set-retention app-a/prod \ --size 10G --time 7d # 积压配额积压超过 20G 时拒绝生产者写入 bin/pulsar-admin namespaces set-backlog-quota app-a/prod \ --limit 20G --policy producer_request_hold # 建一个 6 分区的 topic bin/pulsar-admin topics create-partitioned-topic \ persistent://app-a/prod/order.created --partitions 6 # 查看生效的策略 bin/pulsar-admin namespaces get-retention app-a/prod bin/pulsar-admin namespaces get-backlog-quota app-a/prod--size和--time是「或」的关系任一条件满足就删。只设时间不设大小遇上突发流量磁盘会先满只设大小不设时间低频 topic 的旧数据会一直占着。--policy有三个取值producer_request_hold是拒绝新消息producer_exception是直接抛异常给客户端consumer_backlog_eviction是丢弃最老的消息。生产环境选前两个让上游感知到背压consumer_backlog_eviction会静默丢数据除非业务能容忍否则别用。分区数一旦定下来2.9.1 只能增不能减扩容用bin/pulsar-admin topics update-partitioned-topic。分区数往上加之后Key_Shared 订阅的 key 分布会重新洗牌如果有顺序依赖要提前规划好。4.3 订阅模式选型四种语义的适用边界订阅模式选错了表现往往不是报错而是消息乱序或者重复消费排查起来很费劲。四种模式的差别在于「同一个订阅下允许多少消费者、消息怎么分发」。模式同订阅消费者数消息分发顺序保证典型场景Exclusive1全部给唯一消费者全局有序单消费者串行处理、状态机Failover多主备只发给主消费者全局有序需要高可用又要求顺序Shared多轮询分发不保证无状态并行消费、吞吐优先Key_Shared多按 key 哈希分发同 key 有序按业务主键保序的并行消费Key_Shared 是这四种里最有实用价值也最容易配错的。它保证同一个 key 的消息落到同一个消费者从而在并行消费的同时保住业务主键级别的顺序。但它要求生产端显式设置 key不设 key 的消息会被随机分发另外 2.9 版本的 Key_Shared 在消费者扩缩容时会有短暂的 key 重分配那段时间内同一个 key 可能被两个消费者各处理一次幂等逻辑不能省。用命令行验证订阅模式时直接指定类型即可# Shared 模式起两个消费者观察消息被分摊 bin/pulsar-client consume persistent://app-a/prod/order.created \ -s shared-sub -t Shared -n 20 -p Earliest-t后面跟订阅类型-s是订阅名。同一个订阅名起两个进程Shared 模式下两边会各拿到一部分换成 Exclusive第二个进程直接报错退出。这个对比实验五分钟就能做完比看文档印象深。5. 避坑与排查2.9.1 二进制包最容易翻车的五个地方这一章是纯经验账。下面五条都是我在不同环境里真实撞过的按「现象 → 原因 → 解决」写能省掉的排查时间按小时算。5.1 启动阶段卡住的三条现象一bin/pulsar standalone启动后进程存活但日志反复刷KeeperException: ConnectionLossWeb 端口 8080 打不开。原因advertisedAddress填了主机名而本机 hosts 里没有对应记录ZooKeeper 客户端解析失败后又不断重试。standalone 模式下 ZK 和 broker 在同一进程里这个自连接失败会拖住整个启动流程。解决/etc/hosts里补一条127.0.0.1 your-hostname或者把advertisedAddress直接改成 IP。集群模式下则要保证所有节点互相能解析。现象二启动脚本执行后秒退日志里有UnsupportedClassVersionError或InaccessibleObjectException。原因机器上默认 JDK 是 17 或更高2.9.1 的依赖链里有反射调用老 API 的代码模块系统一拦就崩。解决装一个 JDK 11在conf/pulsar_env.sh里显式指定JAVA_HOME不要依赖 PATH。顺手在脚本里加一行打印java -version的检查以后再换机器不容易翻车。现象三bookie 启动报No writable ledger dir进程直接退出。原因ledgerDirectories指向的目录不存在或者存在但属主不是启动用户。还有一个变体是磁盘写满BookKeeper 检测到剩余空间低于阈值会拒绝启动。解决mkdir -p建目录并chown给启动用户df -h确认剩余空间。BookKeeper 默认要求磁盘至少留 5% 余量调这个阈值用diskSpaceThreshold。5.2 运行阶段翻车的两条现象四生产者能连上并发送成功消费者一直收不到消息pulsar-admin topics stats里msgBacklog持续增长。原因两个可能。一是订阅位置用了Latest消费者起来时消息已经发完二是客户端从 broker 拿到的是advertisedAddress而那个地址在客户端侧不可达于是消费者反复重连、永远建不起消费流。解决先确认订阅位置用-p Earliest重试再在客户端机器上telnet advertisedAddress 6650验证连通性。broker 侧的advertisedAddress配错是最常见的一类改完要重启 broker 才生效。现象五跑压测一段时间后 broker 日志刷Too many open files新连接建不上已有消费延迟飙升。原因ulimit -n是会话级的bin/pulsar-daemon启动的进程继承的是当前 shell 的限制。机器上配了 65536但启动脚本在另一个 shell 里跑的实际生效的可能是 1024。解决在conf/pulsar_env.sh里显式ulimit -n 65536重启进程用cat /proc/pid/limits确认生效。这条坑的隐蔽之处在于它要跑一段时间才暴露压测十分钟看不出来。6. 把 2.9.1 用出长期价值分层存储、压测口径与版本边界6.1 分层存储让老消息离开 SSD2.9.1 自带 offloaders 目录里面已经躺好了 aws-s3、gcs、azure、filesystem 几种驱动的 nar 包不用自己编。配置写在conf/broker.conf# conf/broker.conf以 S3 兼容存储为例 managedLedgerOffloadDriveraws-s3 managedLedgerOffloadMaxThreads2 s3ManagedLedgerOffloadBucketpulsar-offload s3ManagedLedgerOffloadRegionus-east-1 s3ManagedLedgerOffloadServiceEndpointhttp://storage.internal:9000 offloadersDirectory./offloaders配置要重启 broker 才生效然后用阈值触发bin/pulsar-admin namespaces set-offload-threshold --size 5G app-a/prod--size 5G的意思是某个 topic 的 Ledger 累计超过 5G 就自动把最老的段搬到对象存储broker 本地只留索引。搬运是异步的对在线读写影响很小。要注意s3ManagedLedgerOffloadServiceEndpoint这个参数用自建的对象存储时必须填而且不要带桶名路径。6.2 压测口径别只看 QPSpulsar-perf是包里自带的压测工具用法和 pulsar-client 类似但参数更细bin/pulsar-perf produce persistent://app-a/prod/order.created \ -r 5000 -s 512 -t 8 -n 500000 bin/pulsar-perf consume persistent://app-a/prod/order.created \ -s perf-sub -r 5000 -t 8-r是目标速率条/秒-s是消息体字节数-t是线程数-n是总条数。压测结果里我会固定看四个数发布 P99 延迟、消费 P99 延迟、msgBacklog是否归零、以及 broker 的 CPU 和网卡是否打满。单看 QPS 数字很容易被误导——限速 5000 跑出来正好 5000说明瓶颈根本不在服务端。6.3 停留在 2.9.1 的边界2.9.1 是 2021 年前后的版本线之后社区继续往 2.10、2.11、3.x 走元数据层从 ZK 解耦、最终在 3.0 移除 ZK 是明确方向。这意味着 2.9.1 适合做 PoC、做选型验证、跑内部压测但不适合当作长期演进的基线如果业务已经确定要上升级路径要提前画出来尤其是自定义的认证插件和 Functions 代码跨大版本迁移时改动量不小。从那以后我每次起 Pulsar 环境都强制走一遍「java -version→ulimit -n→advertisedAddress解析验证 →pulsar-admin brokers list」这四步不管多急都跑完再往下做这四步能挡掉我遇到的八成翻车场景。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。