Kafka vs Pulsar 消息队列性能对比:百万级吞吐下的延迟、持久化与运维成本复盘
Kafka vs Pulsar 消息队列性能对比百万级吞吐下的延迟、持久化与运维成本复盘一、技术选型的岔路口Kafka 的统治地位和 Pulsar 的计算存储分离团队的消息基础设施承担着日均 120 亿条消息的吞吐量峰值 QPS 约 15 万。现有的 Kafka 集群12 个 Broker128 分区/Topic在运行两年后逐渐暴露出几个结构性问题Topic 扩容需要手动 Rebalance 分区且耗时长4 小时冷数据3 天前和热数据共享同一存储层冷数据占用大量 Broker 本地磁盘却很少被读取跨数据中心复制需要 MirrorMaker 额外组件。Apache Pulsar 的架构设计——计算存储分离——理论上有解决方案。BookKeeper 作为存储层可以独立扩缩容Broker 作为计算层可以在流量峰值时水平扩展而不移动数据。但架构优势不等于实际性能优势需要基于实际业务 Profile 做对等 Benchmark。二、吞吐量与延迟的 Benchmark 对比测试环境8 节点集群16C32G 2TB NVMe SSD10GB 网络。每个 Topic 64 分区消息体 1KB异步发送 ACK1。场景KafkaPulsarPulsar 优势单 Topic 吞吐1 Producer120 MB/s155 MB/s29%单 Topic 吞吐10 Producer580 MB/s720 MB/s24%10 Topic × 10 Producer1.2 GB/s1.45 GB/s21%P50 延迟稳定1.2ms0.8ms-33%P99 延迟稳定8.5ms5.2ms-39%P99 延迟分区 Rebalance 期间350ms12ms-97%最显著的差异出现在 Rebalance 期间的延迟稳定性上。Kafka 在分区迁移时会出现消费中断Consumer Group Rebalance 的 Stop-The-World 效应P99 从 8.5ms 飙升至 350ms。Pulsar 的 Broker 无状态设计使得 Rebalance 几乎不影响在途消息的延迟。三、存储层的成本对比Kafka 的本地存储模式在纯吞吐场景下效率高但在冷热数据混合的场景中产生了隐性成本# Kafka 数据保留策略所有分区统一按时间清理 # Topic 的日志段文件无法区分热数据和冷数据 log.retention.hours72 # 所有分区保留 72 小时 # 问题前 24 小时的数据热数据被高频读取 # 后 48 小时的数据冷数据几乎不读但占用了同级 SSD 空间Pulsar 的分层存储Tiered Storage自动将冷数据卸载到对象存储S3/MinIO存储指标Kafka全 SSDPulsarSSD S3节约SSD 使用量7 天数据40 TB12 TB-70%对象存储使用量028 TB—月存储成本¥42,000¥18,500-56%冷数据读取延迟8ms80ms10xPulsar 的优势在于冷数据读虽然慢10x但冷数据本身的读取频率极低0.1% 的消息回读多出的 72ms 延迟在可接受范围内。四、运维复杂度与故障恢复运维场景KafkaPulsar集群扩容新 Broker 加入后需手动 Rebalance耗时 4~8 小时Broker 无状态新节点加入后立即分担负载磁盘故障恢复需从副本 Copy 全部数据TB 级耗时数小时仅 Copy 故障磁盘上活跃的 LedgerGB 级耗时 10~20 分钟跨数据中心复制需要 MirrorMaker 2.0 组件独立维护原生 Geo-ReplicationBroker 内置监控指标数量约 150 个 MBean约 400 个 MetricsPulsar 的运维优势在计算存储分离架构中体现明显但它的监控指标数量是 Kafka 的 2.7 倍400 vs 150运维团队的初期学习成本更高。五、总结Kafka vs Pulsar 的选择不应基于口号而应基于团队的优先级如果弹性伸缩是最高优先级 → PulsarBroker 无状态的秒级扩容在流量波动剧烈的场景中价值显著如果冷数据存储成本是最高优先级 → Pulsar分层存储可以将存储成本降低 56%但冷数据查询延迟增加 10 倍如果运维团队对 Kafka 有深度积累 → 继续 KafkaPulsar 架构优势在 4 人以下的小团队中可能被学习成本所抵消如果 Rebalance 容忍度低 → PulsarKafka 的 Rebalance 是Stop-The-World式的Pulsar 的计算存储分离几乎消除了这个痛点。推荐路径新项目或计划大规模扩容时考虑 Pulsar已有 Kafka 深度积累的团队先优化 Kafka 配置增大replica.fetch.max.bytes、调优num.network.threads不到非换不可的地步不要贸然迁移。