微服务设计与治理16条常用原则
架构师的常用能力——微服务设计与治理
- 如何设计合理的微服务架构?
- 如何保持微服务健康运行?
这是我们对微服务进行架构设计过程中非常关注的两个问题。
本文对微服务的生命周期定义了七个阶段,如下图所示。
围绕这七个阶段总结了16条常用原则。
微服务规划
原则1: 按照业务能力(business capabilities)来规划或拆微服务。
康威定律:Conway’s law: Organizations which design systems[…] are constrained to produce designs which are copies of the communication structures of these organizations.
(设计系统的组织,其产生的设计和架构等价于组织间的沟通结构。)
组织的沟通和系统的设计之间紧密相连,特别是复杂系统,解决好人与人的沟通才能有一个更好的系统设计。
设计系统
墨菲定律是一种心理学效应,1949年由美国的一名工程师爱德华·墨菲(Edward A. Murphy)提出的,亦称墨菲法则、墨菲定理等。
原文为:如果有两种或两种以上的方式去做某件事情,而其中一种选择方式将导致灾难,则必定有人会做出这种选择。根本内容是:如果事情有变坏的可能,不管这种可能性有多小,它总会发生。
规划系统
康威定律是马尔文康威1967提出的:“设计系统的架构受制于产生这些设计的组织的沟通结构。”通俗的来讲:产品必然是其(人员)组织沟通结构的缩影。
康威定律可总结为四个定律:
第一定律组织沟通方式会通过系统设计表达出来。
第二定律时间再多一件事情也不可能做的完美,但总有时间做完一件事情。
第三定律线型系统和线型组织架构间有潜在的异质同态特性。
第四定律大的系统组织总是比小系统更倾向于分解。
《人月神话》中总结出了随着人员的增加沟通成本呈指数增长的规律:沟通成本 = n(n-1)/2。举例说明:
- 5人项目组,需要沟通的渠道是 5*(5–1)/2 = 10
- 15人项目组,需要沟通的渠道是15*(15–1)/2 = 105
- 50人项目组,需要沟通的渠道是50*(50–1)/2 = 1,225
系统越复杂,人手越多,沟通成本也呈指数增长。因此,分而治之便是大多数公司选择的解决方案。分不同的层级,分不同的小团队,让团队内部完成自治理。
原则2: 按照领域驱动设计(Domain-Driven Design,DDD)来规划或拆解微服务。
领域驱动设计是微服务领域的热门话题,本文不展开说明,仅说明几点重要事项: - 基本过程:抽象业务、分析流程、识别边界、建立模型、映射到服务和代码
- 避免过度耦合、存在贫血领域对象等情况
- 划分界限上下文,厘清上下文之间的映射关系,比如合作关系、共享内核、客户方-供应方开发、防腐层、开放主机服务等等。
- 细化上下文对象,区分实体、值对象、聚合根、领域服务、领域事件
原则2与原则1的区别在于,原则1关注组织架构领域,原则2更偏向软件工程设计领域。
微服务设计
原则3:微服务的设计应该遵循「单一职责」原则
所谓单一职责原则,就是对一个服务而言,它的功能要单一,只做与它相关的事情。
在微服务的设计过程中要按职责进行设计,彼此保持正交,互不干涉。
什么样的单一领域对象的单一职责微服务才是有价值的?就是不断有业务变化,能够维持业务持久性,有业务生命力的领域对象。举例来说: - 与别的功能点相比,调用频率非常高
- 或者其数据量存量大,数据增速快,TB级甚至是PB级的。
那么就很有价值独立为一个微服务,实现独立演进、个性化的弹性伸缩。
所以,我们在进行微服务设计时,要能够分析、预测出需求变化的点在哪里?高并发的点在哪些?数据增长的位置在哪里?与DDD分析相结合,找出最有价值的那个单一职责,进行合理、适度的领域、子领域、有界上下文分解,才能更好的应对复杂的业务、不断变化的业务。
原则4: 微服务的设计应该遵循「高内聚」原则
过度追求「单一职责」,或者拆分微服务过细,往往会带来不良后果。微服务的设计并不是越细越好,过度拆分会导致调用性能变差、数据一致性难以保障、系统可用性降低等问题。
因此,「高内聚」原则要求: - 完全独立。微服务粒度的下界是它至少应满足独立,能够独立发布、独立部署、独立运行与独立测试
- 足够内聚。强相关的功能与数据在同一个服务中处理
- 足够完备。一个服务包含至少一项业务实体与对应的完整操作
原则5:微服务的设计应该遵循「低耦合」原则 - 避免数据过度暴露
- 避免数据库共享
- 最小化同步调用,如有必要,引入事件驱动进行异步调用
微服务实现
原则6:服务无状态。
什么是「状态」?如果一个数据需要被多个服务共享,才能完成一笔交易,那么这个数据被称为状态。
依赖这个「状态」数据的服务被称为有状态服务,反之称为无状态服务。
「无状态」原则并不是说在微服务架构里就不允许存在状态,而是要把有状态的业务服务改变为无状态的计算类服务,那么状态数据也就相应的迁移到对应的“有状态数据服务”中。
场景说明:例如我们以前在本地内存中建立的数据缓存、Session缓存,到现在的微服务架构中就应该把这些数据迁移到分布式缓存中存储,让业务服务变成一个无状态的计算节点。迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。
只有服务无状态,才能实现快速弹性扩缩容,应对流量峰谷。
原则7:服务高可用。
接入高可用中间件(如sentinal),实现限流、熔断、降级,增强可用性
原则8:服务可观测。
除了默认系统监控外,微服务需要梳理并定义必要的「业务监控指标」。
原则9:服务配置可管理。
微服务相关配置需要统一接入配置中心进行管理、控制。
微服务调用
原则10:避免「分布式大单体」
只做单向调用,避免循环调用。
多个服务循环依赖调用形成集中式“分布式大单体”,违背微服务的原则。
原则11:异步解耦。
按需接入消息队列,实现「依赖解耦」、「流量削峰」
-
串行同步调用异步化,提高响应能力和响应速度
-
应对突发流量,实现流量削峰与流量控制
-
解耦核心业务逻辑不必要的依赖
-
业务设计中的最终一致性
原则12:引入BFF层,降低客户端与后端微服务之间的耦合
尽量设计BFF层,把前端的特殊需求交给BFF层,使后端服务逻辑具有高内聚、高复用性的精简核心逻辑。
微服务发布
原则13:服务发布遵循安全发布三板斧
保证「可灰度」、「可监控」、「可回滚」。
微服务治理
原则14:正视「架构腐化」,遵循「持续演进」原则
「架构腐化」的常见场景: -
多人维护一个微服务,出现「频繁代码冲突」,影响快速迭代,那么这个微服务就需要拆分了。
-
当你修改了一个边角的小功能,但是你不敢马上上线,因为你依赖的其他模块才开发了一半,出现大量「功能耦合」,那么这个微服务就需要拆分了。
-
当你发现微服务A内聚合a的功能变成了海量高频业务。这时聚合a就会拖累整个微服务A,并且因为聚合a面临性能瓶颈,在微服务A进行弹性扩缩时,也会造成资源浪费。这时,我们就可以将聚合a从微服务A中整体拆分,独立为一个新微服务B。在资源配置方面也可以更加有针对性的投入到微服务B,可以随时满足高频访问的性能要求了。
-
当你发现在领域建模时错误地将聚合d放到了微服务C里,或者随着业务发展聚合d更适合放在微服务D里。由于领域模型的不合适,可能会导致微服务之间出现频繁调用,进而导致微服务之间出现「紧耦合关系」。这时,我们就可以对领域模型做出调整,将聚合d从微服务C整体迁移到微服务D里。
原则15:参考「AKF扩展立方」模型,服务除了「水平扩容」外,还可以考虑「功能拆分」或者 「数据分区」
-
X轴:服务和数据的水平扩容。
-
Y轴:功能/业务拆分
-
Z轴:沿客户边界的服务和数据分区
「水平扩容」比较容易理解,直白点说就是加机器。根据AKF模型,除了加机器外,我们还可以考虑「功能拆分」或者 「数据分区」。
「功能拆分」相对复杂,一般包括几种模式: -
微服务拆分。根据具体业务模型、领域模型拆分更细粒度的微服务。
-
业务隔离拆分。利用消息队列,将在线业务(OLTP)和耗费大量资源的计算任务拆分隔离。
-
核心与非核心隔离。对于一个微服务,可以将SKA客户与普通客户进行隔离,SKA客户使用独立的集群资源,提高稳定性。
「数据分区」往往指的是数据库层面。需要引入数据库中间件,像 sharding-jdbc、mycat 等,在数据层面需要配置相应的分片逻辑。正确的拆分对提高系统的容量有很大的帮助,失败的拆分可能会造成热点集中,得不偿失。常用的分区逻辑包括 按照时间分区、按照用户id取模分区等。
微服务下线
原则16:对于「废弃服务」,需要做好「下线」工作,包括服务下线、存储释放等。
清理无效代码、环境,减少维护成本。同时释放资源,节约成本。
总结
架构师在进行微服务设计和微服务治理时,可以围绕微服务生命周期的七个阶段展开。
本文总结了16条常用原则,希望能提供一些思路和启发。
附:微服务架构面临的挑战
限界上下文
限界上下文概念起源于领域驱动设计 (DDD) 圈子。它的出现促进了优先对象模型的服务方法,定义了服务责任和绑定的数据模型。有边界的上下文澄清、封装并定义了模型的特定责任。每个模型都必须在子域内隐式定义一个上下文,并且每个上下文都定义了边界。换句话说,服务拥有其数据并对其完整性和可变性负责。它支持微服务最重要的特性:独立性和解耦。
动态扩展和缩减
不同微服务上的负载可能在不同类型的实例上。除了自动扩展你的微服务应该自动缩减。降低了微服务的成本,可以动态分配负载。
监控
传统的监控方式与微服务差异性很大,微服务有多个服务组成了单个应用程序支持的相同功能。当应用程序中出现错误时,找到根本原因具有很大的挑战性。
容错
容错是不会降低整个系统的单个服务。当故障发生时,应用程序可以在一定的满意度下运行。如果没有容错能力,系统中的单个故障可能会导致完全崩溃。断路器可以实现容错,它是一种将请求包装到外部服务,并检测它们何时出现故障的模式。微服务需要一定程度上容忍内部和外部故障。
循环依赖
跨不同服务的依赖管理,这个功能非常重要。如果不及时识别和解决,循环依赖可能会产生问题,当依赖关系形成了环,最终导致:在安装 A 软件包之前,必须要安装 A、B、C、D 软件包
DevOps 文化
微服务非常适合 DevOps。它提供更快的交付服务、跨数据的可见性和具有成本效益的数据。它可以将他们对容器化转换的使用从面向服务的架构 (SOA) 扩展到微服务架构 (MSA)。
微服务的其他挑战
- 随着我们添加更多微服务,我们必须确保它们可以一起扩展。更多的粒度意味着更多的组件,增加了系统的复杂性。
- 传统的日志记录是无效的,因为微服务是无状态的、分布式的、独立的。日志记录必须能够跨多个平台关联事件。
- 当更多的服务相互交互时,失败的可能性也会增加。