微服务如何拆分

191 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

产生背景

单体服务与微服务

image.png

单体应用中所有处理请求的逻辑都跑在一个进程中,允许我们根据编程语言的特征将应用拆分成各个类、函数和空间。刚开始业务复杂度低单体应用是可以满足需求的,但随着业务复杂度增大,越来越多的应用被部署到云上,高耦合的单体应用缺陷就被暴露出来:
运维成本:对应用程序的一小部分进行更改,要求整个单体应用必须重建和部署,增加了运维成本。扩展时也需要整个应用程序进行调整,而不是需要更多资源的部分单独进行扩展。
稳定性&敏捷迭代:随着时间的流逝,通常很难保持一个良好的模块化结构,即一个小模块的更改仅仅影响模块本身,对服务敏捷迭代和稳定性影响很大。
研发效率:当单体应用发展到几十个人开发的规模时,修改提交代码也容易出现冲突。

2010年后,出现了微服务架构,这个架构更为松耦合。每一个微服务都能独立完整地运行(所谓的自包含),后端单体的数据库也被微服务这样的架构分散到不同的服务中。除了服务是独立部署和可扩展的事实外,每种服务还提供了一个牢固的模块边界,甚至允许使用不同的编程语言编写不同的服务。他们也可以由不同的团队管理。

而它和传统 SOA 的差别在于,服务间的整合需要一个服务编排或是服务整合的引擎。就好像交响乐中需要有一个指挥来把所有乐器编排和组织在一起。一般来说,这个编排和组织引擎可以是工作流引擎,也可以是网关。当然,还需要辅助于像容器化调度这样的技术方式,如 Kubernetes。微服务的出现使得开发速度变得更快,部署快,隔离性高,系统的扩展度也很好,但是在集成测试、运维和服务管理等方面就比较麻烦了。

image.png

这是网上找到的一篇文章:微服务、容器、云原生、Kubernetes、SOA、Paas平台、Devops 之间相互促进、相互依赖、相互关联,它们之间的关系如下:

image.png

微服务如何拆分

拆分原则

1、高内聚、低耦合-单一职责原则

每个微服务只需关心自己的业务规则,确保职责单一,避免职责交叉;盗图发一张:每个服务只做自己服务相关的事,相关的事情放在一个服务里,服务间不能耦合。

image.png

2、服务自治、接口隔离原则

尽量消除对其他服务的强依赖,这样可以降低沟通成本,提升服务稳定性。服务通过标准的接口隔离,隐藏内部实现细节。这使得服务可以独立开发、测试、部署、运行,以服务为单位持续交付。 所有依赖都在这个微服务的组件内,修改微服务时,不涉及其他微服务。如果做需求总是需要同时更改两个或更多的系统,那就需要考虑是否合并或者微服务拆解不合理。

3、服务接口可扩展性

服务拆分之后,由于服务是以独立进程的方式部署,所以服务之间通信就不再是进程内部的方法调用而是跨进程的网络通信了。所以服务接口设计时,要考虑后续接口的可拓展性,在服务变更时会造成意想不到的错误,比如接口参数的个数、结构、类型在不同序列化方式下兼容性考虑点也不同,接口定义时需要考虑清楚兼容性。

4、避免环形依赖与双向依赖

尽量不要有服务之间的环形依赖或双向依赖,服务升级的的时候会比较头疼,不知道应该先升级哪个,后升级哪个,难以维护。因此我们需要极力避免服务间的这种复杂依赖关系。原因是存在这种情况说明我们的功能边界没有划分清楚或者有通用的功能没有下沉下来(可以拆分为第三方服务)。

image.png

5、持续演进不过度拆分

微服务的过度拆分会造成架构复杂度的急剧升高,开发、测试、运维等环节很难快速适应,将会导致故障率大幅增加,可用性降低,非必要情况,应逐步拆分细化,持续演进,避免微服务数量的瞬间爆炸性增长。同时运维成本也是增加的,如果业务并不复杂却为了拆分而拆分增加过多服务,那就得不偿失。

拆分策略

  1. 功能维度拆分策略

主要是基于业务复杂度拆分。当业务复杂度高时,可以基于领域驱动拆分服务;当业务复杂度低时,可以基于数据驱动拆分服务。

基于数据驱动拆分服务:自下而上的架构设计方法,根据表之间的关系拆分服务。拆分步骤:需求分析、抽象数据结构、划分服务、确定调用关系和业务流程验证

基于领域驱动拆分服务:自上而下的架构设计方法,确定关键业务场景,确定边界上下文。拆分步骤:建立模型,业务分析,寻找聚合,确定调用关系,业务流程验证和持续优化。采用的主要设计方法可以利用 DDD,DDD 的战略设计会建立领域模型,可以通过领域模型指导微服务的拆分,主要分四步进行:

  • 第一步,找出领域实体和值对象等领域对象。
  • 第二步,找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合。
  • 第三步,根据业务及语义边界等因素,定义限界上下文。
  • 第四步,每一个限界上下文可以拆分为一个对应的微服务,但也要考虑一些非功能因素。
  1. 非功能维度拆分策略
  • 扩展性: 区分系统中变与不变的部分,不变的部分一般是成熟的、通用的服务功能,变的部分一般是改动比较多、满足业务迭代扩展性需要的功能,我们可以将不变的部分拆分出来,作为共用的服务,将变的部分独立出来满足个性化扩展需要。同时根据二八原则,系统中经常变动的部分大约只占 20%,而剩下的 80% 基本不变或极少变化,这样的拆分也解决了发布频率过多而影响成熟服务稳定性的问题。
  • 复用性: 将公共服务拆分出来,供其他服务调用。不同的业务里或服务里经常会出现重复的功能,比如每个服务都有鉴权、限流、安全及日志监控等功能,可以将这些通过的功能拆分出来形成独立的服务。实际上很多公司都是抽象很多公共服务的,业务中的公共服务也可参照此中思路进行复用。
  • 高性能: 将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其它服务且方便扩展。常见的拆分方式和具体的性能瓶颈有关,如电商的抢购中入口排队功能。同时,我们也可以基于读写分离来拆分,比如电商的商品信息,在 App 端主要是商详有大量的读取操作,但是写入端商家中心访问量却很少。因此可以对流量较大或较为核心的服务做读写分离,拆分为两个服务发布,一个负责读,另外一个负责写。还有数据一致性是另一个基于性能维度拆分需要考虑的点,对于强一致的数据,属于强耦合,尽量放在同一个服务中(但是有时会因为各种原因需要进行拆分,那就需要有响应的机制进行保证),弱一致性通常可以拆分为不同的服务。
  • 高可用: 将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。具体拆分的时候,核心服务可以是一个也可以是多个。比如针对商家服务,可以拆分一个核心服务一个非核心服务,核心服务供交易服务访问,非核心提供给商家中心访问。
  • 安全性: 不同的服务可能对信息安全有不同的要求,因此把需要高度安全的服务拆分出来,进行区别部署,比如不同国家和地区对于安全合规要求不同,对合规要求严格区域可以进行分区部署,达到合规要求。
  • 异构性: 对于不同开发语言种类有要求的业务场景,可以用不同的语言将其功能独立出来实现一个独立服务。

总结

微服务拆分是跟随业务发展和团队发展而自然演进的选择,不能为了拆而拆,而是需要拆且能拆时再拆,拆了后是否收益更大。拆分时要考虑对现有服务稳定性的影响,拆了后的维护,拆分边界和力度是否合理。随着业务的演进,旧的拆分方式是否还适应新业务的演进,如果不符合了需要重新划分好领域边界。