前言

从大型机到单体架构,从微服务架构到无服务架构,每一次架构模式的演进都是一次涅槃。每一个软件系统都是由大量服务构成的生态体系,个体服务的“死亡”和“重生”是整个系统能否持续可靠运行的关键因素。笔记从5个方面全面剖析了如何构建一个可靠的分布式系统,同时给出了Spring Boot、Spring Cloud、Kubernetes、Istio、AWS Lambda五种架构风格的样例工程

由5个维度全面探索如何构建可靠的大型分布式系统:

  • 从架构演进
  • 架构设计思维
  • 分布式基石
  • 不可或缺的基础设施
  • 技术方法论

第一部分 演进中的架构

架构并不是被发明出来的,而是持续演进的结果。本章我们暂且放下代码与技术,借讨论历史之名,来梳理软件架构发展历程中出现过的名词术语,以全局的视角,从这些概念的起源去分析它们是什么,它们取代了什么,它们为什么能够在竞争中取得成功,为什么变得不可或缺,以及它们为什么会失败,在斗争中被淘汰,逐渐湮灭于历史的烟尘当中。

第二部分架构师的视角

远程服务将计算机程序的工作范围从单机扩展至网络,从本地延伸至远程,是构建分布式系统的首要基础。而远程服务又不仅仅是为分布式系统服务的,在网络时代,浏览器、移动设备、桌面应用和服务端的程序,普遍都有与其他设备交互的需求,所以今天已经很难找到没有开发和使用过远程服务的程序员了,但是没有正确理解远程服务的程序员却不少。

事务处理几乎在每一个信息系统中都会涉及,它存在的意义是为了保证系统中所有的数据都是符合期望的,且相互关联的数据之间不会产生矛盾,即数据状态的一致性(Consistency)。按照数据库的经典理论,要达成这个目标,需要三方面共同努力来保障。

现代的企业级或互联网系统,“分流”是必须要考虑的设计,分流所使用的手段数量之多、涉及场景之广,可能连它的开发者都未必能全部意识到。这听起来似乎并不合理,但笔者认为这恰好是优秀架构设计的一种体现,“分布广阔”源于“多级”,“意识不到”谓之“透明”,也即本章我们要讨论的主题“透明多级分流系统[1]”(Transparent Multilevel Diversion System)的来由

即使只限定在“软件架构设计”这个语境下,系统安全仍然是一个很大的话题。我们谈论的计算机系统安全,不仅仅是指“防御系统被黑客攻击”这样狭隘的安全,还至少应包括(不限于)以下这些问题的具体解决方案。

第三部分布式的基石

在正式探讨分布式环境中面临的各种技术问题和解决方案前,我们先把目光从工业界转到学术界,学习几种具有代表性的分布式共识算法,为后续在分布式环境中操作共享数据准备打好理论基础。

微服务架构的一个重要设计原则是“通过服务来实现独立自治的组织件”(Componentization via Service),强调应采用“服务”(Service)而不是“类库”(Library)来构建组件化的程序,这两者的差别在于类库是在编译器静态链接到程序中的,通过调用本的方法来使用其中的功能,而服务是进程外组件,通过调用远程方法来使用其中的功能。

容错性设计(Design for Failure)是微服务的另一个核心原则,也是笔者书中反复强调的开发观念转变。不过,即使已经有一定的心理准备,大多数首次将微服务架构引入实际生活产系统的开发者,在服务发现、网关路由等支持下,踏出了服务化的第一步以后,很可能仍会经历一段阵痛期,随着拆分出的服务越来越多,随之而来会面临以下两个问题的困扰。

微服务提倡分散治理(Decentralized Governance),不追求统一的技术平台,提倡让团队员有自由选择的权利,不受制于语言和技术框架。在开发阶段构建服务时,分散治理打破了由技术栈带来的约束,好处是不言自明的。但在运维阶段部署服务时,尤其是在考量安全问题时,由Java、Go、Python、Node.js等多种语言和框架共同组成的微服务系统,出现安全漏洞的概率肯定要比只采用其中某种语言、某种框架所构建的单体系统更高。为了避免由于担忧一旦服务节点出现漏洞被攻击者突破,进而导致整个系统和内网都遭到入侵,我们就必须打破一些传统的安全观念,以构筑更加可靠的服务间通信机制。

随着分布式架构渐成主流,可观测性(Observability)一词也日益频繁地被人提起。最初,它与可控制性(Controllability)一起,是由匈牙利数学家Rudolf E.Kálmán针对线性动态控制系统提出的一组对偶属性,原本的含义是“可以由其外部输出推断其内部状态的程度”。

第四部分 不可变基础设施

容器是云计算、微服务等诸多软件行业核心技术的共同基石。容器的首要目标是让软件分发部署过程从传统的发布安装包、靠人工部署转变为直接发布已经部署好的、包含整套运行环境的虚拟化镜像。在容器技术成熟之前,主流的软件部署过程是由系统管理员编译或下载好二进制安装包,根据软件的部署说明文档准备好正确的操作系统、第三方库、配置文件、资源权限等各种前置依赖以后,才能将程序正确地运行起来。Chad Fowler在提出“不可变基础设施”这个概念的文章“Trash Your Servers and Burn Your Code”[1]的开篇就直接吐槽:要把一个不知道打过多少个升级补丁、不知道经历了多少任管理员的系统迁移到其他机器上,毫无疑问会是一场灾难。

本章我们将讨论虚拟化网络方面的话题,如果不加任何限定,“虚拟化网络”是一项内容十分丰富,研究历史十分悠久的计算机技术,是计算机科学中一门独立的分支,完全不依附于虚拟化容器而存在。网络运营商常提及的“网络功能虚拟化”(Network FunctionVirtualization,NFV),网络设备商和网络管理软件提供商常提及的“软件定义网络”(Software Defined Networking,SDN)等都属于虚拟化网络的范畴。对于普通的软件开发者而言,要完全理解和掌握虚拟化网络,需要储备大量开发中不常用到的专业知识,消耗大量的时间成本,一般并无必要。

容器是镜像的运行时实例,为了保证镜像能够重复地产出具备一致性的运行时实例,必须需要求镜像本身是持久且稳定的,这决定了在容器中发生的一切数据变动操作都不能真正写入镜像当中,否则必然会破坏镜像稳定不变的性质。为此,容器中的数据修改操作大多是基于写入时复制(Copy-on-Write)策略来实现的,容器会利用叠加式文件系统(OverlayFS)的特性,在用户意图修改镜像时,自动将变更的内容写入独立区域,再与原有数据叠加到一起,使其从外观上看来像是“覆盖”了原有的内容。这种改动通常都是临时的,一旦容器终止运行行,这些存储于独立区域中的变动信息也将被一并移除,不复存在。由此可见,如果不进行额外的处理,容器默认是不具备持久化存储能力的。

调度是容器编排系统最核心的功能之一,“编排”一词本身便包含“调度”的含义。调度是指为新创建的Pod找到一个最恰当的宿主机节点来运行它,这个过程成功与否、结果恰当与否则,关键取决于容器编排系统是如何管理与分配集群节点的资源的。可以认为调度是必须的容器编排系统的资源管控为前提,那我们就先从Kubernetes的资源模型谈起。

容器编排系统管理的最细粒度只能到达容器层次,在此粒度之下的技术细节,仍然只能依赖程序员自己来管理,编排系统很难提供有效的支持。2016年,原Twitter基础设施工程师William Morgan和Oliver Gould在GitHub上发布了第一代服务网格产品Linkerd,并在很短的时间内围绕Linkered组建了Buoyant公司。随后,在担任CEO的William Morgan发表的文章“What’s A Service Mesh” />

第五部分 技术方法论

附录A技术演示工程实践

附录B部署Kubernetes集群