如何拆分微服务架构?

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

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

1. 几乎所有成功的微服务架构都是从一个巨大的单体架构开始的,并且由于太大而被拆分为微服务架构。

2. 几乎所有我听说过从一开始就构建为微服务架构的故事,最终都遇到了巨大的麻烦。在服务划分之前,应该保证基础设施及公共基础服务已经准备完毕,可以通过监控快速定位故障,通过工具自动化部署、管理服务,通过服务化框架降低服务开发的复杂度,通过灰度发布提升可用性,通过资源调度服务快速申请、释放资源,通过弹性伸缩快速扩展应用。

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

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

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

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

微服务设计原则

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

垂直划分优先原则

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

  • 调用次数更多导致性能大幅下降。
  • 实现一个功能要跨越更多服务,沟通成本升高。

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

持续演进原则

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

服务自治、接口隔离原则

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

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

自动化驱动原则

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

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

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

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

研发环境和流程上的转变

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

自动化工具链

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

微服务框架

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

快速申请资源

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

故障发现反馈机制

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

研发流程上的转变

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

拆分前先做好解耦

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

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

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

状态外置

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

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

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

(1)定时任务。

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

(2)本地存储。

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

(3)本地缓存。

某些业务会将数据存放在本地做缓存,例如Session数据,如果要去掉本地缓存,则可以通过分布式缓存和Cookie解决业务服务带状态的问题。

当然,本地缓存也有适用的业务场景,不能一概而论。

去触发器、存储过程

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

  • 整体的伸缩受到数据库的限制,因为触发器、存储过程难以扩展。
  • 当存在水平分表的时候,可能无法满足需求。
  • 如果触发器、存储过程过多,则会导致运维复杂度升高。

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

通过接口隔离

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

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

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

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

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

微服务划分模式

虽然服务是逐步被拆分出来的,随着业务的演进,在某一时刻,可能需要我们重新审视服务划分的是否合理。本节向大家推荐两种服务划分的方法。首先介绍如何选择服务划分的方法。

基于业务复杂度选择服务划分方法

根据业务复杂度划分服务,如图所示。当业务复杂度足够高的时候,应该基于领域驱动划分服务,而领域驱动本身足够复杂,很多概念比较抽象,应用范围并不是特别广泛,所以当业务复杂度较低时,可以选择基于数据驱动模式划分服务,数据驱动模式更容易理解和上手,也就是说,除非业务复杂度非常高,否则应该优先以数据驱动模式划分服务。这里的业务复杂度专指业务逻辑,而非数据量、并发量等相关复杂度。

在做出选择的时候,还有一个参考指标是,团队以前是否已经基于领域驱动开发业务,也就是说如果产品已经基于领域驱动开发了一段时间,具备了领域驱动开发的能力,那么当然推荐继续选择领域驱动划分服务。如果是一个全新的产品,则可以灵活选择。

选择服务划分的方法要重点考虑的条件如下。

  • 业务复杂度。
  • 团队对领域驱动的熟悉程度。

基于数据驱动划分服务

数据驱动是一个自下而上的架构设计方法,数据驱动强调的是数据结构,也就是通过分析需求,确定整体数据结构,根据表之间的关系划分服务。

通常基于数据驱动划分服务的步骤如下。

第一步,需求分析。通过领域专家(或者产品经理)确定目标,然后总结user story,确定核心的业务流程。通过工具呈现比较粗糙的界面,内部讨论。不断迭代此环节,直到满意为止。

第二步,抽象数据结构。根据需求总结use case,协助分析需求,从中抽象数据结构。

第三步,划分服务。分析数据结构,识别服务,服务应该满足高内聚、低耦合、单一职责等特征。

第四步,确定服务调用关系。先分析出主要流程,根据请求需要调用的服务确定服务调用关系。如果存在问题,需要从第一步重新开始。

第五步,业务流程验证。重新回到user story,以服务为粒度实现时序图,注意此阶段重点是要验证服务划分是否合适,要关注如下问题。

  • 一次更新操作如果要跨越更多服务,那么一致性的要求是什么。
  • 跨服务查询时,是否要做关联查询,一个服务内是否能解决问题。
  • 性能是否能满足要求。
  • 成本是否满足要求。

第六步,持续优化。

基于领域驱动划分服务

领域驱动是一个自上而下的架构设计方法,通过和领域专家建立统一的语言,不断交流,确定关键业务场景,逐步确定边界上下文。领域驱动更强调业务实现效果,认为自下而上的设计可能会导致技术人员不能更好地理解业务方向,偏离业务目标。

通常基于领域驱动划分服务的步骤如下。

第一步,通过模型和领域专家建立统一语言。建立统一语言是为了更深入的理解需求,通用语言尽量以业务语言为主,而非技术语言。通用语言和代码一样,需要不断的重构。

第二步,业务分析。确定核心的业务流程,然后逐步扩展到全部。最好通过工具呈现比较粗糙的界面,内部讨论。

第三步,寻找聚合。显式地定义了领域模型的边界。为大家介绍一下最近比较热门的事件风暴,事件风暴是一种基于领域驱动分析业务,划分服务的方法。

事件风暴就是把所有的关键参与者都召集到一个很宽敞的屋子里来开会,并且使用便利贴来描述系统中发生的事情。

第四步,确定服务调用关系。先分析出主要流程,根据一次请求需要调用的服务确定服务调用关系。如果存在水平划分,则需要根据服务依赖原则确定关系。如果存在问题,则需要从第一步重新开始。

第五步,业务流程验证。以服务为粒度实现时序图,注意此阶段重点是要验证服务划分是否合适,重点要关注的问题如下。

  • 一次更新操作如果要跨越更多服务,那么一致性的要求是什么。
  • 跨服务查询时,是否要做关联查询,一个服务内是否能解决问题。
  • 性能是否能满足要求。
  • 成本是否满足要求。

第六步,持续优化。

从已有单体架构逐步划分服务

在大多数场景下,并非从开始阶段就采用微服务架构,而是随着业务不断发展,从最初的单体架构中逐步拆分服务。下面描述了从一个单体架构逐步拆分的步骤。

第一步,所有微服务成功的故事都是从一个单体架构太大,需要被拆散开始的,下图所示。我们应该以单体架构开始,当系统规模足够大、团队人数足够多时,再逐步拆分服务,通常前后端分离是拆分的第一步。

第二步,提取公共基础服务,如单点登录,拆分可以遵循逻辑分离和物理分离两种方法。另外随着系统压力的增加,可能会用到消息中间件、分布式缓存等服务。

第三步,不断地从老系统抽象服务,垂直划分优先,如下图所示。

第四步,当业务越来越复杂的时候,API Gateway做了太多的事情,会成为一个瓶颈点,服务之间的依赖关系也会变得越来越复杂,此时,需要适当地进行水平切分。

微服务拆分策略

1.当不断从单体架构中抽象服务的时候,哪些服务优先被拆分,哪些服务不需要被拆分?以下几个策略可以帮助拆分。

2.比较独立的新业务优先采用微服务架构。从成本角度考虑,新业务采用新的架构是最合理的,对老业务的影响最小。

3.优先抽象通用服务。因为通常通用服务的边界比较明显,耦合度低,比较容易分离。

4.优先抽象比较容易识别的、边界比较明显的服务。如果原有包结构比较清晰,可以基于原有包结构中有明显边界的、比较完整的业务进行划分,这是从成本角度考虑。如果已经基于单体架构开发了一段时间,对业务的理解程度已经非常高了,那么这时往往开发及架构人员能够比较容易地提炼出一些边界比较明显的服务。

5.优先抽象核心服务。因为微服务的开发及运维成本比较高,并不是所有的地方都要划分的粒度比较小,往往一些比较边缘的运营、管理的系统就不会拆分,另外,随着时间的推移,有一些业务可能会发生改变,因此,应该先抽象出核心服务。

6.优先抽象具有独立属性的服务。根据功能的变更频率,资源占用,技术栈等属性划分服务。

发表评论
留言与评论(共有 0 条评论)
   
验证码:

相关文章

推荐文章

'); })();