论文翻译:构建健壮的系统
读书笔记
Aug 6, 2023
type
Post
status
Published
date
Aug 6, 2023
slug
build-robust-system-translate
summary
本文是 SICP 的作者 Gerald Jay Sussman 教授的一篇文章,系统的阐述了他对生物系统的观察而得到的构建健壮系统的观点,读完受益匪浅,故尝试翻译记录于此
tags
论文
Architecture
category
读书笔记
icon
password
Property
Aug 15, 2023 12:30 PM

摘要

构建健壮的系统是困难的:这些系统在设计者预期之外的更大范围的情况下都有可接受的行为。最健壮的系统是可演化的:它们只需要进行微小的修改,就可以轻易地适应新的环境。那我们如何设计这样灵活的系统呢?
对生物系统的观察为我们提供了关于如何构建健壮且可进化系统的宝贵启示。最初为支持符号主义人工智能而开发的技术可以被视为增强程序和其他工程系统的健壮性和可演化性的方式。相比之下,计算机科学的常见实践实际上是在阻碍健壮系统的构建。

健壮性

设计一种具有通用性且能够非常好地完成特定工作的机制是困难的,因此大多数工程系统都被设计为执行特定的工作。像螺丝紧固件这样的通用发明,当它们出现时就具有重大意义。数字计算机就是这种突破,因为它是一种通用机器,可以模拟任何其他信息处理机器。我们通过编写软件来配置我们的计算机为我们需要完成的特定工作进行这种模拟行为。
我们一直在设计我们的软件来非常好地完成特定的工作,作为我们过去工程实践的延伸。每一个软件都被设计为完成相对狭窄的工作。随着需要解决的问题发生变化,软件必须进行更改。但是,对需要解决的问题的小改动并不意味着只需要对软件进行小改动。软件设计得过于紧密,以至于没有太多的灵活性。因此,系统不能优雅地演化。它们是脆弱的,必须随着问题域的演化被完全新的设计所替代。而这是缓慢且昂贵的。
我们的工程系统并不必须是脆弱的。互联网可以适用于小规模的系统到全球规模的系统。我们的城市有机地演化,以适应新的商业模式、生活方式以及交通和通信方式。实际上,通过观察生物系统,我们看到可以构建能够适应环境变化的系统,无论是个体还是作为一个演化的整体。为什么这不是我们设计和构建大多数软件的方式呢?有历史原因,但主要原因是我们不知道如何通用地做到这一点。在这个时刻,如果一个系统对需求变化具有健壮性,那只是一个偶然的结果。

冗余和简并

生物系统已经演化出了大量的健壮性。生物系统的一个特点是它们是冗余的。像肝脏和肾脏这样的器官具有高度的冗余性:它们的容量远远超过完成工作所需,因此,缺失一个肾脏的人并不会明显地失去能力。生物系统还具有高度的简并性:通常有许多方法可以满足给定的需求。例如,如果一个手指受伤,其他手指可以通过不同的方式来拾取物体。我们可以从各种各样的来源获取生命所需的能量:我们可以代谢碳水化合物、脂肪和蛋白质,尽管从这些来源中消化和提取能量的机制各不相同。
遗传密码本身就是简并的,因为从密码子(三联体核苷酸)到氨基酸的映射并不是一对一的:有 64 种可能的密码子只能指定大约 20 种可能的氨基酸。因此,许多点突变并不改变由编码区指定的蛋白质。这是变异可以积累而不明显影响表型的一种方式。如果一个基因被复制(这并不是一种罕见的现象),副本可能会无声无息地发生分歧,允许未来可能变得有价值的变体的发展,而不干扰当前的生存能力。此外,副本可以被放置在不同的转录控制下。
简并性是演化的产物,它肯定允许演化。可能简并性本身就是被选择的,因为只有具有大量简并性的生物才足够适应,能够在环境变化时生存下来。例如,假设我们有一些生物(或工程系统),它在简并性方面表现为有几种非常不同的相互依赖的机制来实现每个基本功能。如果环境变化(或需求变化)使得实现基本功能的一种方式变得不可行,生物将继续生活和繁殖(系统将继续满足其规格)。但是,已经停止运作的子系统现在可以进行突变(或修复),而不会影响到整个系统的生存能力(或当前操作)。
工程系统可能会包含一些冗余,特别是在故障成本极高的关键系统中。但是,除了作为非最优设计的副作用外,它们几乎从不故意包含生物系统中发现的那种简并性。

探索行为

生物系统健壮性中最强大的机制之一是探索行为。其思想是,期望的结果是由生成和测试机制产生的。这种组织方式允许生成机制是通用的,并且独立于接受或拒绝特定生成结果的测试机制。
例如,支持细胞形状的刚性骨架的一个重要组成部分是微管的阵列。每个微管都由聚集形成微管的蛋白质单位组成。在活细胞中,微管不断被创建和销毁;它们在所有方向上生长。然而,只有那些在细胞膜中遇到稳定剂的微管是稳定的,从而支持由稳定剂位置决定的形状。因此,生长和维持形状的机制相对独立于指定形状的机制。这种机制部分决定了复杂生物中许多类型细胞的形状,它在后生动物中几乎是普遍的。
探索行为出现在生物系统的所有细节层面。发育中的胚胎的神经系统产生的神经元数量远远超过成年人中会保留的数量。那些在其他神经元、感觉器官或肌肉中找到合适靶点的神经元会存活下来,而那些找不到靶点的神经元会自杀。手的形成是通过生成掌腹和通过细胞的凋亡删除手指之间的材料实现的。我们的骨骼不断被成骨细胞(构建骨骼)和破骨细胞(破坏骨骼)重塑。骨骼的形状和大小由其环境决定的约束决定:它们必须与之相关的部分,如肌肉、韧带、肌腱和其他骨骼。
因为生成器无需知道测试器如何接受或拒绝其提案,测试器也不需要知道生成器如何提出其提案,所以这两部分可以独立发展。这使得适应和演化更加高效,因为对这两个子系统中的一个或另一个的突变不需要伴随着对另一个的互补突变。然而,这种隔离是昂贵的,因为生成和拒绝失败提案的工作都是无用的。(译者注:因为彼此无感且独立演进,这种无用的努力概率上比较大,因此不能忽视)
事实上,生成和测试是所有演化的隐喻。生物变异的机制是随机突变:遗传指令的修改。大多数突变是中性的,因为它们不会因为系统的简并性而直接影响适应性。自然选择是测试阶段。它不依赖于变异的方法,变异的方法也不预测选择的效果。
还有更令人震惊的现象:即使在密切相关的生物中,一些在成体中几乎相同的成分在胚胎中是由完全不同的机制构造的。对于远亲关系来说,构造共同结构的发散机制可能被归因于“趋同进化”,但对于近亲关系来说,更有可能是细节层次分离的证据,其中结果以某种独立于实现方式的方式被指定。

区室和定位

我们身体中的每一个细胞都是单个受精卵的后代。所有的细胞都有完全相同的遗传禀赋(大约 1GB 的ROM!)。然而,还是会有皮肤细胞、神经元、肌肉细胞等。细胞将自己组织成为离散的组织、器官和器官系统。因为一个细胞的分化和专化取决于其环境让这种情况变得可能。几乎所有的后生动物都有同源盒基因,如 Hox 复合体。这样的基因在发育中的动物体中产生一个大致的坐标系统,将发育中的动物分隔成不同的区域。这些区域为细胞的分化提供了上下文。并且,从与邻居的接触中得到的信息产生了更多的上下文,这些上下文让细胞从其遗传程序中可用的可能行为中选择特定的行为。
这种组织方式有一些明显的优点。灵活性通过细胞之间的信号是允许性的而不是指导性的这一事实得到了增强。也就是说,细胞的行为不是编码在信号中;它们是在基因组中单独表达的。信号的组合只是启用一些行为并禁用一些。这种弱链接允许在不修改定义区域的机制的情况下,实现在各个区域启用的行为的变化。因此,以这种方式组织的系统是可演化的,因为它们可以在一些区域适应变异,而不改变其他区域的子系统的行为。
良好的工程设计也有类似的特点,即良好的设计是模块化的。以无线电接收器的设计为例 。已经发现了几种宏大的“机体计划”,如直接转换、TRF(调谐无线电频率)和超外差。每一种都有一系列的区域,由等效 Hox 复合体的工程结构定义,从天线到输出传感器的系统都这个模式。例如,一个超外差接收器有以下区域:
每个区域都能以许多可能的方式实例化。 部分可能只是一个滤波器,或者可能是一个复杂的滤波器和放大器组合。事实上,有些部分可能会被递归地细化(就好像 Hox 复合体被复制了一样!),以获得多转换接收器。
当然,与生物机制不同,在模拟电子学中,组件通常不是通用的,即原则上一个组件不可以充当任意其他组件。但是原理上说有通用的电子构建块(例如带有模拟接口的可编程计算机!)。对于低频应用,可以用这些通用块构建模拟系统。如果每个块都有可以成为系统中任何块所需的全部代码,只是通过与邻居的交互进行专门化,而且如果包中有额外的未专门化的“干细胞”,那么我们就可以想象出构建了具有自我重构和自我修复能力的模拟系统。
生物系统中区室的结构由复杂的基础设施支持。这个基础设施的一个重要组成部分是动态地将标签附加到正在操作的材料的能力。例如,在真核细胞中,蛋白质是带有描述其目的地的标签构建的。例如,一个跨膜蛋白,可能是离子运输孔的一部分,它被指向质膜,而其他一些蛋白可能被指向高尔基体。细胞中有机制(它们自身由蛋白质组装体组成)来识别这些标签并将部件运输到它们的目的地。标签也被用来清理和处理各种废物,如没有正确折叠或不再需要的蛋白分子。这些蛋白被标记(泛素化)并被运送到蛋白酶体进行降解。
这种区室的结构也在更高的组织层次上得到支持。有一些组织专门化为区室的边界和相互连接它们的管道。器官由这样的组织界定并通过这样的管道相互连接,整个结构被包装在一起以适应在高等生物中由专门组织覆盖的体腔。

防御、修复和再生

生物系统总是受到捕食者、寄生虫和入侵者的攻击。它们已经发展出复杂的防御系统,从细菌中的限制性内切酶到哺乳动物的免疫系统。先进的防御系统依赖于对外部和内部环境的持续监控,以及区分自我和他者的机制。
在一个由单个受精卵衍生出来的复杂生物体中,原则上每个细胞都能够完成其他细胞的功能。因此,数量上存在冗余。更重要的是,这提供了一种修复和再生的机制。一个复杂的生物是一个由潜在的通用、可互换和可复制的部分构成的动态且可重配置的结构:如果一个部分受损,附近的细胞可以重新定位以填补空缺并承担受损部分的功能。
计算机软件行业只是最近开始理解来自捕食者、寄生虫和入侵者的威胁。早期的软件系统是为在友好、安全的环境中工作而构建的。但随着网络的全球化以及依赖于攻击和合并脆弱系统的经济策略的发展,环境已经变成了一个实质上的敌对环境。当前的防御措施,如防病毒和反垃圾邮件软件,在这种环境中几乎无效(尽管有很多人尝试开发受生物系统启发的计算机“免疫系统”)。
一个严重的问题是单一化。几乎每个人都使用相同的计算机系统,大大增加了脆弱性。在生物系统中,有巨大的中性空间,允许在功能上微小的变化中产生巨大的变异:人们可以有几种血型;有不同大小、形状、颜色等的人。但他们都是人,都有类似的能力,可以在各种各样的环境中生活,他们用语言交流!然而,并非所有人都有相同的脆弱性:具有镰状细胞特征的杂合子对疟疾有一定的抵抗力。
在我们的工程系统中,我们通常没有利用这种多样性。我们还没有利用备用设计中可用的多样性,也没有利用静默突变中可用的多样性。部分原因是单一性在经济上有优势。但这种经济是短视和虚幻的,因为单一性对故意和演化的攻击极度脆弱。
生物系统有很强的修复损伤的能力,而且在某些情况下能够再生丧失的部分。这种能力需要广泛和持续的自我监控,以注意到损伤的发生并启动修复过程。它需要动员资源进行修复,还需要关于如何进行修复的信息。
使用探索行为构建结构的系统可以很容易地被用来支持修复和再生。然而,仍然需要控制探索性的增殖以达到期望的最终状态。这似乎是通过稳态约束机制来安排的。例如,伤口可能需要产生新的组织来替换丧失的材料。新的组织需要供应氧气和营养物质,并且需要排除废物,因此,必须为其提供正确地与循环系统相互连接的新毛细血管。没有得到足够氧气的细胞会产生刺激血管向其方向增殖的激素。因此,构建循环系统的机制不需要知道目标组织的几何形状。他们的关键任务是通过探索和局部约束满足来实现的。这些机制同时支持胚胎的形态建成和成人的愈合。
有很少的工程系统具有自我修复和再生的实质能力。高质量的操作系统有“文件系统修复器”,可以检查文件系统的完整性,并使用文件系统中的冗余来修复损坏的结构和再生一些丧失的部分。但这是一个特例。我们如何使这种自我监控和自我修复成为规则而不是例外?
在防御和修复这两种情况下,关键的组成部分是意识——监控环境中即将发生的威胁和自我损伤的能力。(译者注:所以具备可观测性应该成为系统的默认内建特性😄)

构成

大型系统由许多较小的组件组成,每个组件都为整体功能做出贡献,有的通过直接提供部分功能,有的通过与其他组件以系统架构指定的模式相互连接来合作完成某种功能。系统工程中的一个核心问题是建立允许组件的互连的接口,以便可以组合这些组件的功能来构建复合功能。
对于相对简单的系统,系统架构师可以为各种接口制定正式规范,互连组件的实现者必须满足这些接口。事实上,电子学的惊人成功就基于制定这样的规范并满足它们是可行的。高频模拟设备用具有标准阻抗特性的同轴电缆和标准化的连接器系列进行互连。组件的功能和其接口行为通常只能用几个参数来指定。在数字系统中,情况更加明确,有关于信号含义的静态规范(数字抽象),也有关信号时序的动态规范,以及关于组件形状因素的机械规范。
遗憾的是,随着系统复杂性的增加,由先验经验形成规范变得愈发困难。计算机软件组件的规范通常非常复杂,它们很难构建,而保证符合这样的规范更是困难,许多软件相关的脆弱性都是因为这种复杂性。
相比之下,生物学构建的系统具有巨大的复杂性,但规范却不庞大。人类基因组大约是 1 GByte。这与一个主要的计算机操作系统的规范相比要小得多。这是怎么做到的呢?我们知道,大脑的各个部分通过大量的神经元束连接在一起,而基因组中的信息远远不足以详细指定这种互连。可能是大脑的各个部分学会了相互交流,以此来分享重要的经验。因此,接口必须基于一些一致性规则、环境信息和广泛的探索行为进行自我配置。这在启动时间上相当昂贵(配置一个活着的人需要几年的时间),但它提供了一种我们迄今为止在工程实体中未发现的稳健性。

代价

对于乐观主义者,杯子半满。对于悲观主义者,杯子半空。对于工程师来说,杯子比它应该的尺寸大两倍 - 无名
遗憾的是,通用性和可演化性需要冗余、简并性和探索行为。从孤立的角度来看,这些都是昂贵的。一个在广泛输入范围内都能工作的机制必须做更多的工作才能得到与专门针对特定输入的机制相同的结果,一个冗余的机制比一个等效的非冗余机制有更多的部分,一个简并的机制看起来更加奢侈,可这些都是可演化系统的要素。为了构建真正健壮的系统,我们必须愿意支付看起来相当繁杂的基础设施的代价。增强的适应性的价值可能更高。事实上,我们在脆弱的基础设施上的成本可能大大超过了稳健设计的成本,无论是在灾难的成本还是在由于重新设计和重建的时间而失去的机会成本上。

正确性的问题

但是,以一种使系统的适用范围大于我们在设计时考虑的情况集的方式来构建系统可能会有更大的成本。因为我们打算在未被设计的情境中使用我们的系统,我们不能确定它们是否能正确工作!
我们被教导说,软件的“正确性”至关重要,而正确性是通过建立组件和组件系统的正式规范,并通过提供证据证明组件组合的规范符合组件的规范和组件组合的模式来实现的。我断言这个教条增加了系统的脆弱性。事实上,为了构建真正健壮的系统,我们必须抛弃这样严格的规训。
要求证明的问题在于,证明一般机制的一般属性通常比证明在受限情况下使用的特殊机制的特殊属性更难。这鼓励我们尽可能地使我们的部件和组合特殊化,以便我们能够简化我们的证明。
我并不是在反对证明。当它们可用时,它们是非常好的。事实上,对于关键的系统组件,如垃圾收集器(或核糖体!)来说,它们是必不可少的。然而,即使对于安全至上的系统,如自动驾驶仪,将适用性限制在系统被证明为按规定正确的情况下,也可能导致不必要的失败。实际上,我们希望自动驾驶仪能够认真地尝试安全驾驶一架在设计者未预见的方式下受损的飞机!
我反对的是要求证明的风纪:允许在某种情况下使用之前,必须证明一切都适用于那种情况的要求,过度地抑制了使用能增强设计的健壮性的技术的可能性。这对于允许一种方法在其已证明的领域之外,严格地使用,以及为未来的扩展提供可能性,而不对事物可以被扩展的方式设限的技术尤其如此。
遗憾的是,我倡导的许多技术使得证明的问题变得更加困难,如果不是实际上不可能的话。另一方面,有时候解决问题的最好方法是将其概括,直到证明变得简单。

支持通用性的基础设施

我们希望构建可以轻易地超越其初始用途进行泛化的系统。让我们考虑可以应用于软件设计的技术。我并不是在倡导一个宏大的计划或语言,如 Planner,而是在适当的时候整合这些技术的基础设施。

部件的通用性

最稳健的系统是由部件系列来构建的,每个部件都具有非常广泛的适用性。这些部件在比任何特定应用所需更广泛的条件下都具有可接受的性能。例如,如果我们有可以为给定的输入产生输出的部件,我们需要能够产生合理输出的可接受输入的范围非常宽泛。使用拓扑学的语言来说就是,用于解决当前问题的任何组件的可接受输入的种类应该是一个“开放集”,围绕着它在当前问题的实际使用中会遇到的输入。
此外,这些部件在这个宽泛的输入范围上的输出范围应该相当小且定义明确:比可接受输入的范围小得多。这与我们在计算机系统入门课程中向学生传授的数字抽象里的 static discipline 类似(译者注:找不到合适的中文翻译,详情见 Wiki)。数字抽象的能力在于输出总是比可接受的输入更好,所以它抑制了噪声。使用更通用的部分为我们的系统的整个结构构建了一定程度的灵活性。小的需求变动可以在不发生灾难的情况下进行调整,因为每个组件都被构建为接受扰动(噪声)输入。
有多种技术可以帮助我们创建比我们在设计组件时考虑的应用程序所需的更通用的组件族。这些技术并不新鲜。它们经常被无意识地用于帮助我们克服某些特定的问题。然而,我们没有统一的理解或共同的基础设施来支持它们的使用。此外,我们已经形成了一种文化,认为这些技术中很多是危险的或肮脏的。我的目的是将这些技术暴露在统一的上下文中,以便我们可以学习利用它们来构建更健壮的系统。

可扩展的泛型操作

一个好主意是在可扩展泛型操作符的基础上构建一个系统。现代动态类型编程语言通常有内置的通用算术,适用于各种类型的数值,如整数、浮点数、有理数和复数。这已经是一个优势,但它肯定会使推理和证明变得复杂,并且使实现比简单的系统更复杂,效率更低。然而,我正在考虑一种更通用的方案,在这种方案中,可以定义加法、乘法等的含义,用于语言设计者无法想象的新数据类型。因此,例如,如果一个系统的算术运算符是可扩展的泛型,用户可以扩展它们以允许算术扩展到四元数、向量、矩阵、对素数取模的整数、函数、张量、微分形式. . . .这不仅使新功能成为可能,它还扩展了旧的程序,因此,为操作简单数值而编写的程序可能对操作标量值函数有用。
然而,这存在一个风险。一个依赖于数值乘法交换性的程序对于矩阵肯定不会正确工作。(当然,一个依赖于整数操作精确性的程序对于不精确的浮点数也不会正确工作)这正是伴随着进化而来的风险——一些突变将是致命的!但是,这种风险必须与在必要时不尝试使用该程序的成本进行权衡。
另一方面,一些突变将是极有价值的。例如,可以将算术扩展到符号量。做到这一点的最简单的方法是对所有的操作符进行通用扩展,以接受符号量作为参数,并返回一个表示对参数进行指定操作的数据结构。加上一个代数表达式的简化器,我们突然就有了一个符号操作器。这在调试纯数值计算时非常有用,因为如果它们被给予符号参数,我们可以检查结果的符号表达式,以确保程序正在计算我们准备计算的内容。这也是用来优化数值程序的部分求值器的基础。并且,函数微分可以被视为对超实数数据类型的算术的通用扩展。
可扩展的通用操作不适合胆小的人。在事后扩展操作符的能力既提供了极端的灵活性,也带来了全新的类型的 BUG!当原始操作可以被扩展时,除了它在定义的类型上工作时能正常工作之外,可能无法证明程序的更多内容。这是一个简单但危险的通用化路径。
可扩展的泛型操作跟解释代码和编译代码的互操作,意味着必须用所需的信息标记数据,这些信息决定使用哪些步骤来实现所指示的操作。但是,一旦我们能够标记数据,就可以将标记用于其他用途。例如,我们可以标记数据的出处,它是如何派生的,或者它基于的假设。此类审计跟踪对于访问控制、跟踪敏感数据的使用或调试复杂系统可能是必不可少的。因此,除了用于确定泛型的标签之外,我们还可以为任何数据项附加任意标签,从而获得强大的功能。

生成和测试

我们通常认为生成和测试,以及其在搜索中的特殊使用,是一种 AI 技术。然而,它可以被视为一种构建模块化和可独立演化系统的方式,就像生物系统的探索行为一样。考虑一个非常简单的例子:假设我们必须解一个二次方程,二次方程有两个根,我们可以返回两个,并假设解的使用者知道如何处理它们,或者我们可以返回一个并期待最好的结果。(标准的 sqrt 例程返回正平方根,尽管有两个平方根!)返回两个解的缺点是,接收结果者必须知道用两个解都尝试计算,出于充分的理由拒绝一个,或者返回他的计算的两个结果,这可能本身已经做出了一些选择。只返回一个解的缺点是,它可能不是接收者想要的那个。
处理这个问题的更好方法是在基础设施中构建一个回溯机制。平方根程序应返回其中一个根,并提供一个选项:如果接收者确定第一选择不合适,可以改变主意并返回另一个。这是,也应该是,接收者的责任来确定其计算的因素是否是适当和可接受的。这本身可能需要一个复杂的计算,涉及到可能在没有进一步计算的情况下后果不明显的选择,所以这个过程是递归的。当然,这将使我们陷入潜在的致命的对程序所有可能的分配和所有的选择指数搜索。通常,模块化的灵活性可能是危险的。
但是,如果选择机制将描述其状态的标签附加到它选择的数据上,如果组合数据的原始操作正确地组合这些标签,那么我们总是可以知道哪些选择对任意特定的数据片段有贡献。有了这样一个系统,搜索可以被优化,以便在任何特定的回溯中只需要考虑相关的选择。这是依赖引导的回溯的本质。如果这样一个系统被构建到基础设施中,那么探索行为可以和任何明确的选择集合的操作有一样效率,而不需要程序知道哪些数据的贡献者实际上是可能性的集合。虽然它确实会带来程序测试其结果的一致性并在必要时拒绝它们的开销。当然,这在任何旨在可靠和健壮的系统中都是重要的。
 

未完待续

 
  • 论文
  • Architecture
  • 论文翻译:构建健壮的系统(2)
    Kafka 并发消费的设计和实现