来自 Butler W. Lampson 的经验之谈
系统设计与算法设计截然不同,它充满了不确定性。外部需求模糊多变,内部结构复杂交错。成功的关键不在于找到唯一的“最佳”方案,而在于避免糟糕的选择,并清晰地划分各部分职责。以下是从无数成功与失败的系统中提炼出的设计精髓。
图表:将设计原则依据“设计目标(Why)”和“实现层面(Where)”进行二维可视化呈现。
接口应只包含最核心的要素。不做过度泛化,因为泛化往往是错的。一个臃肿的接口,其实现必然复杂、缓慢且难以维护。
简洁和抽象不能替代正确性。要警惕因不恰当的抽象选择而导致的灾难性性能问题(如 O(n²) 的查找)。
如果底层能快速完成某项任务,上层抽象不应将其封装在更通用但更慢的操作中。抽象旨在隐藏不必要的复杂性,而非有用的能力。
接口只解决一个核心问题,将其他问题(如调度策略、语义处理)留给客户端实现,从而获得简洁、灵活和高性能的统一。
对于任何创新系统,第一个版本几乎注定要被完全重做,才能达到满意的大小、速度和可维护性。承认这一点,并计划一个原型阶段,成本会低得多。
将难题分解为多个简单的子问题。当资源受限时,先处理能容纳的部分,其余的留给下一次迭代。
将昂贵计算的结果存储起来,供后续重复使用。这是提升交互式系统性能的关键,核心在于设计缓存结构,使局部变更只导致少量缓存失效。
“提示”是可能出错的缓存。它加速了常规路径,但使用前必须有验证其正确性的机制。例如,文件系统中的 B-Tree 索引就是磁盘数据真实位置的“提示”。
在交互式系统中,尽可能快地响应用户,将耗时任务(如垃圾回收、数据同步)推迟到处理器空闲时在后台执行。
与其让系统过载崩溃,不如主动控制需求。例如,当资源紧张时,网络可以丢弃数据包,系统可以拒绝新用户连接。
随着硬件成本下降,一个简单、直接、易于分析但消耗大量计算资源的方案,往往优于一个复杂、依赖诸多假设的“聪明”方案。
如果不确定如何共享资源,就采用固定切分的方式。专用资源通常访问更快、分配器行为更可预测,代价是可能牺牲资源利用率。
真正的可靠性必须由应用层(端到端)来保证。任何中间环节的检查和恢复都只是为了性能优化,而非逻辑上的必需。例如,文件传输的最终校验必须在目标磁盘上完成。
使用一个简单、可靠、只追加的日志来记录对象状态的“真相”。发生故障时,可以通过重放日志来恢复状态。当前的对象状态可视为日志真相的一种“提示”。
原子操作(事务)要么完全成功,要么完全没影响。这极大地简化了故障恢复逻辑,因为无需处理操作执行到一半的中间状态。