Skip to content

从人的意图到系统结果:CS 世界的顶层图谱

目的

这不是一份按语言、框架或工具罗列的课程目录,而是一张帮助新人定位问题的总图:一个人的意图如何经过软件系统,最终变成可见、可共享、可恢复的结果。

学习新技术时,先把它放回这张图中,回答三个问题:它在解决哪一类问题?依赖哪些层?它的失败会传导到哪里?

总体因果链

text
人、业务目标与真实世界
        |
        v
交互终端表达意图
        |
        v
网络与协议传递命令和结果
        |
        v
服务判断、协调并维护共享事实
        |
        v
运行环境分配资源、隔离风险、处理故障
        |
        v
结果再次呈现给人,并进入下一轮反馈

整个系统的中心不是某一个框架,而是这件事:把局部、暂时的用户操作,转化为多个参与者能够信赖的共享结果。

四个观察视角

我们最开始讨论的出发点

这张图不是先从课程目录或技术名词推出来的。它从最开始讨论时的四句原始描述出发;后续的机制树、案例和学习路径,都只是把这四句展开,不能替换它们:

  • 公共 infra 基础
  • client 交互终端
  • server 共享事实
  • 集群/沙箱/架构运行协调

它们不是严格的上下楼层,而是观察同一系统的四个窗口。遇到新技术或问题,先回到这四句,判断它主要在解决哪一类问题,再向下追机制与边界。

这四项不是严格的建筑楼层。它们从不同角度观察同一个系统,许多概念会同时出现在多个位置。

text
                  人、业务目标、真实世界
                               |
        +----------------------+----------------------+
        |                                             |
交互终端层                                      共享事实层
Client Runtime                                  Server Runtime
Web / Native / TUI                              API / 业务 / 数据 / 并发
状态、事件、呈现                                权限、事务、一致性、任务
        |                                             |
        +---------------- 网络与协议 ----------------+
                               |
                       运行与协调层
       集群、进程、沙箱、部署、资源、故障恢复、架构
                               |
                       公共计算基础层
语言与编译、操作系统、网络、存储、密码学、算法、工程工具

1. 公共计算基础层

最初描述:公共 infra 基础。

这里回答“计算、通信、存储和隔离凭什么成立”。它提供所有软件共同依赖的机制,而不等于只学习底层实现细节。

1.1 这一层真正回答的问题

  • 一段程序如何变成可执行的指令,并在某个地址空间里运行?
  • 多个程序如何共享 CPU、内存、磁盘和网络,同时互不破坏?
  • 数据如何在本地介质和远程机器之间被可靠地表示、传输和保护?
  • 协作开发时,如何让构建、测试和发布成为可重复的过程?

1.2 关键机制树

text
公共计算基础层
├── 程序表示与执行
│   ├── 语言抽象:类型、内存模型、错误处理、并发原语
│   ├── 编译 / 解释 / JIT:源码如何变成机器可执行形态
│   └── ABI 与运行时:调用约定、垃圾回收、标准库
├── 操作系统与进程模型
│   ├── 进程与线程:隔离、调度、上下文切换
│   ├── 内存:虚拟内存、页表、堆栈、共享映射
│   ├── I/O:文件、套接字、阻塞与非阻塞、事件通知
│   └── 权限与信号:用户态/内核态、能力边界、中断
├── 网络与协议
│   ├── 分层:链路、IP、传输层、应用层
│   ├── 可靠性:丢包、重传、超时、拥塞控制
│   ├── 连接语义:连接状态、半开、关闭顺序
│   └── 应用协议:HTTP、WebSocket、gRPC、消息格式
├── 存储与数据组织
│   ├── 字节与结构:编码、序列化、字节序
│   ├── 本地存储:文件系统、页缓存、持久化语义
│   ├── 索引与查询:B+ 树、哈希、顺序写与随机读
│   └── 可靠性:校验、备份、崩溃恢复的基础概念
├── 密码学与信任
│   ├── 哈希、对称/非对称加密、签名
│   ├── TLS 与证书链
│   └── 身份、密钥与最小权限的基础
└── 工程可重复性
    ├── 版本控制与变更历史
    ├── 构建系统与依赖锁定
    ├── 自动化测试与回归保护
    └── 制品、配置与发布脚本

1.3 每块机制要抓住什么

子模块先掌握的机制常见误判
语言与运行时值如何存放、何时复制、错误如何传播只背语法,不关心内存、生命周期和失败路径
操作系统调度、隔离、I/O 模型如何决定程序行为把“卡顿”一律归因于业务代码,忽略阻塞 I/O 或线程耗尽
网络超时、重试、乱序、部分失败是常态把一次 HTTP 调用想成本地函数调用
存储顺序写、随机读、缓存层和持久化语义不同以为“写入成功返回”就等于数据永远安全落盘
密码学保护的是机密性、完整性还是身份把“加密了”当成“系统安全了”
工程工具可重复构建和可验证变更把本地偶然能跑通当作团队可交付

1.4 与上层的连接

  • 交互终端依赖它提供事件循环、线程模型、字体/输入、网络栈和本地存储。
  • 共享事实层依赖它提供事务、锁、网络可靠性和持久化语义的基础。
  • 运行与协调层把这些能力扩展到多机、多进程和多租户场景。

新人不必先成为内核工程师,但要能在故障时判断:问题更像是语言运行时、操作系统资源、网络语义,还是存储语义。

2. 交互终端层

最初描述:client 交互终端。

Client 不等于浏览器前端。它是所有直接与人交互的运行时:Web、原生移动或桌面应用、TUI、嵌入式界面、游戏或图形工具都属于这里。

它们面对的共同结构是:

text
输入事件 / 外部消息
        -> 更新本地状态
        -> 发出命令或请求
        -> 接收异步结果
        -> 将当前状态呈现给人

2.0 这一层真正回答的问题

  • 用户此刻看到的是不是他正在操作的状态?
  • 哪些状态只存在于本地,哪些必须以服务端事实为准?
  • 输入、异步返回和生命周期事件如何汇入同一条状态更新路径?
  • 呈现介质变化时,同一交互模型如何迁移?

2.0.1 机制树

text
交互终端层
├── 输入与事件
│   ├── 用户输入:点击、键盘、手势、语音
│   ├── 系统事件:前后台、旋转、窗口缩放、断连
│   └── 外部消息:推送、WebSocket、轮询结果
├── 本地状态与命令
│   ├── 视图状态:滚动位置、展开折叠、焦点
│   ├── 草稿状态:未提交表单、临时编辑
│   ├── 命令发送:请求、重试、取消、幂等键
│   └── 结果合并:乱序返回、过期响应丢弃
├── 导航与生命周期
│   ├── 页面 / 路由 / 窗口栈
│   ├── 恢复:刷新、进程被杀、会话恢复
│   └── 权限与能力:相机、通知、剪贴板
├── 呈现系统
│   ├── 结构:控件树 / DOM / 场景图 / 字符缓冲
│   ├── 布局:尺寸、位置、约束
│   ├── 绘制:文本、图像、动画、合成
│   └── 性能:增量更新、虚拟列表、图层策略
└── 体验约束
    ├── 无障碍、国际化、输入法
    ├── 弱网、离线、冲突提示
    └── 可观测:前端日志、性能指标、错误上报

共同问题包括状态来源、事件处理、异步结果乱序、失败恢复、导航与生命周期、输入焦点、无障碍、离线或断连,以及呈现性能。

不同终端的差异在于呈现介质和运行时约束:Web 最终操作 DOM 与浏览器能力;原生应用面对操作系统生命周期、权限和设备资源;TUI 在字符网格、键盘输入和远程终端连接内工作。它们可以共享“状态机 + 事件循环 + 呈现器”的思考方式,但不应机械复用某一个 Web 框架的实现模型。

2.1 渲染的通用骨架:从状态到可见结果

先把“渲染”从某个框架或某个浏览器 API 中抽出来。它的职责是:把终端当前能够成立的状态,投影成用户此刻能够看见、操作并理解的结果。

text
输入事件 / 外部消息 / 生命周期变化
                |
                v
       状态转换与命令结果合并
                |
                v
      UI 描述:此刻应该有哪些元素、内容和交互状态
                |
                v
      更新计算:与上一帧或上一棵树相比,哪里变了
                |
                v
      宿主呈现:布局 -> 绘制 -> 合成 / 字符输出
                |
                v
   用户看见结果,并产生下一次输入或等待异步结果

这是一条反馈回路,不是一条从上到下只执行一次的流水线。网络返回、窗口尺寸变化、系统切到后台、定时器、动画和辅助技术事件,都可能在任意时刻进入左侧,再推动下一次状态转换。

环节它要回答什么不属于它的职责
状态转换这次事件后,终端的本地事实是什么决定共享业务事实是否已经写成功
UI 描述对应这个状态,用户应该看见和能操作什么直接把像素画到屏幕
更新计算新旧 UI 之间哪些内容需要变保证网络请求一定成功
宿主呈现如何把变化变成页面、控件、图层或字符替应用决定业务规则
反馈与验证用户是否看见正确结果、能否继续操作用视觉成功掩盖数据尚未确认

一个最小不变量是:**画面必须能解释当前状态,而不是只看起来像成功。**例如,点击“保存”后,画面应区分“草稿还在本地”“请求发送中”“服务端已确认”“失败可重试”;不能用同一个静态提示掩盖这些不同事实。

渲染问题也可以先按骨架定位:

现象先检查的环节
数据到了但界面没变状态合并、UI 描述或更新计算
文字重叠、尺寸跳动、滚动卡顿布局、绘制或合成
画面已经变了但操作仍不可用状态机与交互语义是否一致
用户以为保存成功,换设备却看不到本地投影与共享事实的边界
输入延迟、动画掉帧主线程调度、更新量或宿主呈现成本

后面的浏览器、原生 UI 和 TUI 都放回这条骨架中看:它们最大的差异不是“有没有渲染”,而是 UI 描述的承载物、布局绘制的宿主,以及可接受的延迟和资源约束。

关于客户端渲染的完整专题见:从浏览器内核到 Flutter

2.2 浏览器:从文档、样式到像素

浏览器不是一个把 HTML 直接“画出来”的黑盒。以首次显示一个页面为例,关键路径大致如下:

text
HTML ----------> DOM
                   \
                    +-> 样式计算 -> 布局(Layout) -> 绘制记录(Paint)
CSS -----------> CSSOM                           -> 栅格化(Raster) -> 图层合成(Composite) -> 屏幕像素
JavaScript ----> 修改 DOM、样式或其他页面状态
  • DOM 是文档和元素的结构;CSSOM 是 CSS 规则的结构化表示。
  • 浏览器根据 DOM、CSSOM、继承关系和选择器,为元素计算最终样式;不可见元素不会进入可见的渲染结果。
  • 布局 计算每个盒子的大小和位置,例如一行文字会不会换行、一个弹窗处于哪里。
  • 绘制 记录背景、文字、边框、阴影等应如何被画出;栅格化 将这些指令变成位图;合成 把多个图层交给 GPU 或合成器组合成最终画面。

这不是每次状态变化都必须完整重走的固定流水线。改变文字或尺寸,往往会影响样式、布局和绘制;只改动颜色可能无需重新布局;在合适条件下改动 transformopacity 可以主要由合成阶段处理。性能优化的出发点不是背诵术语,而是判断一次变化究竟触发了哪些阶段。

JavaScript 并不直接“画像素”。它通过修改 DOM、样式、类名、动画状态或触发网络请求,间接驱动浏览器重新进入相关阶段。主线程如果长时间被脚本占用,输入响应、样式计算和布局都会被推迟,用户会感觉页面卡顿。

2.3 React 与浏览器管线的关系

React 处在“应用状态”和“浏览器 DOM”之间,不替代浏览器渲染引擎。

text
应用状态 / props
      |
      v
React 组件树(描述 UI 应该是什么)
      |
      v
Reconciler 比较前后描述,算出需要改动的最小集合
      |
      v
ReactDOM 把这些改动应用到真实 DOM
      |
      v
浏览器继续走样式、布局、绘制、合成

可以把 React 理解成:

  1. 用组件函数或类描述“给定状态时 UI 应该长什么样”。
  2. 在状态变化时,比较新旧描述,找出插入、删除、更新节点的差异。
  3. 通过 ReactDOM 等宿主渲染器,把差异落到真实 DOM。
  4. 之后的像素生成仍然完全由浏览器负责。

因此:

  • React 解决的是 状态如何组织 UI 更新,不是字体排版、图层合成或 GPU 光栅化。
  • setState / useState 触发的是 React 调度更新,不是直接重绘屏幕。
  • key、列表协调、受控输入、并发渲染等概念,都在 DOM 更新之前的协调层。
  • 若组件树更新导致大量 DOM 结构或样式变化,浏览器仍可能发生昂贵的布局和绘制;React 减少的是手动 DOM 操作的复杂度,不自动消灭所有渲染成本。

一个实用判断:

你看到的问题更可能落在哪里
状态改了但界面不对React 状态流、派生数据、错误的 props 传递
界面闪动、输入光标跳动DOM 更新方式、受控组件、列表 key
点击后页面明显卡顿组件重复渲染、过大协调,或浏览器布局/绘制过重
滚动动画掉帧浏览器合成、主线程占用、图层与绘制成本
接口已返回但按钮一直转圈异步结果合并、请求竞态、状态机未闭环

2.4 原生 App:同样的问题,不同的渲染方案

原生应用面对的问题与浏览器高度同构:输入事件进入应用,状态变化后需要更新控件树或场景图,再由系统完成布局、绘制和合成。差别在于宿主能力、生命周期和控件体系。

常见方案可以按“谁拥有渲染权”来理解:

方案状态到像素的路径典型代表取舍
命令式原生控件应用直接创建并修改系统控件;系统负责布局和绘制UIKit、AppKit、传统 Android View最贴近平台能力与系统观感;跨端复用弱,状态更新易散落在命令式代码中
声明式原生 UI用状态描述 UI,框架协调到原生控件树SwiftUI、Jetpack Compose保留原生渲染与可访问性优势,同时获得接近 React 的状态驱动模型
跨端桥接到原生控件JS/Dart 等描述 UI,桥接层映射到平台控件React Native复用 Web 思路和部分代码;桥接与平台差异会成为性能和一致性成本
自绘引擎应用自带布局和绘制引擎,直接画到画布或图形层Flutter、部分游戏/图形框架视觉与布局在多端更一致;不是使用系统控件完成每个界面,需承担引擎、包体和平台适配取舍

上述方案都可放回相同的抽象:

text
状态 -> UI 描述或控件树 -> 计算变化 -> 布局 -> 绘制命令 -> 合成后的画面
输入事件和异步结果 ---------------------------------------> 更新状态

因此,学习浏览器渲染管线并不会把知识限制在 Web。它帮助理解的是更一般的问题:哪些状态变化应影响哪些呈现节点,框架怎样组织更新,宿主怎样把更新转换为屏幕,什么情况下这条路径会成为性能或正确性瓶颈。

2.5 TUI:把像素画面换成字符网格

TUI 省去了图形布局和 GPU 合成,但没有省去交互运行时的本质问题。它仍然维护状态,接收键盘、窗口尺寸或远程连接事件,并把当前状态投影为某个行列位置上的字符、颜色和样式。高性能 TUI 通常比较上一帧和当前帧的字符缓冲,只输出变化的终端控制序列。这与图形界面的增量更新在思想上是相通的,只是最终呈现介质变成了终端字符网格。

2.6 这一层的关键取舍

  • 乐观更新可以让界面更快,但必须设计回滚和冲突提示。
  • 本地缓存能提升体验,也会制造“终端已更新、共享事实未更新”的错觉。
  • 全局状态并非越多越好;先分清视图状态、草稿状态和服务器状态。
  • 性能优化应先定位是状态更新过频、布局过重,还是网络等待被错误表现成渲染卡顿。

3. 共享事实层

最初描述:server 共享事实。

服务端的核心不只是“提供接口”,而是决定什么可以被视为共同认可的事实。

3.1 这一层真正回答的问题

  • 谁有权把一次操作写入共享世界?
  • 多个参与者同时修改时,什么结果算正确?
  • 超时、重试、部分成功之后,系统如何避免重复记账或丢失记账?
  • 读到的数据是“此刻最新”,还是“足够新”?

3.2 机制树

text
共享事实层
├── 业务边界与模型
│   ├── 领域对象:订单、账户、计划、权限主体
│   ├── 不变量:余额不能为负、状态只能按规则迁移
│   └── 用例:创建、支付、取消、恢复、审计
├── 入口与契约
│   ├── API / RPC / 消息消费入口
│   ├── 认证:你是谁
│   ├── 鉴权:你能做什么
│   ├── 校验:输入是否合法
│   └── 版本演进:兼容、废弃、契约测试
├── 写入路径
│   ├── 事务:原子提交与回滚
│   ├── 幂等:同一命令执行一次与多次结果等价
│   ├── 并发控制:锁、版本号、CAS、队列串行化
│   └── 冲突处理:拒绝、合并、后写覆盖、人工介入
├── 读取路径
│   ├── 查询模型:详情、列表、检索、聚合
│   ├── 一致性选择:强一致读 / 最终一致读
│   └── 缓存:何处缓存、如何失效、击穿与穿透
├── 异步与集成
│   ├── 任务:生成、导出、通知、补偿
│   ├── 事件:领域事件、变更通知、订阅消费
│   └── 外部依赖:支付、模型服务、对象存储
└── 可追溯性
    ├── 审计日志:谁在何时因何改变了什么
    ├── 关联 ID:请求、任务、事务的追踪线索
    └── 数据生命周期:草稿、生效、归档、删除

3.3 先掌握的核心概念

概念机制含义为什么重要
不变量无论系统如何失败,某些业务规则必须始终成立防止“接口成功但业务语义已坏”
幂等同一业务命令重复到达时,不会造成重复副作用网络重试和用户连点是常态
事务边界哪些读写必须一起成功或一起失败决定一致性成本和失败恢复方式
并发控制两个写者竞争同一资源时如何裁定决定超卖、脏写、丢失更新是否发生
一致性级别读者何时能看见刚写入的事实影响缓存、副本、跨服务设计
异步化哪些工作可以延后,但仍必须可追踪决定超时、用户等待和补偿流程
契约客户端与服务端共同遵守的输入输出约定决定系统能否独立演进

3.4 一条典型写入链路

text
请求进入
-> 认证与鉴权
-> 参数与业务规则校验
-> 生成或校验幂等键
-> 开启事务 / 获取必要锁或版本
-> 修改聚合根与关联数据
-> 提交事务
-> 发布事件或投递异步任务
-> 返回业务结果与可追踪标识

其中最容易被低估的不是“happy path 代码”,而是:

  • 第 3 步到第 6 步之间进程崩溃;
  • 事件发布成功但数据库提交失败,或反过来;
  • 客户端没收到响应却已经写成功,于是重试;
  • 两个实例同时处理同一资源。

3.5 同步、异步与一致性的取舍

场景更偏同步更偏异步
用户必须立刻知道最终结果下单扣减关键库存、权限变更即时生效报告生成、视频转码、批量通知
失败必须立即回滚支付确认、账户转账可补偿的后续通知、搜索索引更新
跨多个不稳定依赖尽量缩短同步关键路径把慢依赖移出用户等待路径

一致性也不是口号:

  • 强一致:读到的就是刚写成功的结果,成本是延迟、锁和协调。
  • 最终一致:允许短暂旧读,换取吞吐和可用性,但必须处理“用户为何暂时看不见”。
  • 因果一致 / 读己之写:至少保证用户刚完成的操作在自己的视图中可见。

3.6 这一层的常见误判

  • 把 CRUD 接口堆砌等同于业务模型。
  • 只测成功响应,不测重复提交、超时重试和并发双写。
  • 用分布式事务掩盖未想清的业务边界。
  • 缓存加得很快,失效和回源策略却没有主人。
  • 异步任务“丢进队列”后,没有状态机、重试上限和死信处理。

4. 运行与协调层

最初描述:集群/沙箱/架构运行协调。

这一层处理程序最终在哪里运行,以及整个系统在资源有限和组件会失败的现实中如何维持服务。

4.1 这一层真正回答的问题

  • 代码运行在什么隔离边界里,它能碰哪些资源?
  • 实例增减、机器故障、网络分区时,服务是否仍可被发现和调用?
  • 新版本如何进入生产,出错后如何快速收敛?
  • 出故障时,如何从指标、日志和追踪中定位责任层?

4.2 机制树

text
运行与协调层
├── 执行与隔离
│   ├── 进程 / 容器 / 虚拟机 / 沙箱
│   ├── 资源配额:CPU、内存、磁盘、文件句柄
│   ├── 权限边界:系统调用、密钥、网络出口
│   └── 多租户隔离:噪声邻居与逃逸风险
├── 部署与拓扑
│   ├── 单机、多实例、多可用区、多区域
│   ├── 服务发现与负载均衡
│   ├── 配置与密钥分发
│   └── 流量入口:网关、Ingress、边缘节点
├── 弹性与保护
│   ├── 扩缩容与自动恢复
│   ├── 超时、重试、退避、幂等配合
│   ├── 限流、熔断、舱壁隔离
│   └── 降级:保核心路径,牺牲次要能力
├── 变更管理
│   ├── 构建制品与版本标记
│   ├── 滚动发布、蓝绿、金丝雀
│   ├── 回滚与特征开关
│   └── 迁移:配置、数据、流量的顺序
├── 可观测性
│   ├── 指标:延迟、错误率、饱和度
│   ├── 日志:结构化事件与关联字段
│   ├── 追踪:跨服务调用链
│   └── 告警与值夜:谁在何时被叫醒
└── 架构协调
    ├── 同步调用链 vs 事件驱动
    ├── 单体、模块化单体、服务化
    ├── 数据所有权与反腐化层
    └── 成本、合规、安全基线

4.3 先掌握的运行时语义

主题关键问题实践含义
隔离一个组件崩溃或被攻破,会波及多大范围?容器、权限、网络策略不是运维装饰
发现调用方如何找到健康实例?服务发现、健康检查、负载均衡决定可用性
超时等待多久算失败?没有超时的重试会放大故障
重试失败后是否可安全再来一次?必须与幂等和退避一起设计
发布新旧版本并存时行为是否兼容?契约和数据迁移比“部署脚本成功”更关键
观测用户先发现,还是系统先发现?没有黄金信号,故障会静默扩散

4.4 一次请求在这一层如何被托住

text
客户端请求
-> 入口网关 / 负载均衡
-> 鉴权与路由到某个服务实例
-> 实例内部执行业务
-> 访问数据库、缓存、消息队列或其他服务
-> 指标与追踪被记录
-> 响应返回;若实例异常,流量被切走或重试到健康实例

当系统变大后,真正决定体验的常常不是业务 if/else,而是:

  • 某个下游变慢后,调用方线程是否被拖死;
  • 重试风暴是否把偶然故障打成全面故障;
  • 配置错误是否比代码错误更快让系统不可用;
  • 回滚是否真的能回到上一个已知良好状态。

4.5 架构选择的实质

架构不是画更多方框,而是决定变化与故障的边界:

  • 模块化单体:先把业务边界和数据所有权理清,降低分布式复杂度。
  • 服务化:在团队、发布频率和扩展需求真正出现时,再支付分布式成本。
  • 事件驱动:适合解耦和异步传播,但要接受最终一致与排查复杂度。
  • 平台能力下沉:把认证、限流、观测、配置做成统一能力,避免每个业务重复造轮子。

4.6 这一层的常见误判

  • 把“容器跑起来了”当成“系统可运营了”。
  • 只做部署,不做回滚演练和迁移顺序设计。
  • 监控很多,但没有围绕用户路径定义的黄金信号。
  • 在没有幂等和超时预算时大面积加重试。
  • 过早微服务化,把本地函数调用变成不可靠网络调用。

同一概念会跨越多层

知识地图最终应当是图,而不是一棵只允许单一归属的树。同一个词在不同层解决不同问题;只记住技术名词,无法说明它为什么出现、失效后会怎样。

跨层概念对照

概念公共基础层交互终端层共享事实层运行与协调层
缓存内存、页缓存、局部性本地草稿、列表预取、离线包查询缓存、对象缓存、只读副本多实例共享缓存、失效广播、容量治理
状态进程内存、寄存器、持久化介质视图状态、草稿、提交中业务实体、状态机、审计记录部署版本、配置、集群成员状态
失败系统调用错误、磁盘满、信号输入被拒、请求超时、界面回滚事务回滚、幂等冲突、补偿实例摘除、重试、熔断、回滚发布
一致性存储语义、校验、崩溃恢复界面是否反映用户刚做的操作多写者下的业务正确性多副本、多版本并存时的收敛
安全加密、密钥、最小权限原语本地敏感数据、生物识别、XSS/CSRF 面认证鉴权、审计、数据权限网络策略、密钥分发、沙箱逃逸防护
性能CPU、I/O、算法复杂度交互延迟、掉帧、首屏查询计划、锁竞争、热点写入队列堆积、饱和度、扩缩容滞后
身份证书、密钥、用户与进程身份登录态、会话恢复、设备绑定主体、角色、资源授权工作负载身份、服务间 mTLS、密钥轮换
队列内核队列、缓冲、背压基础用户操作排队、乐观更新队列任务队列、Outbox、消费位点集群队列、死信、分区与重平衡

阅读方式:遇到一个词,先问“它在这一层保护的是体验、事实、资源,还是物理可行性?”

一个更细的跨层例子:缓存

text
用户打开列表
-> 终端先显示本地缓存,标记“可能不是最新”
-> 请求打到服务;服务读查询缓存或只读副本
-> 若缓存未命中,回源数据库并回填
-> 若某实例写入了新数据,需要失效或版本推进
-> 集群中的其他实例不能无限期提供旧读
-> 基础层决定内存不够时谁被挤出、磁盘页缓存是否还热

关键不是“用了 Redis”,而是:

  1. 哪个读取路径允许旧数据?
  2. 写入后谁负责让旧缓存失效?
  3. 缓存穿透、击穿、雪崩时,压力会落到哪一层?
  4. 用户看到旧值时,界面如何避免误操作?

业务模型:图的最上方

技术四层之下,还应有一个更上层的问题域。否则系统会变成“为了搭技术而搭技术”。

text
现实问题 / 业务目标
        |
        v
业务对象、规则、约束、成功标准
        |
        v
交互如何表达这些对象
服务如何维护这些对象的事实
运行如何保证这些对象持续可用
基础机制如何支撑以上一切

业务模型要先写清的四件事

  1. 对象:系统里真正存在的东西是什么?例如学习计划、订单、账户、权限主体。
  2. 动作:谁可以对对象做什么?创建、修改、提交、取消、恢复。
  3. 不变量:无论怎样失败,哪些规则不能破?例如“同一订单不能重复支付成功”。
  4. 成功标准:对用户和业务而言,什么叫做完了?不是接口返回 200,而是结果可核对、可恢复、可审计。

没有业务模型时,四层技术很容易各自优化局部指标:界面更炫、接口更多、集群更花,却回答不了“系统到底在维护什么事实”。

本地状态与共享事实的边界

这是交互终端层与共享事实层之间最重要的契约。

状态类型归属例子设计原则
视图状态终端本地滚动位置、弹窗开关、主题丢了可重建,不必持久化为业务事实
草稿状态终端为主,可选择性同步未提交表单、编辑中的文案可丢失或可恢复,但不能被当成已生效
提交中状态终端投影 + 请求关联loading、乐观更新、重试计数必须能对应到某次命令,避免无限转圈
已确认事实共享事实层已创建记录、已支付、已授权以服务端结果为准,终端只做投影
派生视图两端都可能有列表汇总、未读数、进度百分比要声明来源;过期时如何刷新

判定规则

  • 只有一个用户、关掉就消失、不影响他人:多半是本地状态。
  • 换设备仍要看见、多人共享、涉及钱/权限/审计:必须是共享事实。
  • 终端可以乐观显示,但冲突时要有回滚或合并策略。
  • “请求已发送但结果未知”应被建模为明确状态,而不是靠猜测。
text
用户点击保存
-> 终端:draft -> submitting(本地)
-> 服务:接受命令,写入事实,返回版本号
-> 终端:submitting -> confirmed,并用服务端事实替换乐观值
   或 submitting -> failed,回滚并提示重试

一个贯穿四层的工作例子

以“用户在任意终端创建一条记录,另一台设备能够看到”为例,把每一步映射回层级:

text
[业务] 创建一条可共享、可追溯的记录
[终端] 输入校验、显示提交中、保留草稿
[网络] 携带身份、幂等键、请求超时
[事实] 鉴权、规则校验、防重、事务写入
[运行] 实例承接请求,记录日志/追踪,失败可转移
[基础] TCP/HTTP、磁盘持久化、进程与内存保障
[终端2] 拉取或接收变更,更新列表呈现

成功路径

text
用户输入
-> 客户端保存局部状态并显示提交中
-> 请求携带身份与命令到达服务
-> 服务验证规则,处理重复提交和并发冲突
-> 数据被持久化,必要时发布同步事件
-> 运行环境保证任务执行、记录日志并暴露追踪信息
-> 另一终端拉取或接收变化,更新自己的呈现

失败路径清单

故障主要落点系统应有的行为
断网点击提交终端 + 网络保留草稿,明确“未同步”,支持稍后重试
请求超时但服务已成功终端 + 事实靠幂等键重试,不能新建第二条;终端最终收敛到同一事实
两台设备同时改同一记录事实层并发控制版本冲突、拒绝或合并,并提示用户
数据库提交成功、事件未发出事实 + 异步集成Outbox/补偿,避免另一端永久看不到
处理实例中途崩溃运行与协调请求重试到健康实例,或任务重新投递且保持幂等
磁盘满 / 连接耗尽公共基础 + 运行快速失败、告警、保护核心路径,而不是静默写丢

沿这条链路提问,比按框架章节学习更快建立系统感:断网时客户端怎么做?超时重试为何必须幂等?容器重启会不会丢任务?另一台设备读到旧值是否可接受?

在 AI 加速局部实现后,人应保留的跨层判断

AI 很擅长生成某一层内部的代码,但跨层语义仍需人来守。

人要重点保留的能力

  1. 定义问题:系统到底要维护什么业务事实,成功标准是什么。
  2. 划分边界:哪些状态本地即可,哪些必须进共享事实;哪些同步,哪些异步。
  3. 设计失败:超时、重试、双写、乱序、部分成功时,用户和数据各看到什么。
  4. 选择一致性与成本:不是越强越好,而是哪条路径值得付锁和延迟的代价。
  5. 验证证据:用测试、日志、追踪、界面状态证明“做对了”,而不是看 AI 声称做对了。
  6. 控制复杂度:什么时候不该拆服务、不加缓存、不上队列。

可直接拿来审 AI 产出的问题单

  • 这个改动影响的是体验、事实、资源隔离,还是基础机制?
  • 重复执行一次会怎样?
  • 旧版本客户端遇到新版本服务会怎样?
  • 没有网络 / 下游变慢 / 进程重启时会怎样?
  • 我如何用一条日志或一个测试证明它?

面向新人的分层学习路径

不要先试图学完四层。按“能解释一条链路”推进。

阶段 A:建立总图

  • 目标:能画出四层,并指出任一技术大致落点。
  • 练习:任选 App 里一个按钮,口述它穿过哪些层。
  • 验收:不要求实现,但能区分本地显示成功和共享事实成功。

阶段 B:打通一条最小链路

  • 目标:终端 -> API -> 存储 -> 再读回终端。
  • 练习:创建一个记录并在另一页面/设备看到。
  • 验收:补齐断网、超时、重复提交三种失败。

阶段 C:加深单层机制

  • 终端:状态机、渲染更新、竞态。
  • 事实:事务、幂等、权限、基本索引。
  • 运行:日志、指标、超时、健康检查。
  • 基础:HTTP、进程、SQL 存储语义。
  • 验收:能把一个线上故障归到主责层,并说明可能波及层。

阶段 D:做跨层取舍

  • 目标:在真实约束下做设计,而不是堆技术。
  • 练习:给“生成报告/导出/支付/协同编辑”选同步或异步,并写清一致性与用户提示。
  • 验收:能解释为什么不采用另一方案。

阶段 E:把 AI 当杠杆

  • 先自己写状态机与验收标准,再让 AI 补实现。
  • 让 AI 专门生成反例和测试,而不是只生成 happy path。
  • 每次复盘 5 行:原假设、实际机制、跨层影响、验证方式、下次判断。

面向日常使用的记录模板

每次遇到陌生技术或故障,记四行即可:

  1. 落点:交互 / 共享事实 / 运行协调 / 公共基础?
  2. 契约:输入、输出、前提、不变量?
  3. 失败:用户看见什么,数据是否正确,如何恢复?
  4. 证据:测试、日志、追踪或界面结果如何证明?

仍待继续展开的问题

  • 每层各自的“最小可掌握集合”如何进一步拆成周计划?
  • 数据建模、API 设计、前端状态管理如何做成可对照的专题页?
  • 如何把真实项目故障复盘持续回填到这张图的节点上?
  • 安全与合规应作为贯穿属性写深,还是拆成独立观察视角?

学习规划与研究资料库