分布式架构演进
架构演进的路径
微服务架构绝对是当今技术圈的“网红”词汇。我们常说任何技术都不是凭空产生,都有一定的历史背景,微服务架构也一样。从大型机(Mainframe)、原始分布式(Distributed)、大型单体(Monolithic)、面向服务(Service-Oriented)、微服务(Microservice)、服务网格(Service Mesh)再到无服务(Serverless)架构,主流的软件架构风格一直在演进。
促进架构不断演进发展的原因有很多,有理论研究上的成果,也有很多技术牛人的实践探索。但是笔者个人觉得其中最大的推手还是互联网行业的蓬勃发展带来的复杂业务需求以及云计算等技术的持续发展带来的硬件资源使用方式的持续改变。
大型机时代
大型机(Mainframe)可以简单地理解为一种性能强劲、超级稳定以及具有极高安全性的计算机。时间追溯到20世纪60年代,那个在那个时期,大型机是仅有的一种计算机类型。由于大型机先天“优越”的条件,那时的软件架构方式也大多是单体风格架构。但是由于大型机高昂的费用,注定它只是少数公司或者机构的“玩物”。
对于现在的软件开发者来说,大型机是一个很陌生的名词,而且估计很少有开发者能直接接触到大型机。但是这并不意味大型机在市场上销声匿迹了,相反,大型机在大多数全球性大型企业的日常工作中仍然扮演着核心的角色,其中包括很多财富1000强的大公司。
![image-20220317215519380](https://oss-csx-picture.oss-cn-shanghai.aliyuncs.com/picture/image-20220317215519380.png)
原始分布式时代
20世纪70年代末期到80年代初,计算机从大型机往微型计算机转变,很多公司能真正消费的起。但这个时候的微型计算机性能比较弱,很多时候都不能满足人们的计算需求。
为突破硬件算力的限制,高校、研究机构、软硬件厂商开始分头探索,寻找使用多台计算机共同协作来支撑同一套软件系统的可行方案。(所以说构建分布式系统的最初目的就是突破单台计算节点的性能限制,协同多个计算节点来完成某个任务)
然而构建分布式系统需要面对的问题远远要比当时的人们想象的多的多。譬如,远程的服务在哪里(服务发现),有多少个(负载均衡),网络出现分区、超时或者服务出错了怎么办(熔断、隔离、降级),方法的参数与返回结果如何表示(序列化协议),信息如何传输(传输协议),服务权限如何管理(认证、授权),如何保证通信安全(网络安全层),如何令调用不同机器的服务返回相同的结果(分布式数据一致性)等一系列问题,全都需要设计者耗费大量精力。
面对上面这些问题,国际开放标准组织邀请了当时世界上的计算机大厂一起制定了名为“分布式计算环境”(DCE)的技术体系。
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的服务认证规范;还有时间服务、命名与目录服务,甚至现在程序中很常用的通用唯一识别符(Universally Unique Identifier,UUID)也是在DCE中发明出来的。
限于当时的硬件水平,以及复杂分布式技术体系上的投入和带来的收益完全不成比例,这个阶段的分布式研究并没有取得实质性的成功。但是这个阶段对分布式的研究为后续分布式系统的发展打下了基础。
单体系统时代
20世纪80年代正是摩尔定律开始稳定发挥作用的黄金时期,微型计算机的性能以每两年增长一倍的惊人速度提升,硬件算力束缚软件规模的链条很快变得松动,信息系统进入以单台或少量几台计算机即可作为服务器来支撑大型信息系统运作的单体时代,且在很长的一段时间内,单体都将是软件架构的绝对主流。尽管如此,对于另外一条路,即对分布式计算、远程服务调用的探索也从未中断。
这边说的单体架构一般是指应用系统的架构是单体架构。比如下面的应用中有订单模块、产品模块、用户模块以及展示页面。将这些功能全部运行在一个进程中对外提供服务,这就是我们常常提到的单体架构。单体架构最大的特点就是系统中所有的功能模块都部署在一个进程中。
![image-20220317221018394](https://oss-csx-picture.oss-cn-shanghai.aliyuncs.com/picture/image-20220317221018394.png)
对于上面的架构,常常会有一些变体。比如说为了前后端开发方便,进行前后端分离。为了保证高可用、高性能,一般会对单体应用进行水平扩展,形成下面这种架构形式。
![image-20220317221043343](https://oss-csx-picture.oss-cn-shanghai.aliyuncs.com/picture/image-20220317221043343.png)
但是对于后端的应用程序来说,这种架构模式本质上还是单体架构。
现在的IT圈好像有一种“不良风气”,一提到单体架构就会用一种“不屑”的口吻来谈论这种架构模式。但其实没有哪种架构风格是绝对好或者绝对坏的,所有的架构风格都有其优缺点。
对于小型系统,单台机器就足以支撑其良好运行的系统,不仅易于开发、测试、部署,且由于系统中各个功能、模块、方法的调用过程都是进程内调用,不会发生进程间通信(Inter-ProcessCommunication,IPC),因此连运行效率也是最高的。
单体系统的不足,必须在软件的性能需求超过了单机、软件的开发人员规模明显超过了“2 Pizza Team”范畴的前提下才有讨论的价值。单体系统的不足主要体现在以下几个方面:(也就是只有单系统足够复杂,开发系统的人数达到某些数量时谈论单体的缺点才是有意义的)
- 没有错误隔离的能力,比如说某个程序员写的代码出现了死循环,导致CPU占用率过高,可能就导致整个系统不能用了。
- 难以实现技术异构,每个模块的代码通常都需要使用一样的程序语言,乃至一样的编程框架去开发。单体系统的技术栈异构并非一定做不到,譬如JNI就可以让Java混用C或C++实现,但这通常是迫不得已的,并不是优雅的选择。
- 无法实现按需扩展,比如说上面系统中的订单模块的访问压力很大,需要进行扩展。在单体架构下只能扩展整个服务,而产品模块和用户模块明明是不需要扩展的,这种情况下就会导致资源浪费。
对于单体应用,随着其承载的业务越来越复杂,其开发,测试,运维成本将越来越大。
对于开发来说,由于项目越来越大,项目每次启动调试的时间变长,开发的人也变多,代码冲突的概率也越多,同时为了应对不同需求的上线窗口,往往还要维护不同的分支。
对于测试人员来说,系统中随便修改一个小的功能,可能都需要做整体的回归测试(对于app应用,web应用可能可以放宽点)
另外随着系统越来越复杂,系统将变得更加脆弱。因为功能越多,就越容易出问题,由于单体系统没有错误隔离能力所以出问题的概率也更大。还有就是复杂的系统,新的开发人员进入项目组之后往往需要熟悉整体情况后才能进入开发状态,这个需要的成本也比较大。
SOA架构时代
SOA架构,面向服务的架构。其包含的许多概念、思想都能在今天的微服务中找到对应的身影了,譬如服务之间的松散耦合、注册、发现、治理,隔离、编排等。
SOA不能简单视为一种架构风格,而是一套软件设计的基础平台。
- 它拥有领导制定技术标准的组织Open CSA;
- 有清晰的软件设计的指导原则,譬如服务的封装性、自治、松耦合、可重用、可组合、无状态,等等;
- 明确了采用SOAP作为远程调用协议,依靠SOAP协议族(WSDL、UDDI和WS-*协议)来完成服务的发布、发现和治理;
- 利用企业服务总线(Enterprise Service Bus,ESB)的消息管道来实现各个子系统之间的交互,令各服务在ESB的调度下无须相互依赖就能相互通信,实现了服务松耦合,也为以后进一步实施业务流程编排(Business Process Management,BPM)提供了基础
- 使用服务数据对象(Service Data Object,SDO)来访问和表示数据,使用服务组件架构(Service Component Architecture,SCA)来定义服务封装的形式和服务运行的容器
但是SOA技术最终还是偃旗息鼓了,最主要的原因还是SOA基于SOAP协议,SOAP协议过于严格的规范定义带来过度的复杂性,而构建在SOAP基础之上的ESB、BPM、SCA、SDO等诸多上层建筑,进一步加剧了这种复杂性。
SOA自诞生的那一天起,就已经注定只能是少数系统阳春白雪式的精致奢侈品,它可以实现多个异构大型系统之间的复杂集成交互,却很难作为一种具有广泛普适性的软件架构风格来推广。SOA最终没有获得成功的致命伤与当年的EJB如出一辙,尽管有Sun和IBM等一众巨头在背后力挺,EJB仍然败于以Spring、Hibernate为代表的“草根框架”,可见一旦脱离人民群众,终究会淹没在群众的海洋之中,连信息技术也不曾例外。
SAO的设计理念和简单透明相悖甚远。
微服务架构时代
微服务真正崛起是在2014年。微服务是一种通过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建。各个服务可以采用不同的编程语言、不同的数据存储技术,运行在不同的进程之中。服务采取轻量级的通信机制和自动化的部署机制实现通信与运维。
微服务的九个核心的业务与技术特征:
- 围绕业务能力构建
- 强终端弱管道
微服务的一些优缺点:
- 每个项目复杂度降低;
- 团队的职责更加明确;
- 扩展更加灵活,只需扩容流量特别大项目;
- 部署更加灵活,不会因为一个小功能部署就部署所以系统,让整个团队留下来验证;
- 独立部署,错误隔离
缺点就是会引入分布式系统的复杂性。
微服务和SOA的区别
从以上微服务的定义和特征中,你应该可以明显地感觉到微服务追求的是更加自由的架构风格,摒弃了几乎所有SOA里可以抛弃的约束和规定,提倡以“实践标准”代替“规范标准”。
可是,如果没有了统一的规范和约束,以前SOA解决的那些分布式服务的问题,不也就一下子都重新出现了吗?的确如此,对于服务的注册发现、跟踪治理、负载均衡、故障隔离、认证授权、伸缩扩展、传输通信、事务处理等问题,微服务中将不再有统一的解决方案。
即使只讨论Java范围内会使用到的微服务,仅一个服务间远程调用问题,可以列入解决方案的候选清单的就有RMI(Sun/Oracle)、Thrift(Facebook)、Dubbo(阿里巴巴)、gRPC(Google)、Motan2(新浪)、Finagle(Twitter)、brpc(百度)、Arvo(Hadoop)、JSON-RPC、REST,等等;仅一个服务发现问题,可以选择的就有Eureka(Netflix)、Consul(HashiCorp)、Nacos(阿里巴巴)、ZooKeeper(Apache)、etcd(CoreOS)、CoreDNS(CNCF),等等。其他领域也与此类似。
微服务所带来的自由是一把双刃开锋的宝剑,当软件架构者拿起这把宝剑,一刃指向SOA定下的复杂技术标准,将选择的权力夺回的同一时刻,另外一刃也正朝着自己映出冷冷的寒光。
在微服务时代,软件研发本身的复杂度确实有所降低。一个简单服务,并不见得会同时面临分布式中的所有问题,也就没有必要背上SOA那百宝袋般沉重的技术包袱。需要解决什么问题,就引入什么工具;团队熟悉什么技术,就使用什么框架。此外,像Spring Cloud这样胶水式的全家桶工具集,通过一致的接口、声明和配置,进一步屏蔽了源自具体工具、框架的复杂性,降低了在不同工具、框架之间切换的成本,所以,作为一个普通的服务开发者,作为一个“螺丝钉”式的程序员,微服务架构是友善的。
可是,微服务对架构者却是满满的“恶意”,对架构能力的要求已提升到史无前例的程度。技术架构者的第一职责就是决策权衡,有利有弊才需要决策,有取有舍才需要权衡,如果架构者本身的知识面不足以覆盖所需要决策的内容,不清楚其中利弊,恐怕将无可避免地陷入选择困难症的境遇之中。微服务时代充满着自由的气息,微服务时代充斥着迷茫的选择。
单体架构和微服务架构对比
如果说共享同一进程获得简单、高效的代价是同时损失了各个功能模块的自治与隔离能力,那这两者孰轻孰重呢?这个问题的潜台词似乎是在比较微服务、单体架构哪种更好用、更优秀。笔者认为“好用和优秀”不会是放之四海皆准的,这点不妨举一个浅显的例子加以说明。譬如,沃尔玛将超市分为仓储部、采购部、安保部、库存管理部、巡检部、质量管理部、市场营销部等,划清职责,明确边界,让管理能力能支持企业的成长规模。但如果是你家楼下开的小卖部,爸、妈加儿子,再算上看家的中华田园犬小黄一共也就只有四名员工,再去追求“先进管理”,划分仓储部、采购部、库存管理部……那纯粹是给自己找麻烦。单体架构下,哪怕是信息系统中两个相互毫无关联的子系统,也依然会部署在同一个进程中。当系统规模小的时候,这是优势,但当系统规模大或程序需要修改的时候,其部署的成本、技术升级的迁移成本都会变得非常昂贵。继续以前面的例子来比喻,当公司小时,让安保部和质检部这两个不相干的部门在同一栋大楼中办公是节约资源;但当公司人数增加,办公室已经拥挤不堪时,最多只能在楼顶加盖新楼层(相当于增强硬件性能)来解决办公问题,而不能让安保部和质检部分开地方办公,这便是缺陷所在。
单体架构什么时候应该转换成微服务架构
云原生架构时代
容器技术给分布式架构提供了新思路。
当虚拟化的基础设施从单个服务的容器扩展至由多个容器构成的服务集群、通信网络和存储设施时,软件与硬件的界限便已模糊。一旦虚拟化的硬件能够跟上软件的灵活性,那些与业务无关的技术性问题便有可能从软件层面剥离,悄无声息地在硬件基础设施之内解决,让软件得以只专注业务,真正围绕业务能力构建团队与产品。
从软件层面独立应对分布式架构所带来的各种问题,发展到应用代码与基础设施软、硬一体,合力应对架构问题,这个新的时代现在常被媒体冠以“云原生”这个颇为抽象的名字加以宣传。云原生时代追求的目标与此前微服务时代追求的目标并没有本质改变,都是在服务架构演进的历史进程中,所以笔者更愿意称云原生时代为“后微服务时代”。
Kubernetes成为容器战争胜利者标志着后微服务时代的开启,但Kubernetes仍然没能完美解决全部的分布式问题。
微服务A调用了微服务B的两个服务,称为B1和B2,假设B1表现正常但B2出现了持续的500错,那在达到一定阈值之后就应该对B2进行熔断,以避免产生雪崩效应。如果仅在基础设施层面来处理,这会遇到一个两难问题,切断A到B的网络通路会影响B1的正常调用,不切断则会持续受B2的错误影响。
为了解决这一类问题,虚拟化的基础设施很快完成了第二次进化,引入了今天被称为“服务网格”(Service Mesh)的“边车代理模式”(Sidecar Proxy)。
在虚拟化场景中的边车指的是由系统自动在服务容器(通常是指Kubernetes的Pod)中注入一个通信代理服务器,相当于那个挎斗,以类似网络安全里中间人攻击的方式进行流量劫持,在应用毫无感知的情况下,悄然接管应用所有对外通信。这个代理除了实现正常的服务间通信外(称为数据平面通信),还接收来自控制器的指令(称为控制平面通信),根据控制平面中的配置,对数据平面通信的内容进行分析处理,以实现熔断、认证、度量、监控、负载均衡等各种附加功能。通过边车代理模式,便实现了既不需要在应用层面加入额外的处理代码,也提供了几乎不亚于程序代码的精细管理能力。