参考凤凰架构: 演进的架构一大章节

演进中的架构

计算机总是务实, 某一架构的出现和兴盛往往都是在承载着某个历史使命, 衰败则是因为使命的消失或者有了更好的方案. 所以想要知道某个架构的历史使命, 我们不得不向过去看, 了解到它到底是解决了之前无法解决的什么问题, 也要向现在看它为什么能兴盛, 最后向未来看它为什么衰败, 如此才能理解这个架构的历史和使命 @fung

原始分布式时代

令人惊讶的是, 使用多个独立的分布式服务共同构建一个大型系统的设想和尝试, 反而是比今天的大型单体系统出现的时间更早

在20世纪70年代末期到80年代初, 计算机硬件的运算能力的局促, 直接妨碍到了在单台计算机上信息系统软件能够达到的最大规模. 于是计算机科学家们开始探索一种多台计算机共同协作来支撑一套软件系统, 这就是原始分布式时代

负责制定 UNIX 系统技术标准的“开放软件基金会”(Open Software Foundation,OSF,也即后来的“国际开放标准组织”)邀请了当时业界主流的计算机厂商一起参与,共同制订了名为“分布式运算环境”(Distributed Computing Environment,DCE)的分布式技术体系.

UNIX 的分布式设计哲学

Simplicity of both the interface and the implementation are more important than any other attributes of the system — including correctness, consistency, and completeness

保持接口与实现的简单性,比系统的任何其他属性,包括准确性、一致性和完整性,都来得更加重要。

—— Richard P. GabrielThe Rise of ‘Worse is Better’,1991

OSF尝试设计出来一种简单的, 透明的远程方法调用模式, 让程序员无需关注自己使用的方法是本地方法还是远程方法

调用远程方法的网络环境带来了一系列的新问题

  • 远程服务在哪里(服务发现)
  • 有多少个(负载均衡)
  • 网络出现了分区, 超时或者服务出错怎么办(熔断, 隔离, 降级)
  • 方法的参数和返回结果如何表示 (序列化协议)
  • 信息如何传输 (传输协议)
  • 服务权限如何管理 (认证, 授权)
  • 如何保证通信安全 (网络安全层)
  • 如何令调用不同机器的服务返回相同的结果 (分布式数据一致性)

尝试的结果自然是失败的, 不然我们现在看到的应用也不会是现在的以单体应用为主的模式了. 远程和本地方法在性能上的鸿沟是无法跨越的(抛弃了内联等优化), 在当时得硬件条件下如果想为用户提供可以接受的速度的远程调用服务, 不得不使用将多个方法打包到同一个方法体中等奇技淫巧, 并且开发者必须无时无刻不意识到本地和远程之间的边界. 设计为性能让步, DCE的努力”付之东流”

不过在这个时期产出了很多后续分布式的关键技术和概念

  • 源自 NCA 的远程服务调用规范(Remote Procedure Call,RPC),当时被称为DCE/RPC,它与后来 Sun 公司向互联网工程任务组(Internet Engineering Task Force,IETF)提交的基于通用 TCP/IP 协议的远程服务标准ONC RPC被认为是现代 RPC 的共同鼻祖;
  • 源自 AFS 的分布式文件系统(Distributed File System,DFS)规范,当时被称为DCE/DFS;源自 Kerberos 的服务认证规范;
  • 还有时间服务、命名与目录服务,就连现在程序中很常用的通用唯一识别符 UUID 也是在 DCE 中发明出来的。

原始分布式时代的教训

Just because something can be distributed doesn’t mean it should be distributed. Trying to make a distributed call act like a local call always ends in tears

某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作,只会自寻苦果

—— Kyle Brown,IBM Fellow,Beyond Buzzwords: A Brief History of Microservices Patterns,2016

单体系统时代

单体架构(Monolithic)

“单体”只是表明系统中主要的过程调用都是进程内调用,不会发生进程间通信,仅此而已。

单体架构是最早的架构, 也是使用得最自然的架构, 以至于相当长的一段时间里人们都没有去为单体架构专门下一个定义出来

在剖析单体架构之前, 非常有必要理清的一个概念就是, 单体架构并不是”不如”微服务架构, 在书籍中常出现的作为微服务架构的对比对象, 来说明单体系统的不足, 实际上是大型单体系统, 对于小型系统上单体架构有自己的优势

小型系统上, 单体架构易开发, 易部署, 易测试. 调用过程都是进程间调用, 运行效率最高

同时在纵向上, 无论是单体还是微服务抑或是其他架构风格, 都会对代码进行纵向的分层, 这点无关架构.

在横向角度上看, 单体架构同样能将系统从技术, 功能, 职责上进行模块拆分. 横向拓展上来看, 部署多个单体副本也是个常见的需求.

单体系统的真正缺陷不在于拆分, 而是拆分后的隔离性与自治能力上的欠缺. 在隔离性上, 单体系统所有的代码都共享同一片进程空间, 错误的传播范围是全局性并且式难以隔离的. 自治能力的欠缺会导致在错误传播以后往往会对程序造成极大的破坏. 同时因为隔离性的不支持, 也带来了单体系统难以动态更新的问题. 并且很难优雅地支持异构(Java的native方法还是支持异构的, 所以还是支持的, 只是支持的形式不优雅).

但是上面列举的问题, 都不是现今微服务取代单体系统成为潮流趋势的核心原因. 最核心的原因是, 单体系统难以支持Phoenix的特性(程序动态迭代更新, 并通过不可靠的部件构建出来一个可靠的整体服务). 单体架构风格潜在的观念是希望系统的每一个部件, 每一处代码都尽量可靠, 靠不出或少出缺陷来构建可靠系统. 单体系统靠高质量来保证的思路, 在小规模软件上还能运作良好, 但是规模越大, 交付一个可靠的单体系统就变得越来越具有挑战性.

为了允许程序出错, 为了获得隔离, 自治的能力, 可以技术异构等目标, 是继为了性能和算力之后, 让程序再次选择分布式的理由.

SOA(面向服务架构)时代

SOA 架构(Service-Oriented Architecture)

面向服务的架构是一次具体地、系统性地成功解决分布式服务主要问题的架构模式。

对一个大型的单体项目进行拆分, 使每个子系统都能独立部署, 运行, 更新, 开发者尝试了很多种架构

  • 烟囱式架构, 也叫信息孤岛架构, 各个模块之间没有任何的信息交互, 严格来说这个甚至不能叫做架构, 只是各个独立的子系统部署在了一起而已
  • 微内核架构, 也叫插件式式架构, 在烟囱架构中, 即使是没有任何的业务往来的子系统之间也需要共享一些如人员权限, 组织等公共的主数据, 那就不妨将这些被共享的资源集中在一块, 成为一个被各个组件共同依赖的核心, 这个核心就是微内核, 具体的业务以插件的形式存在. 常用于桌面端的程序, 比如Web程序. 不过微内核架构也有它的缺陷和局限性, 微内核架构的假设是各个组件之间是相互独立的, 不可预知系统安装了哪些模块, 只是向他们提供一些公共的数据, 所有组件之间是不会有直接交互的. 我们必须找到方法既能拆分系统, 又能让子系统之间进行交互

  • 事件驱动架构, 为了能让子系统之间交互, 我们在子系统之间提供一个事件队列管道, 子系统可以从事件管道中获取到自己的想要的也就是订阅了的事件, 同时子系统也能发布事件. 和发布订阅机制比较像. 同时每个子系统之间是独立, 高度解耦合的

在事件驱动架构之后, 软件架构发展就迎来了SOAP协议的诞生, 这个时候SOA (Service Oriented Architecture, SOA), 已经有了登上历史舞台的全部的前置条件.

软件架构来到了SOA时代, 许多的概念, 思想都已经能在今天的微服务中找到对应的身影了, 譬如服务之间的松散耦合, 注册, 发现, 治理, 隔离, 编排等等, 这些在今天微服务中耳熟能详的名词概念. SOA针对这些问题, 甚至是软件开发这件事情本身, 都提供了更加系统性, 更加具体的探索

  • 更具体: 体现在尽管SOA本身还属于抽象的软件架构风格, 但是实际上提供一套软件设计的基础平台
  • 更系统: 是SOA的宏大理想, 终极目标是希望能总结出来一套自上而下的软件开发研究的方法论

SOA在21世纪初10年间曾风靡一时, 最终还是偃旗息鼓, 沉寂下去. SOAP会被边缘化的本质原因: 过于严格的规范定义带来过度的复杂性. 而构建在SOAP基础之上的ESB, BPM, SCA, SDO等诸多上层建筑, 进一步加剧了这种复杂性. 过于精密的流程和理论也需要懂得复杂概念的专业人员才能驾驭, 它能实现多个异构代行系统之间的复杂集成交互, 却很难作为一种具有广泛普适性的软件架构风格来推广

经过了30年的技术进步, 软件受架构复杂性牵制越来越大, 已经离透明二字越来越远了, 这是不是忘记了我们最开始透明和简单的初心? 微服务时代, 似乎正是带着这样的自省开始的

微服务时代

微服务架构(Microservices)

微服务是一种通过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建。各个服务可以采用不同的编程语言,不同的数据存储技术,运行在不同的进程之中。服务采取轻量级的通信机制和自动化的部署机制实现通信与运维。

微服务并不是SOA的变体

Microservices and SOA

This common manifestation of SOA has led some microservice advocates to reject the SOA label entirely, although others consider microservices to be one form of SOA , perhaps service orientation done right. Either way, the fact that SOA means such different things means it’s valuable to have a term that more crisply defines this architectural style

由于与 SOA 具有一致的表现形式,这让微服务的支持者更加迫切地拒绝再被打上 SOA 的标签,尽管有一些人坚持认为微服务就是 SOA 的一种变体形式,也许从面向服务方面这个方面来说是对的,但无论如何,SOA 与微服务都是两种不同的东西,正因如此,使用一个别的名称来简明地定义这种架构风格就显得更有必要。

—— Martin Fowler / James Lewis,Microservices

微服务有九个核心的业务与技术特征

  • 围绕业务能力构建 (Organized around Business Capability): 也就是康威定律, 有怎么样结构, 规模, 能力的团队, 就会产出对应的结构, 规模, 能力的产品. 比如本应该归属于一个产品内的功能被划分到了不同的团队, 必然会产生大量的跨团队的沟通协作, 跨越团队边界进行协作在管理, 工作安排, 沟通上都会有更高昂的成本, 高效的团队必然会进行改进, 当团队和产品磨合稳定以后, 团队和产品就会有一致性的结构
  • 分散治理(Decentralized Governance): 谁家的孩子谁来管, 服务对应的开发团队有直接对服务运行质量负责的责任, 也应该有着不受外界干预地掌控服务各方面的权力. 实际开发中并不会有高程度的技术异构, 甚至一般来说是有主语言, 微服务更加强调的是确实有必要的技术异构时, 应该有选择不统一的权力
  • 通过服务来实现独立自治的组件(Componentization via Services): 通过服务这种进程外组件, 以远程调用的形式提供服务, 虽然有着更高昂的车成本, 但是这是为组件带来隔离和自治能力的必要代价
  • 产品化思维 (Products not Projects): 避免把软件研发视作去完成某个功能, 而是要视作一种持续改进, 提升的过程. 微服务下, 要求开发团队中的每个人都有产品化思维, 关心整个产品的全部的可行性. DDD也是在这个时期提出的理念.
  • 数据去中心化 (Decentralized Data Management): 微服务明确体长数据应该按照领域分散管理, 更新, 维护, 存储. 如果使用中心化的存储, 所有领域必须修改和映射到同一个实体身上, 这使得不同服务之间相互影响, 丧失了独立性. 尽管在分布式中要想处理好一致性问题相当困难, 但是两害相权取其轻, 有一些必要的代价是值得付出的
  • 强终端弱管道 (Smart Endpoint and Dumb Pipe) : 弱管道可以说是指名道姓地反对SOAP和ESB地复杂地通信机制. 认证, 事务一致性, 授权等一系列的工作, 构建在通信管道上对于一些应用程序来说是必要的, 但是对于更多的应用程序来说都是一个负担. 如果服务需要额外的通信能力, 应该在自己的Endpoint上解决, 而不是在管道上一揽子地解决
  • 容错性设计 (Design for Failure) : 不再虚幻地追求服务永远稳定, 而是接受服务总是会出错的现实, 要求在微服务的设计中, 有自动的机制对其依赖的服务能够进行快速的故障检测, 并在持续出错的时候进行隔离, 服务恢复的时候进行重建. 如果没有容错性的设计, 系统很容易就会因为一两个服务的崩溃所带来的雪崩效应给淹没. 可靠系统完全可能由会出错的服务组成, 这也是微服务最大的价值
  • 演进式设计 (Evolutionary Design) : 容灾性设计是允许服务出错, 演进式设计则是承认服务会报废淘汰, 一个设计良好的服务应该是能够报废的, 而不是期望能够得到永生. 系统中出现不可替代, 不可更改的服务, 不能说明这个服务有多重要, 恰恰相反, 是说明了系统设计上的脆弱性
  • 基础设施自动化 (Infrastructure Automation) : 微服务下运维的对象比起单体架构要有数量级上的增长, 使用微服务的团队更加依赖于基础设施上的自动化

微服务所带来的自由是一把双刃开锋的宝剑,当软件架构者拿起这把宝剑,一刃指向 SOA 定下的复杂技术标准,将选择的权力夺回的同一时刻,另外一刃也正朝向着自己映出冷冷的寒光。微服务时代中,软件研发本身的复杂度应该说是有所降低。一个简单服务,并不见得就会同时面临分布式中所有的问题,也就没有必要背上 SOA 那百宝袋般沉重的技术包袱。需要解决什么问题,就引入什么工具;团队熟悉什么技术,就使用什么框架。此外,像 Spring Cloud 这样的胶水式的全家桶工具集,通过一致的接口、声明和配置,进一步屏蔽了源自于具体工具、框架的复杂性,降低了在不同工具、框架之间切换的成本,所以,作为一个普通的服务开发者,作为一个“螺丝钉”式的程序员,微服务架构是友善的。可是,微服务对架构者是满满的恶意,对架构能力要求已提升到史无前例的程度,笔者在这部文档的多处反复强调过,技术架构者的第一职责就是做决策权衡,有利有弊才需要决策,有取有舍才需要权衡,如果架构者本身的知识面不足以覆盖所需要决策的内容,不清楚其中利弊,恐怕也就无可避免地陷入选择困难症的困境之中。

后微服务时代

后微服务时代(Cloud Native)

从软件层面独力应对微服务架构问题,发展到软、硬一体,合力应对架构问题的时代,此即为“后微服务时代”。

分布式架构中出现的问题 : 注册发现, 跟踪治理, 负载均衡, 传输通信等, 这些问题从原始分布式时代就已经出现了, 只要是分布式架构就无法避免, 但是这些问题一定要软件系统来自己解决吗?

后微服务时代就是通过容器化和虚拟化技术, 在硬件层面实现了这些服务.

表 1-1 列出了在同一个分布式服务的问题在传统 Spring Cloud 中提供的应用层面的解决方案与在 Kubernetes 中提供的基础设施层面的解决方案,尽管因为各自出发点不同,解决问题的方法和效果都有所差异,但这无疑是提供了一条全新的、前途更加广阔的解题思路。

表 1-1 传统 Spring Cloud 与 Kubernetes 提供的解决方案对比

Kubernetes Spring Cloud
弹性伸缩 Autoscaling N/A
服务发现 KubeDNS / CoreDNS Spring Cloud Eureka
配置中心 ConfigMap / Secret Spring Cloud Config
服务网关 Ingress Controller Spring Cloud Zuul
负载均衡 Load Balancer Spring Cloud Ribbon
服务安全 RBAC API Spring Cloud Security
跟踪监控 Metrics API / Dashboard Spring Cloud Turbine
降级熔断 N/A Spring Cloud Hystrix

一旦虚拟化的硬件跟上了软件的灵活性, 那些与业务无关的技术问题有可能从软件层面剥离, 悄无声息解决于硬件基础设施之内, 让软件只需要关注业务/

但是Kurbernets并没有完美解决所有的分布式问题, 仅从功能上看k8s甚至不如之前的Spring Cloud方案. 因为有一些问题处于应用系统与基础设置的边缘, 使得很难在基础设施层上进行处理. 举个例子,微服务 A 调用了微服务 B 的两个服务,称为 B1和 B2,假设 B1表现正常但 B2出现了持续的 500 错,那在达到一定阈值之后就应该对 B2进行熔断,以避免产生雪崩效应。如果仅在基础设施层面来处理,这会遇到一个两难问题,切断 A 到 B 的网络通路则会影响到 B1的正常调用,不切断的话则持续受 B2的错误影响。基础设施是针对整个容器来管理的,粒度相对粗旷,只能到容器层面,对单个远程服务就很难有效管控

为了解决这一类问题,虚拟化的基础设施很快完成了第二次进化,引入了今天被称为“服务网格”(Service Mesh)的“边车代理模式”(Sidecar Proxy). 系统自动在服务容器(通常是指 Kubernetes 的 Pod) 中注入一个通信代理服务器, 以类似网络安全里中间人的方式进行流量劫持, 在应用毫无感知的情况下, 悄然接管所有的对外通信. 这个代理处理实现正常的服务间通信以外 (数据平面通信), 还接收来自控制器的指令, 以实现熔断, 认证, 度量, 监控, 均衡负载等各种附加功能. 这样便实现了既不需要在应用层面加入额外的处理代码,也提供了几乎不亚于程序代码的精细管理能力

img