体系建构
体系建构的目标,是把软件开发还原到一个统一的形式化模型上,让所有具体的设计原则都能在它上面找到坐标。这个模型建立在冯诺依曼体系的最底层之上。
计算的三主题
冯诺依曼体系下的计算机问题,归根结底围绕三个主题展开:计算研究如何用高效快捷的算法求解问题,存储研究如何把数据简洁地存放同时又能快速查询,通信研究计算与存储之间如何高吞吐低延迟地传递资源。软件设计的绝大多数议题,都可以归到这三者之一的延长线上,理解一个原则时追问它服务于哪个主题,往往就能看清它的边界。
时空模型
分析设计一个程序系统,可以从空间结构和时间流程两个方向入手,先看空间结构,再看结构之间的交互流程。这套分析方法建立在一个二维空间加一维时间构成的三维时空之上。
这里有一个值得注意的错位:物理空间是三维的,但软件设计的结构却基于二维——我们通常只关心"在同一进程内"和"跨进程"这两类空间关系,第三维往往被压平。这种降维是工程上的简化,也意味着许多分布式难题,本质上是被压平的第三维重新抬起时所产生的。
空间结构
当下的软件系统在空间形态上有两个明显的边界——进程边界和主机边界,它们把空间划分成三层结构,恰好对应本章节的三个域。
进程内(进程域)关注模块拆分、作用域划分、依赖管理与状态生命周期,是开发者最常驻的层次。本机上(主机域)关注文件管理、微服务治理与数据配置分离,处理同一主机上多个进程协作的问题。主机间(网络域)关注分布式锁与通信代理,跨进程通信引入外部 IO 与失败的可能,跨主机通信则进一步引入网络延迟与阻塞。
通用域则不专属任何一层,承载容器、权衡、依赖管理等横切主题。
控制面与数据面
任何稍具规模的系统,都可以从另一个角度切成两面:数据面负责实际承载和搬运业务数据,控制面负责指挥数据面的行为——路由、调度、配置、发现。理解一个系统,往往先看清它的控制面和数据面各自由谁承担、如何分离,复杂度便豁然开朗。配置中心、服务注册发现、Kubernetes 的声明式编排,本质上都是把控制面从业务数据面里抽离出来的努力。
时间流程
时间是基于空间结构的。流程研究的不是抽象的时间,而是空间结构如何随着时间的推移而发生变化。
早期的开发者只关注单进程内的时间流程,后来逐步扩展到整个集群架构层级。把软件的整个生命周期纳入视野,会发现开发、测试、运维本是空间结构中不同的组成,却在时间流程上存在空窗:开发在编写程序时,测试和运维的时间被阻塞浪费,于是出现了 DevOps,把测试和运维的任务在时间维度上向开发折叠。
需求分析、开发设计、测试评估、运维监控构成一个长时间迭代的时间维度模型。初次编写一个软件的耗时只占整个生命周期的一小部分,因此设计软件时,从一开始就该站在整个生命周期的全过程去考虑,而不是只盯住眼前的初次实现。
两个本质洞见
第一个洞见是 IO 的本质就是变量赋值。读文件、收网络包、查数据库,剥去外壳后都是把外部状态搬进进程内的某个变量;理解了这一点,所有 IO 的失败、缓存、重试问题就有了统一的抓手。
第二个洞见是依赖解析的本质是图遍历。无论是依赖注入容器查找 bean、模块加载器解析 import、还是文件系统按路径定位文件,底层都是在一棵依赖图上做遍历——依赖管理中的健壮性问题,根因往往就出在这张图的建模方式上。