
做消息中间件性能评估这件事最麻烦的从来不是写压测脚本本身而是怎么保证“测出来的数字是可信的”。我之前用openmessaging-benchmark做过一轮Kafka压测从环境搭建到报告产出用了不到半天但中间踩了不少坑尤其是配置细节和结果口径这两块网上资料比较零散。这篇就把完整流程、参数逻辑和排查经验一起梳理出来给准备做Kafka容量评估、版本对比或者选型验证的同学参考。openmessaging-benchmark原本是面向多消息系统的通用压测框架核心思路是“同一套负载模型、同一套统计口径”所以它不只测Kafka也支持Pulsar、RabbitMQ等。Kafka作为被测对象只是其中一种driver你不需要为Kafka单独写压测脚本只需要提供driver配置、workload配置和运行环境。适合的场景有三类一是消息中间件选型时对比不同系统的吞吐和延迟二是Kafka版本升级前后的回归测试三是确认当前集群水位、判断是否能支撑业务峰值。1. 工具选型与整体设计思路1.1 为什么不用Kafka自带的压测脚本Kafka生态里其实已经有两个很常用的工具kafka-producer-perf-test和kafka-consumer-perf-test。它们做单机吞吐验证非常方便命令一行就能跑起来很多运维同学排查问题时也会先用它们确认broker基本状态。但它们有几个天然短板没有统一的负载模型。生产者和消费者是分开测的没法在一个场景里同时观察端到端延迟也就不好评估“生产端抖动对消费端的影响”。没有标准的统计输出。自带工具输出的是吞吐和延迟分位数但格式比较“原生态”不方便直接和另一套系统做同口径对比。没有分布式压测能力。脚本跑在单机上受客户端机器网卡、CPU、连接数的限制很难把大集群压到真实瓶颈。不同消息系统之间无法公平对比。比如Kafka自带工具和Pulsar自带工具的指标口径不一致对比意义不大。openmessaging-benchmark恰好把这些问题都覆盖了。它把压测拆成“driver定义”“workload定义”“worker执行”三层driver管客户端连接逻辑workload管消息量、生产者/消费者数量、消息大小这些负载参数worker负责真正跑压力。想换一个被测系统只需要换掉driver文件workload和统计逻辑都不用动。1.2 它解决了哪些关键问题先说公平性。openmessaging-benchmark对每个driver都定义了相同的指标采集方式比如生产延迟、消费延迟、端到端延迟、backlog统计口径完全一致。这意味着你用同一份workload去压Kafka和另一个消息系统出来的P99、P999可以直接放一起比不存在“一个用一个口径、另一个用另一个口径”的问题。再说可控性。它支持预热warmup和正式测试duration两段式设计预热阶段会把连接、线程池、broker缓存都“热”起来然后才进入正式统计。这个设计很重要因为冷启动时JVM刚起来、连接还没建立测出来的延迟会明显偏高。还有可观测性。它会把message size、producer/consumer数量、分区数、订阅模式这些参数全部显式写在workload里测试结果能明确归因到某组参数上。做回归测试时把workload文件存下来以后随时可以复现。1.3 部署架构和执行模式openmessaging-benchmark支持两种运行模式。第一种是本地模式所有组件在一个进程里跑适合快速验证driver配置、workload语法、结果输出格式。第二种是分布式模式worker可以部署在多台机器上通过docker swarm或者直接暴露端口的方式组成压测集群适合做正式测试。单机压测和分布式压测的结果差异非常大。单机模式下生产者、消费者和driver都在同一台机器上本机内存拷贝占了一部分延迟网络抖动几乎为零分布式模式下worker和broker之间走真实网络延迟数据更接近生产环境。所以我建议正式测试至少用两台机器一台跑broker一台跑worker。2. 环境准备与部署细节2.1 先准备一套可复用的Kafka环境被压的Kafka集群不需要搞复杂的多机房部署但有几个基本要求broker的JVM堆内存要提前调好建议根据分区数和消息量预留磁盘IO要确保不被其他任务占用最好用独立的SSD或者云硬盘还要关闭page cache回收的干扰项比如把系统zone_reclaim_mode设为0避免内存回收影响broker的读写性能。版本上有一个比较实用的经验被测Kafka版本和driver里的客户端版本要尽量接近最好一致。Kafka的客户端和broker是兼容的但跨大版本时可能出现连接参数不识别、schema版本校验失败这类问题压测时多花时间排这个不值得。Kafka本身是用KRaft模式还是ZooKeeper模式openmessaging-benchmark并不关心它只通过bootstrapServers连接。所以我个人建议被测环境直接用你生产环境对应的部署方式这样测试结果才更有参考意义。2.2 编译openmessaging-benchmark整个工具是Java项目用Maven管理。第一次编译会拉不少依赖如果网络环境一般提前准备好Maven镜像源会快很多。命令比较简单git clone 官方仓库地址 benchmark cd benchmark mvn -DskipTests install编译完以后目录结构里最核心的是driver-kafka子模块、workloads目录下的workload文件、bin/benchmark脚本以及worker目录下的Dockerfile。如果编译过程中报依赖下载失败先检查Maven仓库配置再确认JDK版本。我遇到过项目要求JDK8但环境默认JDK11导致编译异常的情况换版本后就很顺利。编译成功后可以先跑一次本地模式做冒烟验证。本地模式不需要docker swarm直接执行bin/benchmark并指向本地driver和workload就行。它会在本机起一个内嵌的worker进程Kafka broker跑在另一台机器上通过driver里的bootstrapServers连接。这个阶段主要验证驱动能否正常连上broker、能否创建topic、消息是否能正常收发。2.3 分布式worker部署正式测试建议用分布式模式。这里我用的方案是docker swarm因为openmessaging-benchmark对swarm的支持最成熟命令最少。# 初始化swarm docker swarm init --advertise-addr 本机IP # 构建worker镜像 cd worker docker build -t benchmark-worker . # 标记并推送镜像到worker节点单机伪集群可以跳过推送那一步直接用本地镜像构建worker镜像时会进入一个比较长的下载依赖阶段尤其是driver相关的jar包。如果网络不稳构建容易失败建议在docker build之前先把Maven依赖拉全或者设置Docker构建时的代理环境变量。worker节点启动后bin/benchmark会把测试任务下发到这些worker上每个worker独立负责一部分生产者和消费者客户端。这里有个经验worker的内核数和内存直接决定了单worker能承载的客户端数量。默认配置下一个worker起几十个客户端线程没什么压力但如果每个workload里producerCount和consumerCount超过几十最好按“4核8GB每worker”的标准估算节点数量。3. 核心配置解析与参数设计3.1 driver文件Kafka驱动怎么配driver文件是压测的第一个配置文件它告诉框架如何连接Kafka以及如何使用客户端。下面是一份常用的Kafka driver配置type: kafka kafka: bootstrapServers: 192.168.1.10:9092,192.168.1.11:9092 commonConfig: max.partition.fetch.bytes: 1048576 producerConfig: acks: all linger.ms: 5 batch.size: 16384 compression.type: lz4 consumerConfig: enable.auto.commit: false auto.offset.reset: earliest serdeFormat: BINARYbootstrapServers没什么好说的填broker地址列表。commonConfig里的参数会同时应用到生产和消费者两端producerConfig和consumerConfig分别覆盖各自客户端的配置。重点关注acks和compression.type。acksall是最严格的可靠性配置延迟会明显比acks1高但也更接近生产环境的默认选择。compression.type选lz4或zstd能显著降带宽占用但对客户端CPU有额外开销。压测时应该根据目标场景选不要盲目追求“好看的数字”。serdeFormat指定消息体序列化方式Kafka driver通常用BINARY即不经过任何额外的序列化转码直接按字节写。如果选JSON或AVRO会引入额外的schema处理和对象创建开销压测的是“完整链路”而不是纯broker性能两者目的不同。3.2 workload文件负载模型怎么定义workload是压测的核心常用的workload文件如下name: kafka-128-1kb topics: 8 partitionsPerTopic: 16 messageSize: 1024 producerCount: 4 consumerPerTopic: 4 subscriptions: - test-subscription producersPerSubscription: 4 consumerCount: 4 consumerConfig: ... producerConfig: ...字段含义很直观。topics决定创建多少个topicpartitionsPerTopic决定每个topic的分区数messageSize是单条消息大小producerCount是总生产者数量consumerPerTopic是每个订阅的消费者数量。这里有几个容易被忽略的细节subscriptions是个列表每项代表一个订阅组。消息会被所有订阅组各自消费一遍。如果要测“一读一写”只配一个订阅如果要测“fanout”场景可以配多个订阅组。consumerPerTopic和consumerCount要配合着理解。实际消费者总数 订阅组数量 × consumerPerTopic加consumerCount是为了兼容历史配置新配置里优先用consumerPerTopic。producersPerSubscription这个字段在某些版本里控制每个订阅的生产者数配置时最好对照一下版本文档不同版本对这个字段的解析有细微差异。我一般会把workload拆成几个固定模板1KB消息、4KB消息、64KB大消息单分区、多分区低并发高延迟关注型、高并发吞吐关注型。这样后续测试只需要改几个数字不用从头写配置。3.3 参数设计背后的逻辑很多人上来就问“topics配多少、partitions配多少”这其实没有标准答案要看被测Kafka集群的规模和压测目标。单broker验证场景topic数随意分区数控制在8~16即可。分区太少会限制并行度分区太多会让broker端副本同步和元数据开销变大反而掩盖真实性能。多broker容量评估场景分区总数建议至少是broker数的倍数这样分区才能均匀分布。比如3台broker分区总数建议取6、9、12这类3的倍数而不是7。关注端到端延迟的场景分区数不宜过多producer并发适度即可重点制造稳定小消息流。关注吞吐峰值的场景分区数要给足producer和consumer并发拉高让broker先到瓶颈而不是客户端先到瓶颈。另外messageSize的选择要与业务实际匹配。如果是日志类写入1KB左右比较典型如果是交易类事件可能只有几百字节如果是图片处理链路可能几十KB甚至上百KB。压测时一定要用业务实际消息大小不要为了方便都用1KB。4. 测试执行流程与实施实录4.1 benchmark命令参数解析执行压测是bin/benchmark脚本一个典型的命令长这样bin/benchmark \ --drivers driver-kafka.yaml \ workloads/kafka-128-1kb.yaml \ --workers 192.168.1.20:8080,192.168.1.21:8080 \ --warmup 60 \ --duration 300 \ --reporting-interval 10 \ -o results--drivers指定driver文件workloads/kafka-128-1kb.yaml是被测workload--workers是worker节点的地址列表--warmup是预热秒数--duration是正式测试秒数--reporting-interval是指标上报间隔-o是结果输出目录。--warmup和--duration是很关键的参数。预热至少给60秒让JIT编译完成、连接池铺满、broker缓存预热否则正式阶段的延迟数据会混入冷启动噪声。正式测试一般建议300秒以上太短了数据不稳定尤其要看P999这种长尾延迟时至少得跑5分钟以上才能看到规律。还有一个参数--reporting-interval容易被忽视。它控制worker向控制端汇报指标的时间区间默认值可能偏大如果间隔太长吞吐曲线会显得很平间隔太短监控数据量大、结果文件多。10秒比较适中既能看清波动又不至于刷屏。4.2 测试过程中的监控配合压测不是把命令跑起来就完事我一般会同时开三个监控窗口一个看worker机器的CPU和内存一个看broker机器的CPU和磁盘IO一个看top里客户端进程的状态。有一个很典型的经验如果worker机器CPU先到100%说明压测瓶颈在客户端而不是broker结果跑出来的是“客户端极限”而不是“Kafka极限”。这时候要么降低单worker的并发数要么增加worker节点。反过来如果broker的CPU和磁盘IO接近瓶颈而上层指标还没恶化说明配置离真实容量边界还有余量。磁盘IO这个指标尤其要盯紧。Kafka的写入是顺序追加正常情况下磁盘利用率不高但如果broker的日志目录和操作系统分区共用磁盘压测时其他日志写入会干扰结果。最好单独挂载一份磁盘给Kafka日志目录IO调度器设为deadline或none压测时观察iowait不超过10%比较理想。4.3 输出数据目录与文件结构压测结束后结果会写到-o指定的目录下面。默认结构是按workload名和启动时间分目录里面有几个核心文件pub.csv生产者吞吐按时间戳记录每秒发布消息数和字节数。cons.csv消费者吞吐同样按时间戳记录。pub_latency.csv生产延迟的分位数数据通常会记录p50、p99、p999、max。e2e_latency.csv端到端延迟也就是从消息被生产到被消费之间的时间差。backlog.csv未消费消息积压量这是判断系统是否稳定的重要指标。打开pub_latency.csv会发现每一行都有大量列命名类似p50、p75、p99、p999。不要只盯着p50压测Kafka时真正值得关注的是p99和p999。很多系统“平均延迟很低但偶尔抖动”p999就是用来抓这个的。5. 结果解读与容量判断5.1 先看吞吐曲线找拐点拿到上面这些csv之后我习惯先把吞吐数据画成趋势图横轴是时间纵轴是每条消息的发布速率。判断标准很简单如果整条曲线在某个时间点之后突然下降且后面回不来说明系统进入了过载状态。这个时间点对应的并发或消息速率就是当前配置下的容量上限。举个例子某次压测里producerCount从4调到8后前60秒吞吐稳步上升但从第90秒开始broker端的CPU持续满载吞吐反而掉到之前80%的水平延迟全面恶化。这就说明8个生产者已经超过集群承受能力4个生产者还处于安全水位。吞吐曲线正常的情况下再看生产速率和消费速率是否一致。如果消费速率长期低于生产速率backlog.csv里的积压量会持续上涨。小幅度积压可以通过消费者扩容消化持续上涨则意味着整个链路某一环已经是瓶颈需要定位。5.2 用延迟分位数做质量评估吞吐只能说明系统“能跑多快”延迟才能说明“质量如何”。这里有个容易踩的坑openmessaging-benchmark统计的端到端延迟和Kafka自带的producer延迟口径不一样。pub_latency统计的是生产者客户端发送消息到收到broker确认ack之间的时间。e2e_latency统计的是消息生产到消费者消费之间的时间包含了网络传输、broker存储、消费者拉取、反序列化等完整链路。做容量评估时以e2e_latency为准做生产端调优时pub_latency更能反映客户端配置和broker写入路径的影响。两者要结合着看不能只看一个就下结论。延迟从p50到p999的增长能反映系统在极端情况下的表现。如果p50很低但p999非常高说明大部分消息很快但偶尔出现大抖动这种场景往往和垃圾回收、PageCache换页、网络突发拥塞有关需要压测结束后再看broker日志。5.3 判断是否需要调优的几个信号压测完成后建议按下面几个信号判断Kafka集群的健康度信号一acks1和acksall两种配置下生产延迟差距超过预期。这说明副本同步路径存在瓶颈可能是磁盘同步写慢或者网络带宽不足。信号二p999延迟有明显周期性波动。每隔一段时间出现一次高延迟大概率是JVM GC或操作系统内存回收导致的。信号三消费端吞吐低于生产端持续超过30秒。需要检查consumer group的rebalance频率、消费者数量、分区分配是否均衡。信号四吞吐增长到某个点后突然曲线“断崖式下跌”。这往往是broker线程池耗尽或者网络连接数到顶的信号不是正常过载是配置不合理。6. 常见问题与排查实录6.1 driver版本与Kafka版本不匹配我遇到过压测一开始报UnsupportedVersionException的情况。主要原因是driver引入的Kafka客户端版本低于broker版本broker端启用了新版本才有的协议参数。解决方式很简单修改driver-kafka模块的pom.xml把Kafka客户端版本改成和broker一致然后重新编译整个项目。不要在运行时强行改driver配置里的参数来绕过版本检查那样测出来的结果没有参考意义。6.2 压测数据抖动明显延迟忽高忽低这是最让人头疼的情况。排查顺序建议按从外围到核心先看worker机器的CPU是否被其他任务抢占再看broker机器内存是否触发swap最后看网络是否存在拥塞。有一次压测数据总是周期性升高查遍了broker和worker的指标都没发现异常最后发现是某台worker机器上另一个进程在做定时任务把CPU抢走了。压测机器尽量保持干净不要和业务服务混部。Kafka broker所在机器的page cache回收也可能造成延迟抖动。可以通过/proc/sys/vm/swappiness先确认系统没有频繁swap再看dmesg里有没有内存回收相关的日志。如果日志目录所在磁盘是机械盘还要考虑io调度器问题建议压测环境全部用SSD并用deadline调度器。6.3 worker连接数达到上限压测无法继续分布式模式下worker默认支持的连接数不是无限的。当workload里的producerCount和consumerCount拉得过高时会出现连接失败或者客户端线程创建失败。排查时先用ss -s看单机TCP连接总数再用ulimit -n确认当前进程的连接数上限。我一般会把worker的ulimit -n调到65535以上同时把系统级的fs.file-max同步提高。还有一个方式是把一些客户端分散到更多worker节点上不要挤在一台。6.4 结果文件里出现大量空值或NaN这个通常是测试时间太短某个指标在某个上报区间内没有产生数据。比如生产速率为0的区间延迟自然为空。解决方案是把--reporting-interval适当调大或者延长整体测试时长。不要在用脚本读csv时直接忽略空行最好先把空值明确标记出来否则统计出来的均值会偏低。6.5 docker swarm部署时worker连不上控制端常见原因有几种swarm防火墙没放行端口、worker地址配错、镜像内的driver依赖没构建完整。先做两步验证第一步在控制端机器上curl一下worker的8080端口看是否有响应第二步在worker容器里手动执行bin/benchmark的worker入口确认能正常启动。如果容器内driver依赖不完整直接本地构建时会报ClassNotFound。这一步没别的好办法只能重新构建镜像并确认Maven依赖全部下载成功。一些实际使用心得整体用下来openmessaging-benchmark最大的价值在于“标准”。一套workload定义清楚之后不管测Kafka还是测别的系统不管这个月测还是下个月测结果都有可比性。对于团队里长期维护消息中间件的人来说把workload文件当成资产存起来比每次临时写脚本靠谱得多。我目前固定维护了三组workload一组模拟日志写入小消息顺序写一组模拟交易事件小消息高可靠一组模拟批量同步大消息高吞吐。每次Kafka升级或者调整broker参数后都会挑一组workload复跑一遍前后对比p999延迟和生产吞吐。这个习惯已经替我发现过两次潜在线程池配置问题。如果刚开始接触这个工具建议先不要追求复杂的分布式部署用本地模式把workload跑通看清结果文件长什么样再逐步上多worker。等参数能控制住了再去处理docker swarm、网络、监控这些外围事情。工具本身不难难的是把指标口径和业务场景对应起来这一点只能靠多测多对比攒经验。