营销活动压测面试案例 —— 云盘会员日活动
一、案例背景
某运营商开展“全网云盘会员日”营销活动,活动规则如下:
- 活动时间:长期运营活动
- 核心玩法:每月根据会员等级给基础积分;会员日开启当天,用户可通过完成拉新、拉活、AI工具试用等任务获取积分,用于抽奖或兑换奖品。
- 约束条件:奖品设有库存限制,手动领奖时扣减库存
- 数据统计周期:当天
关键特征
- 仅在当天倒计时结束时开放抽奖。
- 任务包括:拉新/拉活/指定活动停留浏览几秒/AI工具(如文生图)试用,跳转到对应活动h5,满足条件后(通过http回调)获得抽奖次数。
- 奖品包括:云盘白银会员、联合会员、打车券、外卖券、现金红包(热点奖品)。
活动核心数据及术语说明
基于前端埋点获取到,压测计划时上一期的活动数据
| 指标 | 数值 |
|---|---|
| PV | 1,315,078 |
| UV | 626,039 |
| 登陆用户数 | 469,431 |
| 登录转换率 | 74.98% |
| 中奖数 | 154,130 |
| 中奖率 | 32.83% |
| 中奖用户数 | 115,953 |
| 领奖数 | 78,024 |
| 领奖率 | 50.62% |
术语说明:
- PV(Page View,页面浏览量) 指活动页面被打开/刷新的总次数(用户每刷新一次即累计一次),反映页面被请求的总量;
- UV(Unique Visitor,独立访客) 指访问页面的去重设备/用户数(同一用户多次访问仅计一次),用于衡量真实的用户覆盖规模。
两者结合可计算人均浏览深度(PV/UV)及分析活动吸引力。
二、技术风险分析
基于活动特性,从测试工程师角度,识别以下技术风险:
| 序号 | 风险点 | 测试要点 | 严重等级 |
|---|---|---|---|
| 1 | 中奖奖品被重复领取 | 检查幂等、高并发(针对奖品库存)、领奖记录最终一致性 | L0 |
| 2 | 倒计时结束瞬时高发流量 | 检查瞬时高并发时系统表现是否达标 | L1 |
| 3 | 缓存击穿/雪崩 | 检查热点奖品Key的缓存压测时不失效,防止大量请求直接打到数据库 | L1 |
| 4 | 兜底策略 | 检查存在服务降级、熔断、限流等 | L2 |
| 5 | 消息队列积压 | 模拟高并发下消息队列积压,检查有预警和扩容机制 | L2 |
基于压测常见瓶颈,增加测试要点
- 维持30分钟高并发压测,检查是否内存泄露出现
OutOfMemory(OOM);或者压测结束后,CPU使用率锯齿状且一直未下降,需要检查是否GC频繁、死循环、激烈锁竞争;结合arthas排查高cpu线程。 - 韧性测试,故障注入模拟服务故障的降级、兜底处理。
三、压测关键指标获取与计算
数据来源以APM(SkyWalking)埋点日志为主
1. 数据来源及业务配比计算
遵循 "历史基线 + 业务预期 + 漏斗转化" 三层校准。
数据来源权重建议:
| 数据源 | 权重 | 理由 |
|---|---|---|
| APM 埋点日志 | 50-60% | 最精准,含实时调用量、RT、错误率 |
| 数据侧预测增长 | 20-30% | 业务侧对营销力度、用户增长有预判 |
| 漏斗转化衰减 | 10-20% | 修正压测脚本,确保符合业务逻辑 |
团队协作职责边界:
| 职责项 | 主要负责方 | 测试人员需掌握/行动 |
|---|---|---|
| SkyWalking 探针部署 | 开发/运维 | 提醒运维压测期间临时关闭或过滤探针,避免污染线上监控 |
| SkyWalking 数据提取 | 测试人员(自行动手) | 登录 Dashboard 按接口维度导出调用量 CSV |
| 业务转化率(中奖率等) | 数据侧(BI/DBA) | 主动提出统计口径(如按小时粒度),拿到数据后校准压测脚本 |
| Nginx 原始日志捞取 | 运维/基础架构组 | 掌握 Shell(awk/grep)自行分析,不依赖运维代劳 |
2. 高峰期QPS、TPS、并发数计算
SkyWalking 提供的数据指标
| 原生指标 | 含义 | 获取路径 |
|---|---|---|
| CPM | Calls Per Minute(每分钟调用数) | Dashboard → Service/Endpoint → CPM 折线图 |
| 响应时间 | P50/P75/P90/P95/P99 RT | Dashboard → Response Time 折线图 |
| 错误率 | Error Rate % | Dashboard → Error 折线图 |
| SLA | 成功率 % | Dashboard → SLA 折线图 |
数据提取步骤
步骤一:定位高峰时段
1. Dashboard → Service Dashboard
2. 时间选活动当天全天
3. CPM 折线图峰值 → 鼠标悬停看具体时间(如 10:05)
步骤二:放大高峰时段获取接口级数据
1. 时间范围改为高峰时段(如 10:00-10:15)
2. Service → Endpoint List
3. 点击 Export → CSV(含各接口 CPM、P99 RT)
QPS/TPS/并发数计算
| 指标 | 计算公式 | 说明 |
|---|---|---|
| QPS | CPM ÷ 60 | 读接口(GET)每秒查询数 |
| TPS | CPM ÷ 60 | 写接口(POST/PUT/DELETE)每秒事务数 |
| 并发数 | QPS × 平均响应时间(秒) | Little 定律:并发 = QPS × RT |
计算示例(高峰时段 10:00-10:10):
| 接口 | CPM | P99 RT | QPS/TPS | 并发数 |
|---|---|---|---|---|
| GET /api/user/info | 18,000 | 150ms | 300 QPS | 300×0.15 = 45 |
| POST /api/lottery/draw | 6,000 | 300ms | 100 TPS | 100×0.3 = 30 |
| POST /api/task/submit | 30,000 | 200ms | 500 TPS | 500×0.2 = 100 |
| 合计 | - | - | 900 | 175 |
注意事项
- CPM 粒度为 1 分钟,高峰时段建议取多个点求平均
- 区分读写:GET → QPS,POST/PUT/DELETE → TPS
- 并发计算用平均值更准确,压测目标设定用 P99
- 如需按业务关键字筛选(如"会员日"),需提前埋
ActiveSpan.tag("activity", "会员日")
最终得出压测目标: - 目标TPS:700-1000 - 实际达标TPS:850
四、方案与资源规划
五、面试追问高频题与深度答疑
Q1:压测链路业务配比,当只有elk+nginx时,如何获取?
面试官考察点:在没有 APM 的情况下,是否具备从基础日志中提取压测数据的能力。
(一)Nginx 日志提取接口 PV 分布
当没有 APM 时,Nginx access.log 是最基础的数据来源。
基础命令:
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
生产级优化(面试加分点):
# 1. 过滤静态资源,只看业务接口
cat access.log | grep -v -E '\.(css|js|png|jpg|gif|ico)$' \
| awk '{print $7}' | sort | uniq -c | sort -rn | head -20
# 2. 按高峰时段切片(如活动开启后10分钟)
grep "15/Jul/2025:10:0[0-9]" access.log \
| awk '{print $7}' | sort | uniq -c | sort -rn | head -20
# 3. 统计各接口 QPS(按分钟粒度)
grep "15/Jul/2025:10:0[0-9]" access.log \
| awk '{print $4, $7}' | awk -F: '{print $2":"$3}' \
| uniq -c | awk '{print $1/60, $2}' # 每分钟调用量 ÷ 60 = QPS
推荐工具: - GoAccess:实时分析 Nginx 日志,生成 HTML 报告,直接看接口 PV 排名 - ELK/Kibana:将 Nginx 日志导入 ES,用 Kibana Visualize 做接口调用量折线图
(二)ELK 提取业务转化率
ELK 存储的是应用日志(如业务日志、错误日志),可用于提取转化率。
典型查询(Kibana Discover):
# 查询中奖日志
message: "lottery_success" AND activity: "会员日"
# 统计中奖次数
activity: "会员日" AND message: "lottery_success" | count()
# 计算中奖率
中奖次数 / 抽奖请求次数(需两条查询合并)
注意:ELK 查业务转化率的前提是应用日志中有业务埋点(如中奖、领奖的日志输出)。如果没有业务埋点,则需从业务数据库/数据仓库获取。
(三)业务配比计算流程(ELK + Nginx 组合)
1. Nginx 日志 → 各接口 PV 比例(如登录 10%、任务提交 60%、抽奖 30%)
2. ELK 日志 → 业务转化率(如中奖率 32.83%、领奖率 50.62%)
3. 业务数据库 → 用户漏斗数据(如登录转化率、任务完成率)
4. 三者结合 → 校准压测脚本的业务配比
(四)局限性对比
| 数据源 | 优势 | 局限 |
|---|---|---|
| Nginx 日志 | 有 PV/QPS,无需额外部署 | 无响应时间、无业务转化率 |
| ELK 日志 | 可查业务埋点、错误堆栈 | 需应用埋点,无调用链路 |
| APM | 有调用链路、RT、错误率 | 需部署探针,有学习成本 |
面试金句:
"当只有 Nginx + ELK 时,我会先用 Shell 命令或 GoAccess 从 Nginx 日志提取接口 PV 分布,再用 ELK 查业务埋点日志获取转化率。虽然没有 APM 的链路追踪,但组合使用仍能得到合理的压测配比。如果条件允许,我会建议团队引入 SkyWalking 等轻量级 APM。"
通过nginx日志统计接口访问量
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
# 命令含义:awk '{print $7}' 提取URI路径 → sort排序 → uniq -c统计次数 → sort -rn降序 → head -20取Top20。
“Nginx日志法仅适用于宏观PV总量和接口热点排序,但无法支撑压测配比决策,因为:
1.无法关联用户行为漏斗(不知道这15万次抽奖是由多少‘登录用户’发起的,也无法知道‘任务完成数’)。
2.无法剔除静态资源/爬虫/健康检查(K8s的/actuator/health探针会污染统计)。
3.实时性差(离线分析,无法动态调整压测模型)。”
Q2:如何评估并发用户数?压测机台数怎么算?(QPS=1万,RT=500ms)
面试官考察点:是否掌握 Little定律 和施压机性能瓶颈。
第一步:计算总并发线程数(VU)
- Little定律:并发用户数 = QPS × 平均响应时间(秒)
- 代入:10,000 QPS × 0.5s = 5,000 并发线程(VU)
第二步:计算所需压测机台数
单台施压机上限认知:
- JMeter每个线程(VU)默认占用约 1MB 堆内存
- 8C16G施压机,分配
-Xmx12G,理论上限 12,000 线程 - 生产经验值:单台安全并发控制在 1,500 ~ 2,000 VU(超过则GC频繁,数据失真)
计算公式:
- 所需台数 = 5,000 ÷ 1,500 ≈ 3.3 台
- 最终建议:准备 4台施压机(3主力 + 1冗余),确保施压机带宽 > 被压机带宽
Q3:压测时 TPS 上不去但 CPU/内存不高的排查方向?Arthas 何时上场?
面试官考察点:区分"业务处理慢"与"网络/中间件/OS限制"的能力,以及是否会使用高级动态诊断工具。
核心结论:TPS上不去但CPU/内存空闲 → 瓶颈不在应用计算或堆内存,而是卡在"IO等待"或"外部协调"。
(一)基础排查方向(按优先级):
| 优先级 | 排查方向 | 具体方法 |
|---|---|---|
| 1 | 数据库/Redis连接池 | 查看 DataSource 活跃连接数是否等于 maximumPoolSize,大量线程在等待连接 |
| 2 | 下游依赖超时(AI/第三方) | 检查调用第三方接口的响应超时配置,线程堆栈是否有大量 SocketTimeout |
| 3 | 网卡带宽打满 | 用 iftop/nload 查看网卡流量是否达到内网限速(如1Gbps) |
| 4 | OS内核限制 | 检查 net.ipv4.ip_local_port_range(默认3万)是否耗尽,TIME_WAIT 堆积 |
| 5 | 分布式锁竞争 | 抽奖扣库存用 Redisson 或 select...for update,大量线程在自旋等待锁释放 |
| 6 | GC停顿 | 查看GC日志,检查是否频繁发生STW(虽然CPU不高,G1的Concurrent Mark阶段偶发) |
(二)高级武器——Arthas(在线动态诊断,无需重启)
当 jstack 只能看到线程状态但无法定位深层原因时,Arthas 是冲击高分的关键。以下是3个杀手级场景:
| 场景 | Arthas 命令 | 实战效果 |
|---|---|---|
| 定位外部接口超时根因 | trace com.xxx.TaskService submitTask |
动态打印方法内每一步的精确耗时。若发现 900ms 耗费在 HttpClient.execute(),而业务代码仅用 1ms → 秒级定位网络IO超时。 |
| 线上临时开启 DEBUG 日志 | logger --name ROOT --level debug |
怀疑分布式锁 tryLock 在空转,但线上 INFO 级别看不到。实时改为 DEBUG 观察等待时间,验证完再改回,免重启。 |
| 反编译验证线上代码版本 | jad --source-only com.xxx.StockService |
怀疑部署包不是最新代码。直接反编译 JVM 内存中的字节码,若发现确实缺少判空逻辑 → 实锤截图,终结扯皮。 |
面试金句:
“当
top -H和jstack看不出明显锁等待时,我会果断上 Arthas 的watch命令,监控扣库存方法的入参和返回值,观察remainingStock是否在临界值异常归零。Arthas 让我能在不停服的情况下完成‘手术刀式’的精准诊断。”
Q4:压测有没有遇到什么瓶颈,做了什么优化?
思路:现象 → 分析排查 → 优化方向
大Key
现象
压测持续到第10分钟时:
- RT曲线陡增:核心抽奖接口的P99 RT从30ms飙升到1.2秒。
- DB负载异常:MySQL的CPU从10%突然飙到80%。
- 资源矛盾:应用服务器CPU和内存都还有余量,说明瓶颈不在应用层。
- 监控面板:redis 命中率下降至60%,evicted_keys(淘汰键数)指标正在以每秒几百个的速度疯狂增长,同时Redis内存使用量一直压在maxmemory的水位线上。
排查
1.分析Redis驱逐策略与Key分布
redis-cli INFO stats | grep evicted_keys
redis-cli CONFIG GET maxmemory-policy
策略是 allkeys-lru,说明最近最少使用的Key被清掉了。结合现象,怀疑是大key挤占了其他缓存的空间。
2.定位大Key(Big Key)
redis-cli --bigkeys
redis-cli MEMORY USAGE {key}
使用 redis-cli --bigkeys 扫描,重点查看 prize_config 对应的数据结构大小。
3.怀疑缓存穿透/击穿流量
查看Redis监控的 keyspace_misses(未命中数)与 keyspace_hits(命中数)曲线。未命中数陡增的时间点应与 evicted_keys 陡增时间点完全重合,并确认是否未做缓存空值处理或布隆过滤器前置拦截或缓存失效时间(TTL)设置是否集中。
根因
技术债务+业务演进
由于奖品配置由运营同事在平台配置的,加上最初设计时所有奖品都存在string格式的key,后续有效的、无效的奖品都存在这个key里,加上ttl永不过期。
在压测高并发下,内存很快触达maxmemory。
虽然 allkeys-lru 没有淘汰这个极热的大Key(因为它太常被访问),但这个Key自身占用了大量内存,导致Redis中其他业务缓存(如抽奖校验、限流计数器等)被allkeys-lru频繁淘汰。
优化
- 大Key拆解为Redis Hash,只缓存有效奖品
- 逻辑过期 + 本地缓存:引入逻辑过期机制,物理TTL设为永不过期,在Value中埋入logicExpireTime,由后台异步线程定时刷新,彻底消除击穿风险。同时,在应用内存引入Caffeine本地缓存作为第一道防线,压测时99%的奖品读请求由本地内存直接返回,完全不经过Redis。
六、压测通过标准建议
| 维度 | 标准 |
|---|---|
| 核心接口 RT(P99) | ≤ 500ms |
| 登录接口 RT(P99) | ≤ 1s |
| 错误率 | ≤ 0.1%(不含业务预期的"库存不足") |
| 系统资源水位 | CPU ≤ 70%,内存 ≤ 80% |
| 压测时长 | 峰值水位持续 30分钟 无劣化趋势 |
| 超卖检测 | 库存字段 ≥ 0,无负数记录 |
面试官终极追问提示:"如果压测时发现 P99 响应时间合格,但 P999 飞得很高,你如何排查?"——此时可回答:检查GC日志中的Full GC频率、查看是否有跨机房调用、检查是否有热点Key的驱逐重加载逻辑。