微服务架构设计必须懂的原则

最后更新 : 2020.01.11  

文章来源于《Spring Cloud 微服务架构开发实战》,感觉原则特别重要
如何进行微服务架构设计呢?简单来说可分为下面三个步骤:第一步,把应用中关键的需求定义出来;第二步,识别出采用微服务架构时应用中所包含的所有服务;第三步,将第一步所定义出的关键需求作为架构需求的场景来描述服务之间如何进行协作。这个步骤很像单体架构下我们所做的系统高层架构设计,通过高层架构设计会识别并定义出各个业务领域模型,这些业务领域模型包含了业务对象的关键操作流程,通过这些业务领域模型就可以辅助我们规划出整个应用架构,即各模块之间的协作关系。

在识别应用中的服务时,应首先专注于业务,通过业务逻辑的视角可以快速有效地将核心微服务识别出来。核心微服务识别出来之后,就可以围绕核心服务把相关联的服务都定义出来,并可以对这些服务进行分组、合并处理,最终完整定义出应用的一系列微服务。切记,不可以一开始从技术的角度去拆分,否则由于业务之间的关联关系很有可能会将设计出来的微服务拉入“焦油坑”中。

当识别出应用的每一个微服务后,我们就需要考虑这些微服务之间如何进行协作。定义各个微服务之间协作关系最有效的方式就是根据每一个业务进行分析。有些业务场景可能只需要某一个服务就可以完成,有些业务场景则可能需要两个或多个服务才可以。这些协作可能是实时同步的,也可能是异步执行,我们可以根据这些具体需求来确定使用何种方式进行交互(是使用REST、RPC,还是消息)。此外,还有一个需要我们第一时间去考虑的问题就是用户的服务请求最初是由哪个服务承担的。

微服务粒度

可能我们在接触微服务架构之初,一腔热血看哪个都可以作为一个微服务,最终将系统中的每一个部分都拆分成了一个微服务。但如果是这样,我们所设计的微服务粒度将太过细,每一个微服务可能只实现了数据处理,而业务处理需要粘合过多的代码才能够让这些微服务整合来完成一个具体的业务处理,反而造成了更加复杂的系统,明显不合理。对于设计微服务来说,最好的方式是先专注于各个服务之间的交互,先把它们划分成粗颗粒度的服务,然后随着系统的升级和功能的提升,再将这些粗颗粒度的服务逐渐细化,形成更为合理的微服务粒度。

随着应用功能需求的增加或变化,原来的一个微服务中所承担的责任也在增加,当我们发现在一个微服务中已经承担了多种职责的话,这个时候就是考虑对微服务进行拆分的最佳时机。

那么如何衡量我们所设计的微服务粒度是否合适呢?对于过于粗粒度的微服务来说,该微服务一定承担了太多的职责,往往在服务中塞了过多的业务逻辑和业务规则,而且业务流程也非常复杂,难以理解(常常会让你感觉好像还是在开发单体系统一样)。对于粗粒度的微服务另一个明显的表现就是拥有众多数据的管理权限。对于一个粒度合适的微服务来说,其所管辖的数据也是有限的,一旦某个微服务所管辖的数据众多,并且这些数据之间也没有合适的业务关联,那么显然该微服务粒度太粗了,需要进行细化。

反之,如果所设计的微服务颗粒度太细,一个明显的标志就是每一个微服务几乎都需要和其他的微服务进行沟通,每个微服务只承担其中很少量的业务处理,然后就交给其他微服务处理,造成了一个外部请求需要经过太多的微服务才能够完成处理。当你想单拎出一个服务时,发现几乎不可能,因为每一个微服务都依赖于其他微服务,同时又被其他微服务所依赖。

微服务架构的设计一定是与时俱进的,因此我们也不可能在第一次设计时就设计出一个完美的架构体系。因此,在最初构建粗颗粒度的服务要优于过细的微服务,因为粗粒度的微服务会随着系统升级而逐渐细化形成粒度合适的微服务,而过细的微服务在构建和管理上非常复杂,也难以重构、合并成合适的大小。此外,也不要太过纠结教条式的设计规约,在开始时甚至可以允许两个微服务之间的数据进行相互处理和聚合,因为对于许多业务对象之间毕竟并没有一个清晰的界限。还是那句话:实践出真知,只有你行动了,开始着手让微服务“跑”起来了,终究有一天会找到那个合适你的微服务架构方案,否则即使讨论百遍也得不到。

微服务拆分原则

微服务架构的开发尚处于“蛮荒”时代,并没有一些成型的指导原则和模式供我们参考,但是我们可以从面向对象的开发理论中进行借鉴。Robert C. Martin在《敏捷软件开发:原则、模式与实践》一书中提出了面向对象开发的一系列原则与模式,其中有以下两个原则可以在微服务拆分的时候借鉴:

  • 单一职责原则(SRP);
  • 同封闭原则(CCP)。

单一职责原则

单一职责原则(Single Responsibility Principle, SRP):一个类应该有且只有一个变化的原因。

There should never be more than one reason for a class to change.

我们在开发的时候深有体会,每一个职责都是一个变化引起类变化的中心。当功能变化时,通常需要通过更改相关的类来实现。如果一个类拥有多个职责,那么就会有多于一个原因来导致这个类的变化。另外,一个类承担多个职责后,往往这些职责就会耦合在一起,某一职责的改变可能会影响到其他的职责。这样的类设计是非常脆弱的,从而会导致应用的稳定性。因此,我们在进行类设计时要遵守单一职责原则。

同样,对于微服务设计来说,如果一个微服务承担太多职责的话,也会导致微服务业务之间的耦合,为业务进行改变时埋下了不稳定因素。所以,单一职责原则同样也适用于微服务设计,我们可以将微服务保持足够小,仅拥有一个业务职责,保持微服务的业务单一性,从而提升应用的稳定性。

共同封闭原则

共同封闭原则(Common Closure Principle, CCP):包中的所有的类对于同一种性质的变化应该是共同封闭的。一个变化若对一个封闭的包产生影响,则将对该包中的所有类产生影响,而对其他包则不造成任何影响。

The classes in package should be closed together against the same kinds of changes. A change the affects a package affectsall the classes in that package.

简单来说,共同封闭原则是延伸了面向对象开发中六大原则之一的开闭原则(OCP)中的关闭概念。就是说当需要修改某项业务时,我们需要将修改的范围限制在同一个包内,而不是遍布在很多包中。共同封闭原则指导我们如何对类进行有效的组织,将那些在业务概念上联系得非常紧密、通常一起发生改变的类,封装到同一个包中。通过共同封闭原则可以提升对应用组织上的管理。

同样,通过使用共同封闭原则可以将那些在业务上联系紧密,由于同一个原因而改变的服务组织在一个微服务中。这样一方面我们可以减少微服务的数量,另外一方面当业务发生改变时我们只需要一个业务开发团队进行单独修改,只需要重新部署该服务即可,减少了不同微服务开发团队之间沟通成本。

微服务自治原则

一个团队越大,那么沟通与协助成本就会越高。因此,在微服务治理中有一个重要的理念就是自治,自治范围并不只是代码和数据,还包含微服务的运行和维护管理,所以亚马逊的微服务有一个规则:你构建,你运行。

将微服务分而治之的另一个重要方面是数据管理的分而治之。传统单体架构应用的开发在很多时候多个业务之间的数据交互是直接通过操作数据库来完成,当需要更改某一业务数据库表时往往会涉及多个模块,甚至有时候根本不清楚修改这张数据库表到底会影响到多少业务代码,从而不敢动数据库表的定义,只好退而求其次,通过增加表来处理,进而加剧了系统架构的恶化。

虽然现在O/R mapping技术的出现从一定程度上解决了这个头痛的问题,但终未从根本上解决。而微服务中的分而治之理念,不但是指业务功能,也同时包含了对业务数据的管理。将业务数据管理进行私有化之后就进一步降低了业务之间的耦合度,所以实施微服务的架构师,一定要保持业务数据管理的私有化,即使你在项目中不能够分库,也要牢记这条规则,严格要求各微服务团队看好自己的数据。

微服务架构中的数据自治是指每个微服务拥有其业务领域对象下的数据,只有该微服务可以对这些数据进行操作(包含读取与更改),而其他微服务只有通过该服务才能访问到这些数据,不能直接通过数据库进行沟通。因此,我们可以不用为每一个微服务创建一个独立数据库,可以将它们统一存放在一个数据库中,保障不破坏上述的数据访问原则即可。

微服务交互原则

当我们开始使用微服务架构进行开发时,一个清晰明了、规范的交互方式将极大提升应用开发效率。通常,我们可以使用以下原则作为微服务接口设计的准则。

  • 使用REST协议:REST可以说在微服务互相调用之间起着非常重要的角色,强烈建议大家使用HTTP作为服务的调用协议,并在服务处理上使用HTTP标准动词(GET、PUT、POST和DELETE)。
  • 使用URI表达:服务端点的URI应该能够清晰表达出我们所要解决的问题、提供的方法、相应资源信息及资源之间的关联关系。
  • 使用JSON数据格式:JSON作为轻量级数据格式协议,及自带的序列化和反序列化机制,几乎已经成为通信中的数据标准协议,并且对于前端开发来说非常容易使用与整合。
  • 使用HTTP标准状态码:HTTP协议本身具有非常丰富的状态码,那么使用这些状态码来作为服务调用结果的状态是非常合适的。
  • 使用HTTP标准状态码:HTTP协议本身具有非常丰富的状态码,那么使用这些状态码来作为服务调用结果的状态是非常合适的。

以上准则,总结起来就是让所设计的微服务接口更清晰,更容易让其他开发者掌握和使用。

微服务架构迁移

将单体架构应用迁移到微服务架构意味着一个漫长的过程,不过这和在开发时经常做的代码重构类似,只是变成了对架构的重构,因此可以从中吸取一些思路。对于代码重构,有一个很重要的指导思想就是不要大规模进行重构,而是一小步一小步来。作为开发人员,每次听到重写代码可能会很兴奋,但实际上却是充满了风险,道路也是非常崎岖坎坷,最终也有可能会失败,每一个重写过代码的开发者可能对这一点深有体会。

因此,与大规模进行重构相反,在进行微服务架构迁移时可以使用Martin Fowler提出绞杀(Strangler)模式。该策略名字来源于雨林中的绞杀藤,绞杀藤为了能够爬到森林顶端都要缠绕着某棵大树生长,最终使被缠绕的大树死掉,只留下树形一样的绞杀藤。通过这种策略,我们在迁移时应首先围绕着传统应用开发出新的微服务应用,并逐渐替代传统应用中的部分业务功能。通过这种方式逐步构建微服务应用,并替代、兼容整合旧的传统应用,直到微服务承担全部应用功能,而传统单体架构应用此时也就可以退出历史舞台了。

- END -

37
0