两份都「正常」的交付报告
版本评审时,消消的客户端负责人说:「下载成功率 97.5%,正常。」方舟的负责人说:「我们 98.6%,比消消好。」
两份报告都没有说谎,但它们回答的不是同一个问题。消消的 97.5% 里,弱网用户只占 22%,却贡献了大部分失败;方舟的 98.6% 建立在一个 350MB 首包和 1,490MB 分包之上,一个失败用户损失的是消消的七倍流量。
单看一个数字,永远得不出「正常」的结论。 网络与资源的交付需要一张矩阵:把指标按组排开,给每组定预算,再按产品分别判定。
矩阵的五个组
卷二讨论的所有链路,可以收进五组指标:
| 组 | 回答的问题 | 典型指标 |
|---|---|---|
| 连接质量 | 玩家在网络上的起点有多差 | RTT P95、丢包率、弱网会话占比 |
| 可用性 | 连接断了能不能回来 | 断线率、重连成功率 |
| 状态恢复 | 回来之后状态对不对 | 重连后需补状态比例 |
| 流量 | 一次会话花掉多少 | 每会话流量、每 DAU 日流量 |
| 下载 | 资源能不能顺利到达 | 下载成功率、弱网下载成功率 |
五组不是并列关系,而是一条链:连接质量差会推高断线率,断线率高会放大状态校准成本,状态校准又会增加流量,而下载失败会让玩家根本进不到这条链里。
矩阵的价值,是让这条链上的每一段都有数字,而不是只盯着最末端的结果指标。
把矩阵写成校验
下面的模块把指标、预算与判定写成代码。它不采集数据,只负责回答一个问题:给定一组指标值和预算,哪款产品超标,超在哪一档。
// file: src/lab/net-matrix.ts
// tier: L1
// verify: node --test test/lab/net-matrix.test.ts
export type ProductId = "match3" | "ark";
export type Direction = "lower" | "higher";
export interface MetricRow {
readonly id: string;
readonly group: string;
readonly unit: string;
readonly match3: number;
readonly ark: number;
readonly direction: Direction;
readonly budget: number;
}
export const METRIC_GROUPS = ["连接质量", "可用性", "状态恢复", "流量", "下载"] as const;
export const NET_METRICS: readonly MetricRow[] = [
{ id: "rtt_p95", group: "连接质量", unit: "ms", match3: 310, ark: 240, direction: "lower", budget: 350 },
{ id: "loss_rate", group: "连接质量", unit: "%", match3: 0.4, ark: 0.6, direction: "lower", budget: 2 },
{ id: "weak_session", group: "连接质量", unit: "%", match3: 22, ark: 14, direction: "lower", budget: 25 },
{ id: "disconnect", group: "可用性", unit: "%", match3: 2.1, ark: 1.4, direction: "lower", budget: 2.5 },
{ id: "reconnect_success", group: "可用性", unit: "%", match3: 97.2, ark: 98.1, direction: "higher", budget: 96 },
{ id: "resync_needed", group: "状态恢复", unit: "%", match3: 8, ark: 31, direction: "lower", budget: 35 },
{ id: "session_traffic", group: "流量", unit: "MB", match3: 3.2, ark: 18, direction: "lower", budget: 20 },
{ id: "download_success", group: "下载", unit: "%", match3: 97.5, ark: 98.6, direction: "higher", budget: 97 },
];
export type Verdict = "ok" | "watch" | "fail";
export function judge(row: MetricRow, value: number): Verdict {
const ratio = row.direction === "lower" ? value / row.budget : row.budget / value;
if (ratio <= 1) {
return "ok";
}
if (ratio <= 1.1) {
return "watch";
}
return "fail";
}
export function evaluateProduct(metrics: readonly MetricRow[], product: ProductId): readonly string[] {
const problems: string[] = [];
for (const row of metrics) {
const value = row[product];
const verdict = judge(row, value);
if (verdict === "fail") {
problems.push(`${product}:${row.id}=${value}${row.unit} fails budget ${row.budget}${row.unit}`);
}
}
return problems;
}
export function matrixGaps(metrics: readonly MetricRow[]): readonly string[] {
const covered = new Set(metrics.map((row) => row.group));
return METRIC_GROUPS.filter((group) => !covered.has(group));
}
export function watchRows(metrics: readonly MetricRow[], product: ProductId): readonly string[] {
return metrics
.filter((row) => judge(row, row[product]) === "watch")
.map((row) => row.id);
}
export function formatMatrix(metrics: readonly MetricRow[]): string {
const header = "id\tgroup\tunit\tmatch3\tark\tbudget\tdirection";
const rows = metrics.map((row) =>
`${row.id}\t${row.group}\t${row.unit}\t${row.match3}\t${row.ark}\t${row.budget}\t${row.direction}`,
);
return [header, ...rows].join("\n");
}
judge 的规则很简单:
- 越小越好的指标,
值 / 预算 ≤ 1为ok,≤ 1.1为watch,再高为fail; - 越大越好的指标反过来,用
预算 / 值计算同一比例。
watch 这一档是刻意加的。真实项目里很少有「刚好卡在预算线上」的指标,更多是「超了 3%」——直接判失败会让人麻木,直接判通过又会掩盖趋势。watch 的作用是进入周报,不阻塞发布,但必须写原因。
双案例矩阵
用默认指标跑一遍,得到两张可对照的表:
| 指标 | 消消 | 方舟 | 预算 | 判定 |
|---|---|---|---|---|
| RTT P95 | 310ms | 240ms | 350ms | 均 ok |
| 丢包率 | 0.4% | 0.6% | 2% | 均 ok |
| 弱网会话占比 | 22% | 14% | 25% | 均 ok |
| 断线率 | 2.1% | 1.4% | 2.5% | 均 ok |
| 重连成功率 | 97.2% | 98.1% | 96% | 均 ok |
| 重连后需补状态 | 8% | 31% | 35% | 均 ok |
| 每会话流量 | 3.2MB | 18MB | 20MB | 均 ok |
| 下载成功率 | 97.5% | 98.6% | 97% | 均 ok |
这张表最容易被误读的地方是「均 ok」。它不表示两款产品一样健康,而表示它们各自在自己的约束内。消消的弱网占比 22% 接近预算 25%,方舟只有 14%;方舟的补状态比例 31% 接近预算 35%,消消只有 8%。
同样一条预算线,对两款产品的意义完全不同。 消消的风险在「弱网用户多」,方舟的风险在「重连后要补的东西多」。矩阵的作用不是排名,而是指出每款产品离自己的红线有多远。
缺口检测
矩阵还有一种用法:检查有没有哪一组完全没指标。
matrixGaps 返回没有被任何指标覆盖的组。它看起来是个很弱的检查,实际非常有效——很多团队的网络监控只有「在线人数」「连接数」和「错误数」,五组里只覆盖了半组「可用性」,剩下四组全靠事故驱动。
一个典型的缺失是「状态恢复」:团队会监控断线和重连,却很少有人统计「重连后有多少比例需要补状态」。而这个比例直接决定了重连体验——补得越多,玩家回来看到的世界越不一样。
三档判定怎么用
ok、watch、fail 三档对应三种动作:
| 判定 | 动作 | 责任人 |
|---|---|---|
| ok | 记录基线,不额外处理 | 无 |
| watch | 进入周报,写原因与趋势 | 指标 owner |
| fail | 阻塞发布,先修再发 | 版本负责人 |
watch 最容易被忽略。团队通常只处理 fail,把 watch 当成「还好」。但网络指标的特点是波动大:这个版本 3% 的超出,下个版本可能变成 15%。watch 是趋势的入口,不是免死金牌。
反过来,fail 也不该一律阻塞。如果一款产品正在做大规模弱网优化,短期 RTT 超标可能是预期内的;这时应该在评审记录里写明「带病发布」与恢复时间,而不是偷偷把预算调高。
矩阵怎么维护
指标矩阵不是发布时填一次的表,它随版本滚动:
- 每个大版本重跑一次,保留上一版,用来看趋势。
- 预算随产品阶段调整,但调整必须写原因;悄悄放宽预算等于取消门禁。
- 新增指标先加组,不要把所有东西塞进「其它」。
- 跨产品比较前先对齐口径,尤其是流量与下载成功率。
矩阵的另一个作用是给新功能定预算。上线一个实时排行榜之前,先问它属于哪一组、占多少流量、会不会把 session_traffic 推过 watch 线。没有这一步,功能做完才发现流量超标,只能砍。
别把矩阵做成 KPI
矩阵一旦被当成考核指标,就会迅速失真:
- 指标 owner 会把「接近预算」解释成「波动」,把
fail说成「极端个例」; - 产品会用短期活动把数字做好看,而不解决结构问题;
- 团队会挑容易测的指标上报,把难测的组留空。
避免的方式不是取消矩阵,而是把矩阵的用途限定清楚:它是发布门禁与趋势记录,不是个人绩效。 评审看的是「这个版本能不能发」,不是「谁的指标更好看」。责任人的作用是在超标时解释原因、给修复计划,而不是为数字辩护。
指标从哪来
矩阵本身不采集数据,但每一项指标都要有明确的采集口径,否则矩阵只是填表游戏:
| 指标 | 采集位置 | 采样方式 | 已知偏差 |
|---|---|---|---|
| RTT P95 | 客户端连接层 | 每次请求响应计时,按会话聚合 | 只统计成功的请求,失败请求偏慢 |
| 丢包率 | 客户端与服务端双向 | 序号缺口 / 发送总数 | 服务端视角看不到下行丢失 |
| 弱网会话占比 | 客户端 | 按会话 P95 与丢包阈值判定 | 阈值选择影响结果 |
| 断线率 | 客户端连接状态机 | 前台会话内非主动断开 | 切后台是否计入要统一 |
| 重连成功率 | 客户端 | 30 秒内恢复且完成校准 | 只统计发起重连的设备 |
| 需补状态比例 | 服务端下发 + 客户端确认 | 重连后下发增量的会话占比 | 增量大小不敏感 |
| 每会话流量 | 客户端网络层 | 上行 + 下行字节数 | 不含资源下载 |
| 下载成功率 | 下载器 | 开始到校验通过 | 分母按下载次数 |
采集口径比指标值更重要。同一个「断线率」,包含切后台与不包含,能差出一个数量级;矩阵里每个数字旁边都应该有口径,而不是只有一个数。
一次版本评审的读表顺序
拿到矩阵后,不要从第一行读到最后一行。推荐的顺序是:
- 先看
fail:有没有指标直接阻塞发布,分别属于哪款产品。 - 再看
watch:超预算 10% 以内的项,趋势是变好还是变差。 - 再看
matrixGaps:有没有哪一组完全没数据,这通常意味着监控盲区。 - 最后看跨产品差异:同一指标两款产品差距大,往往不是网络问题,而是产品形态差异。
- 记录结论:通过、带病发布、阻塞,三选一,写清责任人与复查时间。
这个顺序的好处是把「讨论数字」变成「讨论动作」。评审会上最常见的低效,是花半小时争论某个数字准不准,却没决定这个版本发不发。
与相邻章节的关系
本节依赖第 3 章的重连指标、第 7 章的流量账与第 12 章的下载数据。那三章分别给出单点测量方法,本节把它们合并成一张跨产品可复核的矩阵。
上一节 12.3 讲下载失败怎么降级,本节回答「降级之后整体指标是否达标」;下一节 13.2 把矩阵接进交付流水线,变成发布门禁的一部分。
小结
- 网络与资源指标分五组:连接质量、可用性、状态恢复、流量、下载。
- 单看一个数字得不出「正常」结论,矩阵要按组、按预算、按产品分别判定。
- 判定分三档:ok 记录、watch 进周报、fail 阻塞发布。
- 同样一条预算线对两款产品的意义不同,矩阵用来指风险,不用来排名。
matrixGaps检查有没有哪一组完全没指标,比单点指标更容易发现监控盲区。- 矩阵随版本滚动,预算可以调整,但调整必须写原因。
- 新功能立项时先领指标预算,不要等上线后再砍。
阅读导航:上一节:12.3 下载失败的降级与重试 · 下一节:13.2 交付流水线与发布门禁 。
常见错误与边界
- 只监控结果指标。 只看下载成功率和在线人数,无法定位是连接、恢复还是资源的问题;五组指标要一起看。
- 用平均值代替分位数。 平均 RTT 65ms 可能由 60ms 和 800ms 拼成;网络指标至少看 P95。
- 把 watch 当通过。 超出预算 10% 以内不阻塞发布,但必须进周报并写原因,否则趋势会被忽略。
- 预算一年不变。 产品阶段、机型分布、网络环境都在变;预算不更新,门禁会失效。
- 跨产品直接排名。 消消与方舟的会话时长差 5 倍,流量与断线率不能直接比大小。
- 把矩阵当监控系统。 本节产物只做判定,不采集数据;采集仍要靠埋点与监控平台。
- 指标没有 owner。 没有责任人的 watch 项,下个版本大概率变成 fail。
- 新增指标塞进「其它」。 分组是判断链的一部分,塞进其它等于放弃判断。
- 为了过门禁调预算。 调整预算必须与指标变化一起评审,不能只改数字。
- 忽略机型与区域分层。 全局 97% 的下载成功率,可能在低端机上只有 88%;矩阵应能按分层重算。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。