【书籍】“凤凰架构”-构建可靠的大型分布式系统
什么是“凤凰架构”
“Phoenix”这个词东方人不常用,但在西方的软件工程读物——尤其是关于 Agile、DevOps 话题的作品中时常出现。软件工程小说《 The Phoenix Project》讲述了徘徊在死亡边缘的 Phoenix 项目在精益方法下浴火重生的故事;马丁·福勒(Martin Fowler)对《 Continuous Delivery》的诠释里,曾多次提到“ Phoenix Server”(取其能够“涅槃重生”之意)与“ Snowflake Server”(取其“世界上没有相同的两片雪花”之意)的优劣比对。也许是东西方的文化的差异,尽管有“失败是成功之母”这样的谚语,但我们东方人的骨子里更注重的还是一次把事做对做好,尽量别出乱子;而西方人则要“更看得开”一些,把出错看做正常甚至是必须的发展过程,只要出了问题能够兜底使其重回正轨便好。
在软件工程里,任何产品的研发,只要时间尺度足够长,人就总会疏忽犯错,代码就总会携有缺陷,电脑就总会宕机崩溃,网络就总会堵塞中断……如果一项工程需要大量的人员,共同去研发某个大规模的软件产品,并使其分布在网络中大量的服务器节点中同时运行,随着项目规模的增大、运作时间变长,其必然会受到墨菲定律的无情打击。
为了得到高质量的软件产品,我们是应该把精力更多地集中在提升其中每一个人员、过程、产出物的能力和质量上,还是该把更多精力放在整体流程和架构上?
笔者先给这个问题一个“和稀泥”式的回答:这两者都重要。前者重术,后者重道;前者更多与编码能力相关,后者更多与软件架构相关;前者主要由开发者个体水平决定,后者主要由技术决策者水平决定;
然而,笔者也必须强调此问题的另外一面:这两者的理解路径和抽象程度是不一样的。如何学习一项具体的语言、框架、工具,譬如 Java、Spring、Vue.js……都是相对具象的,不论其蕴含的内容多少,复杂程度高低,它是至少能看得见摸得着。而如何学习某一种风格的架构方法,譬如单体、微服务、服务网格、无服务、云原生……则是相对抽象的,谈论它们可能要面临则“一百个人眼中有一百个哈姆雷特”的困境。谈这方面的话题,若要言之有物,就不能是单纯的经验陈述。笔者想来,回到这些架构根本的出发点和问题上,真正去使用这些不同风格的架构方法来实现某些需求,解决某些问题,然后在实践中观察它们的异同优劣,会是一种很好的,也许是最好的讲述方式。笔者想说一下这些架构,而且还想说得透彻明白,这需要代码与文字的配合,于是便有了这个项目。
可靠的系统
让我们再来思考一个问题,构建一个大规模但依然可靠的软件系统,是否是可行的?
这个问题令人听起来的第一感觉也许会有点荒谬:废话。如果这个事情从理论上来说就是根本不可能的话,那我们这些软件开发从业人员现在还在瞎忙活些什么?但你再仔细想想,前面才提到的“墨菲定律”和在“大规模”这个前提下必然会遇到的各种不靠谱的人员、代码、硬件、网络等因素,从中能得出的一个听起来颇为符合逻辑直觉的推论:如果一项工作要经过多个“不靠谱”的过程相互协作来完成,其中的误差应会不断地累积叠加,导致最终结果必然不能收敛稳定才对。
这个问题也并非杞人忧天庸人自扰式的瞎操心,计算机之父冯·诺依曼(John von Neumann)在 1940 年代末期,曾经花费了大约两年时间,研究这个问题并且得出了一门理论《 自复制自动机》(Theory of Self-Reproducing Automata),这个理论以机器应该如何从基本的部件中构造出与自身相同的另一台机器引出,其目的并不是想单纯地模拟或者理解生物体的自我复制,也并不是简单想制造自我复制的计算机,他的最终目的就是想回答一个理论问题:如何用一些不可靠的部件来构造出一个可靠的系统。
自复制机恰好就是一个最好的用不可靠部件构造的可靠的系统例子。这里,“不可靠部件”可以理解为构成生命的大量细胞、甚至是分子。由于热力学扰动、生物复制差错等因素干扰,这些分子本身并不可靠。但是生命系统之所以可靠的本质,恰是因为它可以使用不可靠的部件来完成遗传迭代。这其中的关键点便是承认细胞等这些零部件可能会出错,某个具体的零部件可能会崩溃消亡,但在存续生命的微生态系统中一定会有其后代的出现,重新代替该零部件的作用,以维持系统的整体稳定。在这个微生态里,每一个部件都可以看作一只不死鸟(Phoenix),它会老迈,而之后又能涅槃重生。
架构的演进
软件架构风格从大型机(Mainframe),到 原始分布式(Distributed),到 大型单体(Monolithic),到 面向服务(Service-Oriented),到 微服务(Microservices),到 服务网格(Service Mesh),到 无服务(Serverless)……技术架构上确实呈现出“从大到小”的发展趋势。当近年来微服务兴起以后,涌现出各类文章去总结、赞美微服务带来的种种好处,诸如简化部署、逻辑拆分更清晰、便于技术异构、易于伸缩拓展应对更高的性能等等,这些当然都是重要优点和动力。可是,如果不拘泥于特定系统或特定某个问题,以更宏观的角度来看,前面所列这种种好处却都只能算是“锦上添花”、是属于让系统“活得更好”的动因,肯定比不上系统如何“确保生存”的需求来得关键、本质。笔者看来,架构演变最重要的驱动力,或者说这种“从大到小”趋势的最根本的驱动力,始终都是为了方便某个服务能够顺利地“死去”与“重生”而设计的,个体服务的生死更迭,是关系到整个系统能否可靠续存的关键因素。
举个例子,譬如某企业中应用的单体架构的 Java 系统,其更新、升级都必须要有固定的停机计划,必须在特定的时间窗口内才能按时开始,必须按时结束。如果出现了非计划的宕机,那便是生产事故。但是软件的缺陷不会遵循领导定下的停机计划来“安排时间出错”,为了应对缺陷与变化,做到不停机地检修,Java 曾经搞出了 OSGi 和 JVMTI Instrumentation 等这样复杂的 HotSwap 方案,以实现给奔跑中的汽车更换轮胎这种匪夷所思却又无可奈何的需求;而在微服务架构的视角下,所谓系统检修,不过只是一次在线服务更新而已,先停掉 1/3 的机器,升级新的软件版本,再有条不紊地导流、测试、做金丝雀发布,一切都是显得如此理所当然、平淡寻常;而在无服务架构的视角下,我们甚至都不可能去关心服务所运行的基础设施,连机器是哪台都不必知道,停机升级什么的就根本无从谈起了。
流水不腐,有老朽,有消亡,有重生,有更迭才是生态运行的合理规律。请设想一下,如果你的系统中每个部件都符合“Phoenix”的特性,哪怕其中某些部件采用了由极不靠谱的人员所开发的极不靠谱程序代码,哪怕存有严重的内存泄漏问题,最多只能服务三分钟就一定会崩溃。而即便这样,只要在整体架构设计有恰当的、自动化的错误熔断、服务淘汰和重建的机制,在系统外部来观察,整体上仍然有可能表现出稳定和健壮的服务能力。
凤凰架构
在企业软件开发的历史中,一项新技术发布时,常有伴以该技术开发的”宠物店(PetStore)”作为演示的传统(如 J2EE PetStore、 .NET PetShop、 Spring PetClinic等等)。作为不同架构风格的演示时,笔者本也希望能遵循此传统,却无奈从来没养过宠物,遂改行开了书店(Fenix's Bookstore),里面出售了几本笔者撰写过的书籍,算是夹带一点私货,同时也避免了使用素材时可能的版权问题。
尽管相信没有人会误解,但笔者最后还是多强调一句,Oracle、Microsoft、Pivotal 等公司设计宠物店的目的绝不是为了日后能在网上贩卖小猫小狗,只是纯粹的演示技术。所以也请勿以“实现这种学生毕业设计复杂度的需求,引入如此规模的架构或框架,纯属大炮打苍蝇,肯定是过度设计”的眼光来看待接下来的“Fenix's Bookstore”项目。相反,如果可能的话,笔者会在有新的技术、框架发布出来时,持续更新,以恰当的形式添加到项目的不同版本中,可能使其技术栈越来越复杂。笔者希望把这些新的、不断发展的知识,融合进已有的知识框架之中,让自己学习、理解、思考,然后将这些技术连同自己的观点看法,传播给感兴趣的人。
也算是缘分,网名“IcyFenix”在二十多年前我的中学时代开始使用,最初它是来源于暴雪公司的即时战略游戏《星际争霸》的 Protoss 英雄 Fenix——如名字所预示的那样,他曾经是 Zealot,牺牲后以 Dragoon 的形式重生,带领 Protoss 与刀锋女王 Kerrigan 继续抗争。尽管中学时期我已经笃定自己未来肯定会从事信息技术相关的工作,但显然不可能预计到二十年后我会写下这些文字。
所以,既然我们要开始一段关于“Phoenix”的架构讨论与代码实践,那便叫它“凤凰架构”,如何?