
1. 从零搭建在线聊天平台的性能测试方案聊天平台这个项目我前前后后测了将近一个月。先说结论在线聊天平台和普通业务系统最大的区别在于连接模型——大多数系统是“请求-响应”聊天平台是“长连接消息推送”这也决定了测试思路必须推倒重来。普通的HTTP压测脚本根本不能直接套用必须从协议层、连接管理、消息路由三个维度重新设计。我这次负责的在线聊天平台核心功能包括单聊、群聊、消息撤回、离线消息拉取以及在线状态展示。技术栈是Spring Boot搭建的WebSocket服务端消息中间件用RabbitMQ做集群在线状态放在Redis数据库存历史消息。压测环境是三台8核16G的云服务器JMeter版本是5.6.3用到了它最新的WebSocket插件和HTML报告模板。在做测试方案之前我花了整整两天梳理业务链路。聊天消息从A用户发出到B用户收到中间要经过WebSocket连接建立 → 客户端发送消息帧 → 服务端鉴权 → 消息写入RabbitMQ → 消费者从队列拉取 → 通过连接会话推送至B用户 → B客户端回执确认。这里面任何一环出现瓶颈都会表现为消息延迟或者掉线。这里我踩了第一个坑一开始我只关注WebSocket的推送链路忽略了RabbitMQ的消费能力。结果压测时发现WebSocket连接数上去了但消息大量积压在队列中消费者处理不过来表现为“消息发出去但对方收不到”。后来我在JMeter里面加了RabbitMQ消费者性能的独立测试才把这个瓶颈暴露出来。我需要澄清的是测试报告的呈现方式也很关键。JMeter 5.6.3的HTML报告升级之后比老版本的CSV图表工具好用太多但前提是要弄清每个指标在聊天场景下的业务含义这一点后面我会专门讲。2. 测试方案设计聊天场景不同于普通Web系统2.1 为什么不能直接套用HTTP压测方案传统的HTTP压测核心指标是并发用户数和事务响应时间。一个典型登录接口1000个线程同时发起HTTP请求服务器返回200这个事情就完了。但聊天平台完全不是这个逻辑它的核心矛盾在于连接是长久的用户连上WebSocket后可能几小时都不断开服务器保持的连接数和实际活跃用户数是两码事。消息是双向的A发消息给BB的客户端会收到推送这个推送是由服务器主动发起的JMeter既要模拟发送方也要验证接收方是否真的收到。状态是实时的在线状态、未读消息数、群成员列表这些数据在消息到达时可能同步更新对Redis和数据库产生额外的读写压力。所以我的测试设计拆成了四条线连接建立与保持、消息上行吞吐、消息下行推送、离线消息拉取。四条线分开测再组合起来验证混合场景。2.2 混合场景的拟定方法我的做法是先做单场景基准测试拿到每个环节的最大能力值再设计混合场景。基准数据大致是这样的场景并发连接数消息大小预期TPS实际瓶颈点单聊发送2000256B800RabbitMQ消费能力群聊广播500群×50人512B1500广播扇出带宽离线拉取500100KB含历史120MySQL分页查询混合场景3500连接256B~1KB1000连接状态同步这里我必须补充说明一个做测试方案时容易忽略的点在线聊天平台的“并发用户数”和“并发连接数”不是同一个概念。用户可能连上了WebSocket但半天不发消息也可能在多个终端登录产生多路连接。所以定义测试模型的时候要明确“在线连接数”“活跃发送数”“广播扇出倍数”三个参数的关系。比方说3500个连接里实际同时发消息的可能只有800个但如果群里有一个人发消息50个成员都要收消息扇出就是1:50上行TPS 200下行推送TPS就是10000。这种一压就爆的模型在测试计划里必须先算清楚。2.3 场景建模需要业务数据支撑做场景建模不能拍脑袋。我找开发要了线上聊天业务的历史数据分析最后确认了一个比例单聊消息占65%群聊消息占30%系统通知占5%。群聊平均成员数45人用户平均在线时长40分钟峰值集中在上午10点和晚上9点。有了这些业务参数JMeter里的线程组设置、循环次数、思考时间等到时间就是think time就能落到实处而不是随便填数字。3. JMeter 5.6.3压测在线聊天平台的核心配置3.1 环境准备和依赖安装JMeter 5.6.3要求JDK 8以上我用的JDK 17。插件方面测试WebSocket必须要装WebSocket Sampler插件。有两种选择一种是JMeter插件管理器Plugins Manager里直接搜索WebSocket Sampler安装另一种是下载JMeter WebSocket Samplers项目的jar包放到lib/ext目录。我推荐第一种插件管理器还能顺带把依赖的包一起装好省得缺了某个类导致启动报错。验证插件是否安装成功的方法启动JMeter在“选项”菜单里看到“Plugins Manager”选项并且在“添加-取样器”菜单里能看到“WebSocket Sampler”相关的选项就说明装成功了。3.2 WebSocket连接建立与消息发送的配置JMeter脚本里首先要配置一个WebSocket连接这是所有消息测试的基础。我在测试计划里用一个“WebSocket Connection”取样器建立连接填入聊天平台的WebSocket地址比如ws://chat.example.com/ws?tokenxxx。这里有个细节聊天平台的鉴权通常放在连接阶段的URL参数或Header里JMeter里可以直接添加Header管理器把Authorization: Bearer xxx带上去也可以在URL后拼token参数。建立连接之后发送消息用“WebSocket Sampler”的Binary或Text格式。聊天消息一般是JSON格式的文本帧我在取样器里填入{ type: chat, msgId: uuid-xxx, from: user_1001, to: user_2002, content: hello, this is a test message, timestamp: 1710000000000 }这里必须提醒一句msgId一定要用JMeter的内置函数动态生成否则消息内容一样服务端可能做去重或幂等处理影响真实压测效果。我用的函数是${__UUID()}或${__time(yyyyMMddHHmmssSSS,)}拼在消息里。发送消息之后需要验证服务端确实把消息推给了接收方。这一步很多人会漏掉。我的做法是再加一个“WebSocket Single Read Sampler”专门读取服务端推送回来的消息帧然后用断言断言就是Assertion检查返回的内容里是否包含预期的msgId或接收方ID。这样才能确定消息是真的走通了链路而不是“发出去就算成功”。3.3 群聊广播的测试配置群聊场景里一个人发的消息要广播给群内所有成员。如果测试脚本里对每个群成员都单独建立一个WebSocket连接资源开销会非常大。我的做法是用CSV文件维护群成员账号列表每组数据里包含群ID和成员ID。用“线程组”模拟群内成员每线程代表一个成员线程数等于群人数。群主发言的消息通过单独的发送线程发出但目标群ID在CSV变量中引用。接收端用断言校验是否收到本群的消息类型并验证群ID匹配。这种方案模拟了真实业务流程但又不需要真正给每个用户配一台“手机”。实际上我在JMeter中让每个线程同时建立两个WebSocket会话一个会话用来接收本群的消息订阅群组频道另一个会话用来主动发送消息代表用户发言。这样就同时覆盖了上行和下行。3.4 心跳机制和断线重连的验证在线聊天平台通常都有心跳机制客户端每隔一段时间发送一个Ping帧服务端回Pong帧用来维持连接和检测死链。JMeter的WebSocket插件里提供了Ping/Pong帧的支持但有一点要特别注意JMeter默认的“连接超时”和“读取超时”设置以及单次取样器的执行时间都要大于心跳间隔。否则测试线程会认为连接已经断了主动关闭WebSocket影响长连接测试的准确性。我在测试计划里专门写了“长连接稳定性”的一组测试120个线程持续连线运行12小时每30秒发送一次心跳观察连接断开重连次数。JMeter里可以记录连接建立和断开的日志测试结束后通过日志统计掉线率。实际跑下来发现服务端有一个内存泄漏问题连接关闭后会话映射表里的记录没有及时清除导致内存缓慢增长第7小时开始出现大量连接超时。这个问题就是长连接稳定性测试的价值所在。4. 用JMeter 5.6.3生成测试报告命令与指标解读4.1 报告生成命令写法JMeter 5.6.3不需要再用老版本的Listener去保存CSV然后手动用Excel画图。直接命令行跑完之后用内置的jmeter -g命令把结果文件转成HTML报告很方便。我执行的命令是这样的jmeter -n -t chat_platform_test.jmx -l result.jtl -e -o output_report参数说明-n非GUI模式压测必须用命令行跑不能开着JMeter界面压窗口渲染会占用系统资源。-t指定测试计划文件。-l保存原始结果到JTL文件后面生成报告要用的。-e测试结束后立即生成报告。-o指定输出目录这个目录必须不存在或为空否则会报错。如果是已经跑完了只有JTL文件想重新生成一份HTML报告可以单独执行jmeter -g result.jtl -o output_report4.2 聊天场景下报告指标怎么看报告生成之后会有一个index.html打开就能看到整体情况。但这里我必须提醒JMeter默认的聚合报告指标在聊天场景里有些需要特殊解读。吞吐量Throughput表示每秒完成的请求数。在聊天场景里发送消息的上行请求和接收消息的下行推送是分开统计的。我关注的是“上行消息发送TPS”和“下行消息接收TPS”。如果一份报告里吞吐量很高但消息接收方验证失败率也很高那说明消息丢了吞吐量再高也没有意义。响应时间Response TimeJMeter报告中有平均值、中位数、90%和99%分位。聊天场景里最关键的指标是99%分位也就是最差的那1%用户感知到的延迟。我设定的目标是99%分位消息延迟不超过2000ms平均值不超过800ms。如果平均值看起来不错但99%分位突然飙到5000ms以上说明存在长尾延迟一般和GC停顿或线程池排队有关。错误率Error%这里我要专门讲一下WebSocket测试里一个容易误判的点——如果连接没建立成功JMeter会直接报错但如果消息发出去了服务器没有返回预期响应单靠默认的响应码判断不出来。所以我给WebSocket Single Read取样器加了JSR223断言里面用Groovy脚本检查返回的消息内容。活跃线程数Active Threads这个图表能看出来测试过程中真实在跑的线程数如果线程数掉下去了说明连接断了JMeter在重连或者线程退出。4.3 报告中的图表怎么用来定位瓶颈JMeter 5.6.3的HTML报告里我最常用的几个图是Over Time图响应时间和TPS随时间变化的趋势。我用来定位“是不是某个时间点开始性能劣化”如果是就回去看那个时间点对应什么操作。Throughput vs Threads随着并发线程增加吞吐量是线性增长还是到达峰值后回落。如果峰值后回落说明系统已经有瓶颈。Latency vs Request散点分布。如果图上出现一条斜向上的“尾巴”说明响应时间在请求增加时持续拉长典型的排队效应。这些图组合起来看能快速判断瓶颈在客户端、服务端还是中间件。有一次线上压测响应时间全部超时检查JMeter所在机器的CPU和网络后发现是压测机带宽打满了撑不住每秒几万个消息。这个血泪教训提醒我压测机的配置不能比服务器低太多否则报告在分析上毫无参考价值。5. 测试结果分析与性能调优记录5.1 第一轮测试结果和瓶颈定位第一轮测试结果出炉情况不太好看。基础指标如下指标目标值第一轮实测状态消息上行TPS1000623未达标端到端响应时间99分位≤2000ms3480ms未达标错误率≤1%2.3%未达标持续稳定连接数35002890未达标看到结果之后我第一反应是服务器配置问题但检查CPU和内存发现使用率不高。接着查了RabbitMQ监控面板发现消息队列的消费速率基本跑满队列堆积严重。换句话说WebSocket网关能接收消息但消息投递到RabbitMQ之后消费者消费不过来导致端到端延迟变大。这个问题的直接原因有两个一是消费者线程池配置太小核心线程数只有10二是一次性批量拉取消息的条数设得太小导致消费者频繁去Broker拉取吞吐量上不去。开发调整了配置之后消费能力从600 TPS提升到1100 TPS。5.2 混合场景下的长连接稳定性问题第二轮测试主要跑混合场景。结果暴露了两个新问题连接数达到3000以上时服务端握手开始超时打开TCP全连接队列的溢出统计发现ListenOverflows一直在涨。这是因为应用层的连接处理是同步阻塞模型大量并发握手时进程来不及调用accept系统内核的accept队列被占满。最终方案是调整操作系统层网络参数并把应用层连接处理改成异步非阻塞模式。群消息扇出时CPU飙高群聊广播会对每个在线成员遍历一遍连接表连接表是全局锁保护的HashMap并发一大就锁竞争激烈。开发把连接存储改成分段锁的ConcurrentHashMap并引入本地缓存和批量推送合并CPU的锁竞争问题才缓解下来。5.3 最终达标数据经过两轮调优之后最终测试数据长这样指标目标值最终实测状态消息上行TPS10001052达标端到端响应时间99分位≤2000ms1780ms达标错误率≤1%0.42%达标持续稳定连接数35003700达标群聊广播扇出TPS1500群消息/秒1680达标这个结果证明了问题基本聚焦在消费能力和锁竞争上。聊天平台的性能测试重点不是单机压出多高的TPS而是看系统性瓶颈在哪里以及调优之后整个链路能不能稳定扛住业务峰值。6. 测试执行过程中的踩坑记录与排查技巧6.1 JMeter本身制造的压力瓶颈刚开始压测时我用的JMeter单机跑5000个线程连接WebSocket结果JMeter这一侧先崩了内存溢出连接建立超时。排查后发现JMeter每建一个WebSocket连接都会在内存里维护会话状态5000个会话对Heap压力非常大默认的1GB堆内存完全不够。调整方案是在jmeter启动脚本里修改JVM_ARGS堆内存调到4GB并且启用G1垃圾回收器export JVM_ARGS-Xms4g -Xmx4g -XX:UseG1GC同时每台压测机只跑2000个连接三台压测机组成分布式压测集群用JMeter的Controller-Agent模式协调。还有一个必须注意的细节Agent和Controller之间的通信结果汇总也会占用带宽压测机之间最好走内网不要经过公网转发否则测试数据本身就会成为延迟来源。6.2 WebSocket的消息帧大小和编码问题在线聊天平台消息内容支持图片、表情、文件链接消息体的JSON大小从几百字节到几KB不等。我最初统一用256字节的小消息压测后来改成混合大小之后TPS立刻掉了一截。原因是服务端对消息体做了Base64转码和敏感词过滤大消息的处理时间明显增长。测试脚本里我用了CSV文件配置不同大小的消息体按比例随机选择尽量逼近线上真实情况。关于编码还有一个小经验JMeter里发送TextFrame时要注意字符编码默认UTF-8没问题但如果发送二进制内容比如图片消息的Base64字符串要用Binary Frame并且把内容转换好否则服务端解析会报错。6.3 服务器内核参数的调整聊天平台连接数要撑到几千甚至上万Linux系统默认的文件描述符限制是不够的。压测开始前我就把系统参数调整了# 单个进程可以打开的最大文件描述符数量 ulimit -n 65535 # 系统总文件描述符限制 sysctl -w fs.file-max100000 # TCP连接重用和快速回收 sysctl -w net.ipv4.tcp_tw_reuse1 sysctl -w net.ipv4.ip_local_port_range1024 65000这些参数在服务器端配置好连接受限的问题就不会在压测中途冒出来干扰测试结论。JMeter这一侧的机器也同样要调否则压力源先瓶颈了后面的测试数据就完全没有意义。6.4 离线消息拉取场景的数据库瓶颈离线消息拉取的压测和普通Web压测不一样需要模拟“用户重新登录后拉取所有未读消息”的动作。这个动作最终落在MySQL的一个大分页查询上SELECT * FROM chat_message WHERE user_id ? AND msg_id ? ORDER BY msg_id ASC LIMIT 500并发拉取一上来这条SQL直接把数据库CPU干到100%。问题出在联合索引上没有包含user_id和msg_id。开发调整索引之后同样的场景从120 TPS提升到400 TPS。这里我想再提醒一句测试报告中如果只关注TPS容易忽略数据库慢查询日志里藏着的杀手。我在每轮测试之后都会同步收集MySQL慢查询日志和Redis的慢命令日志两个一起对照分析往往能定位到意想不到的瓶颈。7. JMeter 5.6.3新特性实测感受与报告优化细节7.1 新版本报告模板比老版强在哪JMetter 5.6.3的HTML报告模板比5.1之前的版本清爽不少。最直观的感受是三类信息放在首页就能看到APDEX应用性能指数反映用户对系统性能的满意程度0到1之间越接近1越好。我做聊天平台设置APDEX阈值为“满意2000ms容忍5000ms”最终结果显示0.92属于良好区间。统计摘要表直接列出样本数、平均值、中位数、90%、95%、99%分位和错误率一眼就能看清楚。时间线图表把吞吐量、响应时间、活跃线程数按时间维度展示方便拉长时间轴看整体趋势。7.2 让报告更贴合聊天场景的几个配置默认报告里的“事务名称”比较简陋显示的可能是“WebSocket Sampler”这样的名字。为了让报告更有业务含义我在脚本里给每个取样器取了有意义的名字比如“发送单聊消息”“拉取离线消息”“验证群聊推送”报告里能直接看出来每类操作的性能参数。另一个优化项是在用户自定义变量里管理服务器的地址、端口、并发数报告里的标签页能显示测试参数组合。这样每次压测生成报告后稍微看一眼标签就知道这次跑的是“5000连接-256B消息-60分钟”还是“3500连接-1KB消息-30分钟”。这些细节能让报告在团队里流转的时候少很多解释成本。7.3 报告分享时容易被忽略的上下文信息测试报告不只是给测试组自己看的还要给开发、产品和运维看。我习惯在报告目录里额外放一个README写清楚本次压测的环境配置几台服务器每台规格、JMeter版本、插件版本、业务场景设定在线用户数、消息大小比例、群聊人数、关键结论和调优建议。否则单看一张吞吐量图表外行根本不知道为什么第一轮失败第二轮成功。这里我想强调一下很多团队拿到了JMeter的HTML报告看完就扔了三个月后再来复盘环境参数早就忘了报告等于白做。补上一个简单的环境说明文件花费不了几分钟后期省下的沟通成本非常大。8. 关于消息可靠性和测试报告深度的补充8.1 压测中发现的可靠性问题在线聊天平台除了性能指标还有两个隐藏的可靠性指标必须测消息不丢失和消息不重复。我在压测过程中发现当RabbitMQ出现短暂不可用时客户端会重发消息但服务端缺少幂等处理机制导致同一消息被写入多次接收方出现重复消息。解决方案是让开发在服务端加一层基于msgId的去重表压测脚本里的msgId用UUID生成刚好能用唯一性约束去验证去重逻辑是否生效。测试报告里我单独增加了一个“消息去重验证”小节包含压测期间产生的重复消息数量、去重表大小、去重耗时等数据。8.2 测试报告里应该包含哪些性能结论一份完整的在线聊天平台测试报告我习惯分成六个部分测试概述背景、范围、环境测试模型设计业务比例、连接模型、场景设计执行结果吞吐量、延迟分位、错误率、稳定性性能瓶颈分析每个瓶颈的定位过程、根因、调优措施调优前后对比用表格列出各类指标的前后变化风险与建议剩余风险、扩展性建议、监控告警建议第六部分往往最容易被忽略。我的建议是测试报告不能只报“通过”还要给出“什么时候需要扩容”“哪些参数需要持续监控”等建议。比如这次压测结论里我明确写了当在线连接数达到5000时需要将RabbitMQ消费者实例扩到至少4个当端到端延迟P99超过3000ms时首先检查消息队列堆积其次检查数据库慢查询。这样运维同事拿到报告后可以直接落地到监控告警规则里而不是干看着一堆数据。8.3 测试数据资产化最后我建议压测的JTL原始文件、JMeter脚本、压测机配置、调优前后参数快照都要归档保存。我经历过好几次线上出问题回去翻测试报告时发现脚本已经从磁盘上清掉了只能凭记忆复现耗时还不一定准确。JMeter脚本本身就是测试资产加上用5.6.3版本生成的测试报告自带时间戳和指标快照归档价值很高千万别清理。每轮压测后的原始数据都按日期命名归档后期做版本对比和容量规划的时候直接调出来用比自己临时造数据靠谱得多。