跳转到内容

从需求到稳定运行:工程全景

前面的笔记按主题拆开解释概念。这一篇换一种方式:只用一个案例,把分散的知识重新放回完整链路。目标不是再列一遍技术名词,而是让你看到一个功能从需求、代码和数据,走到浏览器、服务器和线上运行环境时,每一层分别在回答什么问题。

案例故意做得很小。它不是完整产品,但已经包含保存、查询、权限、失败处理、部署和可选模型能力。把这条链路走通以后,换成支付、聊天、内容平台或 Agent 任务,变化的只是业务规则和处理成本,排查问题的位置并没有变。

1. 先建立一张能复述的全景图

跳转到“1. 先建立一张能复述的全景图”

一个在线系统可以先用下面的顺序理解:

用户目标
↓
浏览器交互
↓
DNS / TLS / CDN / WAF
↓
负载均衡 / API
↓
应用服务
↓
数据库 / 缓存 / 队列 / 对象存储 / 模型服务
↓
部署、日志、指标、trace、备份与回滚

请求向下走,反馈向上走。用户看到的结果只是最后一层;真正决定系统能否稳定运行的,是每一层怎样处理输入、保存状态、返回输出,以及失败时怎样收场。

这张图不是组件清单。图上每增加一个框,就多一套部署、监控、权限和故障模式。小系统不需要一开始就有缓存、消息队列、微服务和向量数据库。更可靠的做法是先画出最小主链路,再根据证据增加组件。

还有几项能力横穿所有层:

  • 身份与权限:谁能读、写、删除什么。
  • 安全:输入、文件、网络请求、密钥和模型输出是否可信。
  • 测试:正常路径、异常路径和边界条件怎样验证。
  • 版本与交付:代码、配置、数据库迁移能否一起发布和回滚。
  • 可观测性:线上是否真的按照设计在工作。
  • 成本:请求、存储、模型调用和运维人力是否可持续。

2. 案例:一个学习资料收藏夹

跳转到“2. 案例:一个学习资料收藏夹”

假设要做一个只给自己和小团队使用的收藏夹,用来保存学习资料、写几句笔记并打标签。

第一版只做这些事:

  • 保存一条资料:URL、标题、笔记、标签。
  • 按时间浏览,按关键词和标签筛选。
  • 修改、归档和删除自己的资料。
  • 所有资料默认私有,不能看到别人的内容。

明确不做:多人实时编辑、公开分享页、推荐流、跨设备离线同步、自动摘要。它们不是永远不做,而是暂时不进入第一版。

还要写下数量假设。没有数字,架构讨论很容易变成“越高并发越好”:

维度 第一版假设
用户 1,000 个注册用户,100 个同时在线
数据 10 万条收藏,每条连同索引约 2 KB
流量 峰值约 20 QPS,读取明显多于写入
延迟 保存 p95 小于 500 ms,列表 p95 小于 300 ms
可用性 月度 99.5%,允许短暂只读或功能降级
备份 RPO 24 小时,RTO 2 小时

按这些数字估算:

并发请求 ≈ 20 QPS × 0.1 秒 ≈ 2 个
原始数据 ≈ 100,000 × 2 KB ≈ 200 MB

第一版用一个应用服务和一个 PostgreSQL 就足够。这个结论不是“永远不扩容”,而是让当前方案与当前约束匹配。升级应该由指标触发,例如数据库连接池长期接近上限、查询 p95 持续超出预算、搜索扫描行数过大、模型请求阻塞主接口,而不是由“听说大公司都这么做”触发。

相关笔记:系统设计方法与案例、计算机系统一页通。

3. 从源码到可运行进程

跳转到“3. 从源码到可运行进程”

浏览器发出的一个 HTTP 请求,最终会变成网卡上的字节;服务代码由操作系统调度成进程;数据经过内存、文件系统和磁盘;跨机器通信还要经过 DNS、路由和 TLS。平时不需要记住每个硬件细节,但必须能在故障时判断问题发生在哪一层。

例如:

  • connection refused 更像进程没监听、端口写错或被防火墙拒绝。
  • TLS 握手失败更像证书、域名、时间或代理配置问题。
  • 请求已经进入服务但很慢,要继续看应用、数据库、缓存或下游依赖。
  • 服务被系统杀死,要检查内存、资源限制和进程日志,而不只是改业务代码。

更完整的层次关系见计算机系统一页通。

“在我机器上能跑”通常意味着运行条件没有被记录。至少要固定:

操作系统或容器镜像
语言运行时版本
依赖清单与锁文件
数据库版本
启动命令和端口
配置项名称
数据库迁移与初始化数据

配置和密钥分开。仓库里可以放变量名和没有敏感信息的示例:

APP_ENV=development
APP_PORT=3000
DATABASE_URL=postgres://user:password@localhost:5432/bookmarks
SESSION_SECRET=replace-me
MODEL_API_KEY=optional

MODEL_API_KEY 只能存在于服务端,不能进入浏览器代码、日志或 Git 历史。真实值通过环境变量或密钥管理服务注入。

推荐把启动、迁移、测试和健康检查写成明确命令,而不是依赖某个人记得一串步骤。容器可以帮助固定运行环境,但它不是备份,也不会自动解决配置、数据迁移或网络问题。相关笔记:可复现环境、Linux 与部署。

3.2 用版本库保存“为什么”

跳转到“3.2 用版本库保存“为什么””

Git 记录的不只是文件差异。一个好的提交应该说明这次改动解决了什么问题,必要时把接口、迁移、测试和文档放在同一个变更里。数据库结构变化尤其不能只改线上,而不留下迁移脚本。

不要提交真实 .env、数据库文件、用户数据或密钥。提交前先看 git status 和 git diff,确认没有把调试输出、临时文件和凭据带进去。

调试时按下面的顺序缩小范围,比同时重装依赖、清缓存和改代码有效得多:

  1. 写下预期结果和实际结果。
  2. 找到能稳定复现的最小步骤。
  3. 判断请求停在哪一层:浏览器、网络、服务、数据库还是外部依赖。
  4. 每次只改变一个变量,记录结果。
  5. 修复后用测试和真实请求验证,而不是只看日志里不再报错。

相关笔记:Git 与调试、可复现环境。

4. 浏览器怎样把一次操作变成请求

跳转到“4. 浏览器怎样把一次操作变成请求”

用户打开页面时,浏览器获取 HTML、CSS 和 JavaScript,解析成 DOM 和 CSSOM,经过布局、绘制与合成后显示出来。前端框架可能还涉及组件渲染、状态更新和水合。点击“保存”以后,JavaScript 处理事件、组织请求、等待响应,再更新界面。

这条路径决定了前端必须处理的不只是“成功”:

空闲 → 保存中 → 已保存
↘ 失败 → 重试 / 修改输入

保存期间要禁用重复提交,显示明确状态;失败时保留用户输入,给出可操作的错误;没有数据时显示空状态,而不是一片空白。不要在网络请求还没成功时假装已经保存。

相关笔记:浏览器运行模型、前端工程与应用设计。

4.1 先定接口,再写两端代码

跳转到“4.1 先定接口,再写两端代码”

前端和服务器之间需要一个明确契约。保存收藏可以用下面的请求:

POST /api/bookmarks
Content-Type: application/json
Cookie: session=<opaque-token>
Idempotency-Key: 7c3e4b1d-8b7a-4f6d-9e63-2f9a30c7c711
{
"url": "https://example.com/http-cache",
"title": "HTTP 缓存笔记",
"note": "整理缓存命中与失效",
"tags": ["http", "cache"]
}

成功时返回 201 Created:

{
"id": "b_01J8Z6Q4R2",
"status": "active",
"createdAt": "2026-10-31T09:20:00Z"
}

错误也属于契约。不要只返回一段自然语言:

{
"error": {
"code": "INVALID_URL",
"message": "URL 必须是 http 或 https 地址",
"requestId": "req_01J8Z6R7K9"
}
}

接口要写清:

  • 认证方式和允许的角色。
  • 字段长度、必填项和规范化规则。
  • 分页方式,例如游标分页而不是无限增长的 offset。
  • 幂等键是否必需,重复请求返回什么。
  • 超时、限流、错误码和重试建议。
  • 删除是立即生效、软删除还是延迟清理。

浏览器端校验改善体验,服务器端校验保证安全。前端不能相信任何来自地址栏、表单、剪贴板或第三方接口的数据。

5. 服务端怎样处理一次保存

跳转到“5. 服务端怎样处理一次保存”

请求到达服务后,可以按下面的顺序理解:

接收入口
→ 解析 HTTP 与路由
→ 认证用户
→ 限流与基础防护
→ 校验并规范化输入
→ 检查对象级权限
→ 事务写入数据库
→ 提交后发送异步任务
→ 返回结果和 requestId

认证回答“你是谁”,授权回答“你能不能操作这条数据”。用户已登录,不代表他能读取另一个用户的收藏。查询时要把资源 ID 和当前用户范围一起写进条件,而不是先查出来再在代码里比较。

保存逻辑可以先用伪代码表达:

handleSave(request):
user = authenticate(request)
input = validate(request.body)
return db.transaction:
existing = findBookmark(owner=user.id, url=input.canonicalUrl)
if existing:
return existing
bookmark = insertBookmark(owner=user.id, input)
outbox.add("bookmark.created", bookmark.id)
return bookmark

这段伪代码有一个重要漏洞:两个并发请求可能同时查不到记录,然后都尝试插入。最终仍必须依靠数据库唯一约束兜底,冲突后返回已有记录。只在应用层“先查再写”不够。

Idempotency-Key 解决的是另一个问题:浏览器超时后重试,或者网关重发同一个请求。服务端把“用户 + 幂等键 + 请求摘要”保存起来,重试时返回第一次的结果,避免重复创建或重复触发任务。

响应返回前,不要把摘要生成、图片下载或模型调用塞进主链路。它们可以进入队列,由后台 worker 执行。主请求只负责确认数据已经可靠保存。相关笔记:请求与 API、并发与缓存、语言与结构。

6. 数据模型决定系统能回答什么

跳转到“6. 数据模型决定系统能回答什么”

这个应用至少需要用户和收藏两类数据。先关注收藏表:

CREATE TABLE bookmarks (
id UUID PRIMARY KEY,
owner_id UUID NOT NULL REFERENCES users(id),
canonical_url TEXT NOT NULL,
title TEXT NOT NULL,
note TEXT,
tags TEXT[] NOT NULL DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX bookmarks_owner_url_active
ON bookmarks (owner_id, canonical_url)
WHERE deleted_at IS NULL;
CREATE INDEX bookmarks_owner_created
ON bookmarks (owner_id, created_at DESC);
CREATE INDEX bookmarks_tags_gin
ON bookmarks USING GIN (tags);

这里每个决定都有含义:

  • owner_id 把对象权限放进数据模型,查询不能漏掉它。
  • canonical_url 是规范化后的 URL,避免同一地址因大小写或尾斜杠产生重复记录。
  • 部分唯一索引只约束未删除记录,删除后可以重新收藏同一地址。
  • created_at 支持“我的收藏按时间倒序”。
  • tags 的 GIN 索引支持标签包含查询。
  • deleted_at 表示软删除,便于恢复和审计,但真正删除仍需单独流程。

URL 规范化不能随意丢掉查询参数,因为参数可能决定页面内容。规则应由测试固定,而不是靠字符串截断。

6.1 事务、异步和一致性

跳转到“6.1 事务、异步和一致性”

保存收藏和写入 outbox 事件应该在一个事务里。这样不会出现“数据库里有收藏,但后台永远不会生成摘要”的半成功状态。worker 采用至少一次投递,因此事件处理必须按事件 ID 幂等。

不是所有读取都需要强一致:

  • 刚保存完马上刷新列表,应该能看见自己的数据。可以读主库,或在会话中等待复制追上。
  • 其他人看到的公开统计可以慢几秒。
  • 搜索索引可以短暂落后,但界面要说明“结果可能延迟”。

一致性是逐个操作定义的要求,不是给整个系统贴一个标签。

6.2 搜索与数据生命周期

跳转到“6.2 搜索与数据生命周期”

小数据量下,简单的关键词查询可能够用;数据增长后,LIKE '%词%' 往往会扫描大量行。可以根据需求选择全文索引、trigram 索引或独立搜索服务,但都要限制返回数量,并继续带用户范围。

还要提前回答:

  • 删除后多久从备份和搜索索引中消失?
  • 是否允许导出自己的全部数据?
  • 备份是否做过恢复演练,而不只是显示“成功”?
  • 分析事件是否包含笔记正文、URL 中的敏感参数或用户身份?

用户数据、分析数据和对账数据的事实来源不同。分析表可以冗余,但不能反过来决定用户看到什么。相关笔记:数据库与存储、指标与治理。

7. 系统变大以后,先改哪里

跳转到“7. 系统变大以后,先改哪里”

第一版可以是一个部署单元:

浏览器 → 应用服务 → PostgreSQL
└→ outbox → worker → 可选摘要任务

这不是落伍的“单体”,而是一个模块边界清楚的模块化单体。它的优点是事务简单、部署少、排查路径短。微服务把代码耦合换成网络调用、数据一致性和独立运维成本,只有在团队边界、独立扩缩容或故障隔离有明确需求时才值得。

扩容次序应该由证据决定:

现象 先验证 再考虑 不要先做
列表变慢 查询计划、索引、返回字段 缓存、只读副本 直接拆微服务
写入冲突 事务范围、唯一约束、热点行 批处理、分区 直接换分布式数据库
长任务占满请求 超时、阻塞点、并发数 队列、worker 池、背压 无限加线程
读取集中 命中率、热点键、权限粒度 进程内缓存、共享缓存 无失效策略地全量缓存
多地域访问 真实延迟与合规要求 CDN、区域副本 未经设计就做双主

7.1 可靠性是失败时仍能解释的行为

跳转到“7.1 可靠性是失败时仍能解释的行为”

可靠性不只等于“多部署几个实例”。至少要处理:

  • 超时:每个外部调用都有上限,不能让一个慢依赖拖住整条请求。
  • 重试:只重试可安全重试的操作,设置次数、退避和抖动,避免重试风暴。
  • 熔断与隔离:下游持续失败时暂停调用,保护线程、连接和内存。
  • 降级:摘要失败时仍能保存和搜索;模型不可用时退回关键词结果。
  • 背压:队列积压时限制新任务,而不是无限接收直到内存耗尽。
  • 优雅停止:发布或缩容时停止接新请求,等待在途请求结束或安全取消。
  • 备份与恢复:定期验证能否在目标 RTO 内恢复,而不只是备份文件存在。

常见故障可以这样推演:

故障 正确行为
数据库不可用 返回明确的 503,不要假装保存成功;保留可重试提示
缓存全部失效 限流回源,保护数据库,必要时只提供降级读取
队列积压 主保存继续,摘要和通知延后,界面显示待处理状态
模型超时 返回关键词搜索结果或“稍后重试”,不让主接口一起挂掉
请求重复到达 幂等键和唯一约束返回同一结果,任务不重复执行
新版本代码回滚 数据迁移保持向后兼容,旧版本仍能读取新结构中的必填部分

相关笔记:分布式与高可用、系统设计方法与案例。

8. 上线不是把代码复制到服务器

跳转到“8. 上线不是把代码复制到服务器”

一次可回滚的发布大致包括:

提交代码
→ 静态检查与测试
→ 构建不可变产物
→ 检查数据库迁移
→ 小流量部署
→ 健康检查与指标观察
→ 扩大流量或回滚

数据库迁移建议采用“扩展—迁移—收缩”:先增加兼容字段或表,双写或回填,切换读取,确认旧版本不再依赖后再删除旧结构。不要在一个发布里同时删除字段、修改代码和回滚机制。

Linux、容器和网络边界仍然适用:进程需要监听正确端口,容器需要资源限制和健康检查,数据不能只放在临时容器层,服务跨网络访问要经过 DNS、TLS、防火墙和安全组。容器解决的是运行环境封装,不自动解决持久化、备份和权限。

相关笔记:Linux 与部署、网络与云安全。

对这个应用,最现实的威胁不是“黑客电影式入侵”,而是普通错误:

风险 具体问题 控制
越权读取 只按收藏 ID 查询,没带当前用户 所有对象查询带 owner_id,并测试他人 ID
XSS 把笔记或标题当 HTML 渲染 默认按文本渲染;必须富文本时做白名单净化
CSRF Cookie 会话下被第三方站点代为提交 SameSite、CSRF token、检查请求来源
SSRF 服务器自动抓取用户填写的 URL 限制协议、解析后阻断内网地址、限制重定向和响应大小
密钥泄露 把模型或其他服务密钥打进前端 密钥只放服务端,日志和错误信息脱敏
滥用 批量保存、无限搜索、模型调用刷量 认证、限流、配额、审计和异常检测

权限检查要覆盖读取、修改、删除和导出,不只是保存接口。相关笔记:安全与交付。

线上至少需要三类信号:

  • 日志:时间、请求 ID、用户范围、接口、状态码、错误码、耗时;不记录密钥和完整敏感正文。
  • 指标:请求速率、错误率、延迟分位、数据库连接池、缓存命中率、队列积压、模型调用耗时与费用。
  • trace:一次请求在接入层、应用、数据库和下游中的时间分布。

健康检查分存活和就绪:进程活着不代表能接流量。数据库连接没准备好、迁移没完成或依赖不可用时,就绪检查应该阻止新流量进入。

下面用一个故障把前端、数据库、部署和观测串起来:

  1. 新版本加入了关键词搜索,发布后接口 p95 从 300 ms 升到约 2 秒,错误率超过 3%。
  2. 仪表盘显示浏览器和 CDN 正常,延迟集中在 API;trace 显示大部分时间花在数据库查询。
  3. 日志显示搜索使用了 LIKE '%关键词%',没有可用索引,而且分页没有上限;并发稍高就占满连接池。
  4. 先关闭搜索功能开关,恢复主路径,再回滚不兼容部分;不要用无限加连接数掩盖慢查询。
  5. 修复方式是增加合适的全文或 trigram 索引,限制返回条数和查询超时,按真实数据量做压测,再通过灰度重新发布。
  6. 事后补上 p95、连接池和慢查询告警,并把“查询在 10 万行数据上的计划”加入发布检查。

这个例子里,缓存也许能暂时减少请求,但根因是查询模式和索引。监控的价值是帮你定位,而不是让告警看起来更专业。相关笔记:可观测性。

9. 需要时再加模型能力

跳转到“9. 需要时再加模型能力”

模型不是每个应用的必要层。收藏、列表和权限用确定性代码更便宜、更稳定。只有当任务本身模糊、需要自然语言理解或生成时才加入,例如:

  • 为长笔记生成摘要。
  • 根据一段描述找“意思相近”的资料。
  • 对多篇资料做有来源的回答。

以“根据收藏回答问题”为例,完整流程仍然是普通系统流程,只是中间多了模型:

用户问题
→ 认证与用户范围
→ 关键词召回 + 向量召回
→ 合并、去重
→ 权限过滤
→ 重排候选
→ 组织上下文与提示词
→ 模型生成
→ 结构校验、引用检查
→ 返回答案或降级结果

请求可以是:

{
"question": "HTTP 缓存什么时候会失效?"
}

响应要能说明依据和降级状态:

{
"answer": "资料中提到了过期、主动失效和缓存键变化三种情况。",
"sources": ["b_01J8Z6Q4R2", "b_01J8Z8T1M6"],
"degraded": false
}

相关笔记:大模型与多模态、RAG 与 Agent。

9.1 提示词、检索和 Agent 各自负责什么

跳转到“9.1 提示词、检索和 Agent 各自负责什么”
  • 检索决定把哪些资料放进上下文。文本要先切分、建索引,查询时召回候选并重排。权限过滤必须在候选进入模型前完成。
  • 提示词负责任务说明、输出约束和少量示例。把已收藏网页的内容当“资料”,不要把它当“系统指令”;网页里可能藏着提示注入。
  • Agent适合需要多步工具调用的任务,例如先搜索、再打开资料、再汇总。它需要最大步数、超时、工具权限、失败处理,重要操作还要人工确认。
  • 结构化输出便于程序校验,但模型返回的 JSON 仍可能字段缺失或不符合业务规则,必须验证,不能直接写数据库。

一个简化的提示约束可以写成:

只根据 <sources> 中的内容回答。
如果资料不足,回答“现有资料无法确认”,不要补充外部事实。
每条结论附上来源 id。
输出 JSON:{"answer": string, "sourceIds": string[]}

提示词不能替代数据库权限,也不能解决过期索引。它只是模型层的一部分。

9.2 模型层的失败和评测

跳转到“9.2 模型层的失败和评测”
失败 处理
模型超时或限流 返回关键词搜索结果,标记 degraded,允许重试
输出格式错误 做一次结构修复或重试,仍失败则返回纯检索结果
资料不足或答案无来源 明确说无法确认,不编造引用
提示注入 网页内容只作为数据,工具权限最小化,危险动作需确认
跨用户泄漏 检索前按用户过滤,缓存键包含权限范围
成本失控 限制上下文长度、并发和配额,异步任务单独计量

上线前准备一组固定问题、期望来源和不可接受回答,记录召回率、引用正确性、延迟和费用。模型或提示词变化后先离线评测,再小流量比较;上线后收集人工反馈和失败样本。相关笔记:模型生命周期。

模型层没有改变前面的结论:它仍然通过接口接收输入,仍然需要数据库、队列、权限、超时、降级、日志、测试和回滚。它只是把一部分不确定性从代码转移到了模型和检索。

10. 用测试和交付把改动锁住

跳转到“10. 用测试和交付把改动锁住”

这个案例的测试不应该只覆盖“保存成功”:

层级 例子 主要发现
单元测试 URL 规范化、标签清洗、权限判断 边界规则错误
集成测试 API + 数据库 + 事务 + 幂等键 接口与真实存储不一致
端到端测试 保存、刷新、搜索、删除 浏览器与后端链路断开
契约测试 请求字段、错误码、分页格式 前后端版本不兼容
压力测试 2 倍峰值、慢数据库、队列积压 过载和资源耗尽
安全测试 他人资源 ID、脚本注入、内网 URL 越权和 SSRF
模型评测 召回、引用、拒答、超时降级 演示之外的真实质量

一次完整变更可以按下面的顺序推进:

  1. 写清用户结果、约束和非目标。
  2. 写 API 契约、数据迁移和验收条件。
  3. 准备环境、种子数据和可重复的测试。
  4. 先实现一条最小垂直链路,再补边界。
  5. 覆盖错误、重复、越权和依赖失败。
  6. 检查迁移是否向后兼容、是否可回滚。
  7. 灰度发布,观察请求、错误、延迟和资源。
  8. 记录取舍,清理临时开关和旧代码。

代码质量和设计模式服务于可理解、可修改,而不是为了让目录看起来复杂。只有重复出现、边界明确的问题才值得抽象。第三方库、模型和数据源还要检查许可证、维护状态和数据使用边界。相关笔记:代码与测试、安全与交付。

11. 把整条链路收成一套框架

跳转到“11. 把整条链路收成一套框架”

看懂陌生系统时,可以固定问六个问题:

  1. 用户要完成什么目标?哪些结果不能接受?当前的非目标是什么?
  2. 哪份数据是事实来源?谁拥有、保留多久、怎样删除和恢复?
  3. 一次请求经过哪些边界?每层的输入、输出和超时是什么?
  4. 重复、并发、超时、过载和依赖故障时会发生什么?
  5. 谁可以读、写、删除和导出?最坏的越权后果是什么?
  6. 怎样验证、部署、观测、降级、回滚和继续演进?

可以把这套框架记成:

系统 = 用户目标
+ 数据与状态
+ 请求链路
+ 失败处理
+ 权限边界
+ 运行反馈
+ 演进成本

九个章节也可以放回这条链路中:

章节 在案例里回答什么 对应笔记
计算机系统 程序在哪里运行,内存、I/O 和网络为什么会影响性能 计算机系统一页通
开发环境与工具 怎样复现环境、管理版本和定位故障 可复现环境、Git 与调试
浏览器与前端 用户操作怎样变成请求,页面怎样处理等待和错误 浏览器运行模型、前端工程与应用设计
服务器与后端 请求怎样认证、校验、写入并安全返回 请求与 API、并发与缓存、语言与结构
数据 数据存在哪里,谁能查,怎样保证索引、事务和生命周期 数据库与存储、指标与治理
架构与系统设计 何时扩容、拆服务和降级,怎样记录取舍 分布式与高可用、系统设计方法与案例
基础设施与运维 怎样部署、联网、保护边界和观察线上 Linux 与部署、网络与云安全、可观测性
模型系统 何时需要模型,检索、提示词、Agent 和评测怎样接入 大模型与多模态、RAG 与 Agent、模型生命周期
工程素养 怎样用代码、测试、安全和交付把改动变成可靠结果 代码与测试、安全与交付

如果只能记住一句话:先确定用户结果和事实来源,再沿着一次真实请求穿过所有边界,最后检查失败、权限、观测和回滚。技术栈会变化,这套提问顺序不会。

  1. 给收藏夹增加“公开分享链接”。写出新的数据字段、权限规则、撤销方式、缓存和搜索引擎风险。
  2. 假设流量增长 100 倍。先列出需要测量的指标,再只选择两项最小的架构变化,并说明为什么其他变化暂时不做。
  3. 给案例增加“根据收藏回答问题”。画出召回、权限过滤、模型调用和降级的路径,再列出三种不能把模型输出直接写入数据库的情况。

能独立完成这三题,基本就能把分散的概念串成一条可以用于设计、排查和交付的完整链路。