AI 微信客服上线前的一夜压测:消息洪峰测出的三个真问题
上线评审会上,老板问了一句:“客服机器人扛得住双十一吗?”会议室安静了十秒。没人答得上来——并发设计是有的,队列是有的,但没人真的往里灌过洪峰。
于是定了规矩:上线前压测一晚。这一晚没测出什么漂亮的性能数字,倒是测出了三个上线后一定会炸、Demo 阶段永远看不见的真问题。
一、压测目标先定清楚
压测最忌讳“跑个工具看看 QPS”。出发前先写下要回答的三个问题,产出物就是这三个答案:
- 限频边界在哪:发送速率提到多快时,平台侧开始出现异常
- 失败长什么样:过载时是全量失败还是部分失败,错误返回长什么样
- 恢复是什么样:洪峰停掉之后,系统需要多久回到正常,期间还会发生什么
QPS 数字本身不重要,三个问题的答案才决定上线方案。
二、压测环境:三条隔离原则
压测等于主动制造故障,环境不隔离就是拿生产开刀:
- 实例隔离:用测试实例压,别碰生产实例——压测打出的问题会连累真实用户的会话
- 对象隔离:发送目标全部走 filehelper(文件传输助手),测的是发送链路的吞吐,不碰任何真实用户
- 环境隔离:代理用公用测试档就行,压测不需要好环境,需要的是稳定可复现的环境
这三条里第一条最容易被心存侥幸地跳过——“就压五分钟没事的”。别跳,压测的事故案例基本都是从这五分钟开始的。
三、洪峰怎么灌:递增速率,不是一步到位
模拟真实业务的关键是 ramp-up(递增加压),从一个正常速率起步,每一轮往上加一档,观察系统在哪一档开始变形:
# WTAPI 微信机器人接口平台 | 文档: https://weiti.apifox.cn
# 本文依赖:
# - 发送: postText (appId/toWxid/content, 压测目标全部为 filehelper)
import time
import threading
import requests
from dataclasses import dataclass, field
BASE_URL = "https://wx.chuapi.com"
HEADERS = {"Content-Type": "application/json", "X-finder-TOKEN": "<your_token>"}
@dataclass
class Probe:
rate: int # 本轮目标速率: 条/分钟
latencies: list = field(default_factory=list)
errors: int = 0
def fire_once(app_id: str, probe: Probe) -> None:
start = time.monotonic()
try:
resp = requests.post(
f"{BASE_URL}/finder/v2/api/message/postText",
headers=HEADERS, timeout=10,
json={"appId": app_id, "toWxid": "filehelper",
"content": f"[loadtest] {probe.rate}"},
).json()
probe.latencies.append(time.monotonic() - start)
if resp.get("ret") != 200:
probe.errors += 1
except requests.RequestException:
probe.errors += 1
def ramp_up(app_id: str, rates: list[int]) -> list[Probe]:
results = []
for rate in rates: # 例如 [10, 30, 60, 120, 200]
probe = Probe(rate=rate)
interval = 60.0 / rate
for _ in range(rate):
threading.Thread(target=fire_once, args=(app_id, probe)).start()
time.sleep(interval)
time.sleep(30) # 每档之间留观察窗
results.append(probe)
print(f"速率{rate}/分钟: p95={sorted(probe.latencies)[int(len(probe.latencies)*0.95)]:.2f}s, 错误={probe.errors}")
return results
每一档结束打一行摘要,变形点在哪一目了然。注意 content 里带上速率标记——出问题时能从记录里反查出当时在压哪一档。
四、三个真问题
发现一:限频不是悬崖,是斜坡。 原本以为速率超限的表现是“突然全是错误”。实际是:错误率还没动,p95 延迟先从 0.3 秒爬到 3 秒,再爬一段,部分失败才开始出现。也就是说,延迟是比错误率早得多的预警信号。这直接改写了发送端的设计——客户端要有延迟自适应降速(连续几条延迟超过阈值就主动降速),而不是等错误出现再重试。
发现二:无脑重试会把部分失败放大成全量失败。 一档速率里出现 10% 失败后,重试逻辑立刻把这些任务重新打出去,恰好叠加在下一档的加压上,10% 部分失败滚成 60% 全面过载。压测前的重试代码看起来很合理,压测后才看清:没有退避和全局限速闸的重试,本质是第二个洪峰源。改法:指数退避 + 全局并发闸(在途请求超过阈值就暂停重试放行)。
发现三:恢复期比故障期更危险。 洪峰停掉的那一刻系统并没有恢复——积压的重试、队列里排着的任务还在往外打,错误率反而在洪峰结束后第三分钟创了新高。这和掉线排查里“恢复实例先观察再回池”是同一条原理:恢复必须限速回放,不能放开闸门。压测后把“恢复期降速系数”写进了发送器配置。
五、写进上线检查单
- 发送器有延迟自适应降速吗?触发阈值是多少?
- 重试有指数退避和全局在途闸吗?——没有就是埋着第二个洪峰源
- 故障恢复后的回放速率,降速系数定了吗?
- 三个压测问题的答案(边界、失败形态、恢复时间)写进上线文档了吗?
- 限频红线以接口平台公开文档写明的规范为准——压测的加压档位别越红线,压出问题不是目的,看清边界才是
接口路径与限频规范以 WTAPI 文档为准。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)