《凤凰架构》阅读笔记(一):软件架构演进
原始分布式架构
受限于早期计算机硬件局促的算力,单台计算机上搭载的信息系统难以达到较大规模。在大型单体系统出现之前人们就曾构想过 使用多个独立的分布式服务构建大型系统,并据此制定了分布式运算环境的技术体系。此阶段是分布式的最早期探索,目标是分布式环境中的服务调用、资源访问、数据存储等操作尽可能 透明化、简单化,使开发人员不必过于关注本地调用还是远程调用。
因时代局限,简单、透明、性能、正确、鲁棒、一致的目标在当时无法完成:服务发现,负载均衡,熔断隔离降级,序列化协议,传输协议,认证授权,网络安全,数据一致等一系列问题难以兼顾实现。比如网络环境与本地环境访问文件的速度有数量级上的差距,与运用分布式突破单机算力限制的初衷矛盾,且这种巨大的性能差异已使得程序开发难以做到简单透明(必须意识到是分布式程序开发)。
当时人们的经验是某个功能可进行分布式,并不意味着就该如此,不能强行追求透明的分布式操作(by Kyle Brown)。尽管违背了 UNIX 设计哲学,但只能迫于现实让步;再加上 摩尔定律 开始发挥稳定作用、微机性能快速提升,相比起构筑完美的分布式系统,在很长一段时间内 提升单机的处理能力,发展单体软件架构 的发展道路都是绝对主流(前者并未停止发展)。
单体架构
常被称为 巨石系统,在软件架构演进过程中出现最早、应用最多最广。而实际上这在早期是唯一选择,“单体”是相对后来“微服务”而言形成的概念。
单体架构本身很简单,兼顾性能与便捷性:易于开发、测试、部署,主要的过程调用是进程内调用、不会发生进程间通信,运行效率最高(无须考虑网络分区、对象复制等问题)。
拆分和扩展
纵向角度:可对代码进行纵向层次划分,收到的外部请求在各层之间以不同形式的数据结构进行流转传递,触及最末端的数据库后按相反的顺序回馈响应。
横向角度:支持按技术、功能、职责等维度将软件拆分为各种模块,以便重用和管理代码。它可以由多个 JAR、WAR、DLL、Assembly 或者其他模块格式来实现程序封装;从横向扩展(Scale Horizontally)的角度衡量,在负载均衡器之后同时部署若干个单体系统副本,达到分摊流量压力的效果也很常见。
缺陷
单体系统在规模较小时具备优势,但随着规模变大、在拆分之后的隔离与自治能力上有所欠缺:
任何一部分代码出现缺陷,过度消耗了进程空间的资源,不仅只对某一功能造成的影响,而是全局性、难以隔离的(比如内存泄漏、线程爆炸、阻塞、死循环等问题,或是数据库连接池泄漏等高层次资源的消耗,甚至可波及整台机器)。
所有代码都共享着同一进程空间,也就难以单独停止、更新、升级某一部分代码,可维护性较差(需要制定专门的停机更新计划,做灰度发布、A/B 测试也相对更复杂)。
通常每个模块的代码都需要使用一样的程序语言、编程框架开发,难以实现技术栈上的异构。
而且在单体架构下程序的规模决定难以让全部人员关注完整的产品,组织中有开发、运维、支持等细致的分工的成员,各人只关注于自己的一块工作。
作者认为单体系统依靠高质量来保证高可靠性,随着系统规模变大,这种可靠性变得难以保证。随着软件架构演进,构筑可靠系统从追求尽量不出错到允许程序出错,为了获得隔离、自治的能力,为了可以技术异构等目标的转变,微服务架构渐渐取代单体架构。
SOA 架构
大型单体系统必然面临拆分,使得每个子系统独立部署、运行、更新,有以下三种方案:
烟囱式架构(Information Silo Architecture):又名信息孤岛。即完全不与其他相关信息系统进行交互或协调工作的设计模式(如使用独立的数据库和服务器)。
问题是完全独立、没有任何重叠的系统是无法实现的。
微内核架构(Microkernel Architecture):又称插件式架构。将主业务数据和公共服务、数据、资源集中到被所有业务系统共同依赖的核心系统,具体的业务系统以插件模块的形式存在,可提供可扩展的、灵活的、天然隔离的功能特性。微内核架构还能嵌入到其他架构模式中,作为插件来提供新功能的定制开发能力。
由于它假设各个插件模块之间是互不认识、不可预知,因此插件系统只能访问公共资源、不会直接交互,这在互联网应用等许多场景都不成立。
事件驱动架构(Event-Driven Architecture):在子系统之间建立事件队列管道,来自系统外部的事件消息发送至管道中,子系统从管道里获取自己感兴趣、可处理的事件消息,或为事件新增或者修改其中的附加信息,或自己发布新的事件到管道队列中,每个消息处理者都是独立、高度解耦,但又能与其他处理者通过事件管道进行互动。
SOA 设计
在系统演化到事件驱动架构时,与单体架构并行发展但占下风的分布式架构设计者提出了 SOAP 协议。
SOA 不指某一具体的技术,而是为解决服务之间的松散耦合、注册发现、跟踪治理、负载均衡、故障隔离、认证授权、伸缩扩展、传输通信、事务处理等问题,以及软件开发本身进行更系统和更具体的探索。
操作性比前面的架构风格更强,可称为一套软件设计基础平台。在一整套成体系可以互相精密协作的技术组件支持下,在技术可行性上解决了分布式环境下主要的技术问题:
目标是总结出一套自上而下的软件研发方法论,遵循 SOA 的思路解决软件开发过程中的问题:挖掘需求、将需求分解为业务能力、编排已有服务、开发测试部署新的功能等。SOA 还关注研发过程中涉及到的需求、管理、流程和组织,希望使软件开发实现工业化大生产,大幅提升整个社会实施信息化的效率。
缺陷
SOAP 协议却被渐渐边缘化,其本质原因:过于严格的规范定义带来过度的复杂性。并由构建在 SOAP 基础之上的 ESB、BPM、SCA、SDO 等进一步加剧。过于精密的流程和理论对专业人员要求过高,难以作为具有广泛普适性的软件架构风格来推广。
微服务架构
微服务通过多个 围绕业务能力(而非特定的技术标准)来构建的小型服务组合构建单个应用,各个服务可以采用异构技术运行在不同的进程之中,之间采取轻量级的通信机制,结合自动化部署机制实现通信与运维。
早期的微服务作为 SOA 的轻量化补救方案被提出,也可理解为 SOA 的变种形式。其追求更自由的架构风格,提倡以“实践标准”代替“规范标准”摒弃大量约束和规定。这种自由是双刃剑,在 SOA 时解决的分布式服务问题又再重新出现,且不再有统一解决方案;优点则是软件研发复杂度降低,简单服务不一定同时面临分布式中所有的问题,没有必要背上 SOA 的技术包袱,引入工具、技术因具体问题而异。
因此微服务对普通服务开发者友好,对架构者的能力要求极高。
微服务包含九个核心的业务与技术特征:
围绕业务能力构建
强调康威定律的重要性。团队结构、规模、能力决定了与之相称的产品。如本该归属同一产品内的功能被划分在不同团队中,必然产生大量的跨团队沟通协作,在管理、沟通、工作安排上都有更高昂的成本。
针对其进行改进,当团队、产品磨合调节稳定之后就会拥有一致的结构。
分散治理
服务对应的开发团队有直接对服务运行质量负责的责任,也应该有着不受外界干预地掌控服务各个方面的权力(比如选择异构技术实现服务)。在真正实践时存有宽松的处理余地,一般会有统一的主流语言、技术栈或专有的技术平台,同时保留自由选择的权利。
通过服务实现组件
类库是在编译期静态链接到程序中,通过本地调用提供功能;服务是进程外组件,通过远程调用来提供功能。
尽管远程服务有更高昂的调用成本,但也带来了隔离与自治能力。
产品化思维
把软件研发视作持续改进、提升的过程。团队为软件产品的整个生命周期负责,开发者除了开发软件,还应该知道它如何运作,用户如何反馈,售后支持工作如何进行(用户也指消费这个服务的其它服务)。
开发团队中每个人都要具有产品化思维,关心整个产品的全部方面是具有可行性的。
数据去中心化
明确提倡数据应按领域分散管理、更新、维护、存储。
单体服务系统的功能模块通常会使用中心化存储,这天生就更容易避免一致性问题。但是同一数据实体在不同服务的视角里其抽象形态往往也是不同的,如使用中心化存储,所有领域都必须修改和映射到同一个实体之中,使得不同的服务很可能会互相产生影响而丧失独立性。
比如 Bookstore 应用中的书本,在销售领域中关注价格,在仓储领域中关注库存数量,在商品展示领域中关注书籍介绍信息。
数据分布式存储后难以使用传统的事务处理来保证一致性,只能说是两害取其轻。
强终端弱管道
相比起 SOAP 和 ESB 等复杂的通信机制,使用弱管道(Dumb Pipe)可避免不必要的负担:处理事务、一致性、认证授权等一系列工作不是所有服务都需要的。如果服务需要额外通信能力,应该在服务自己的 Endpoint 上解决,而不是在通信管道上统一处理。
微服务提倡类似经典 UNIX 过滤器的简单直接的通信方式,因此 RESTful、RPC 风格的通信更合适。
容错性设计
接受单个服务总会出错,不追求永远稳定,用可能出错的服务构建可靠系统。
这要求服务设计上有自动的机制对其依赖的服务能够进行快速故障检测,在持续出错时隔离,在服务恢复时重新联通。因此“断路器”组件是必须的,如缺少容错性设计系统容易发生雪崩效应。
演进式设计
承认服务会被报废淘汰。设计良好的服务应该是能够报废而不是长存永生的。假设系统中出现不可更改、无可替代的服务,反而是系统设计上脆弱的表现。
微服务追求的独立、自治,反对这种脆弱性。
基础设施自动化
如 CI/CD 的长足发展显著减少了构建、发布、运维工作的复杂性。微服务下运维的对象比起单体架构要有数量级的增长,团队更加依赖于基础设施的自动化,人工难以支撑成百上千乃至成千上万级别的服务。
作者希望信息系统能同时拥有微服务的自由权利,围绕业务能力构建服务而不受技术规范管束,同时又不必以自行解决分布式的问题为代价。
云原生架构
分布式架构的问题不必由软件系统自行解决。首先各种问题几乎都能通过硬件解决,比如:服务器集群、负载均衡器、TLS 传输链路、DNS 服务器等。而在微服务时代人们在代码层面解决这些问题,很大程度上是硬件基础设施跟不上软件服务的灵活性的无奈之举。
虚拟化与容器化
虚拟化和容器化技术使得可以运用硬件灵活地构建基础设施。
早期以 Docker 为代表的容器技术用作快速启动的服务运行环境,以方便程序的分发部署;更有 软件定义网络、软件定义存储 等技术用于解决分布式架构问题。
后来 Kubernetes 在容器编排领域占据统治性的地位,其提供了全新的、前途更加广阔的解题思路,虚拟化基础设施去解决分布式架构问题才被业界广泛认可和普遍采用。
虚拟化基础设施从单个服务容器扩展至多个容器构成服务集群、通信网络和存储设施,当虚拟化硬件跟上软件的灵活性,与业务无关的技术性问题就可能从软件层面剥离,在硬件基础设施内解决,让软件得以只专注业务,围绕业务能力构建团队与产品。
作者认为从软件层面独力应对分布式架构所带来的各种问题,发展到应用代码与基础设施软、硬一体合力应对架构问题(弹性伸缩、服务发现、配置中心、服务网关、负载均衡、服务安全、跟踪监控、降级熔断),被称为 云原生时代。其与此前微服务时代中追求的目标并没有本质改变,更愿意称其为“后微服务时代”。
服务网格
由于一些问题处于应用系统与基础设施边缘,完全在基础设施层面中很难精细化地处理(比如细粒度、定制化的服务管理,Spring Cloud 就可以通过代码轻松实现)。基础设施针对整个容器来管理,粒度只能粗到容器层面,对单个远程服务就很难有效管控。
服务网格(Service Mesh)是边车代理模式(Sidecar Proxy),即由系统自动在服务容器(比如 Pod)中注入一个通信代理服务器,在应用毫无感知的情况下以类似中间人攻击的方式进行流量劫持、悄然接管应用所有对外通信。这个代理除了实现服务间通信(数据平面通信)外,还接收控制器指令(控制平面通信)。根据控制平面中的配置,对数据平面通信的内容进行分析处理,以实现熔断、认证、度量、监控、负载均衡等各种附加功能。既不需要在应用层面加入额外的处理代码,也提供了精细管理能力。
作者认为未来 Kubernetes 将会成为服务器端标准运行环境,服务网格将会成为微服务之间通信交互的主流模式,把服务治理问题隔离于程序代码之外,微服务只需要考虑业务逻辑,是最理想的 Smart Endpoints 解决方案。
无服务架构
云计算落地十多年,已实现了相对意义上的服务器的无限性能。无服务(Serverless)以简单为卖点:
后端设施:指数据库、消息队列、日志、存储等,用于支撑业务逻辑运行、但本身无业务含义的技术组件,后端设施运行在云中,被称为“后端即服务”(Backend as a Service,BaaS)。
函数:指业务逻辑代码,这里的“函数“指无服务中的函数运行在云端,不必考虑算力问题和容量规划(由于按量计费,所以还是要考虑的),被称为“函数即服务”(Function as a Service,FaaS)。
其目标是让开发者只需要纯粹地关注业务,不需要考虑技术组件。后端技术组件不存在:
采购、版权和选型的烦恼(现成、可直接取用)
部署的考虑(完全托管到云端、自动完成)
算力的考虑(有整个数据中心支撑,可认为是无限的)
运维上操心(云计算服务商负责维护系统持续平稳运行)
无服务架构下开发者不再关心技术层面的细节,然而其具备某些特点使之难以普适:
无服务架构擅长短链接、无状态、适合事件驱动的交互形式,因此适用于不需要交互的离线大规模计算的应用、多数 Web 资讯类网站、小程序、公共 API 服务、移动应用服务端等,可降低开发和运维环节的成本。
“无限算力”意味着按用量计费以控制消耗算力的规模。函数在有请求时才会运行,不便依赖服务端状态,以及会有冷启动时间,响应的性能不可能太好(尤其 Java 这类启动性能差的应用)。对于具有业务逻辑复杂、依赖服务端状态、响应速度要求较高、需要长链接的应用(比如信息管理系统、网络游戏等),目前是相对不适合的。
作者认为微服务架构是分布式系统所能做到的极致,无服务架构则是“不分布式”云端系统的起点,两者是并行关系。软件开发将会有多种具针对性的架构风格同时并存、融合互补。“分布式”与“不分布式”两条路线边界逐渐模糊、在云端数据中心交汇:比如使用无服务的云函数去实现微服务架构,将无服务作为技术层面的架构,将微服务视为应用层面的架构,把它们组合使用。微服务可通过物理机、虚拟机、容器,或无服务云函数实现。