微服务架构

微服务架构是Cloud Native的重要组成部分。微服务架构给我们带来收益的同时,也会带来副作用,我们应该在什么阶段采用微服务架构?如何拆分微服务架构?拆分粒度多大比较合适?本章内容从问题开始,循序渐进,带领读者逐步深入微服务架构的各个角落。

微服务架构的起源

2005年,Peter Rodgers博士在云端运算博览会上提出微Web服务(Micro-Web-Service),将程序设计成细粒度的服务(Granular Service),以作为Microsoft下一阶段的软件架构。其核心思想是让服务按照类似Unix管道的存取方式使用,而且复杂的服务背后使用简单URI来开放界面,任何服务都能被开放(exposed)。这个设计在HP的实验室被实现,具有改变复杂软件系统的强大力量。

2014年,Martin Fowler与James Lewis共同提出了微服务的概念,定义了微服务架构是以开发一组小型服务的方式来开发一个独立的应用系统,每个服务都以一个独立进程的方式运行,每个服务与其他服务使用轻量级(通常是HTTP API)通信机制。这些服务是围绕业务功能构建的,可以通过全自动部署机制独立部署,同时服务会使用最小规模的集中管理(例如Docker)能力,也可以采用不同的编程语言和数据库。

实际上,微服务的诞生绝非偶然,敏捷开发帮助我们减少浪费、快速反馈,以用户体验为目标;持续交付促使我们更快、更可靠、更频繁地改进软件;基础设施即代码(Infrastructure As Code)帮助我们简化环境的管理,这些都是推动微服务诞生的重要因素。如果没有这些基础,微服务架构在展现魅力的同时,可能由于各种问题导致最终失败。

从SOA架构到服务化架构,再到微服务架构,是一个逐步演进的过程。Amazon被认为是微服务的鼻祖。2015年我曾经接触过一个Amazon的工程师,他并不是特别了解微服务这个名词,直到看完Martin Fowler关于微服务的文章,才发现自己一直在做的就是微服务架构。可以说微服务架构并不是什么技术创新,而是开发过程发展到一定阶段对技术架构的要求,是在实践中不断摸索而来的。每个公司所信奉的架构思想有相同之处,但是也不尽相同。这种化繁为简的拆分方式,不只在技术上带来突破,更带来了很多潜在的价值,如关注点分离、沟通效率提升、快速演进、快速交付、快速反馈等。

为什么采用微服务架构

单体架构与微服务架构 

就像很难用一个绝对的方式去判断架构好坏一样,在大多数场景下,我们也很难从一个外部的视角去判断服务拆分粒度的合理性,需要对上下文非常了解才能做出一个好决策。例如,团队规模多大,代码规模多大,有没有平台化,有没有工具链,是否需要持续交付,团队文化如何等。因此,一个外部的架构师是很难在短时间内将架构规划合理的,这需要一个过程,当真正了解这一切之后,不断权衡才能最终确定。在规划之前,有必要参考表2-1,综合各方面的情况,最终做出决策。

表2-1  单体架构与微服务架构对比

因    素

单体架构

微服务架构

说    明

交付速度

较慢

较快

服务拆分后,各个服务可以独立并行开发、测试、部署,交付效率提升,产品的更新速度会更快,用户体验更好。代码规模越大,微服务的优势越明显

故障隔离范围

线程级

进程级

服务独立运行,通过进程的方式隔离,使故障范围得到有效控制、架构变得更简单可靠。根据业务的重要程度划分服务,把核心的业务划分为独立的服务,这样从数据库到服务可以保持有效的故障隔离,进而保持稳定

整体可用性

较低

更高

微服务架构由于故障范围得到有效隔离,整体可用性更高,降低一点故障对整体的影响

续表  

因    素

单体架构

微服务架构

说    明

架构持续演进

困难

简单

由于微服务的粒度更小,架构演进的影响面就更小。不存在大规模重构导致的各种问题。微服务架构对架构演进更友好

沟通效率

业界普遍认为团队规模越大,沟通效率越低,微服务架构按业务构建全功能团队,把权力下放,不会出现决策瓶颈点,缩小了沟通规模,提高了沟通效率

技术栈选择

受限

灵活

如果某个业务需要独立的技术栈,可以通过服务划分、接口集成的方式实现。例如搜索的技术栈、专业细分领域都不相同,通常采用独立的服务实现

可扩展性

受限

灵活

微服务架构可以根据服务对资源的要求以服务为粒度扩展,符合AKF扩展立方体中的Y轴扩展,而单体架构只能整体扩展,只能做到AKF扩展立方体中的X轴扩展

可重用性

微服务架构可以实现以服务为粒度通过接口共享重用

实现业务复杂性分解难度

困难

容易

微服务架构通过将业务分解为更多的服务,业务边界更清晰,更容易把一个复杂的问题分解为简单的小问题

产品创新复杂度

困难

容易

微服务架构以服务为粒度独立演进,团队有更多的自主决策权,更多的试错机会,更利于创新

一致性实现成本

服务划分后,如果服务A同时调用服务B和服务C,如何保证同时成功或失败?单体架构下的单库事务变成了分布式事务问题

时延

服务划分后,调用次数增加,导致响应时间增加、吞吐量降低,如何弥补

资源成本

吞吐量的下降意味着要增加更多的资源,对于交付型项目,特别是小规模部署的场景下,是比较致命的

关联查询复杂度

简单

复杂

微服务架构的一个非常明显的特征就是,一个服务所拥有的数据只能通过这个服务的API来访问。通过这种方式来解耦,往往会带来查询问题。以前通过join就可以满足要求,现在如果需要跨多个服务集成查询,就会非常麻烦

远程调用

不涉及

涉及

微服务存在更多的远程调用,需要额外考虑序列化、通信协议、数据压缩、服务间的负载均衡、容错等问题

服务治理

不涉及

涉及

由于服务数量变多,微服务架构需要额外考虑服务的注册发现、依赖关系、治理等问题

对开发人员的要求

微服务架构更复杂,开发人员端到端负责,既要考虑接口定义,又要考虑数据库设计,对开发人员的水平要求更高

对工具的依赖

较低

较高

微服务架构中服务的数量较多,使用工具的效果更明显,依赖程度更高

运维复杂度

微服务架构中服务的数量较多,对服务的监控、健康检查要求更高,整体运维复杂度更高

什么时候开始微服务架构

产品初期优先选择单体架构。面对一个新的领域,对业务的理解很难在开始阶段就比较清晰,往往是经过一段时间之后,才能逐步弄清楚。很多时候,从一个已有的单体架构中逐步划分服务,要比一开始就构建微服务简单得多。另外,在资源受限的情况下,采用微服务架构风险较大,很多优势无法体现,性能上的劣势反而会比较明显。

单体、组件化、微服务架构成本趋势,如图2-1所示。当业务复杂度达到一定程度后,微服务架构消耗的成本才会体现优势,并不是所有的场景都适合采用微服务架构,服务的划分应逐步进行,持续演进。产品初期业务复杂度不高的时候,应该尽量采用单体架构。

 

图2-1  单体、组件化、微服务架构成本趋势

 

摘自Martin Fowler博客的内容简单翻译如下。

 

 

在服务划分之前,应该保证基础设施及公共基础服务已经准备完毕。可以通过监控快速定位故障,通过工具自动化部署、管理服务,通过服务化框架降低服务开发的复杂度,通过灰度发布提升可用性,通过资源调度服务快速申请、释放资源,通过弹性伸缩快速扩展应用。

如何决定微服务架构的拆分粒度

微服务架构中的微字,并不代表足够小,应该解释为合适。但是“合适”过于含糊,每个人理解的合适都不尽相同。实际上,有时候对于一个对业务理解不够深入,对团队情况又不是很了解的人,根本无权协助确定服务的粒度。况且,就算本团队的架构师,也很难确定粒度。随着业务发展,开发人员水平的提升,粒度可能会发生变化。这是一个磨合的过程,一个不断演进的过程,没有绝对的对与错。

如果实在找不到合适的依据,可以参考表2-2。决策占比是从通用的角度考虑,并不适用所有的情况,某些公司认为团队规模是决定性的,也有些公司认为架构演进是决定性的,还有些公司认为交付速度是决定性的,找到那个你认为的决定性因素,去做合理的拆分即可。

表2-2  微服务拆分粒度决策参考表

因    素

决策占比

说    明

团队规模

50%

团队规模变大会出现决策瓶颈点,即所有的决策都要依赖于某个会议或某个人,没有人愿意承担责任,效率十分低下

交付速度要求

30%

毫无疑问,拆分粒度越小,交付时受到的约束越小,速度越快

其他

20%

例如,对占用资源的要求、对性能的要求、对一致性的要求、对架构演进速度的要求、对创新速度的要求

微服务设计原则

在微服务架构的设计过程中,我们应该遵循哪些原则?以下原则在微服务架构中经常被提起,遵循这些原则能够让我们少走弯路。

垂直划分优先原则

应该根据业务领域对服务进行垂直划分,因为水平划分服务可能会导致如下问题。

· 调用次数更多,导致性能大幅下降。

· 实现一个功能要跨越更多服务,沟通成本增加。

垂直划分服务可以以最简单的方式缓解上述问题,并且可以让团队从上至下关注业务实现,端到端负责,持续改进。图2-2简单描述了一个按业务领域垂直划分的微服务架构示例,在业务垂直方向切分服务,通过API Gateway聚合内容。

图2-2  垂直划分服务

持续演进原则

服务数量快速增长带来架构复杂度急剧升高,开发、测试、运维等环节很难快速适应,会导致故障率大幅增加,可用性降低。非必要情况,应逐步划分、持续演进,避免服务数量的爆炸性增长。这等同于灰度发布的效果,先拿出几个不太重要的功能拆分出一个服务做试验,如果出现故障,则可以减少故障的影响范围。另外,除了业务服务数量的增加,还需要准备持续交付的工具、微服务框架等,并加强监控。

服务自治、接口隔离原则

尽量消除对其他服务的强依赖,这样可以降低沟通成本,提升服务稳定性。服务通过标准的接口隔离,隐藏内部实现细节。这使得服务可以独立开发、测试、部署、运行,以服务为单位持续交付。

直接访问对方的数据库会造成一定的耦合性,应该尽量避免。

自动化驱动原则

部署与运维的成本会随着服务的增多呈指数级增长,每个服务都需要部署、监控、日志分析等运维工作,成本会显著提升。在服务划分之前,应该首先构建自动化的工具及环境。开发人员应该以自动化为驱动力,简化服务在创建、开发、测试、部署、运维上的重复性工作,通过工具实现更可靠的操作。避免微服务数量增多带来的开发、管理复杂度问题。自动化可以从多个方面节省时间、提高效率,它可以快速跟踪整个交付过程并实时向所有参与者报告这个过程,赋予参与者责任感和成就感。如在研发过程中,推行持续集成的文化就特别重要,而持续集成所依赖的工具就是一种自动化的体现。

很多互联网公司都遵循“一切皆自动化”的原则,特别是存在跨地域的研发模式时,使用自动化工具将是至关重要的,如开源的协作模式。

微服务架构实施的先决条件

不提倡从一开始就建立微服务架构的原因之一是没有做好准备,下面我们来看一下建立微服务架构前,需要从哪些方面做准备。

研发环境和流程上的转变

在实施微服务架构之前,我们要准备相关的环境和流程,可以简单地通过以下几个方面建立基本的条件。

自动化工具链

微服务架构的一大优势是快速交付,快速交付不止体现在服务的粒度更小,可以独立交付,还体现在整个流程更快速。微服务架构基于自动化的工具链,以流水线交付的方式串联整个DevOps流程。小团队可以基于服务独立开发、测试、部署、运维。传统的交付周期以月为单位,而微服务架构的交付周期能做到以天为单位,按照传统的开发模式是无法满足这样的交付周期要求的。

微服务框架

微服务框架可以封装、抽象分布式场景下的一些常用能力,例如负载均衡、服务注册发现、容错、远程通信等能力,可以让开发人员快速开发出高质量的服务,在采用微服务架构之前,应该先进行微服务框架的选型和试用。

快速申请资源

如果以天为单位进行交付,就必须能够快速申请资源。基础设施即代码可通过编程的方式管理虚拟机或容器,免去了手动配置、更新各个硬件的环节,这就使得基础设施极具弹性,能够快速、高效、准确地进行重复性操作。开发人员使用同一套配置或代码,就可部署并管理成千上万台物理机。基础设施即代码能够得到更快的速度、更低的成本和更可靠的环境。用代码定义服务器配置意味着在众多服务器之间有绝对的一致性,容易形成标准化。手动调整配置往往会有一些微妙的差异,难以追溯和调试,并且会导致许多诡异的问题。

故障发现反馈机制

当服务数量增多、交付频繁的时候,故障次数可能会大幅上升,我们需要通过全面的监控发现故障,及时处理并发出报警。当生产环境出现问题的时候,需要将故障进行分级,评估影响面,并分配给相应的架构师或者开发人员。开发人员需要不断更新故障的状态,以便管理者、客服、销售人员等问题相关人了解进度,来提供更好的用户体验。

研发流程上的转变

需要重新组建团队,以服务为核心,按照业务领域划分全功能团队,改变原有的研发流程、决策机制。例如,倡导敏捷文化、快速迭代,做更多的自动化测试,加强Code Review,给团队更多的自主决策权等,具体内容可以参考本书第9章和第10章。

拆分前先做好解耦

解耦这个词来源于数学,是指使含有多个变量的数学方程变成能够用单个变量表示的方程组,即变量不再同时直接影响一个方程的结果,从而简化计算。

在软件世界里,解耦强调的是每个单元可以独立变化,尽量减少外界对系统内部的影响。说白了也就是,如果把Memcache换成Redis,那么需要多少工作量,涉及的修改面有多大。解耦也会带来工作量的增加、架构或者代码变得复杂等问题。例如很多人会假设把Oracle换成MySQL,Memcache换成Redis,但是在实际工作中,并不是所有的业务发展速度都有这么快,如果能预料到短期将发生变化,为什么不直接使用MySQL呢?通常这是一个伪命题。如果在未来几年后才发生变化,那么现在去做相应的适配,这不符合敏捷开发的哲学思想,也不是一个高效率的思路。

在转向微服务架构之前,业务服务存在状态、数据库中存在触发器和存储过程、服务之间绕过接口调用等问题,是我们首先要解决的。

状态外置

无状态(Statelessness)指的是服务内部变量值的存储。有状态的服务伸缩起来非常复杂,可以通过将服务的状态外置到数据库、分布式缓存中,使服务变成无状态。通常业界用牲畜来比喻无状态,用宠物来比喻有状态。宠物是需要呵护的,是有名字的,不能被轻易替代的,而牲畜是没有名字的,只生产肉和奶,死掉一个用新的来替代即可。所以,我们期望服务可以做到无状态,可以被轻易地替代。

但是,无状态不代表状态消失,只是把状态转移到分布式缓存和数据库中了。业务服务伸缩的时候,还是要考虑分布式缓存和数据库所能承受的压力限制。那为什么还要外置呢?因为一方面即使不外置到数据库,数据库也存在状态,另一方面,这样可以把复杂度抽象到统一的位置,便于集中处理。例如,服务端的Session信息可以放到分布式缓存中,这一设计方法既可以让业务服务在一定范围内(分布式缓存的上限)伸缩时不受状态的限制,又可以把复杂度抽象到特定的位置,让专业领域开发人员统一做有状态的伸缩。虽然绝大多数服务都可以状态外置,但是并不是所有的业务服务都能设计成无状态,例如客户端与服务端的长连接,这种状态很难外置。

以下三种常见的状态需要和业务服务拆分开来,否则扩展性将受到很大限制。

· 定时任务:因为大多数任务不能重复触发,否则轻则重复做无用功(幂等的情况下),重则会导致不一致。例如从A表中把数据迁移到B表中,如果在两个服务中同时处理,没有一个协调器的话,会导致重复拉取。所以,需要把定时任务从业务服务中提取出来,通过分布式任务调度统一协调。

· 本地存储:在本地存储文件也是比较常见的。当有多个实例的时候,要么全部同步一遍,要么需要根据用户路由到同一个实例,并且在伸缩的过程中需要迁移。

· 本地缓存:某些业务会将数据存放在本地做缓存,例如Session数据。如果要去掉本地缓存,则可以通过分布式缓存和Cookie解决业务服务带状态的问题。当然,本地缓存也有适用的业务场景,不能一概而论。

去触发器、存储过程

触发器、存储过程在系统规模比较小的时候,的确非常简单实用。随着业务的发展,业务服务比较容易扩展,数据库通常变成了伸缩的瓶颈,许多方案都是为了减轻数据库的压力而提出的。触发器、存储过程可能会带来如下问题。

· 整体的伸缩受到数据库的限制,因为触发器、存储过程难以扩展。

· 当存在水平分表的时候,可能无法满足需求。

· 如果触发器、存储过程过多,则会导致运维复杂度升高。

解决方案通常是通过外部的业务服务或者定时任务替换触发器及存储过程。

通过接口隔离

直接访问其他服务的数据库,如图2-3所示。CRM直接调用OA的数据库,没有通过接口调用。对CRM进行微服务架构拆分之前,需要先理清系统的外部依赖关系,如果存在多个系统共享一个数据库,就会导致耦合,影响可用性和扩展性,进而可能出现如下问题。

· 当CRM中的数据结构发生变化的时候,OA也要跟着变化,导致开发的过程互相依赖。

· 有可能在CRM进行的限流是没用的,因为OA没有通过CRM提供的接口进行调用。

· 假设随着业务的发展,需要在CRM的数据库上做缓存,可能存在多个地方要考虑缓存的问题。

图2-3  直接访问其他服务的数据库

总之,接口应该作为唯一对外提供的访问方式,这代表的是控制力。解决方法就是通过接口调用,逐步去除数据库的直接访问。

 

本文节选自《持续演进的Cloud Native:云原生架构下微服务最佳实践》一书,王启军 著。电子工业出版社出版。

   作者王启军,目前就职于华为公司架构部,负责华为公司的Cloud Native、微服务架构推进落地,前后参与了华为手机祥云4.0、物联网IoT 2.0的架构设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值