第 15 章 联调与全链路走通
副题:把整条路串起来——一次请求的完整旅程,第三部分收官
第 8 到第 14 章,我们放大看了请求旅程的每一段:浏览器怎么渲染、网络怎么传、服务端怎么接、数据怎么存。每一段各自都能跑——现在把它们连起来。本章做两件事:第一,解决"连起来就不通"的联调问题;第二,把整条路完整走一遍——图 8-1 那张旅程图,第一次全部点亮。
第三部分地图进度(图 8-1):浏览器站 ✅(8-9)→ 网络站 ✅(10-11)→ 服务端站 ✅(12-14)→ 本章:全链路收官——前面七站各自放大看完了,现在退远一步,看整条路。
本章要建立的认知
- 联调问题不是"运气差",是三类问题的集合:契约问题(字段对不上)、环境问题(本地好的线上不行)、协作问题(各改各的)——本质都是第 7 章"接口是承诺"的违约;
- 一次请求的完整旅程:从老周点击到页面渲染,每一站都有前面章节的机制在管——本章把它们串成一条线;
- 联调走通 = 阶段 1 的验收前置:第 4 章的验收标准,逐项在这里兑现——三周 MVP 的最后一公里(从"各自能跑"到"一起能跑")。
15.1 各自能跑,连起来就不通
三周 MVP 的第二十天。前端、后端、数据库——各自单独跑都好好的:小明在本地能发内容、能看列表、能评论。明天就是上线日,今天要做的事:把前后端部署到测试环境,走一遍完整流程。
下午三点,列表页白屏。
小明打开控制台,第一条报错:TypeError: Cannot read properties of undefined (reading 'author_name')——前端代码里读 author_name,接口返回的字段里没有它。翻开后端代码一看:接口返回的是 authorName(驼峰),前端要的是 author_name(下划线)。第 7 章契约里写的是 author_name——但后端实现时手滑写成了驼峰,前端照着契约写,联调就对不上。
修完字段,第二个问题:"我本地是好的啊"——小明在自己电脑上跑前端,一切正常;部署到测试环境就白屏。排查到最后:本地的前端连的是本地数据库(有小明自己造的一堆测试数据),测试环境的前端连的是测试数据库——空库。列表接口返回空数组,前端拿到空数组渲染空页面,看着像"白屏"。不是代码问题,是环境的数据不一样。
第三个问题接踵而来:CORS——浏览器报 No 'Access-Control-Allow-Origin' header is present。第 12 章提过这个配置:前后端是两个域名(前端域 + api 域),浏览器跨域请求需要服务端配 CORS——本地开发时小明用一个域名跑前后端(没触发跨域),测试环境分了两个域名(触发跨域)——又是"本地没遇到、测试环境才暴露"。
三个问题,一个下午。修完天黑了。小明把这三个问题写在便签上:字段对不上、环境不一样、配置没配齐——它们有一个共同的名字,叫联调。
联调的工具也提一句(它是契约的手工验证形态):Postman/curl——不打开浏览器就能调接口、看返回(第 7 章的契约在工具里逐条验证:这个方法返回什么、那个错误码什么时候出)——联调的一半是"接口对不对",工具负责这一半;另一半是"页面通不通",才需要浏览器。
联调的时间账也先算一笔(它是"联调为什么痛"的量化):三周 MVP 里,联调占了三到四天——十分之一以上的工期花在"把各自能跑的东西连起来"上。这个比例不是小明的项目特有:任何多端系统的联调都是工期的大块支出。所以"减少联调时间"不是偷懒,是实打实的工期优化——第 7 章花整章讲契约、本章花整节讲基本盘,都是在砍这笔账。
其实还有第四个问题,在验收时才暴露:时间格式——列表页要显示"3 分钟前",后端返回的是 ISO 字符串,前端要的是时间戳(或反过来)——第 7 章契约里"时间字段用什么格式"这一行没写,两边各自猜了一个。契约里没写清楚的最小细节,都是联调的雷:时间格式、分页起点(1 还是 0)、空值返回什么(null 还是空串)——这些"小事"第 7 章看起来不重要,联调时每一件都是半小时。
联调的日常形态也补一句(它不只是上线前的集中战):开发期的高频联调(每写完一个接口就前后端对一次——第 12 章说"接口从两三个开始长",每长一个都对一次)和上线前的集中联调(15.1 的这场)是两种形态——高频联调把问题摊薄在开发期,集中联调把问题堆到上线前——前者的痛是每天一点点,后者的痛是一个下午全部爆发——本章的联调清单,两种形态通用。
上线前夜的氛围也补一笔(它是联调故事的真实底色):最后一天改代码是最危险的——修字段时顺手"优化"了别的逻辑,改一处崩一处。联调期的纪律:只修联调暴露的问题,不做任何顺手优化(新功能/重构留给上线后)——"顺手"是上线前事故的头号来源,这个教训第 20 章代码质量还会用上。
15.2 联调问题是什么问题
联调(前后端/多系统联合调试)的痛,痛在"各自都对,合起来错"。但把 15.1 的三个问题放一起看,它们其实不是"运气差",是三个可归类的问题:
契约问题:字段名、参数、错误码、分页规则——两边各写各的,合起来对不上。第 7 章说过"约定不清楚,联调就是灾难"——现在看到了灾难现场:契约写在文档里,但代码没照着文档写(手滑写驼峰),或者文档本身没写清楚(分页从 1 还是 0 开始,前端猜一个,后端实现另一个)。
契约问题的占比也先给个判断(它决定你把力气花在哪):联调问题里契约问题占大头(七成以上)——环境问题靠配置管理能压下去,协作问题靠流程能缓解,只有契约问题每一条都要人到场改。所以第 7 章花整章讲契约,不是"接口洁癖",是把联调的大头成本前置消灭——"第 7 章多写一行契约,第 15 章少吵半小时",这个换算关系是本书讲契约的底气。
契约问题还有三种形态(诊断时细分):文档没写(时间格式、空值语义——15.1 的第四问题);代码没照写(文档写了 author_name,实现写成 authorName——手滑或想当然);两边理解不同("删除"是软删还是硬删?"分页"的 size 上限是多少——同一句话两个理解)。三种形态的修法都一样:回到契约,把话说死——第 7 章的"契约是唯一真相"在这里是操作手册。
环境问题:"本地是好的"——本地和测试环境的差别(数据库数据、域名、配置项、依赖版本),让"本地通过"变成"部署后失效"。环境不一致的本质:代码对环境的假设,在另一个环境里不成立。
协作问题:前端和后端(或两个人/两个阶段)各自开发,联调时才第一次见面——第 7 章说过,一个人开发时接口随便写,第二个人加入冲突才爆发。联调是"冲突集中结算的时刻"——之前欠的账(没说清的约定、没对齐的命名),联调时一次性还。
这三个问题有一个共同的根:接口是两边之间的承诺(第 7 章),联调问题就是承诺违约——承诺没写清(契约问题)、承诺的语境变了(环境问题)、承诺双方没对过(协作问题)。所以联调的解法,不是"联调时多加班",是把承诺在联调之前就管好——这正是第 7 章花了整章讲契约的原因:第 7 章是"联调问题的事前投资",本章是"事后结算"。
联调还有个收尾动作叫复盘(它让联调一次比一次短):联调结束时,把这次暴露的"契约没说清"的新问题补进契约(第 7 章版本化:新约定进文档、两边同步)——联调不只发现问题,还让契约变厚:第一轮联调三天的项目,第二轮可能只要一天——契约是越用越厚的,联调是越用越短的。
诊断表也立一个(联调现场用的):
| 症状 | 归类 | 解法方向 |
|---|---|---|
| 字段/参数对不上、报 undefined | 契约问题 | 查契约→改代码或改契约(第 7 章) |
| 本地好的、测试/生产不行 | 环境问题 | 比配置/比数据/比版本(15.3) |
| 两边各改各的、联调才碰头 | 协作问题 | 契约先行+联调清单(15.3) |
先归类再动手——把问题归到正确的类别,解法方向就出来了;不归类就动手,容易"修了症状没修病因"(比如环境问题当成代码问题改,改完还是不行)。
还有两个边界的澄清(联调话题里的常见疑问):单人开发也有联调——自己写前端+自己写后端,前后端之间同样有契约(第 7 章说过,第二个人加入时冲突才爆发;其实第一个人也会爆发——三周后的自己就是"第二个人",忘了当初约定的字段名,联调时照样对不上);联调的另一个名字叫集成——多系统连起来叫"系统集成",第 26 章拆服务后,服务之间的联调(接口对接、契约版本)是同一个问题的更大规模版本。
15.3 联调的三个基本盘
知道了问题是什么,解法就是三个基本盘——都是前面章节已经铺好的:
基本盘一:契约先行(第 7 章)。接口清单、字段命名、错误码、分页规则——在写代码之前定好,前后端照着同一份契约写。联调时对不上,先查契约:是代码没照契约写(改代码),还是契约本身有歧义(改契约,并让两边同步)——这个"先查契约再吵架"的顺序,能把联调时间砍掉大半。契约不只在文档里:第 7 章的接口清单就是联调清单的骨架——每个接口一行:方法、URL、参数、返回、错误码、权限(第 13 章那张权限表)。
基本盘二:环境分层(图 15-1)。本地(local)→ 测试(test)→ 生产(prod)三层环境,各有各的用途:
本地(开发机) → 代码最新、数据随意、随便折腾
测试(测试服务器) → 接近生产的配置、真实流程走查(联调的主战场)
生产(正式服务器) → 用户在用、改动要流程(第 23 章部署)
环境分层的意义:联调只在测试环境发生——本地是开发的自留地,生产是用户的地盘,测试才是"模拟真实"的舞台。环境问题的大头(15.1 的"本地好了"),靠两件事缓解:配置集中管理(数据库地址、域名这些环境相关的配置,不写死在代码里——第 12 章说过环境变量,这里它是联调的第一课)和测试数据准备(测试环境有固定的造数据脚本——空库不是环境,空库是事故)。
环境分层还有一个"矩阵"问题(联调经验里的隐形杀手):组合爆炸——前端 × 后端 × 数据库 × 浏览器,每个都有版本/环境维度,全组合起来根本测不完。解法不是"全测",是锁定矩阵:测试环境固定一套组合(Node 版本、数据库版本、浏览器用主流的两个),只在锁定组合上联调——"在锁定组合上跑通"比"在所有组合上跑通"现实得多,剩下的组合交给第 23 章部署时的灰度。
测试环境的域名也要单独提一句(它是 15.1 的 CORS 事故的解法落地):测试环境用自己的域名(test.案例.com),CORS 配置在测试环境就按生产的样子配好——本地一个域名不触发跨域、测试环境两个域名才触发,这种"本地永远测不到"的配置问题,只有让测试环境"长得像生产"才能暴露——环境分层的意义:测试环境是生产的彩排,不是本地的复制品。
环境问题的另一半是依赖版本漂移:本地的依赖版本和测试环境不一样("我本地跑得好好的"——因为本地是 Node 18,测试环境是 Node 16)——解法是锁版本(lockfile:把每个依赖的精确版本锁进仓库,部署时照单装)。"版本漂移"是环境问题里最隐蔽的:它不报错、不提示,只是行为不一样——锁定依赖版本,是环境一致性的第二道保险(第 20 章依赖管理会展开)。
测试环境的数据策略也补两句(它决定联调时看到什么):造数脚本(seed)——固定的测试账号(老周/小明)、固定的内容与评论,让每次联调都在已知数据上走;重置能力——测试数据被改乱了,一键恢复到初始状态(脚本重跑一遍)。测试环境的"已知"比"真实"重要:数据已知,才能判断"接口返回对不对"。
基本盘三:联调清单。上线前把"必须走通"的路径列出来,照着走:登录 → 发内容 → 列表看到 → 评论 → 详情 → 删除(只有作者)→ 登出——这七步覆盖了第 7 章的核心接口、第 13 章的 401/403、第 14 章的读写。清单的价值:联调从"想到哪调到哪"变成"照着单子走"——漏掉的路径,就是上线后用户替我们发现的 bug。
清单怎么从第 7 章的 12 个接口里裁剪出来(一句话的裁剪逻辑):核心路径全覆盖 + 边界路径挑着走——发内容/评论/列表/详情是核心(天天用),删除/权限(403)是边界(不常用但必须对),登录/登出是门禁(第 13 章主角)——12 个接口缩成七步,走的还是那条主干道。
联调清单的自动化版是契约测试**(第 18 章测试体系会展开)**:把"字段对得上、状态码对得上"写成自动化的断言,联调时跑一遍——人工清单是第一次,自动化是第 N 次(每次改动后都能跑)。阶段 1 先用手工清单,第 18 章补自动化——清单的进化路径:纸面 → 脚本 → 流水线(第 22 章)。
前端还有一个"不等后端"的联调形态:Mock——后端接口还没好时,前端先用造好的假数据(mock)开发,接口好了再切真——第 15.1 的"字段对不上"在 mock 形态下不会发生(mock 照契约造)。Mock 的边界要记得:mock 是"按契约造数据",不是"绕开契约"——mock 和真实接口都该以契约为准,否则 mock 阶段省的时间,联调阶段加倍还。
联调还有一个前置动作(第 20 章 Code Review 的伏笔):联调前先互相看一遍代码——后端看前端怎么调接口(有没有把字段拼错)、前端看后端返回了什么(有没有藏字段)——"先看代码后联调"能提前发现一半的契约问题(第 20 章会说:Code Review 是质量的第一道闸,这里它是联调的第一道闸)。
15.4 一次请求的完整旅程
联调的三天里,小明把请求从头到尾走了无数遍。现在,把这条完整的路画出来(图 15-2)——图 8-1 那张旅程图第一次全部点亮,每一站都标上它的机制(和它所在的章节):
老周在浏览器打开首页
│
├─ ① 浏览器:输入网址 → DNS 问路(第 11 章:递归解析找到 IP)
├─ ② 网络:TCP 三次握手 + HTTPS 加密(第 10 章:连接四段)
├─ ③ CDN:静态资源就近返回(第 11 章);动态接口回源
├─ ④ 服务端入口:中间件管道(第 12 章:日志→解析→鉴权)
├─ ⑤ 认证门禁:凭证验证(第 13 章:Session 查表,无效 401)
├─ ⑥ 路由+控制器:URL → 处理函数(第 12 章:第 7 章的契约在这里落地)
├─ ⑦ 业务层:决策与编排(第 12 章:校验、敏感词、调数据层)
├─ ⑧ 数据层:查询走索引(第 14 章:EXPLAIN 过的复合索引)
├─ ⑨ 响应原路返回:DTO 组装 → 中间件 → 网络 → 浏览器
└─ ⑩ 浏览器:渲染管线(第 8 章:解析→树→布局→绘制)+ 框架更新界面(第 9 章)
这张图和图 8-1 是同一张图(一级图#3 的两次出现):第 8 章首现时,只有浏览器站亮着,其余是虚线;本章完整点亮——图 8-1 说"一次请求旅程"时它是个承诺,第 15 章是承诺兑现。一级图资产的"放大/点亮"机制(第 4 章铁律),在这里完成了它最大的一次应用。
这一条线,就是第 8 到第 14 章的全部内容——七章各自放大看的一段,在这里拼回一条完整的路。有几个"旅程感"的细节要体会:
把十站走一遍文字版(图是地图,文字是路线——用登录后发一条内容做例子):老周输入网址回车(① DNS 问路,第 11 章)→ 浏览器和服务器握手并加密(② 第 10 章,连接段是 8 秒事故的原址)→ 静态资源从 CDN 就近取、接口请求继续走(③ 第 11 章)→ 请求进服务端过中间件管道(④ 第 12 章,日志记下了这条请求)→ 鉴权中间件查 Session 表,老周的凭证有效(⑤ 第 13 章,req.userId=1)→ 路由把 POST /v1/contents 送到控制器(⑥ 第 7 章的契约在这里变成代码)→ 业务层校验+编排(⑦ 第 12 章)→ 数据层 INSERT 走约束和事务(⑧ 第 14 章)→ 响应带新内容 id 原路返回(⑨ DTO 组装,第 12 章)→ 浏览器收到响应,前端框架更新界面,老周看到"发布成功"(⑩ 第 8/9 章)。十站,每一站都有名字和章节——这就是"看懂一个 Web 系统"的完整形态。
老周视角的同一段旅程(他是这条路的另一端):他只看到"点了一下,出来了"——技术旅程的十站,用户旅程只有两步(操作、结果)——第 1 章的业务线与技术线在这里交汇:用户眼里的"发布了",是技术线十站全部正常的最终输出。
请求是"有去有回"的:去程(请求)和回程(响应)走同一条路的机制,但回程会"轻一点"——响应不需要再鉴权(凭证在去程验过了)、响应可能走 CDN(静态化内容)、响应在浏览器端还有一整段渲染(第 8 章)——去程是"谁来了",回程是"给了什么"。
每一站都有"卡住"的方式:DNS 找不到(第 11 章)、握手超时(第 10 章)、中间件顺序错(第 12 章)、凭证无效(第 13 章)、查询全表扫描(第 14 章)——第三部分每一章的"事故",都是这条路上一个站点的故障。联调排障的本质,就是"判断卡在哪一站"。
旅程的性能视角(第 16 章的伏笔,先立个账):每一站都花时间——DNS 是毫秒级(本地缓存后更快)、连接是往返延迟(第 10 章)、TTFB 是服务端处理(中间件+业务+数据)、下载是响应大小(第 11 章 CDN 就是为这一站)。第 4 章的预算(P95<2s)就是这条路的时间总账——第 16 章做性能优化时,"哪一站超支"就是优化清单。
把 2 秒预算摊到各站看一眼(第 12 章层内预算的旅程版):DNS 约 50ms(第 11 章,本地缓存后更快)、连接约 100ms(第 10 章,Keep-Alive 后省掉)、TTFB 约 200ms(第 12 章层内预算:中间件 10 + 控制器 5 + 业务 20 + 数据 150 ≈ 185ms,大头是数据层)、下载约 1.5 秒(第 8 章 CSS 拆分前是 2MB、拆分后达标)——每一站都有它的主人(第 10 章说过),每一站都有它的预算——这就是"拿预算逐段量"(15.6)的账本。
旅程里其实有两条路(静态与动态的区别,第 11 章深化):静态资源(HTML/CSS/JS/图片)——CDN 就近返回,几乎不走完整链路(第 11 章的 50 个资源从哈尔滨拉,是这条路的旧账);动态接口(内容/评论/下单)——必须走完整链路(要鉴权、要查库、要新鲜数据)。"全链路优化"优化的其实是动态路,静态路由 CDN 管——两条路的账单分开记(第 16 章会用到)。
读请求和写请求的旅程也不同(第 10 章方法语义的落地):GET(读)——可以缓存(CDN/浏览器缓存都能参与),可以走"轻链路"(数据不变时);POST(写)——必须到服务端、必须过事务(第 14 章),不能缓存(第 10 章说过 POST 的语义)——读的旅程可以抄近道,写的旅程必须走完全程——这也是为什么"列表页"比"发布按钮"快那么多。
缓存的机制落地也顺带一句(第 10 章提过 Cache-Control,第 16 章深化):GET 响应带上缓存头(Cache-Control),浏览器/CDN 就知道这份内容能存多久——"读可以抄近道"在协议层就是这一个头——第 16 章做性能优化时,它是"让读更快"的第一块砖。
旅程与第 1 章地图的回扣:第 1 章的全书技术地图有三条线(业务线/技术线/工程线)——旅程图是技术线的完整形态(浏览器→网络→服务端→数据层的观察顺序,就是第 8-14 章的章节顺序)。第 1 章画技术线时它还是虚线,到本章它变成了一条走过一遍的实线。
评论接口的走查(第 14 章留的作业,现在兑现):联调清单里"评论"这一站,走查时特意看了一眼执行计划——第 14 章建的 content_id 索引在日志里出现了(Index Scan)——第 14 章的检查单不是纸面的,它真的在联调里被验证了一次。
白屏的两种判别也顺手记下(它是联调现场最常见的"看着像同一个问题"):第 8 章的白屏是性能问题(JS 没跑完/资源加载慢,页面空白);本章的白屏是数据问题(接口返回空/报错,前端渲染失败)——判别一句话:看 Network 面板——请求失败/超时是数据问题(走旅程排查法),请求成功但页面空是渲染问题(走第 8 章关键路径)。同一个症状,两条路。
15.5 旅程排查法:卡在哪一站
联调排障的方法,其实第三部分已经全部给过了——现在收成一套完整的"旅程排查法"(它就是把前面章节的排查方法串起来):
第一问:断在哪一段?(第 10 章网络四段:DNS → 连接 → TTFB → 下载)——打开 DevTools 的 Network 面板,看请求停在哪一段:DNS 段失败是域名问题(第 11 章),连接段失败是网络/防火墙(第 10 章),TTFB 慢是服务端(第 12/14 章),下载慢是内容大(第 16 章)。"慢"和"断"都能用这张四段图定位到段。
DevTools 的 Network 面板就是四段图的工具形态:每一行请求都拆成四段(DNS 查找/连接/TTFB/下载),鼠标悬停就看得到每段耗时——第 10 章画四段图时它是示意图,这里它是面板上的真实数据——第 10 章的图就是 DevTools 的说明书,反过来也成立。
第二问:服务端卡在哪一层?(第 12 章定位法)——TTFB 慢时:先看数据访问层(EXPLAIN,第 14 章),再看业务层(逻辑慢),最后看中间件(鉴权慢,第 13 章)。分层不只是代码结构,是排障地图(第 12 章说过,这里实战)。
第三问:数据卡在哪个环节?(第 14 章)——EXPLAIN 看执行计划:Seq Scan(索引问题)、rows 离谱(统计问题)、慢查询日志(有没有被记录)。数据层的三件套(EXPLAIN/索引/慢日志),是旅程最后一段的检查表。
三问走完,90% 的联调问题能定位到"某一站的某一个机制"。这套方法的全部零件都在第 8-14 章出现过——第 15 章只是把它们拼成了顺序。这也是第三部分的收官意义:你学到的不是一个一个孤立的知识点,是一条可以逐段排查的路。
一个例外要提前声明(防止三问被误用):安全类问题不走三问——接口"看起来正常但数据泄露"(越权/注入),三问定位不了(它不在旅程的"断/慢"维度里,在"错"的维度里)——那是第 19 章安全章的主场:三问管"通不通、快不快",安全管"对不对"。
排查法的"频率"也交代一句(它决定你花多少时间在这上面):联调期三问是全程用的(每次报错都走一遍);上线后三问交给监控自动化(第 25 章:接口延迟、错误率、慢查询自动报警——报警就是"第一问"的自动化,日志链路就是"第二问"的自动化)——人工排查 → 自动化监控,是同一套三问的两次形态。
把 15.1 的三个问题用三问法重走一遍(看方法怎么用):字段对不上——第一问"断在哪一段":请求到了服务端(TTFB 正常),不是网络段;第二问"服务端哪一层":控制器返回正常,是 DTO 组装时字段名错了——契约问题,改代码;"本地是好的"——第一问:请求完全正常(没有断),是数据问题——第二问、第三问都不用走,直接查环境(配置/数据/版本)——环境问题,不在旅程里,在环境分层里;CORS——第一问:请求发出去了但浏览器拦截(Network 里能看到请求,Console 报 CORS)——这是浏览器安全模型在"旅程之外"的一站(第 19 章安全会看到它的全貌)——配置问题,补 CORS 头。三问法不是万能,但它的价值是把"乱猜"变成"先定位再动手"。
request_id 在联调里的用法(第 7 章埋的,第 25 章会展开):联调时前后端对不上,两边各看各的日志——第 7 章的错误响应里带着 request_id,前端把报错里的 request_id 发给后端,后端按 id 找到那次请求的完整日志(中间件记了哪站进来的、业务层记了决策结果、数据层记了查询耗时——第 12 章说过每层打日志)——联调吵不起来的秘密:两边对着同一个 id 说话。
复现的纪律也补一句(它是排查的"先把问题变稳定"):联调报错,先复现再修——复现三要素:请求(什么接口什么参数)、数据(哪条数据触发)、环境(哪个环境)——三要素齐了,问题才"稳定",稳定的问题才修得掉;"偶尔出现"的问题(第一次没记参数、第二次没看环境)最难修——排查的第一步,是让问题能稳定地出现。
15.6 MVP 验收与上线
联调走通,最后一天:验收。第 4 章的三周 MVP 到了结算时刻——验收标准逐项过:
功能验收(第 4 章需求清单):注册登录、发布内容、列表、详情、评论、删除——联调清单的七步就是功能验收的走查路径,全部通过。第 7 章的 12 个接口全部联调通过——接口清单就是功能验收的代码侧。
非功能验收(第 4 章验收标准):首页 P95 < 2s——用第 15.5 的排查法量了一遍:网络段 + 服务端 + 数据层,全链路在预算内(第 12 章层内预算、第 14 章 150ms 数据层预算都在线内);4G 首屏 3 秒——第 8 章的关键渲染路径优化(2MB CSS 拆分)兑现了它的承诺。非功能验收不是"感觉快",是拿预算逐段量——第 4 章定预算、第 8-14 章建机制、第 15 章逐段验收。
验收标准和需求清单的逐行对照也走一遍(它是最容易被跳过的动作):第 4 章需求清单每一行,都有一个验收动作——"发内容"→ 联调七步里点一遍;"内容不能丢"→ 看事务和备份机制(第 14 章);"首页要快"→ 拿 P95 预算量;"登录 7 天"→ 登录后隔天再看登录态(第 13 章滑动过期)。验收不是"整体感觉行不行",是清单逐行的"接没接住"——第 4 章说"验收标准是未来的测试用例",第 15 章就是那批用例的第一次执行。
验收单的形态也立一个(它是第 4 章清单的表格化):一行一项:需求 → 验收动作 → 结果(通过/不通过)——不通过的项,要么修(代码没接住),要么回到第 4 章砍掉(需求本身不可行——验收也是需求清单的复审)——验收单上"不通过"的每一行,都是第 4 章到第 15 章之间某一步的返工信号。
约束验收(第 4 章约束清单):数据不能丢(第 14 章事务+备份)、登录态 7 天(第 13 章)、交易后置(第 6 章表先建好——order/payment 表安静地等着阶段 3)。约束是"没有也要验"的——数据不能丢不是"没丢过"就通过,是"机制在"才算数。
验收还有一个"另一面":老周是验收人(第 4 章定的)——他不懂契约、不懂索引,他只看两件事:能不能用、卡不卡。功能清单过完,让老周自己点一遍——他点的路径可能和联调清单不一样(用户不会照清单走),他在清单外发现的每个问题,都是清单的盲区——这是"用户验收"和"技术验收"的区别:技术验收证明系统符合契约,用户验收证明契约符合需求。
上线后的第一天(第 25 章可观测性的前哨):看三样东西——错误日志(有没有联调没覆盖的路径报错)、接口延迟(第 15.4 的旅程账,哪一站超预算)、用户行为(老周在用吗?他卡在哪一步?)。上线不是结束,是"问题从已知变成未知"的开始——联调时问题是我们列出来的,上线后问题是用户替我们发现的——第 25 章的所有工具,都是为了"让未知的问题尽快已知"。
**验收通过。三周,从一句话需求到能用的产品。**老周打开首页,看到了自己的第一条内容——阶段 1 的最后一个关键事件:MVP 上线。第 4 章到第 15 章画的这张图(业务→需求→方案→表→接口→一次请求的完整旅程),在用户面前第一次完整地运转起来——也是全书第一次:六张表、十二个接口、十站旅程,全部真实联动(第 6/7/8 章各自建的积木,第一次一起动)。老周不知道这些——他只知道"能用了"——但读者知道:你刚刚看着这十站走完了全程。
三周的时间线也收个尾(第 4 章的三周承诺兑现):第 1-2 周写代码(第 6-14 章的内容,边写边建表、边调接口)——第 3 周联调+验收(第 15 章的本周)——三周里最累的是第 3 周(联调日),但它也是把"各自能跑"变成"一起能跑"的一周。第 4 章说"三周 MVP"时它是个计划,第 15 章它成了事实——计划的兑现,靠的就是这张表里每一行的验收。
上线这个动作本身也有几笔要交代(细节在第 23 章部署展开,这里先立认知):上线=把代码放到生产环境并让它跑起来——第 15.3 的环境分层里,代码从测试环境"向上走"到生产;MVP 上线最容易被忽略的意义:它把"我们以为对的"变成"用户检验的"——第 4 章砍需求时的判断(先社区后交易),从第 15 章起接受真实用户检验。上线这个动作本身只有几分钟,但它的准备(检查单)、它的后续(监控、反馈),把这几分钟放大成了持续的工作——这就是为什么部署(第 23 章)和可观测性(第 25 章)各自要占一整章。
上线的检查单也先列个轮廓(第 23 章填细节):域名解析生效(第 11 章)、HTTPS 证书配好(第 10 章)、数据库迁移跑过(第 6 章)、备份机制在(第 14 章)、配置指向生产——每一项都是前面章节埋的一颗螺丝,第 23 章部署时它们拧成一次发布。
数据层的"历史时刻"也记一笔(它是图 6-2 数据流的第一次真实运转):老周的第一条内容,写进了 content 表的第一行——第 6 章建的表、第 14 章的约束和事务,在这一刻第一次被真实数据填满。从第 6 章建表到第 15 章第一行真实数据,中间隔了九章——这九章就是"从结构到运转"的全部距离。
15.7 本章对应表
| 业务诉求 | 技术选择 | 为什么 | 代价/取舍 |
|---|---|---|---|
| 各自能跑连起来不通 | 联调(契约/环境/协作三归类) | 联调问题=接口承诺违约(第 7 章回扣) | 联调本身是流程成本 |
| 字段/参数对不上 | 契约先行(接口清单=联调清单) | 第 7 章事前投资,本章事后结算 | 契约要维护(版本化) |
| "本地是好的"线上不行 | 环境分层(本地/测试/生产) | 代码对环境的假设要显式管理 | 三层环境要维护(配置/数据) |
| 漏掉路径上线才暴露 | 联调清单(七步走查) | 从"想到哪调到哪"到"照着单子走" | 清单要跟着接口演进 |
| 一次请求怎么完整走 | 旅程集中展示(图 15-2) | 第 8-14 章拼成一条路,排障有地图 | 每站机制要回指原章复习 |
| MVP 怎么算完成 | 验收清单(功能/非功能/约束) | 第 4 章验收标准逐项兑现 | 非功能要"拿预算量"不"凭感觉" |
每一行都在本章正文里有完整论证:联调事故在 15.1,三归类在 15.2,三个基本盘在 15.3,完整旅程在 15.4,排查法在 15.5,验收在 15.6。先看"业务诉求"列——这一章的每一行,都是从"各自能跑,连起来就不通"那一天长出来的。
本章与第 17 章的边界一句话:联调是人工走查(人照着清单点),测试是自动化验证(机器照着断言跑)——联调发现问题,测试防止问题(第 18 章把第 15 章的手工清单变成断言)。
第三部分到这里收官,对两类读者的收成也总结一笔:前端读者——旅程的浏览器端两站(渲染/框架)和第 15 章的排查法,让"页面坏了"变成"能定位到站";后端读者——服务端三站(分层/认证/数据)和旅程图,让"接口背后的世界"有了完整地图;两类读者共同的收获:第 8-15 章八章,把"一个 Web 系统怎么运转"从头到尾走了一遍——这就是北极星目标里"看懂一个 Web 系统"的完整答案。
本章小结
联调速查(第三部分收官,回指全部分时翻回这里):
- 联调问题三类:契约(字段对不上)/ 环境(本地好了)/ 协作(各改各的)——本质是接口承诺违约(第 7 章);
- 三个基本盘:契约先行(接口清单=联调清单)/ 环境分层(本地/测试/生产)/ 联调清单(七步走查);
- 一次请求完整旅程十站:DNS→连接加密→CDN→中间件→鉴权→路由→业务→数据→响应→渲染(图 15-2,各站回指第 8-14 章);
- 旅程排查法三问:断在哪一段(第 10 章四段)→ 服务端哪一层(第 12 章)→ 数据哪个环节(第 14 章 EXPLAIN);
- MVP 验收:功能(12 接口联调通过)/ 非功能(P95<2s、首屏 3s 拿预算量)/ 约束(数据不能丢、交易后置)——第 4 章验收标准逐项兑现;
- 阶段 1 收官:MVP 上线——从一句话需求到能用的产品(三周时间线兑现,第 4 章承诺落地)。 (六条速查对应本章结构:1-2 是"联调怎么想",3-4 是"路怎么走/怎么排障",5-6 是"怎么算完成、阶段怎么收"——第三部分读完翻回这里,本章是整部分的出口和索引;后续章节回指第三部分时,按"性能回 10/14、认证回 13、分层回 12、全链路回 15"找站。)
第三部分(第 8-15 章)总结:八章走完一次请求的完整旅程——第二部分(第 4-7 章)把它"设计出来",第三部分把它"运转起来",两个部分合起来:从一句话需求到一次真实请求,全书地图的右半边走完了。第四部分开始,系统上线了、用户来了、问题也来了——第 16 章从"系统变慢"开始。
回到第 1 章的地图(它是全书的起点):第 1 章说这本书的地图有三条线(业务线/技术线/工程线)——第二部分走完了业务线(需求→方案→设计),第三部分走完了技术线(浏览器→网络→服务端→数据→全链路)——两条线在第 15 章交汇:一次请求旅程,就是业务和技术在这本书里的第一次握手(工程线从第四部分开始走)。
第三部分回指索引(给第四部分用):性能问题回第 10/14 章(四段/索引),认证问题回第 13 章,分层问题回第 12 章,全链路问题回本章三问——第四部分(第 16 章起)会反复引用这张索引。
- 联调问题不是运气差,是三类问题的集合(契约/环境/协作)——本质是第 7 章"接口是承诺"的违约;
- 一次请求的完整旅程:十站、七章、一张图——第 8-14 章每一站的机制,在这里串成一条路;
- 旅程排查法三问:断在哪一段 → 卡在哪一层 → 数据哪个环节——零件都在前面章节,本章拼成顺序;
- MVP 验收:第 4 章的验收标准逐项兑现——功能走查、非功能拿预算量、约束验机制;
- 阶段 1 结束,MVP 上线——下一站,用户开始增长(第 16 章性能优化,阶段 2 开篇:首页变慢的演进条件正式触发)。
下一章
三周 MVP 上线了。老周用得很开心,把产品介绍给了朋友,朋友又介绍给朋友。注册用户开始涨——从几十到几百到几千。
第 4 章定的验收预算(首页 P95 < 2s)开始被突破:内容多了、图片多了、同时在线的人多了——首页从"秒开"变成"转圈"。第 14 章决策记录里写的演进条件("首页变慢信号出现再上缓存"),在这一章触发。
第 16 章,性能优化与缓存:阶段 2 开始——用户增长,系统变慢,把第 10/11/14 章埋的伏笔一次收齐。
写到这里,第三部分八章全部完成(第 8-15 章);第四部分开始前,停一下看一眼全书进度:第 1-3 章打地基、第 4-7 章设计系统、第 8-15 章走通请求——下一位乘客已经排队:阶段 2 的用户增长,会让这条刚刚走通的旅程开始堵车。