1、前言
在软件行业,架构指的是软件系统的顶层设计,主要分为业务架构、 应用架构 、代码架构、技术架构、数据架构和部署架构。关于架构的一些概念可以看这篇文章:什么是架构? 应用架构 是根据业务设计的系统的整体结构和组织方式,是基于业务驱动的。应用架构的发展一方面需要围绕业务的发展而不断迭代,另一方需要不断的优化架构的扩展性和稳定性,从而提高业务的迭代效率以及减少软件执行过程出现的问题。
本篇主要讲架构演进的过程,尤其是围绕应用架构是如何围绕扩展性和稳定性,通过了解每个架构的特点,从而帮助我们设计出更合适的软件系统。
2、单体架构
单体架构是今天绝大多数软件开发者都学习、实践过的一种软件架构,许多介绍微服务的图书和技术资料中也常把这种架构风格的应用称作“巨石系统 单体架构”。在整个软件架构演进的历史进程里,是出现时间最早、应用范围最广、使用人数最多、统治历史最长的一种架构风格。如下图所示:
▲单体架构
首先有一个概念误区:单体架构在架构设计中是一个“反面角色”。对于小型系统,单台机器就足以支撑其良好运行的系统,不仅易于开发、测试、部署,且由于系统中各个功能、模块、方法的调用过程都是进程内调用,不会发生进程间通信,因此运行效率也是最高的。在软件的性能需求超过了单机,软件的开发人员规模明显超过了6~12人,单体架构是存在明显不足的。
单体架构又被称作巨石架构(Monolith),并不能直接的理解这是在形容架构是难以拆分、难以扩展的,因此才不能支撑越来越大的软件规模。这种想法看似合理,其实是有失偏颇的。更恰当的形容应该是叫做“自给自足”,其拆分和扩展效率是非常高的。
- 从纵向看,无论是哪个系统都需要考虑代码分层,而单体架构更容易开发、部署、测试,因此拆分是非常便捷的。
- 从横向看,单体架构也支持按照技术、功能、职责等维度,将软件拆分为各种模块,以便重用和管理代码。
- 从部署看,也可以在负载均衡器之后同时部署若干个相同的单体系统副本,以达到分摊流量压力的效果。
单体架构拆分的劣势,在于拆分之后的自治与隔离能力上。
- 由于所有代码都运行在同一个进程内,这意味着如果任何一部分代码出现缺陷,过度消耗了进程空间内的资源,所造成的影响也是全局性的、难以隔离的,如内存泄漏、线程爆炸、阻塞、死循环等问题。
- 同样,由于不能隔离,也就无法单独停止、更新、升级某一部分代码,灰度发布、A/B实验也相对更复杂。
除此之外,这种架构风格潜在的要求是希望系统的每一个部件、每一处代码都尽量可靠,尽量不出或少出缺陷,一旦出现问题,带来的影响都是毁灭性的。这种靠高质量来保证高可靠性的思路,随着系统规模逐渐变大,将变得越来越困难,这才是这个架构在互联网时代,很容易被淘汰的原因。
3、SOA时代
为了对大型的单体系统进行拆分,将应用程序的不同功能单元(服务)通过定义良好的接口和协议进行组合。这些服务是独立的、可重用的,从而每一个子系统都能独立地部署、运行、更新。软件架构来到SOA时代,其包含的许多概念、思想都已经能在今天的微服务中找到对应的身影了,譬如服务之间的松散耦合、注册、发现、治理,隔离、编排等。
在 架构演进 的过程中,开发者们尝试过很多种方案,有三种较有代表性的架构模式:烟囱架构、微内核架构、事件驱动架构。
3.1、烟囱架构
烟囱架构又名垂直架构、信息孤岛架构,一个单体系统虽然拆分成多个子系统,但是需要假设应用与应用之间不会存在任何交互。也就是说数据、资源等互不共享,这样的拆分显然很难满足企业的需求。
3.2、微内核架构
微内核架构又叫做插件式架构,有两个元素:内核系统和插件。
- 内核系统:将可能被各子系统使用到的公共服务、数据、资源集中到一块,成为一个被所有业务系统共同依赖的核心系统
- 插件:将具体的业务抽象成一个个插件。
▲微内核架构
这种架构是个Saas系统或者需要支持二次开发的软件系统,即开发一个核心的产品,新的功能通过不断新增插件的方式迭代,比如飞书、IDEA、Office等。除此之外,一个微内核架构也可以嵌入到其他架构模式中,通过插件的方式来提供新功能的定制开发能力。通过插件方式,提供了可扩展的、灵活的、天然隔离的功能特性,解决了系统之间的共享问题。
不过,微内核架构也有一些局限性:需要假设系统中各个插件模块之间是互不依赖的。虽然插件会访问内核中一些公共的资源,但无法直接交互,也不可预知系统会安装哪些模块。
3.3、事件驱动架构
事件驱动架构(Event Driven Architecture)是一种流行的分布式异步架构模式,用于创建可伸缩的应用程序。通过一条事件总线,各个子系统可以发布自己的事件,也可以订阅自己感兴趣的事件。子系统与子系统之间是高度解耦的,可以异步地接收和处理事件。
它包括两个主要的拓扑结构:调停者拓扑(Mediator Topology) 和 代理者拓扑(Broker Topology)。
3.3.1、调停者拓扑
调度者拓扑有四个主要的架构组件:事件队列,事件调度者,事件通道,事件处理器。
- 整个事件流以客户端发送一个初始化事件到事件队列为起点,将事件传输到事件调度者。
- 事件调度者接收到初始化事件后,将初始化事件进行编排,拆分成多个子事件
- 事件调度者通过发送额外的异步事件到事件通道来执行每一步的处理
- 事件处理器监听事件通道,接收来自事件调度者的事件,并执行事件的具体业务逻辑。
下图展示了事件驱动架构中一般的调度者拓扑:
调度者拓扑可以提供事件编排能力,从而实现非常复杂的业务流程,比如BPM权限管理系统。下面是一个搬家业务在调停者拓扑中的应用:
事件调度者将一个搬家事件拆分成五个子事件:选择地址、计算价格、更新理赔、调整需求、通知保险。事件之间可以穿行,也可以并行,比如计算价格、设置需求就是并行的。最后每个子事件,交给不同的子系统解决。
3.3.2、代理者拓扑
代理者拓扑和调度者拓扑的不同点在于:代理者拓扑没有中心化的事件调度者。相反,消息流是通过轻量级的消息代理将消息分布到消息处理器组件中。当你有一个简单的事件处理流程,但是你不想或者不需要一个中心化的事件编排时,代理者拓扑将会大有用处。如下图所示
还以搬家为例的代理者流程如下所示:
- 没有中心化的事件调度者来接收初始化事件,用户处理组件则直接接收事件,改变用户的地址,并发送一个新事件:改变地址事件。
- 有两个事件处理器对修改地址事件感兴趣:价格处理和理赔处理。价格处理器组件根据新地址来计算赔率,并发布一个新事件(例如:重新计算价格事件)。理赔处理组件收到相同的地址修改事件,然后他跟新一个未赔付的保险索赔,并发布一个新事件:更新索赔事件。
- 这些新事件由其他事件处理器组件接收,且事件链会持续直到没有新的事件发布。
3.4、SOA架构
SOA架构并没有一个明确的解释,但是基本包含下面两个元素:
- 提倡对服务进行拆分:服务是一个独立的功能单元,一个功能所有的代码和数据都要包含在其中,要比微服务中的“服务”粒度粗的多。
- 提倡复用:为此制定了一系列标准,服务之间使用公共接口标准和架构模式,因此可以快速整合到新应用中。这让应用开发人员无需像之前那样重新开发现有功能,因为有公共的接口标准,开发人员也不必了解如何连接现有功能 。
SOA 针对分布式架构面临的问题,给出的解决方案更具体、操作性更强,并形成一揽子规范标准。相比前面的烟囱架构、微内核架构、事件驱动架构,SOA 就显得具体的多,举几个例子:
- SOA 明确了采用 SOAP 作为远程调用的协议。
- 明确使用企业服务总线(ESB)来实现各个子系统之间的通信交互。
- .....
从技术讲,SOA架构确确实实解决了分布式架构面临的问题,但是过于复杂,落地成本非常高。大多数系统不可能会遇到所有的问题,这个业务体量和复杂程度有关,但是SOA架构难以实现“因地制宜”的架构设计。
4、微服务时代
微服务最早指的是是一种专注于单一职责的、与语言无关的细粒度Web服务。微服务最初可以说是SOA发展时催生的产物,是作为SOA的一种轻量化的补救方案而被提出的。
微服务真正崛起是在2014年,来自一篇Microservices:A Definition of This New Architectural Term的文章,此文给出了现代微服务的概念:微服务是一种通过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建。各个服务可以采用不同的编程语言、不同的数据存储技术,运行在不同的进程之中。服务采取轻量级的通信机制和自动化的部署机制实现通信与运维。
4.1、围绕业务能力构建(Organized around Business Capability)
这里强调了康威定律的重要性,有怎样结构、规模、能力的团队,就会产生对应结构、规模、能力的产品。如果本应该归属同一个产品内的功能被划分在不同团队中,必然会产生大量的跨团队沟通协作。而跨越团队边界,在管理、沟通、工作安排上都有更高昂的成本。因此高效的团队自然会针对其进行改进,当团队、产品磨合稳定之后,团队与产品就会拥有一致的结构。
4.2、分散治理(Decentralized Governance)
这里是指服务对应的开发团队有直接对服务运行质量负责的责任,也有不受外界干预地掌控服务各个方面的权力。微服务更加强调的是在确实需要技术异构时,应能够有选择“不统一”的权利,比如要做人工智能训练模型时可以选择Python。
4.3、通过服务来实现独立自治的组件(Componentization via Service)
之所以强调通过“服务”(Service)而不是“类库”(Library)来构建组件,是因为类库在编译期静态链接到程序中,通过本地调用来提供功能,而服务是进程外组件,通过远程调用来提供功能,尽管远程服务有更高昂的调用成本,但这是为组件带来自治与隔离能力的必要代价。
4.4、产品化思维(Product not Project)
避免把软件研发视作要去完成某种功能,而是视作一种持续改进、提升的过程。譬如,不应该把运维只看作运维团队的事,把开发只看作开发团队的事,团队应该为软件产品的整个生命周期负责,开发者不仅应该知道软件如何开发,还应该知道它如何运作,用户体验如何。
4.5、数据去中心化(Decentralized Data Management)
微服务明确提倡数据应该按领域分散管理、更新、维护、存储。在单体服务中,一个系统的各个功能模块通常会使用同一个数据库,中心化存储天生就更容易避免一致性问题,但是同一个数据实体在不同服务的视角里是不同的,比如销售领域和仓储领域对用户实体的定义存在差异。
4.6、强终端弱管道(Smart Endpoint and Dumb Pipe)
微服务提倡管道的能力应该足够简单,对于一些额外的通信能力,应该交给服务自己解决,比如消息认证授权、事件编排、事务处理等。过多的能力,对于那些不需要的服务反倒是负担。
4.7、容错性设计(Design for Failure)
可靠系统完全可能由会出错的服务组成,不能无穷止追求服务永远稳定,而是接受服务总会出错的现实。微服务要求在系统的设计中,能够有自动的机制对其依赖的服务进行快速故障检测,在持续出错的时候进行隔离,在服务恢复的时候重新联通。如果没有容错性设计,系统很容易被一两个服务崩溃所带来的雪崩效应淹没。这是微服务最大的价值所在,也是本书前言中所说的“凤凰架构”的含义。
4.8、演进式设计(Evolutionary Design)
演进式设计是承认服务会被报废淘汰。一个设计良好的服务,应该是能够报废的,而不是期望得到长存永生。假如系统中出现不可更改、无可替代的服务,反而这种系统是脆弱的,难以维护的。
4.9、基础设施自动化(Infrastructure Automation)
由于微服务架构下运维对象数量是单体架构运维对象数量的数量级倍,使用微服务的团队更加依赖于基础设施的自动化,人工是很难支撑成百上千乃至上万级别的服务的,比如CI/CD。
相比于SOA提供统一、标准的规范和组件,而微服务带来更多的自由度,仅仅提供了设计理念。而开源社区针对这些理念,诞生出了众多优秀的框架,比如仅仅一个服务调用的问题,就有thrift、dubbo、gRpc、bRpc等等多种解决方案。
微服务对于一个服务开发者,是非常友好的,因为众多的框架极大的减少了开发的成本;但对于架构师确提出了更高的要求。一个常见的微服务架构图如下所示:
▲微服务架构图
5、后微服务时代
无论是SOA和最初的微服务,核心都是在解决分布式架构中出现的问题,如注册发现、跟踪治理、负载均衡、传输通信等。解决方案都是通过软件解决这些问题,比如某个系统需要伸缩扩容,通常会购买新的服务器,部署若干副本实例来分担压力;需要负载均衡就需要布置一个负载均衡器;服务发现问题会设置DNS服务器等。这是因为硬件层面的发展速度满足不了需求,因此需要更多软件来解决这些问题。
而容器和K8s出现,改变了这个局面,硬件可以通过一些命令,扩速扩缩容应用服务器,同时天然的支持负载均衡器、DNS服务器、网络链路这些基础设施。云技术让微服务从软件层面独力应对分布式问题发展到软、硬协同应对,也称为“后微服务时代”。
但是K8s仍然没能完美解决全部的分布式问题,单纯的Kubernetes反而不如之前的Spring Cloud方案。这是因为有一些问题处于应用系统与基础设施的边缘,使得很难完全在基础设施层面中精细化地处理。比如微服务A调用了微服务B的两个服务,称为B1和B2,假设B1表现正常但B2出现了持续的500错,那在达到一定阈值之后就应该对B2进行熔断,以避免产生雪崩效应,但是基础设施很难做到某一个服务的熔断。类似的,在服务的监控、认证、授权、安全、负载均衡等方面都有可能面临细化管理的需求,K8s都很难满足。
为了解决这一类问题,虚拟化的基础设施很快完成了第二次进化,引入了今天被称为“服务网格”(Service Mesh)的Sidecar Proxy。整体架构如下:
主要核心内容分为:
- 控制平面: 控制和管理数据平面中的 Sidecar 代理,完成配置分发、服务发现、流量路由、授权鉴权等功能,以达到对数据平面的统一管理。
- 数据平面: 由整个网格内的 Sidecar 代理组成,这些代理以 Sidecar 的形式和应用服务一起部署。这些代理负责协调和控制应用服务之间的所有网络通信。每一个 Sidecar 会接管进入和离开服务的流量,并配合控制平面完成流量控制等方面的功能。
因此,基于K8s和服务网格成功的将微服务中与业务无关的微服务组件剥离出来,让业务服务更干净、更简洁。
6、无服务时代
人们研究分布式架构,最初是因为单台机器的性能无法满足系统的运行需求,尽管在后来架构演进过程中,容错能力、技术异构、职责划分等各方面因素都成为架构需要考虑的问题,但获得更好的性能在架构设计需求中依然占很大的比重。
当今基于云计算的无服务正在快速发展,无服务(Serverless)现在还没有一个特别权威的“官方”定义,但它的概念并没有前面提到的各种架构那么复杂,本来无服务也是以“简单”为主要卖点的。它只涉及两块内容:后端设施(Backend)和函数(Function)。
- 后端设施:是指数据库、消息队列、日志、存储等这类用于支撑业务逻辑运行,但本身无业务含义的技术组件,这些后端设施都运行在云中,在无服务中将它们称为“后端即服务”(Backend as a Service,BaaS)。
- 函数:是指业务逻辑代码,这里函数的概念与粒度都已经很接近于程序编码角度的函数了,其区别是无服务中的函数运行在云端,不必考虑算力问题,也不必考虑容量规划(从技术角度可以不考虑,从计费的角度还是要掂量一下的),在无服务中将其称为“函数即服务”(Function as a Service,FaaS)。
无服务的愿景是让开发者只需要纯粹地关注业务:不需要考虑技术组件,后端的技术组件是现成的,可以直接取用,没有采购、版权和选型的烦恼;不需要考虑如何部署,部署过程完全托管到云端,由云端自动完成;不需要考虑算力,有整个数据中心支撑,算力可以认为是无限的。