微服务拆分之道

头图.png

做者| 修冶

背 景

​ 微服务在最近几年大行其道,不少公司的研发人员都在考虑微服务架构,同时,随着 Docker 容器技术和自动化运维等相关技术发展,微服务变得更容易管理,这给了微服务架构良好的发展机会。 ​ 在作微服务的路上,拆分服务是个很热的话题。咱们应该按照什么原则将现有的业务进行拆分?是否拆分得越细就越好?接下来一块儿谈谈服务拆分的策略和坚持的原则。 ​数据库

拆分目的是什么?

​ 在介绍如何拆分以前,咱们须要了解下拆分的目的是什么,这样才不会在后续的拆分过程当中忘了最初的目的。 ​ 拆分的本质是为了将复杂的问题简单化,那么咱们在单体架构阶段遇到了哪些复杂性问题呢?首先来回想下当初为何选用了单体架构,在电商项目刚启动的时候,咱们只但愿能尽快地将项目搭建起来,方便将产品更早的投放市场进行快速验证。在开发初期,这种架构确实给开发和运维带来了很大的便捷,主要体如今: ​安全

  • 开发简单直接,代码和项目集中式管理。
  • 排查问题时只须要排查这个应用就能够了,更有针对性。
  • 只须要维护一个工程,节省维护系统运行的人力成本。

可是随着功能愈来愈多,开发团队的规模愈来愈大,单体架构的缺陷慢慢体现出来,主要有如下几个方面: ​ 在技术层面,数据库的链接数成为应用服务器扩容的瓶颈,由于链接 MySQL 的客户端数量是有限制的。 ​ 除此以外,单体架构增长了研发的成本抑制了研发效率的提高。好比公司的垂直电商系统团队会被按业务线拆分为不一样的组。当如此多的小团队共同维护一套代码和一个系统时,在配合的过程当中就会出现问题。不一样的团队之间沟通少,假如一个团队须要一个发送短信的功能,那么有的研发同窗会认为最快的方式不是询问其余团队是否有现成的,而是本身写一套,可是这种想法是不合适的,会形成功能服务的重复开发。因为代码部署在一块儿,每一个人都向同一个代码库提交代码,代码冲突没法避免;同时功能之间耦合严重,可能你只是更改了很小的逻辑却致使其它功能不可用,从而在测试时须要对总体功能回归,延长了交付时间。模块之间互相依赖,一个小团队中的成员犯了一个错误,就可能会影响到其它团队维护的服务,对于总体系统稳定性影响很大。 ​ 最后,单体架构对于系统的运维也会有很大的影响。想象一下,在项目初期你的代码可能只有几千行,构建一次只须要一分钟,那么你能够很敏捷灵活地频繁上线变动修复问题。可是当你的系统扩充到几十万行甚至上百万行代码的时候,一次构建的过程包括编译、单元测试、打包和上传到正式环境,花费的时间可能达到十几分钟,而且任何小的修改,都须要构建整个项目,上线变动的过程很是不灵活。 ​ 而这些问题均可以经过微服务化拆分来解决。 ​ 为了方便你更好的理解这块,在此附上一份表格(内容来源:《持续演进的 Cloud Native:云原生架构下微服务最佳》一书),能够更直观地帮助你认识拆分的目的。 ​ 1.png服务器

拆分时机应该如何决策?

​ 产品初期,应该以单体架构优先。由于面对一个新的领域,对业务的理解很难在开始阶段就比较清晰,每每是通过一段时间以后,才能逐步稳定,若是拆分过早,致使边界拆分不合理或者拆的过细,反而会影响生产力。不少时候,从一个已有的单体架构中逐步划分服务,要比一开始就构建微服务简单得多。同时公司的产品并无被市场验证过,有可能会失败,因此这个投入的风险也会比较高。 ​ 另外,在资源受限的状况下,采用微服务架构不少优点没法体现,性能上的劣势反而会比较明显。以下图所示。当业务复杂度达到必定程度后,微服务架构消耗的成本才会体现优点,并非全部的场景都适合采用微服务架构,服务的划分应逐步进行,持续演进。产品初期,业务复杂度不高的时候,应该尽可能采用单体架构。 ​ 2.png网络

随着公司的商业模式逐渐获得验证,且产品得到了市场的承认,为了能加快产品的迭代效率快速占领市场,公司开始引进更多的开发同窗,这时系统的复杂度会变得愈来愈高,就出现单体应用和团队规模之间出现矛盾,研发效率不升反降。上图中的交叉点代表,业务已经达到了必定的复杂度,单体应用已经没法知足业务增加的需求,研发效率开始降低,而这时就是须要考虑进行服务拆分的时机点。这个点须要架构师去权衡。笔者所在的公司,是当团队规模达到百人的时候,才考虑进行服务化。 ​ 当咱们清楚了何时进行拆分,就能够直接落地了吗?不是的,微服务拆分的落地还要提早准备好配套的基础设施,如服务描述、注册中心、服务框架、服务监控、服务追踪、服务治理等几大基本组件,以上每一个组件缺一不可,每一个组件展开又包括不少技术门槛,好比,容器技术、持续部署、DevOps 等相关概念,以及人才的储备和观念的变化。微服务不只仅是技术的升级,更是开发方式、组织架构、开发观念的转变。闭包

至此,什么时候进行微服务的拆分,总体总结以下: ​架构

  1. 业务规模:业务模式获得市场的验证,须要进一步加快脚步快速占领市场,这时业务的规模变得愈来愈大,按产品生命周期来划分(导入期、成长期、成熟期、衰退期)这时通常在成长期阶段。若是是导入期,尽可能采用单体架构。
  2. 团队规模:通常是团队达到百人的时候。
  3. 技术储备:领域驱动设计、注册中心、配置中心、日志系统、持续交付、监控系统、分布式定时任务、CAP 理论、分布式调用链、API 网关等等。
  4. 人才储备:精通微服务落地经验的架构师及相应开发同窗。
  5. 研发效率:研发效率大幅降低,具体问题参加上面拆分目的里提到的。

拆分时应该坚守哪些指导原则?

1. 单一服务内部功能高内聚低耦合并发

也就是说每一个服务只完成本身职责内的任务,对于不是本身职责的功能交给其它服务来完成。 ​ 2. 闭包原则( CCP ) ​ 微服务的闭包原则就是当咱们须要改变一个微服务的时候,全部依赖都在这个微服务的组件内,不须要修改其余微服务。 ​ 3. 服务自治、接口隔离原则 ​ 尽可能消除对其余服务的强依赖,这样能够下降沟通成本,提高服务稳定性。服务经过标准的接口隔离,隐藏内部实现细节。这使得服务能够独立开发、测试、部署、运行,以服务为单位持续交付。 ​ 4. 持续演进原则 ​ 在服务拆分的初期,你其实很难肯定服务究竟要拆成什么样。从微服务这几个字来看,服务的粒度貌似应该足够小,可是服务多了也会带来问题,服务数量快速增加会带来架构复杂度急剧升高,开发、测试、运维等环节很难快速适应,会致使故障率大幅增长,可用性下降,非必要状况,应逐步划分,持续演进,避免服务数量的爆炸性增加,这等同于灰度发布的效果,先拿出几个不过重要的功能拆分出一个服务作试验,若是出现故障,则能够减小故障的影响范围。 ​ 5. 拆分的过程尽可能避免影响产品的平常功能迭代 ​ 也就是说要一边作产品功能迭代,一边完成服务化拆分。好比优先剥离比较独立的边界服务( 如短信服务等 ),从非核心的服务出发减小拆分对现有业务的影响,也给团队一个练习、试错的机会。同时当两个服务存在依赖关系时优先拆分被依赖的服务。 ​ 6. 服务接口的定义要具有可扩展性 ​ 服务拆分以后,因为服务是以独立进程的方式部署,因此服务之间通讯就再也不是进程内部的方法调用而是跨进程的网络通讯了。在这种通讯模型下服务接口的定义要具有可扩展性,不然在服务变动时会形成意想不到的错误。好比微服务的接口由于升级把以前的三个参数改为了四个,上线后致使调用方大量报错,推荐作法服务接口的参数类型最好是封装类,这样若是增长参数就没必要变动接口的签名,而只须要在类中添加字段就能够了 ​ 7. 避免环形依赖与双向依赖 ​ 尽可能不要有服务之间的环形依赖或双向依赖,缘由是存在这种状况说明咱们的功能边界没有化分清楚或者有通用的功能没有下沉下来。 ​ 3.png框架

8. 阶段性合并 ​ 随着你对业务领域理解的逐渐深刻或者业务自己逻辑发生了比较大的变化,亦或者以前的拆分没有考虑的很清楚,致使拆分后的服务边界变得愈来愈混乱,这时就要从新梳理领域边界,不断纠正拆分的合理性。 ​运维

拆分的粒度是否是越细越好?

​ 目前不少传统的单体应用再向微服务架构进行升级改造,若是拆分粒度太细会增长运维复杂度,粒度过大又起不到效果,那么改造过程当中如何平衡拆分粒度呢?分布式

4.png

1. 弓箭原理 ​ 平衡拆分粒度能够从两方面进行权衡,一是业务发展的复杂度,二是团队规模的人数。如上图,它就像弓箭同样,只有当业务复杂度和团队人数足够大的时候,射出的服务拆分粒度这把剑才会飞的更远,发挥出最大的威力。 ​ 好比说电商的商品服务,当咱们把商品从大的单体里拆分出来的时候,就商品服务自己来说,逻辑并无足够复杂到 2 ~ 3 我的无法维护的地步,这时咱们没有必要继续将商品服务拆的更细,可是随着业务的发展,商品的业务逻辑变的愈来愈复杂,可能同时服务公司的多个平台,此时你会发现商品服务自己面临的问题跟单体架构阶段面临的问题基本同样,这个阶段就须要咱们将商品拆成更细粒度的服务,好比,库存服务、价格服务、类目服务、商品基础信息服务等等。

虽然业务复杂度已经知足了,若是公司此时没有足够的人力(招聘不及时或员工异动比较多),服务最好也不要拆分,拆分会由于人力的不足致使更多的问题,如研发效率大幅降低(一个开发负责与其不匹配数量的服务)。这里引伸另一个问题,一个微服务究竟须要几个开发维护是比较理性的?我引用下李云华老师在"从零开始学架构“ 中的一段经典论述,能够解决此问题。 ​ 2. 三个火枪手原则 ​ 为何说是三我的分配一个服务是比较理性的?而不是 4 个,也不是 2 个呢? ​ 首先,从系统规模来说,3 我的负责开发一个系统,系统的复杂度恰好达到每一个人都能全面理解整个系统,又可以进行分工的粒度;若是是 2 我的开发一个系统,系统的复杂度不够,开发人员可能以为没法体现本身的技术实力;若是是 4 个甚至更多人开发一个系统,系统复杂度又会没法让开发人员对系统的细节都了解很深。 ​ 其次,从团队管理来讲,3 我的能够造成一个稳定的备份,即便 1 我的休假或者调配到其余系统,剩余 2 我的还能够支撑;若是是 2 我的,抽调 1 个后剩余的 1 我的压力很大;若是是 1 我的,这就是单点了,团队没有备份,某些状况下是很危险的,假如这我的休假了,系统出问题了怎么办? ​ 最后,从技术提高的角度来说,3 我的的技术小组既可以造成有效的讨论,又可以快速达成一致意见;若是是 2 我的,可能会出现互相坚持本身的意见,或者 2 我的经验都不足致使设计缺陷;若是是 1 我的,因为没有人跟他进行技术讨论,极可能陷入思惟盲区致使重大问题;若是是 4 我的或者更多,可能有的参与的人员并无认真参与,只是完成任务而已。 ​ “三个火枪手”的原则主要应用于微服务设计和开发阶段,若是微服务通过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均 1 我的维护 1 个微服务甚至几个微服务均可以。固然考虑到人员备份问题,每一个微服务最好都安排 2 我的维护,每一个人均可以维护多个微服务。 ​ 综上所诉,拆分粒度不是越细越好,粒度须要符合弓箭原理及三个火枪手原则。 ​

拆分策略有哪些?

​ 拆分策略能够按功能和非功能维度进行考虑,功能维度主要是划分清楚业务的边界,非功能维度主要考虑六点包括扩展性、复用性、高性能、高可用、安全性、异构性。接下来详细介绍下。

功能维度 ​ 功能维度主要是划分清楚业务边界,采用的主要设计方法能够利用 DDD(关于 DDD 的理论知识能够参考网上其它资料),DDD 的战略设计会创建领域模型,能够经过领域模型指导微服务的拆分,主要分四步进行: ​

  • 第一步,找出领域实体和值对象等领域对象。
  • 第二步,找出聚合根,根据实体、值对象与聚合根的依赖关系,创建聚合。
  • 第三步,根据业务及语义边界等因素,定义限界上下文。
  • 第四步,每个限界上下文能够拆分为一个对应的微服务,但也要考虑一些非功能因素。

以电商的场景为例,交易链路划分的限界上下文以下图左半部分,根据一个限界上下文能够设计一个微服务,拆解出来的微服务以下图右侧部分。 ​ 5.png

2. 非功能维度 ​ 当咱们按照功能维度进行拆分后,并非就万事大吉了,大部分场景下,咱们还须要加入其它维度进一步拆分,才能最终解决单体架构带来的问题。 ​

  • 扩展性:区分系统中变与不变的部分,不变的部分通常是成熟的、通用的服务功能,变的部分通常是改动比较多、知足业务迭代扩展性须要的功能,咱们能够将不变的部分拆分出来,做为共用的服务,将变的部分独立出来知足个性化扩展须要。同时根据二八原则,系统中常常变更的部分大约只占 20%,而剩下的 80 % 基本不变或极少变化,这样的拆分也解决了发布频率过多而影响成熟服务稳定性的问题。

  • 复用性:不一样的业务里或服务里常常会出现重复的功能,好比每一个服务都有鉴权、限流、安全及日志监控等功能,能够将这些经过的功能拆分出来造成独立的服务,也就是微服务里面的 API 网关。在如,对于滴滴业务,有快车和顺风车业务,其中都涉及到了订单支付的功能,那么就能够将订单支付独立出来,做为通用服务服务好上层业务。以下图:

6.png

  • 高性能:将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其它服务。常见的拆分方式和具体的性能瓶颈有关,例如电商的抢购,性能压力最大的是入口的排队功能,能够将排队功能独立为一个服务。同时,咱们也能够基于读写分离来拆分,好比电商的商品信息,在 App 端主要是商详有大量的读取操做,可是写入端商家中心访问量确不多。所以能够对流量较大或较为核心的服务作读写分离,拆分为两个服务发布,一个负责读,另一个负责写。还有数据一致性是另外一个基于性能维度拆分须要考虑的点,对于强一致的数据,属于强耦合,尽可能放在同一个服务中(可是有时会由于各类缘由须要进行拆分,那就须要有响应的机制进行保证),弱一致性一般能够拆分为不一样的服务。

7.png

  • 高可用:将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,而后重点保证核心服务的高可用。具体拆分的时候,核心服务能够是一个也能够是多个,只要最终的服务数量知足“三个火枪手”的原则就能够。好比针对商家服务,能够拆分一个核心服务一个非核心服务,核心服务供交易服务访问,非核心提供给商家中心访问 。

  • 安全性:不一样的服务可能对信息安全有不一样的要求,所以把须要高度安全的服务拆分出来,进行区别部署,好比设置特定的 DMZ 区域对服务进行分区部署,能够更有针对性地知足信息安全的要求,也能够下降对防火墙等安全设备吞吐量、并发性等方面的要求,下降成本,提升效率。

  • 异构性:对于对开发语言种类有要求的业务场景,能够用不一样的语言将其功能独立出来实现一个独立服务。

以上几种拆分方式不是多选一,而是能够根据实际状况自由排列组合。同时拆分不只仅是架构上的调整,也意味着要在组织结构上作出相应的适应性优化,以确保拆分后的服务由相对独立的团队负责维护。 ​

服务都拆了为何还要合并?

​ 古希腊哲学家赫拉克利特曾经说过:“人不能两次踏进同一条河流。”随着时间的流逝,任何事物的状态都会发生变化。线上系统一样如此,即便一个系统在不一样时刻的情况也毫不会如出一辙。如今拆分出来的服务粒度也许合适,但谁能保证这个粒度可以一直正确呢。

服务都拆了为何还要合,就是要不断适应新的业务发展阶段,笔者这里作个类比看你们是否清晰,拆至关于咱们开发代码,合至关于重构代码,为何要重构呢,相信你确定知道。微服务的合也是同样的道理,随着咱们对应用程序领域的了解愈来愈深,它们可能会随着时间的推移而变化。例如,你可能会发现因为过多的进程间通讯而致使特定的分解效率低下,致使你必须把一些服务组合在一块儿。 ​ 同时由于人员和服务数量的不匹配,致使的维护成本增长,也是致使服务合并的一个重要缘由。例如,今年疫情的影响致使不少企业开始大量裁人,人员流失可是服务的数量确没有变,形成服务数量和人员的不平衡,一个开发同窗同时要维护至少 5 个服务的开发,效率大幅降低。 ​ 那么若是微服务数量过多和资源不匹配,则能够考虑合并多个微服务到服务包,部署到一台服务器,这样能够节省服务运行时的基础资源消耗也下降了维护成本。须要注意的是,虽然服务包是运行在一个进程中,可是服务包内的服务依然要知足微服务定义,以便在将来某一天要从新拆开的时候能够很快就分离。服务合并到服务包示意图以下: ​ 8.png

拆分过程当中要注意的风险

1. 不打无准备之仗 ​ 开发团队是否具有足够的经验,可否驾驭微服务的技术栈,多是第一个须要考虑的点。这里并非要求团队必须具有完善的经验才能启动服务拆分,若是团队中有这方面的专家当然是最好的。若是没有,那可能就须要事先进行充分的技术论证和预演,至少不打无准备之仗。避免哪一个简单就先拆哪一个,哪一个新业务要上了,先起一个服务再说。不然可能在一些分布式常见的问题上会踩坑,好比服务器资源不够、运维困难、服务之间调用混乱、调用重试、超时机制、分布式事务等等。 ​ 2. 不断纠正 ​ 咱们须要认可咱们的认知是有限的,只能基于目前的业务状态和有限的对将来的预测来制定出一个相对合适的拆分方案,而不是所谓的最优方案,任何方案都只能保证在当下提供了相对合适的粒度和划分原则,要时刻作好在将来的末一个时刻会变得不和时宜、须要再次调整的准备。所以随着业务的演进,须要咱们从新审视服务的划分是否合理,如服务拆的太细,致使人员效率反而降低,故障的几率也大大增长,则须要从新划分好领域边界。

3. 要作行动派,而不是理论派

在具体怎么拆分上,也不要太纠结因而否合适,不动手怎么知道合不合适呢?若是拆了以后发现真的不合适,在从新调整就行了。你可能会说,从新调整成本比较高。但实际上这个问题的本质是有没有针对服务化架构搭建起一套完成的能力体系,好比服务治理平台、数据迁移工具、数据双写等等,若是有的话,从新调整的成本是不会过高的。