7.1 配套代码:从延迟分解到优先级排序
对应小节:7.1 优先级金字塔 把「延迟分解的结果」自动映射到「该从哪一级开始优化」。
一、延迟分解 → 层级映射
# tools/prioritize.py
"""
从延迟分解结果推导优化优先级。
输入:各段的占比(可从第 6.2 节的 decompose-latency.py 得到)
输出:按层级排序的优化建议
"""
import sys
# 延迟分解的段 → 对应的金字塔层级与典型动作
SEGMENT_TO_LEVEL = {
"client_queue": {
"label": "网络 + 客户端排队",
"level": 1,
"level_name": "① 先问要不要做",
"actions": [
"减少返回字段(少传数据 → 省 CPU/内存/网络)",
"启用响应压缩(大响应体)",
"就近部署(CDN / 同可用区)",
"检查压测客户端是否与被测服务同机",
],
},
"service_queue": {
"label": "服务排队(进 handler 之前)",
"level": 3,
"level_name": "③ 架构与并发",
"actions": [
"查连接池 pending(第 6.5 节)",
"查线程池队列深度",
"检查 RUNNABLE 线程数 vs CPU 核数(调度器污染)",
"检查容器 CPU 节流(第 4.8 节)",
"检查是否有阻塞调用在 Dispatchers.Default 上",
],
},
"app_self": {
"label": "应用自身(序列化、业务逻辑)",
"level": 5,
"level_name": "⑤ 微观常数",
"actions": [
"采 CPU 火焰图找热点(第 6.3 节)",
"检查是否重复序列化(JSON → Map → JSON)",
"检查热路径日志",
"检查装箱/对象拷贝(收益通常是个位数 %)",
],
},
"postgres": {
"label": "PostgreSQL",
"level": 2,
"level_name": "② 算法与数据访问",
"actions": [
"查 pg_stat_statements 的 calls(N+1)与 mean(慢查询)",
"EXPLAIN (ANALYZE, BUFFERS) 检查执行计划",
"检查缺失的索引",
"检查事务范围是否过大",
],
},
"redis": {
"label": "Redis",
"level": 2,
"level_name": "② 算法与数据访问",
"actions": [
"检查大 key / 热 key",
"检查是否有 O(N) 命令(KEYS/HGETALL 大集合)",
"检查是否重复查询同一个 key",
],
},
"downstream": {
"label": "下游 HTTP",
"level": 3,
"level_name": "③ 架构与并发",
"actions": [
"检查是否串行调用多个独立下游(可并行化)",
"检查下游本身的健康度",
"加隔板(为每个下游分配独立资源)",
"加熔断(下游持续失败时快速失败)",
],
},
}
def prioritize(segments):
"""segments: [(key, 占比%)],按占比排序并给出层级建议"""
print("═" * 78)
print("优化优先级:从延迟分解推导")
print("═" * 78)
print()
# 按占比排序
ranked = sorted(segments, key=lambda x: -x[1])
print(f"{'段':<30}{'占比':>8}{'金字塔层级':<20}{'优先级'}")
print("-" * 78)
for i, (key, pct) in enumerate(ranked, 1):
info = SEGMENT_TO_LEVEL.get(key)
if not info:
print(f"{key:<30}{pct:>7.1f}% {'(未知)':<20}")
continue
print(f"{info['label']:<30}{pct:>7.1f}% {info['level_name']:<20}第 {i}")
print()
print("═" * 78)
print("详细建议(按优先级)")
print("═" * 78)
for i, (key, pct) in enumerate(ranked, 1):
info = SEGMENT_TO_LEVEL.get(key)
if not info:
continue
print()
print(f"【优先级 {i}】{info['label']} —— {pct:.1f}%")
print(f" 层级:{info['level_name']}")
print(f" 优化收益上限:{pct:.1f}%(即使降为 0)")
print(f" 建议动作:")
for a in info["actions"]:
print(f" - {a}")
print()
print("═" * 78)
print("三个提醒:")
print(" ① 从占比最大的一段开始 —— 它的收益上限最高")
print(" ② 不要跳过「① 先问要不要做」—— 有时最好的优化是少做一件事")
print(" ③ 低级优化可能是高级优化的前提(例如先简化结构再加缓存),")
print(" 但这是「为了达成更大的目标」,不是为了顺手做")
print("═" * 78)
if __name__ == "__main__":
# 示例:从第 6.2 节的分解结果
example_segments = [
("client_queue", 5.0),
("service_queue", 72.0),
("app_self", 3.25),
("postgres", 16.25),
("redis", 1.0),
("downstream", 2.0),
]
prioritize(example_segments)
二、候选优化的排序器
# tools/rank-optimizations.py <optimizations.json>
"""
对候选优化排序:按「收益/成本比 + 金字塔层级」综合排序。
输入格式:
[
{
"name": "消除 N+1(批量查询)",
"level": 2,
"expected_benefit_pct": 70,
"cost": "低",
"risk": "低",
"effort_days": 0.5
},
...
]
"""
import json
import sys
LEVEL_WEIGHT = {1: 5.0, 2: 4.0, 3: 3.5, 4: 2.0, 5: 1.0, 6: 0.5}
COST_SCORE = {"低": 3.0, "中": 1.5, "高": 0.5}
RISK_SCORE = {"低": 3.0, "中": 1.5, "高": 0.5}
def score(opt):
"""
综合得分 = 预期收益 × 层级权重 × 成本分 × 风险分 / 投入天数^0.5
设计意图:
- 层级越高(数字越小)权重越大
- 成本/风险越高,得分越低
- 投入天数开根号(避免低估快速见效的优化)
"""
benefit = opt["expected_benefit_pct"]
lv = LEVEL_WEIGHT.get(opt["level"], 1.0)
cost = COST_SCORE.get(opt["cost"], 1.0)
risk = RISK_SCORE.get(opt["risk"], 1.0)
days = max(opt.get("effort_days", 1.0), 0.1)
return benefit * lv * cost * risk / (days ** 0.5)
def main(path):
opts = json.loads(open(path).read())
ranked = sorted(opts, key=score, reverse=True)
print("═" * 92)
print("候选优化排序(综合:收益 × 层级 × 成本 × 风险 / 投入)")
print("═" * 92)
print()
print(f"{'#':<4}{'优化':<34}{'层级':>5}{'收益%':>8}{'成本':>6}{'风险':>6}{'天数':>7}{'得分':>9}")
print("-" * 92)
for i, o in enumerate(ranked, 1):
print(f"{i:<4}{o['name'][:32]:<34}{o['level']:>5}{o['expected_benefit_pct']:>8.0f}"
f"{o['cost']:>6}{o['risk']:>6}{o.get('effort_days', 1):>7.1f}{score(o):>9.1f}")
print()
print("═" * 92)
print("建议的执行顺序(前 3 项):")
for i, o in enumerate(ranked[:3], 1):
print(f" {i}. {o['name']}")
print(f" 层级 {o['level']},预期收益 {o['expected_benefit_pct']}%,"
f"成本 {o['cost']},风险 {o['risk']},约 {o.get('effort_days', 1)} 天")
print()
print("注意:")
print(" ⚠️ 最后 2 项(通常是 JVM 调优与微观常数)建议【暂缓】——")
print(" 除非前面的优化已经做完且仍不达标")
print("═" * 92)
if __name__ == "__main__":
if len(sys.argv) < 2:
# 用示例数据演示
import tempfile, os
demo = [
{"name": "消除 N+1(批量查询)", "level": 2, "expected_benefit_pct": 70,
"cost": "低", "risk": "低", "effort_days": 0.5},
{"name": "把正则提到顶层", "level": 2, "expected_benefit_pct": 40,
"cost": "低", "risk": "低", "effort_days": 0.2},
{"name": "阻塞调用切到 Dispatchers.IO", "level": 3, "expected_benefit_pct": 60,
"cost": "低", "risk": "中", "effort_days": 0.5},
{"name": "加 Redis 缓存", "level": 4, "expected_benefit_pct": 50,
"cost": "中", "risk": "高", "effort_days": 3.0},
{"name": "换 ZGC", "level": 6, "expected_benefit_pct": 5,
"cost": "高", "risk": "高", "effort_days": 5.0},
{"name": "减少装箱", "level": 5, "expected_benefit_pct": 3,
"cost": "中", "risk": "低", "effort_days": 2.0},
]
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
json.dump(demo, f, ensure_ascii=False)
path = f.name
main(path)
os.unlink(path)
else:
main(sys.argv[1])
预期输出(示例数据):
════════════════════════════════════════════════════════════════════════════════
候选优化排序(综合:收益 × 层级 × 成本 × 风险 / 投入)
════════════════════════════════════════════════════════════════════════════════
# 优化 层级 收益% 成本 风险 天数 得分
--------------------------------------------------------------------------------------------
1 把正则提到顶层 2 40 低 低 0.2 1341.6
2 消除 N+1(批量查询) 2 70 低 低 0.5 1187.9
3 阻塞调用切到 Dispatchers.IO 3 60 低 中 0.5 464.8
4 加 Redis 缓存 4 50 中 高 3.0 43.3
5 减少装箱 5 3 中 低 2.0 6.4
6 换 ZGC 6 5 高 高 5.0 1.7
建议的执行顺序(前 3 项):
1. 把正则提到顶层
2. 消除 N+1(批量查询)
3. 阻塞调用切到 Dispatchers.IO
注意排序结果:「换 ZGC」排最后(得分 1.7),而「把正则提到顶层」排第一(1341.6)——差了 800 倍。这就是金字塔的量化体现。
三、动手改造
| 改动 | 观察什么 |
|---|---|
| 用你真实的延迟分解数据替换示例 | 得到你项目的优化优先级 |
把「加 Redis 缓存」的 risk 从高改成低 |
排名会上升——理解风险如何影响优先级 |
把「换 ZGC」的 expected_benefit_pct 改成 30 |
排名大幅上升——但这个数字需要有依据(不能为了让它排前面而改) |
给 LEVEL_WEIGHT 调整权重 |
体会「层级权重」如何主导排序 |
用 rank-optimizations.py 给你手上的优化任务排序 |
和直觉对比——差距在哪? |
四、这段代码的局限
expected_benefit_pct是主观估计:它的准确性决定了排序的质量。应该用第 6 章的延迟分解来估(比如"数据库占 70%,所以消除 N+1 的上限是 70%")。- 综合得分的公式是启发式的:权重(层级、成本、风险)可以按团队情况调整。
- 它不能替代判断:排序只是建议,实际还要考虑依赖关系(有些优化必须按顺序做)和业务优先级。
- 收益上限 ≠ 实际收益:比如"数据库占 70%“意味着消除 N+1 的上限是 70%,实际可能只降到 30%(因为还有连接开销等其他因素)。