
监控大屏上的TPS曲线像心电图一样平稳稳定在3500上下领导看了很满意可另一边客服工单里堆满了“页面转圈、订单迟迟不更新”的投诉。这种“数字好看、体验翻车”的场景在用性能监控工具做实时TPS与响应时间分析时太常见了行业里给它起了个名字叫TPS虚高。这篇文章我想把一件事讲透实时TPS和响应时间这两组监控指标到底该怎么定义、怎么采集、怎么分析以及为什么它们经常联手骗人。适合正在搭监控体系的运维和开发同学读看完至少能知道自己监控面板上的数字有多少水分。1. 先较个真TPS到底在数什么数错了全都白搭1.1 TPS和QPS、HPS不是一回事很多人把TPS挂在嘴边但真要问“你监控面板上那个TPS数的到底是什么”十有八九答不上来。TPS是Transactions Per Second每秒事务数QPS是Queries Per Second每秒查询数HPS是Hits Per Second每秒点击数。三者的关系可以粗暴理解成一个业务事务往往由多个查询或多次客户端调用组成。用户点一次“提交订单”浏览器可能发了3个请求后端可能执行了8条SQL那这一次下单算不算1个TPS算3个QPS还是算8个数据库查询不同的监控工具、不同的埋点方式答案完全不同。很多APM工具默认把每一次HTTP请求都记为一个事务这本身没错但如果你拿“请求数”去冒充“业务TPS”那数字天然就会被放大几倍。一个订单接口背后挂3个资源请求请求级TPS就是业务TPS的3倍起步。这不是工具的问题是口径问题。1.2 一个业务事务的计数边界怎么划我建议你在设计监控指标之前先做一件事把系统里的“事务”画个边界。所谓业务事务应该以“用户一次完整的业务意图”为准。登录算一个事务下单算一个事务支付回调处理算一个事务。一个事务的内部请求、内部SQL、内部缓存操作都不应单独计为TPS。划边界时最容易踩的坑有三个。第一把健康检查算进去了。负载均衡器每5秒探活一次50个实例每秒就是10个“事务”面板上那点底数是这么来的。第二把静态资源和网关转发都算进去了。图片、CSS、JS的请求不是事务但在很多日志解析方案里它们会被无差别统计。第三把重试请求算了两遍。上游超时重试3次业务只成功1次监控上记了4个事务。口径不统一后面所有分析都是空中楼阁。我的习惯是在采集端就把事务类型打标签transaction_typelogin、transaction_typeorder_create、transaction_typehealth_check统计时先过滤掉非业务标签再做聚合。宁可少统计一点“边缘事务”也要保证核心业务数字是可信的。1.3 吞吐量高不等于性能好两个指标分着看TPS反映的是吞吐量它只回答一个问题系统单位时间内处理了多少事务。它完全不回答这些事务处理得有多快、用户等得有多苦。一个线程池被占满、请求全部在排队等待的系统每秒依然能“受理”大量请求TPS曲线可以非常平稳但用户感受到的是卡顿和超时。所以性能监控必须把TPS和响应时间分开看并且要意识到TPS高不是性能好的充分条件甚至不是必要条件。打个比方TPS像是餐厅门口排号机发出去多少号响应时间像是每桌客人从落座到上齐菜品的时间。发号快不代表上菜快如果后厨已经堵死了发号再多只会让客人越积越多。这个类比在后面排查TPS虚高时会反复用到。2. 实时监控链路怎么搭埋点、聚合与存储的口径选择2.1 三种主流数据来源Agent埋点、中间件指标、日志解析实时TPS监控的数据来源业内无非三条路Agent埋点、中间件指标、日志解析。Agent埋点是在应用内嵌入SDK拦截HTTP入口和RPC调用统计每个接口的成功数、失败数、耗时分布。这条路数据最精准能精确到接口维度但侵入性强而且对语言栈有要求Java系用起来最顺手。中间件指标是另一条路比如Nginx的$request_time变量、Tomcat的线程池指标、消息队列的消费速率这些数据由中间件自己暴露应用不用改代码。好处是无侵入、覆盖广代价是拿不到业务维度信息——你能看到Nginx转发量大但不知道其中多少是真实下单。日志解析则是把访问日志、业务日志捞出来做流式统计用Flink这类引擎实时聚合TPS。日志里能带业务字段灵活度高但延迟天然比前两者高而且对日志规范要求苛刻字段不统一统计出来的数就是糊涂账。成熟团队的做法通常是三者混用Agent埋点做业务TPS主口径中间件指标做容量和饱和度监控日志解析做离线复核和维度分析。2.2 时间窗口和聚合方式直接决定看到的TPS真不真实时TPS的“实时”也是有歧义的。统计窗口用多长看到的曲线完全不同。用1秒窗口,曲线剧烈抖动尖刺特别多图很难看也更真实用1分钟窗口曲线平滑了但突发流量被平均掉了你根本不知道高峰期真实压力是多少。这里我给一个经验值实时监控至少保留10秒粒度的TPS曲线告警则基于1分钟聚合后的数据避免瞬时抖动误报。窗口太短健康检查的周期性探活会变成规律尖刺窗口太长毛刺和异常被抹平失去实时意义。聚合方式同样有讲究。很多人直接sum(rate(...))把所有实例加总成一个总TPS这没问题但一定要同时保留单实例的TPS视图。分布式系统里流量倾斜非常常见8个实例里5个空闲、3个打满总TPS看着还很健康实际上那3个实例已经濒临崩溃。不看实例分布的总TPS就是个安慰剂数字。2.3 小团队够用的选型组合搭一套够用的实时TPS响应时间监控不一定要上全家桶。我的建议是分两档基础档Prometheus Grafana Spring Boot Actuator。应用暴露http_server_requests_seconds_count这类指标Prometheus每15秒拉一次Grafana出图。这套组合胜在轻量、全开源指标语义清晰满足大多数中小团队“看看趋势、报警救人”的需求。缺点是拿不到调用链出了问题还得靠日志慢慢翻。进阶档SkyWalking一类的开源APM工具。它自带Agent能自动采集每个接口的TPS、响应时间、P99、错误率还能生成服务拓扑和分布式调用链。排查“哪个下游把RT拖高”这种问题时APM的调用链视图是纯指标监控给不了的。代价是Agent本身有少量性能损耗内存占用需要评估。选型上我个人的原则是先确认你要回答的问题。只想回答“系统扛不扛得住”Prometheus够用想回答“慢在哪里、哪个调用环节拖后腿”必须上APM或链路追踪。3. 响应时间分析的深水区平均值会骗人长尾才是用户骂点3.1 为什么看P99比看平均耗时靠谱响应时间监控最经典的坑就是盯着平均耗时看。平均值是会被极少数慢请求拉高的但更可怕的是它也会被大量快请求拉低。举个真实例子一个接口每秒1000个请求990个响应在50毫秒以内10个响应要5秒。平均值是(990×0.0510×5)/1000≈0.1秒看平均值你会觉得这接口性能非常好。但实际体验是每100个用户里有1个人被卡了5秒如果这个用户正在下单他已经流失了。所以在响应时间分析里百分位数才是主角。P50代表一半请求的耗时水平P95代表最慢的5%请求P99代表最慢的1%请求。P99的恶化通常比平均值的恶化早几个小时出现它是用户体验恶化的早期预警信号。监控上我会固定看四个数P50、P90、P99、P999。P50和P90管日常基线P99管预警P999管强告警。指标含义监控用途P50一半请求在它以内日常基线反映整体手感P9090%请求在它以内反映大多数用户体感P9999%请求在它以内长尾预警用户体验保险丝P99999.9%请求在它以内强告警通常伴随故障3.2 在客户端测还是在服务端测结果能差出两倍同一个接口服务端监控显示P99是200毫秒客户端真实体验可能接近500毫秒。差出来的部分包括网络传输时间、DNS解析、网关转发、浏览器渲染之前的排队时间。服务端测的是“请求到达应用、应用处理完返回”的耗时而用户感知的是“从点击到页面变化”的全链路时间。所以一套完整的性能监控工具响应时间必须分两层看。服务端测的是应用处理时间客户端或拨测测的是端到端体验时间。我见过不少团队把服务端RT当成用户体验来做SLA承诺结果用户投诉不断监控却一片绿灯问题就出在测的地方不对。建议在小规模范围内做真实用户监控RUM或者用定时拨测模拟用户请求把端到端响应时间单独拉一条曲线。服务端RT和服务端到端RT两条线一对比网络损耗和前端排队问题立刻现形。3.3 从RT分布反推瓶颈匀速恶化还是偶发尖刺响应时间不能只看趋势还要看分布形态。同样的P99数值背后的分布可能完全不同。一种是整体匀速恶化P50、P90、P99同时往上走说明容量不够可能是CPU饱和、数据库连接池耗尽所有请求都在抢资源处理速度均匀下降。另一种是偶发尖刺P50平稳P99经常突然飙升后回落说明系统多数时候健康但存在间歇性阻塞——典型的触发点是Full GC、慢SQL偶发、下游服务超时重试。处理思路也不同。匀速恶化优先扩容、优化资源瓶颈偶发尖刺优先排查GC日志、慢SQL、下游依赖的熔断降级。只看平均值曲线这两种情况几乎长一样但排查方向天差地别。所以我强烈建议响应时间监控至少同时展示P50和P99两条曲线并且单独配一张耗时分布直方图。4. TPS虚高的七种来历看着漂亮实则全是水分4.1 把健康检查和静态资源当成了业务事务这是最浅层的TPS虚高也是最普遍的。只要做一次“事务类型分布”的审计就能澄清很多误会。健康检查类的探活请求特征非常明显周期性、固定间隔、短耗时5秒一次、每次几十毫秒。静态资源请求特征也很明显URI集中在.js、.css、.png且没有业务字段。排查方法不复杂把监控面板的TPS按URI维度拆分看排前几位的都是什么路径。如果/actuator/health排进了Top 10那你面板上的总TPS至少有5%-10%是假业务。处理方式是采集端过滤或用PromQL排除sum(rate(http_server_requests_seconds_count{uri!~/actuator/.*|/health|/static/.*}[1m]))。这一个改动做下去很多团队会发现自己的“高TPS”直接缩水两到三成。4.2 异步丢消息请求收了活儿还没干这是TPS虚高里最坑的一种因为它骗的不只是监控还让整个团队误判系统容量。典型场景在支付回调、消息通知、异步下单这类业务里应用接到上游回调后校验一下签名、把消息丢进MQ立刻返回200。从HTTP协议层看这个“事务”确实处理完了耗时还很短但从业务层面看真正的订单状态更新、数据库写入、下游通知全都在MQ消费者那边排队。监控面板上的TPS记录的是“接收消息的速率”而不是“处理业务的速率”。接收速率是每秒3000消费者处理能力只有每秒500于是MQ堆积越来越严重业务延迟从几秒恶化到几分钟。你盯着TPS面板会陷入困惑明明曲线平稳为什么业务就是慢答案很简单你测的是受理量不是完成量。解法是双轨指标一轨记录请求接收TPS一轨记录业务完成TPS再配一条MQ消费堆积曲线。三条线放在同一个看板上接受量高、完成量低、堆积上升三者同时出现虚高立刻现形。4.3 线程池排队TPS稳如老狗RT早已爆表这种虚高可以用Little‘s Law解释系统内并发数等于吞吐量乘以响应时间。假设一个服务的线程池有200个线程每个请求处理需要2秒那这个系统的真实吞吐上限就是200/2100 TPS。但如果监控上显示1000 TPS余下的900个请求在哪答案是在队列里排队。排队期间请求也算作“被受理”了TPS统计依然在涨但响应时间已经在成倍恶化。这时候去看RT曲线P99已经飙到几秒甚至几十秒可TPS曲线还是一片平地。很多运维第一次遇到这种情况都懵了以为是监控数据错了其实恰好相反TPS没错错的是把它当成“系统处理能力”。要验证很简单看线程池的活动线程数和队列大小。活动线程数打满、队列持续堆积、TPS和RT同时高位这就是排队虚高的铁证。4.4 批量聚合和长轮询的时间错觉批量聚合是另一类隐蔽的水分。有些系统为了吞吐优化把写入操作攒批提交比如消费端攒够100条或者500毫秒才 insert 一次。从业务事务的角度看这100条业务确实在被处理但如果监控口径是“把一次批量写入计为1个事务”TPS就会被低估而不是高估。反过来如果某个框架把批量内的每条记录都计为一次独立事务那TPS就被均匀放大了放大倍率取决于批量大小这种放大平时看不出来压测时数字能大得离谱。长轮询和WebSocket这类长连接请求更特殊。一个长轮询请求可能会挂30秒等消息推送如果监控把连接建立当成事务完成TPS会偏高且虚高比例不稳定如果把连接关闭当成事务结束响应时间会被拉长到一个毫无意义的数值。对这种场景我建议单独分类不要把长连接和普通HTTP请求混在同一个TPS口径里。4.5 网关缓存层造成的“区域性虚高”加了缓存和CDN之后TPS虚高会呈现出明显的分层现象。入口网关的TPS可能高达好几万因为大量请求在网关层或缓存层直接命中返回了根本没打到应用服务器。应用层的TPS可能只有几千数据库层的TPS更低。问题在于你的监控面板默认显示的“系统TPS”到底是哪一层如果只给领导看网关TPS那数字永远漂亮但上层命中缓存后应用层压力根本反映不出来容量规划会严重失算。更危险的是缓存一旦大面积失效所有请求穿透到应用层网关TPS没怎么变应用层TPS瞬间放大几十倍如果应用层的容量预算一直按“系统TPS很低”来设计这一下就能把服务打垮。所以TPS监控必须分层次打标签网关层TPS、应用层TPS、数据层TPS各一条曲线配合缓存命中率一起看。虚高的数字不可怕可怕的是不知道虚在哪里。5. 一次真实排查回调服务TPS虚高的完整定位过程5.1 现场网关收到的TPS曲线一切正常业务却慢了三分钟有一回线上出了这么个事支付回调服务接到上游渠道商的回调请求面板显示吞吐量稳定在3500 TPSP99响应时间只有800毫秒看起来一切健康。结果业务方找过来说对账数据延迟严重订单状态最长要3分钟才能更新完。我当时第一反应就是TPS口径出了问题。3500 TPS但业务处理延迟到分钟级这中间一定有个巨大的“缓冲地带”把请求收进来却拖着不干完。5.2 排查链路从指标定义到MQ堆积排查的第一步是看请求到底在哪条链路上。从网关日志追下去回调请求的确是打到了应用实例上应用返回200也很快。但打开应用日志仔细看发现代码里收到回调后的核心操作是“解析报文、简单校验、发送MQ消息”发送完就返回了。真正的订单更新逻辑在MQ消费者里。于是第二步看MQ的消费曲线。这一看就明白了生产者回调接收端的发送速率大约每秒3500条消费者的处理能力只有每秒400到500条消费位点持续落后堆积条数以肉眼可见的速度往上爬。TPS虚高的根因清楚了——监控面板上的3500 TPS是“接收事务”的速率而业务的完成速率只有500。第三步做验证把消费者的实际完成量和面板TPS拉到同一张图对比。两条曲线一上一下差距越拉越大无论怎么解释口径业务延迟都必然持续恶化。至此根因锁定。5.3 修正后的指标设计修正方案分两步。第一步止血给MQ消费者扩容、调大消费线程数并针对堆积量做了告警。第二步才是治本重新设计这个服务的监控指标接收TPS记录HTTP接口实际接收并返回200的回调请求数作为“入口压力”指标完成TPS记录MQ消费成功后业务落库的完成数作为“真实吞吐”指标堆积量MQ消费位点落后的消息数作为“健康度”核心指标消费耗时P99消费者单条消息的处理耗时作为“处理效率”指标。这组指标上线后再看这个服务就一目了然接收TPS再高也不会误导人只要完成TPS跟不上接收TPS堆积量必然抬头告警立刻触发。后来类似的异步场景我都按这套“入口、出口、积压、耗时”四件套来设计再没被“虚高涨”骗过。6. 报警和看板别让监控变成只有告警没结论的摆设6.1 阈值别用绝对值硬卡基线对比更靠谱很多团队给TPS设告警时喜欢拍脑袋填一个绝对值比如“TPS低于500就报警”。问题是不同业务、不同时段、不同日期的TPS天然不一样深夜低谷500可能正常高峰期500可能是灾难。绝对值阈值只对容量严重不足的系统有参考意义对于大多数稳定运行的系统更好的做法是基线对比。具体操作取过去7天同一时段比如昨天下午3点到4点和上周三下午3点到4点的TPS作为基线当实时TPS偏离基线超过一定比例并持续若干分钟才触发告警。比如实时TPS比基线下降70%以上持续5分钟大概率是挂了或者流量被切走了实时TPS比基线暴涨200%以上持续5分钟可能是活动流量、可能是缓存失效穿透也可能是爬虫攻击。这种相对告警比绝对值告警可靠得多而且不需要你精通每条业务线的容量规划。6.2 告警看组合拳吞吐、延迟、错误率、饱和度缺一不可单看TPS告警有个致命缺陷它只能告诉你“量变了”完全不能告诉你“系统是否还健康”。一个服务TPS从1000暴涨到5000可能是好事活动拉量也可能是坏事缓存穿透甚至可能是灾难告警风暴本身把系统拖垮。所以告警规则一定要用组合条件我自己的组合是吞吐异常TPS偏离基线比例超限延迟异常P99超过业务SLA阈值比如核心接口P99大于1000毫秒持续3分钟错误率异常5分钟内错误率超过1%或绝对错误数超过固定下限饱和度异常线程池活跃率超过80%或者MQ堆积量超过可容忍上限。这四个条件里至少同时满足两个才真正触发告警。比如TPS暴涨且P99同步恶化那是容量问题TPS暴涨但P99平稳、错误率极低多半是缓存命中或者健康的流量增长只需要观察不需要惊动大家。组合条件能砍掉大量无效告警让值班同学把精力留给真正的事故。6.3 看板设计的三层结构看板不是指标越多越好而是要让看图的人在5秒内回答三个问题系统现在忙不忙、系统有没有出错、用户体验有没有恶化。我习惯把看板设计成三层第一层是总览层放全局TPS曲线和全局P99响应时间曲线两张图上下对齐x轴时间一致方便一眼看出吞吐和延迟的联动关系。第二层是维度层放按入口、按服务、按实例拆分的TPS热力图用来定位流量到底集中在哪里、哪个实例出现了倾斜。第三层是根因层放线程池活跃率、MQ堆积、GC耗时、下游依赖的P99这一层平时不怎么需要看但前两层出现异常时第三层负责回答“为什么”。三层看板配合组合告警组成了一个完整的闭环总览发现问题、维度层定位范围、根因层找到答案。很多团队把几十张图表铺满大屏看起来专业真出事时反而不知道该看哪张图。监控的核心不是信息多而是信息能快速变成结论。最后再分享一点个人的体会做性能监控这几年我最大的收获不是学会用多少工具而是学会怀疑数字。TPS虚高这事本质上是监控工具把“中间状态”当成了“最终结果”。每次搭建新的监控指标我都会问自己一个问题这个数字如果突然变得很好看它意味着系统真的变好了还是只是某个环节被绕过去了带着这个疑问去设计指标口径比掌握任何高端工具都重要。