ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

虚拟社交平台压力测试实战:从JMeter脚本到长连接性能优化

虚拟社交平台压力测试实战:从JMeter脚本到长连接性能优化 1. 接到任务先别急着开压测虚拟社交场景的压力模型长什么样先说背景。我之前接手一个主打虚拟时装与房间社交的元宇宙虚拟社交平台上线前一周技术负责人丢给我一句话“下周灰度你先压一下并发能力看看100用户进来平台会不会挂。”我当时第一反应是这还不简单JMeter跑起来就是。但真正把需求拆开之后才发现虚拟社交平台的压力测试分析和传统业务系统的压测根本不是一回事。传统系统是典型的请求-响应模型用户点到哪里请求打到后端返回结果就结束了。虚拟社交平台不一样它更像一个持续在线的状态同步系统。用户进入虚拟房间之后自己的虚拟形象在移动、在换装、在发弹幕这些动作要实时同步给房间里的其他人。也就是说压测对象不仅是REST接口还有WebSocket长连接、消息广播、位置同步、状态扩散等一整条链路。如果不把这些想清楚压测脚本设计出来就是错的方向。1.1 为什么普通接口压测覆盖不了虚拟社交平台很多压测教程教的都是登录、查列表、提交订单这类请求-响应接口脚本写完跑一遍聚合报告看响应时间、TPS、错误率收工。这套流程放在电商、OA系统上问题不大但放在元宇宙社交平台上会有明显的偏差因为虚拟社交的核心不是“请求过去了没”而是“状态同步到不到位”。举个例子。虚拟广场里站着五十个人每个人都看得到身边其他人。当其中一个人往前走了一步他的客户端会发送一条坐标同步消息服务端收到之后要把这条消息广播给房间里的其他四十九个人。这个过程中用户感受到的是“画面里的人在动”但后端经历的是消息接入、会话路由、房间成员遍历、消息编码、广播推送、ACK确认。这是一连串的事件处理不是一个简单的HTTP调用。我当时用普通HTTP压测脚本对房间场景接口跑了100并发响应时间一片红错误率也高。第一反应是平台性能不行后来排查了半天才发现脚本本身就有问题请求之间没有思考时间所有线程都在疯狂点同一个接口而且没有模拟从登录到进房间再到互动的完整行为链。这测的不是平台能力是自己脚本的偏差。这也是我想强调的第一点压测如果脱离真实用户行为结果不具备参考价值。1.2 从业务玩法里拆出真正的压测对象压测之前第一件事是把业务侧的埋点数据和PRD翻出来把用户在平台上的核心操作链路拆开。这个环节看着不起眼但直接决定了后续整个压测方案是否有效。我当时接手的平台核心玩法大概是用户登录、加载自己的虚拟形象、进入广场或主题房间、在房间里漫游走动、查看其他人的虚拟装扮、发表情和弹幕互动、给喜欢的人赠送道具、试穿虚拟时装、有合适的就下单购买。每一个用户动作背后都挂着一串后端调用。光一个“进入房间”就要拉取房间配置、房间成员列表、对象场景数据、其他用户的虚拟形象快照。我拉了一张表把所有业务操作按“触发频率”和“资源消耗”两个维度排了一遍。高频率低消耗的操作有坐标同步、心跳保活、弹幕低频率高消耗的操作有场景初始化、3D模型资源下载、商品详情查询。压测场景设计时不能只盯高频操作也不能只压重接口而是要让各种操作按真实占比组合在一起形成混合场景。否则只压高频接口会发现平台很轻松只压重接口会发现平台到处是问题都不是真实表现。1.3 术语对齐并发用户数、TPS和在线人数的区别还有一件必须先对齐的事就是团队之间对“并发”的理解。产品说“我们要支撑一万人在线”开发说“峰值QPS到几千”测试说“我开了一百个并发线程”这三句话经常不在一个维度上报告出来互相看不懂。在线人数是某个时刻平台上同时有多少在线用户这部分用户里只有一部分在活跃操作剩下的可能在挂机、在看别人动态。并发用户数是同一时刻正在与服务器产生交互的客户端数量它一定小于在线人数。TPS或QPS则是单位时间内服务器处理的事务或请求数量它由并发用户数和每个用户的操作频率共同决定。压测的时候比如模拟100个并发用户不是说只请求100次而是让100个虚拟用户按照各自的行为序列持续地向服务器发请求。100个用户在不加思考时间的情况下每秒可能产生几百个事务。理解了这三者的关系再去看JMeter线程组的数字就不会一脸懵了。2. 场景建模与压测方案100并发这个数字到底怎么算出来的压测前还有一个关键步骤把“100并发”这个数字的来源写清楚。很多人上来就是“先压100并发”看起来很专业但你去问一句“100并发对应什么业务目标”对方往往答不上来。这个数字如果经不起推敲报告写完也没人信。2.1 从预估DAU反推峰值并发我当时是按这个口径估算的。平台公测首月DAU目标5万晚高峰同时在线比例按16%算大概8000人在线。在线的用户里真正处于活跃交互状态的按15%到20%估算差不多是1200到1600个并发用户。这些用户平均每3秒操作一次那平台的峰值写入请求大约是400到600 QPS。再加上WebSocket心跳和状态同步消息平台整体的实时事件吞吐可能在每秒几千条这个量级。把100并发作为第一轮压测起点其实是合理的。公测初期、单区域集群部署100并发足以暴露掉大部分初级问题而且执行成本可控出问题也好定位。先跑通100并发拿到基线数据再按2倍、5倍往上加比一上来就开1000并发直接把系统压崩、日志刷屏要稳妥得多。2.2 核心业务操作与权重分配混合场景的权重不能拍脑袋得有依据。我当时综合了竞品分析和现有埋点经验把虚拟社交平台最常见的操作拆成了下面这张表第一轮压测就按这个比例分配业务操作权重协议说明登录认证8%HTTP获取token、用户信息进入房间/初始化场景12%HTTP拉取房间配置与初始状态快照坐标/状态同步35%WebSocket高频小包房间内广播查看他人形象/装扮15%HTTP读取形象数据与资源链接表情/弹幕互动10%WebSocket短消息全员可见虚拟试穿/购买5%HTTP资产校验与订单创建心跳与离线上报15%WebSocket保活与退出标记这个表的价值在于它让压测脚本有了明确的设计依据。为什么坐标同步权重最高因为这是虚拟社交平台里出现频率最高的动作一个用户移动一次服务端就要给房间其他人广播一次。如果广播链路撑不住用户体感就是“画面卡顿”“别人瞬移”。压测报告出来之后开发和产品也能根据这张表理解优先修哪里。2.3 测试数据准备虚拟用户不是随便注册的账号压测数据这块有个特别容易被忽视的坑如果100个用户都共用同一个账号或者登录接口在拿同一批数据反复打那测出来的根本不是性能是缓存命中率。我一般会提前准备一批独立的测试账号并且保证这些账号在压测前就已经拥有虚拟形象、基础装扮和房间权限让请求尽可能打到真实完整链路上。测试账号的数据也要分批准备。涉及UUID、昵称唯一约束和手机号绑定的表数据重复会导致接口直接报错这些错误会被脚本计进错误率最后报告就很脏还得花时间洗数据。我踩过一次之后学乖了账号池在压测前提前两天备好脚本里通过CSV参数化读取绝不写死在JMeter里。3. JMeter脚本搭建还原虚拟形象、房间漫游和互动操作的完整过程工具我选了JMeter原因很朴素团队现有的技术栈就是它生态全、上手快HTTP和WebSocket协议都能支持跑分布式压测也方便。但工具只是起点脚本写成什么样才是决定压测有效性的关键。3.1 线程组结构设计我建了一个“模拟真实用户”的线程组线程数100Ramp-Up Period设成30秒循环执行。这里有人会问为什么不把Ramp-Up设成0一秒钟把100个并发全怼上去因为100个用户瞬间同时涌入测的是系统对连接风暴的承受能力但在真实业务里用户不可能在同一毫秒集体点击进来。设30秒让连接平滑建立更接近晚高峰用户陆续进入房间的形态。循环次数我设成了-1配合Duration固定运行时间比如跑10分钟由Duration控制结束。这样做的原因是压测数据要有足够的采样量如果脚本跑完一遍就停聚合报告里样本太少方差会非常大结果没有统计意义。10分钟跑下来的数据量足以覆盖一轮完整的GC周期和连接池回收周期。3.2 关键请求的协议选择与脚本实现HTTP接口部分登录、进入房间、试穿、购买这些用JMeter的HTTP Request Sampler就能搞定但要处理几个细节。登录之后响应里的token和userId必须用JSON Extractor提取出来放进后续请求的Header或参数里不能写死。写死的token一旦过期脚本后半段全是401错误率直接爆表而且这种错误跟服务器性能半毛钱关系都没有。进入房间这个接口请求要带房间ID、用户位置、虚拟形象实例ID返回的是房间当前状态快照。这个接口响应体通常很大JSON断言时要设置合理的响应超时时间不然断言失败会误报。虚拟时装购买是写操作压测的时候要防止干扰测试环境数据。我的做法是连测试环境订单服务并用专用压测账号池购买流程走到订单创建就结束不继续支付流程避免不必要的链路干扰。WebSocket部分JMeter需要另外安装WebSocket Samplers插件。连接建立之后WebSocket请求的路径一般是/ws/{userId}/{roomId}请求体是JSON格式的同步事件。比如坐标同步{type:move,x:1.5,y:2.3,z:0,timestamp:1700000000000}脚本里通过${userId}和${roomId}动态获取线程对应的房间和用户保证每个线程连接的是自己的房间。3.3 断言与监听器的配置断言这块别只查HTTP响应码200。虚拟社交平台很多接口即使返回200业务上可能还是失败的比如“进入房间”拉取场景快照失败返回了200但里面的错误码不是0。我习惯在JSON响应体里加一个JSON Assertion校验code字段是否为0或success字段是否为true。同时加一条响应时间断言比如超过5秒标记为失败避免慢请求被默认当作成功。监听器方面除了默认的View Results Tree和Aggregate Report我必加这几个Transactions per Second看吞吐量曲线是否平稳Response Times Over Time看响应时间的变化趋势找毛刺Active Threads Over Time确认线程是否按预期逐步增加PerfMon Metrics Collector监控被测服务器的CPU、内存、磁盘和网络。跑分布式压测时PerfMon要配合JMeter Agent在远端被测服务器上启动否则采集不到机器真实资源数据。我第一次用的时候没启动AgentPerfMon面板一片空白还以为插件坏了。4. 首轮压测执行100并发跑完问题集中暴露在哪儿脚本写完场景配好第一轮10分钟、100并发的压测跑起来。结果整体性看过得去但按接口拆开之后问题一个接一个跳出来了。4.1 首轮结果概览聚合报告的整体概况大致是总请求数约10万吞吐量170 TPS平均响应时间1.3秒错误率3.8%。如果只看这三个平均指标好像还行。但按事务拆开看问题就非常明显业务操作TPS平均RTP95 RT错误率初步判断登录认证13620ms1.1s0.1%正常进入房间/初始化场景192.4s5.8s8.3%严重坐标/状态同步78180ms840ms1.2%可接受查看他人形象/装扮261.8s4.6s6.5%偏高表情/弹幕互动17320ms1.8s0.4%正常虚拟试穿/购买63.1s7.2s4.2%偏高心跳与离线上报1590ms420ms0.2%正常注意一个关键现象出问题的不是35%权重的高频坐标同步而是12%权重但重IO的“进入房间”和15%权重的“查看他人形象”。这说明什么说明压测不能只看总TPS更要注意单个业务事务的响应时间和错误率分布。如果只盯着整体吞吐量做优化你永远找不到真正的瓶颈。4.2 从毛刺到根因一次完整的排查链路“进入房间/初始化场景”这个接口的耗时集中在哪我走了一遍完整的排查链路。第一步看Response Times Over Time曲线。这个接口的响应时间不是匀速的慢而是周期性出现毛刺每隔几十秒就冲到4秒以上然后回落。这个形态很像缓存失效或者连接池重建。第二步看PerfMon监控数据。被测机器的CPU使用率只有40%内存稳定磁盘IO也不高。资源充裕但接口慢说明瓶颈在下游依赖。第三步去MySQL慢查询日志翻。果然找到一条典型的慢SQLSELECT room_id, room_config, member_list, object_list FROM room_snapshot WHERE room_id ?这条SQL有两个问题room_id列缺少合适的索引定位房间要全表扫描room_config里存的是一个大的JSON字段每次查询都要解析大字段非常耗时。更坑的是这个接口还会额外执行一条查询把房间内在线用户的坐标和装扮模型表全表扫一遍。第四步查Redis命中率。room_snapshot确实有缓存但TTL只有5分钟而且压测用的房间ID是分散的很多房间第一次被访问缓存里根本没有直接穿透到数据库。根因清楚了业务侧把“进入房间”定义为高频重接口但代码实现把每次进入都打成了数据库全表扫描加重JSON解析。缓存策略对新房间不友好导致穿透率高。这个案例很有代表性很多性能问题的根子不在服务器资源而在数据库访问路径和数据建模。4.3 第一轮修复的验证修复方向分了四步但每次只改一个变量给room_snapshot表加组合索引字段顺序是(room_id, updated_at)把room_config和member_list从大JSON字段里拆出来改成独立的关联小表为进入房间接口增加二级缓存房间维度本地缓存加Redis缓存叠加场景资源文件前置CDN不经过应用网关透传。改完重新跑同场景压测进入房间接口平均响应时间从2.4秒降到680毫秒P95从5.8秒降到1.2秒错误率从8.3%降到0.3%。这个结果验证了我的一个习惯性能优化一定要单变量验证一次只改一处压测一次看结果。如果一次动三处优化有效但不知道是谁的功劳优化无效也不知道是谁的锅。5. 进阶压测WebSocket长连接场景的线程模型调整HTTP接口压测跑顺之后真正的硬骨头才来长连接场景。虚拟社交平台的核心体验在“同房间实时同步”如果长连接扛不住前面HTTP接口再快用户在房间里感受到的还是卡顿和丢消息。5.1 为什么HTTP线程组不能直接用于长连接JMeter的普通线程组是按“发一个请求、等响应、再发下一个”的模式工作的但WebSocket连接建立之后是长期挂在状态的服务端会随时推送数据过来。如果用普通线程组去压WebSocket要么线程跑完一遍就退出要么连接被长期占用后没法执行后续的发送动作线程模型完全错位。我单独建了一个“ws长连接线程组”。线程数按房间同时在线人数估算比如80。每个线程发起一次WebSocket连接然后通过循环读取消息来模拟持续在线。同时发送频率用Constant Throughput Timer控制避免脚本像机关枪一样连续发消息制造出真实用户不会产生的消息风暴。5.2 虚拟房间内多人互动的压力模拟我用CSV Data Set Config准备了一份房间用户清单每个线程代表一个房间用户按CSV参数连接到不同房间。这样100个线程就能分散到多个房间而不是全部挤在一个房间避免压测出“单房间容量上限”而不是“平台容量上限”的错误结论。房间内互动脚本重点模拟三类消息坐标同步move小包高频约每1到2秒一条表情和弹幕chat中包约每30秒一次送礼互动interact低频但会触发服务端向全房间广播通知。压测中发现当房间人数超过40人时move消息的P95延迟开始明显上升。一开始我以为是网络或带宽问题后来细查服务端日志发现广播逻辑写成了串行遍历在线连接逐个发送。房间人数上来之后CPU大量消耗在上下文切换上消息发送自然变慢。改成按房间批量投递、并发协程推送之后这个阈值从40人提高到120人以上。这类问题不压长连接场景根本暴露不出来也是虚拟社交平台压测最值得投入的部分。5.3 长连接压测的指标读数长连接场景不能只看JMeter聚合报告里的TPS因为连接一直挂着每个连接收到的消息会自动计入响应TPS数字天然“好看”。我更关注下面这几个指标连接建立成功率看连接和断开的速率是否健康消息推送延迟统计从消息发出到服务端ACK的时间这个才是用户体感服务端在线连接数变化正常情况应该长期持平如果只升不降大概率连接泄漏GC频率和内存占用长连接意味着大量会话对象常驻内存GC压力比普通HTTP轮询高得多。压测过程中我遇到过Full GC每两分钟一次客户端大量断线重连。用jstat和heap dump一看会话对象存进ConcurrentHashMap之后只有put没有remove用户离线时没有及时清理。这个Bug不压长连接根本发现不了普通HTTP脚本跑一万遍也测不出来。所以说长连接压测对虚拟社交平台的意义比普通接口压测大得多。6. 压测陷阱与数据可信度这些坑我踩过一次就不再踩了最后这部分讲坑因为我越来越觉得压测结果如果不可信比不压更可怕。它会让团队沿着错误方向做优化浪费大量人力和时间。6.1 思考时间与聚合报告的欺骗性第一版脚本没有设置思考时间100个线程像机器人一样不间断发请求。结果登录接口TPS高得离谱数据库连接池直接被打穿。但冷静想一下真实用户不可能一秒钟点十几次登录这个TPS是虚构的。后来我在关键操作之间加了一个Uniform Random Timer均匀随机延迟1到3秒让整体请求频率贴近真实行为。TPS数字立刻降下来了但CPU和数据库连接的使用率反而更接近生产预期。聚合报告的平均响应时间也有迷惑性它会被少数长尾请求拉高。我看报告的习惯是首先看P95和P99P50作为参考。只有P50和P95同时异常才判断整体性能劣化如果只有P99高优先追长尾原因可能是一个慢SQL或一次Full GC引发的偶发抖动。6.2 客户端瓶颈导致的假失败压测机本身的资源也会成为瓶颈这个特别容易被人忽略。我遇到过100并发HTTP请求错误率一度到20%排查了半天最后发现是JMeter所在机器文件句柄数不够每建一个新连接就报Too many open files。问题根本不在被测系统在施压端。压测前要把施压机器的连接数限制、文件句柄数、JMeter堆内存都调好。尤其是跑WebSocket长连接JMeter一个进程撑到上千连接后默认堆大小根本不够用OOM之后脚本直接卡死。结果测出来的瓶颈是压测机自己不是被测平台。这个坑太常见了建议正式压测前先做一轮小规模冒烟确认施压端能撑住再上完整场景。6.3 压测报告里必须写清楚的三件事一份可用的压测报告光贴聚合报告截图是不够的。团队需要的是能指导优化的结论。我每次都会在报告里写死三件事。第一环境与版本说明。被测服务的代码版本、中间件版本、测试环境配置、压测机配置、脚本版本。没有这些报告里的数字无法复现过两周连自己都说不清当时测的什么环境。第二场景与比例说明。哪些接口按什么权重加入场景、并发量怎么定、思考时间怎么设、压测时长多少。保证换一个人拿到同一份脚本能复现出同样的场景。第三结论与风险清单。每个接口的容量上限是多少、达到上限时的表现是响应时间劣化还是报错、当前最高瓶颈在哪。给开发一个优化优先级给产品一个“可以支撑多少人同时在线”的明确预期。我一般还会在报告最后附上脚本文件和结果CSV的位置方便后续做回归压测对比。这样的报告发到群里大家讨论的都是具体问题而不是争论数字真假效率会高很多。最后再分享一个压测习惯每一轮优化后不要马上宣布达标至少要跑两轮同样的场景中间隔几分钟确认结果曲线不是偶然抖动。我第一次做这个虚拟社交平台压测的时候第一轮优化后P95从5.8秒降到1.2秒当时觉得已经很好了结果第二天重跑又回到3秒。后来发现是缓存预热和数据量变化导致的也就是数据准备阶段没有固定基线。压测数据只有在可重复的前提下才有讨论价值这个原则沿用到现在每次都会遵守。
返回列表