微服务(2)- 微服务拆分粒度

最后更新:2020-03-02

微服务到底拆分到一个多大的粒度,更多时候是需要在粒度与团队之间找到一个平衡点,微服务越小,微服务独立性带来的好处就越多。但是管理大量微服务也会越复杂。基本上拆分需要遵循以下几个原则:

  • 单一职责原则:即”把因相同原因而变化的东西聚合到一起,把因不同原因而变化的东西分离开来”。通过这个原则确定微服务边界。
  • 团队自主原则:团队越大,沟通与协助成本就会越高,我们在实践中一个团队不会超过 8 人,团队内全栈,是一个全功能团队。
  • 先分数据库、后分服务:数据模型能否彻底分开,决定了微服务的边界功能是否彻底划清,实践中我们先讨论数据模型边界,数据模型的边界映射了业务的边界,进而从底向上完成服务拆分。

1. 两个披萨原则

两个披萨原则 (The two pizza prin­ci­ple) 最早是由亚马逊 CEO 贝索斯提出的,他认为如果两个披萨不足以喂饱一个项目团队,那么这个团队可能就显得太大了。也就是团队的人数不应该多到让两个披萨不够吃更多的沟通并不能解决沟通上的问题

因为人数过多的项目会议将不利于决策的形成,而让一个小团队在一起做项目、开会讨论,则更有利于达成共识,并能够有效促进企业内部的创新。

如果两个披萨不足以喂饱一个项目团队,那么这个团队可能就显得太大了。人的大脑无法处理太多人的意见,人多的结果往往导致人云亦云,无法凸显个人的独特想法。参与的人数越多,会议的效率就越低下。大多数与会者最终将会与别人达成一致意见,而不是发表自己独特的见解或想法。因为由于团队太大,成员之间无法深入沟通,结果导致扯皮推诿,最终让项目陷入停顿状态或彻底失败。

”三个火枪手”原则,即一个微服务三个人负责开发。当我们在实施微服务架构时,根据团队规模来划分微服务数量。如果业务规继续发展,团队规模扩大,我们再将已有的微服务进行拆分。例如,团队最初有 6 个人,那么可以划分为 2 个微服务,随着业务的发展,业务功能越来越多,逻辑越来越复杂,团队扩展到 12 个人,那么我们可以将已有的 2 个微服务进行拆分,变成 4 个微服务。

为什么是 3 个人,不是 4 个,也不是 2 个呢?

首先,从系统规模来讲,3 个人负责开发一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能够进行分工的粒度;如果是 2 个人开发一个系统,系统的复杂度不够,开发人员可能觉得无法体现自己的技术实力;如果是 4 个甚至更多人开发一个系统,系统复杂度又会无法让开发人员对系统的细节都了解很深。

其次,从团队管理来说,3 个人可以形成一个稳定的备份,即使 1 个人休假或者调配到其他系统,剩余 2 个人还可以支撑;如果是 2 个人,抽调 1 个后剩余的 1 个人压力很大;如果是 1 个人,这就是单点了,团队没有备份,某些情况下是很危险的,假如这个人休假了,系统出问题了怎么办?

最后,从技术提升的角度来讲,3 个人的技术小组既能够形成有效的讨论,又能够快速达成一致意见;如果是 2 个人,可能会出现互相坚持自己的意见,或者 2 个人经验都不足导致设计缺陷;如果是 1 个人,由于没有人跟他进行技术讨论,很可能陷入思维盲区导致重大问题;如果是 4 个人或者更多,可能有的参与的人员并没有认真参与,只是完成任务而已。

“三个火枪手”的原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均 1 个人维护 1 个微服务甚至几个微服务都可以。当然考虑到人员备份问题,每个微服务最好都安排 2 个人维护,每个人都可以维护多个微服务。

2. 拆分方法

2.1. 基于业务逻辑拆分

这是最常见的一种拆分方式,将系统中的业务模块按照职责范围识别出来,每个单独的业务模块拆分为一个独立的服务。

于业务逻辑拆分虽然看起来很直观,但在实践过程中最常见的一个问题就是团队成员对于“职责范围”的理解差异很大,经常会出现争论,难以达成一致意见。例如:假设我们做一个电商系统,第一种方式是将服务划分为“商品”“交易”“用户”3 个服务,第二种方式是划分为“商品”“订单”“支付”“发货”“买家”“卖家”6 个服务,哪种方式更合理,是不是划分越细越正确?

导致这种困惑的主要根因在于从业务的角度来拆分的话,规模粗和规模细都没有问题,因为拆分基础都是业务逻辑,要判断拆分粒度,不能从业务逻辑角度,而要根据前面介绍的“三个火枪手”的原则,计算一下大概的服务数量范围,然后再确定合适的“职责范围”,否则就可能出现划分过粗或者过细的情况,而且大部分情况下会出现过细的情况。

例如:如果团队规模是 10 个人支撑业务,按照“三个火枪手”规则计算,大约需要划分为 4 个服务,那么“登录、注册、用户信息管理”都可以划到“用户服务”职责范围内;如果团队规模是 100 人支撑业务,服务数量可以达到 40 个,那么“用户登录“就是一个服务了;如果团队规模达到 1000 人支撑业务,那“用户连接管理”可能就是一个独立的服务了。

2.2. 基于可扩展拆分

将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务。稳定的服务粒度可以粗一些,即使逻辑上没有强关联的服务,也可以放在同一个子系统中,例如将“日志服务”和“升级服务”放在同一个子系统中;不稳定的服务粒度可以细一些,但也不要太细,始终记住要控制服务的总数量。

这样拆分主要是为了提升项目快速迭代的效率,避免在开发的时候,不小心影响了已有的成熟功能导致线上问题。

2.3. 基于可靠性拆分

将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。具体拆分的时候,核心服务可以是一个也可以是多个,只要最终的服务数量满足“三个火枪手”的原则就可以。

这样拆分带来下面几个好处:

  • 避免非核心服务故障影响

核心服务例如,日志上报一般都属于非核心服务,但是在某些场景下可能有大量的日志上报,如果系统没有拆分,那么日志上报可能导致核心服务故障;拆分后即使日志上报有问题,也不会影响核心服务。

  • 核心服务高可用方案可以更简单

核心服务的功能逻辑更加简单,存储的数据可能更少,用到的组件也会更少,设计高可用方案大部分情况下要比不拆分简单很多。

  • 能够降低高可用成本

将核心服务拆分出来后,核心服务占用的机器、带宽等资源比不拆分要少很多。因此,只针对核心服务做高可用方案,机器、带宽等成本比不拆分要节省较多。

2.4. 基于性能拆分

基于性能拆分和基于可靠性拆分类似,将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。常见的拆分方式和具体的性能瓶颈有关,可以拆分 Web 服务、数据库、缓存等。例如电商的抢购,性能压力最大的是入口的排队功能,可以将排队功能独立为一个服务。

以上几种拆分方式不是多选一,而是可以根据实际情况自由排列组合,例如可以基于可靠性拆分出服务 A,基于性能拆分出服务 B,基于可扩展拆分出 C/D/F 三个服务,加上原有的服务 X,最后总共拆分出 6 个服务(A/B/C/D/F/X)。

我的实践经验是, 如果程序的一部分逻辑有以下一个或多个行为, 就可以考虑拆分了:

  • 只被其他部分调用, 即处于调用图的底部的逻辑 (通常可能是工具类或存储类)
  • 被很多其他部分调用
  • 调用很多其他部分
  • 业务需求变化非常频繁的部分
  • 使用者不同的部分
  • 请求耗时长的部分
  • 代码量大显著大于其他部分

3. 几种拆分实践

来自沈大在架构师之路的文章

3.1. 统一服务层

最开始,也是最简单的,仅是抽象出一个服务层,所有的服务都是在一起的,全局只有一个服务的概念,这一服务满足上游的所有调用,并对下游进行操作

3 .2. 子业务服务

统一的服务层不利于扩展部署和维护,按照业务的垂直拆分,进行服务划分,一个业务一个服务,下图就是表现了用户服务、朋友服务、群组服务、消息服务

上游的业务可能会调用多个服务,形成了网状关系,复杂性增大,引入网关进行统一转发可以解决这个问题

调用方通过服务号访问网关,网关通过服务号对调用进行分发

3.3. 一个数据库一个服务

一个服务可能会对应多个数据库,如下图的群组服务,就是群组信息、群组成员、群组消息三个库,但由一个统一的群组服务进行访问

一个数据库一个服务就是划分为三种服务:

3.4. 一个接口一个服务

除一个数据库一个服务外,还有更细的粒度,就是按照接口来划分服务,一个接口一个服务:

群组服务包含,更新、添加、获取等接口,按照这些接口再划分为不同的服务

这一粒度太细了,细到依赖于特定的编程语言,如进程轻量级的Go

3.4. 常用的最佳实践

越细的粒度服务化通常会有以下的优点:

  • 易于扩容缩容、独立部署
  • 耦合度小,容错性好

缺点也是显而易见的,那就是复杂:

  • 系统复杂、依赖关系复杂
  • 运维也更复杂
  • 配套设施也更为复杂

真正投入生产实践的,是一个权衡的过程,通常,基于业务作为微服务的划分粒度是最佳实践

分层架构设计,有一条准则:站点层、服务层要做到无数据无状态,这样才能任意的加节点水平扩展,数据和状态尽量存储到后端的数据存储服务,例如数据库服务或者缓存服务。

  • 让上游更高效的获取与处理数据,复用
  • 让下游能屏蔽数据的获取细节,封装

一个合理的分布式系统,系统之间的依赖应该是非常清晰地。依赖,在软件开发中指的是一个应用或者组件需要另外一个组件提供必要的功能才能正常工作。因此被依赖的组件是不知道依赖它的应用的,换句话说,被调用者不需要知道调用方的信息,否则这不是一个合理的依赖

在微服务设计时,如果 domain service 需要通过一个 from 参数,根据不同的渠道做出不同的行为,这对系统的拓展是致命的。例如,用户服务对于访问他的来源不应该知晓;用户服务应该对订单、商品、物流等访问者提供无差别的服务。

因此,微服务的依赖关系可以总结为:上游系统不需要知道下游系统信息,否则请重新审视系统架构。

参考资料

http://mp.weixin.qq.com/s/yRu4aD9e3CXNPSSwxna9zw

《从0开始学架构》

Edgar

Edgar
一个略懂Java的小菜比