软件架构场景之—— 微服务的痛点

单体式架构 VS 微服务架构

为了快速理解单体式架构与微服务架构之间的区别,先来看一个新零售系统的例子

比如门店(门店分为自营店和加盟店)想研发一款新零售系统进行商品售卖,它需要包含订单、营销、门店、商品、加盟商、会员等功能模块

在搭建新零售系统架构时,如果我们使用单体式架构进行设计,它的架构图如下所示

软件架构场景之—— 微服务的痛点
新零售系统:单体式架构图

从图中发现,单体式架构将所有模块的代码存放在一个应用中,所有模块的数据存放在一个数据库中。在这种架构模式下,当业务功能增加到一定程度,我们只要稍微有点小改动,就有可能影响整个应用的其他功能

虽然每次不小心把系统弄崩后,我们都会进行复盘总结,后期需要 Code Review、合理设计、仔细评估风险、一起评审方案,但是就算这么做了这种事情还是会发生。因此,随着风险控制流程的复杂化,代码发布的频率也就越来越慢,最终导致系统迭代不了了

面对这种情况,我们必须把各个模块的代码进行拆分,以免相互影响。于是把单体式架构拆分为如下图所示的微服务架构

软件架构场景之—— 微服务的痛点
新零售系统:微服务架构图

在上面的架构图中, 1 个应用被拆分为了 6 个应用,它们分别负责订单、营销、商品、门店、加盟商、会员等相关业务逻辑,且每个模块的数据分别存放在不同数据库中。如果各个应用之间彼此存在依赖关系,我们可以通过接口、消息、共享缓存、数据库同步等方式解决

 

微服务的好处

将单体式架构迁移到微服务架构后,确实带来了诸多便利,下面我们具体谈谈微服务的好处有哪些

  1. 易于扩展: 某个模块的服务器处理能力不足时,我们在那个模块所处应用的服务器中增加节点就行
  2. 发布简单: 在单体式架构中,因为所有代码存放在一个应用中,所以每次发布代码时,我们需要连带整个应用一起发布,使得整个团队人员都得配合集成测试、统一协调排期。但是迁移到微服务架构后,我们只需要保证对外契约不变就行,发布过程变得特别简单了
  3. 技术异构: 因为各个服务之间相互独立、互不影响,所以我们只需要保证外部契约(一般指接口)不变即可,而内部想使用什么语言或框架都行
  4. 便于重构: 在单体式架构中,因为系统重构的影响面较大,所以在做任何改动时我们都要小心翼翼,以至于我们不敢尝试大的重构或优化,最终出现代码加速腐烂的情况。但是在微服务架构中,因为我们把模块间的影响进行了隔离,所以大大增加了重构的灵活性

 

微服务的痛

(1)微服务职责划分

微服务的难点在于无法对一些特定职责进行清晰划分,比如这个特定职责它应该归属于服务 A 还是服务 B了方便理解这部分内容,下面举几个例子说明下

  • 比如一个能根据商品 ID 找出商品信息的接口,我们把它放在商品服务中就行。再比如单个用户的所有订单,我们把它放在订单服务中就行
  • 业务逻辑服务归属与业务人员的划分可能存在关系,比如每个商品在每个门店的库存应该放在商品服务还是门店服务呢为各自门店的商品库存由各自门店的运营人员管理,最终我们决定把它放在门店系统中
  • 业务逻辑服务归属与产品人员的划分可能存在关系,比如业务部门提了一个新需求,需要我们设计一个能对商品进行相关设置的功能,使得某些门店只能卖某些商品。此时这个功能应该放门店服务还是放商品服务呢就需要看这个功能由哪条业务线的产品负责人负责了,比如由商品系统的产品经理负责,我们就把它放商品服务中就行;比如由门店的产品经理负责,把它放门店服务中就行
  • 业务逻辑服务归属与工期可能存在关系,紧接着上面的例子——实现某些门店只能卖某些商品的需求,最终我们依据 DDD 中的相关原则定了一个划分逻辑,特定门店的特定商品的上架功能放在门店服务中,因为特定商品由门店的运营人员负责上架。但是这种划分逻辑会出现这么一个情况:比如门店服务的开发人员很忙,没空接这个需求,而商品服务的开发人员刚好有空,但他们对门店服务的逻辑不了解。于是,商品的开发人员提议,如果我们想在 2 周内实现上线这个需求,则必须把这个功能放在商品服务中。这种方案看起来比较诡异,不过最终他们确实把这个功能放在了商品服务中,因为再优雅的设计也抵不过业务部门要求的上线压力
  • 业务逻辑服务归属还与组织架构可能存在关系,通过康威定律我们就能很快明白

Conway’s law is an adage named after computer programmer Melvin Conway, who introduced the idea in 1967. It states that. organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations.

康威是个程序员,他在 1967 年提出:设计系统的组织在设计系统时,会设计出基于这些组织的沟通结构的系统

关于微服务职责划分的痛,通过前面几个例子的介绍,应该隐隐约约有所感觉了,接下来再说一个进销存供应链系统的例子,让你有更深刻的体会

这里的进指的是供应商的采购,销指的是门店的销售单,存指的是一些中央仓库的库存,且进销存供应链系统与新零售系统之间紧密结合,对应的架构图如下所示

软件架构场景之—— 微服务的痛点
供应链系统+新零售系统结合后的架构图

在这个架构中,原本门店的商品库存是由门店运营人员(即新零售业务)负责,中央仓库库存由供应链人员管理。后来,不知什么原因领导要求更改供应链总监职责,此时供应链总监需要同时负责门店商品库存+中央仓库库存

先来看看原职责划分情况,对应关系如下图所示

软件架构场景之—— 微服务的痛点
原职责划分对应关系图

在图中可以看到,在原有的组织架构中,新零售业务的产研只对接新零售业务,供应链业务的产研只对接供应链业务。现如今,门店库存管理职责需要划分到供应链业务中,也就是说新零售业务的产研不再负责这个需求,而是交由供应链业务的产研负责了。此时供应链业务的产研会把门店库存积极地搬运到供应链的库存管理中,因为门店库存管理好了,供应链业务方的绩效也就好了,产研的绩效也就高了,年终奖也就更多了

因此,在现实场景中,微服务职责的划分会受太多人为因素的影响,也就能理解为什么市面上关于服务职责划分原则的相关资料太少了

(2)微服务粒度拆分

以上面的新零售系统为例,刚开始系统只有登录和信息管理功能,把这些功能存放在一个服务中就行,实现起来比较简单。随着加盟商的加入,因为加盟商准入、开店、退出都涉及费用问题,因此我们又需要增加财务功能(如应收、应付、实收、实付、退款、对账等)

随着业务的逐步开展,又需要增加加盟商员工管理(员工管理、部门管理、权限管理)返点、加盟商子门店管理等功能,而此时的加盟商管理系统只有一个服务,你觉得合适吗案肯定是不合适。那微服务的粒度到底拆分多少比较合适呢如什么时候拆分加盟商服务比较合适加盟商的财务功能时还是加盟商的员工管理功能时加盟商的返点功能时还是加盟商的子门店管理功能时/p>

一般来说,在设计新功能之前,我们会遵循一个大致原则:根据新的微服务的大小,安排 3-4 人设计即可

但是当一个微服务设计出来后,它的改动成本一般不高,除非实现大规模重构,为了防止开发人员出现闲置的情况,公司会安排他们设计新的功能。而设计新功能时,开发人员倾向于将独立的功能存放在新的服务中,导致加盟商的财务、员工管理及返点功能都被独立出来了。为了避免这种情况的发生,因此,在对微服务粒度进行拆分时,我们还需要考虑另外一个因素——绩效

那服务的粒度大小控制在什么范围比较合适呢只能说没有确切的答案,需要根据实际业务情况来定

 

没人知道系统整体架构的全貌

不知道你有没有碰到过这种情况:每隔几个月或半年,领导就会发话让我们汇报下每个部门的微服务数量、公司微服务总数量、每个微服务都用来做什么等情况。因为企业微服务数较多,所以每次给大领导汇报时,都是长长的一条清单

然后领导开始抱怨:“几百个微服务统这么复杂了吗能知道所有系统的全貌啊,如果出现问题,如何快速定位问题点呢,因此,在实际工作中,很难找到这么一个人,他能知道系统整体架构的全貌,这就是微服务的一个痛点

 

重复代码多

微服务之间存在重复的代码也没事,因为部门之间的重复代码比比皆是,而且技术中心每个部门都有自己的 framework/Common/shared/arc 的 GitLab subgroups,它们可以实现对部门内部的通用代码进行重用。不过,维护这些小小的重复代码总比统一排期做重构、统一评审 JAR 版本的成本低得多

 

耗费更多服务器资源

原来使用的是单体式架构,一共部署了 5 台服务器,后面他们一直抱怨系统耦合性太强,代码之间经常互相影响,并且强烈要求将架构进行迁移,于是,根据业务模块,把原来的单体式架构拆分为了 6 个微服务。考虑到高可用,每个服务至少需要部署在 2 个节点上,再加上网关层需要 2 台服务器,最终,我们一共部署了 15 台服务器。(因为其中一个服务比较耗资源,为了保险起见,我多加了一个节点)

在这个拆分过程中,业务没有变,流量没有变,代码逻辑改动也不大,却无缘无故多出了 9 台服务器,为此我们为此事发生过争执,当时的争议点是如果是这种情形,就不应该一台服务器只部署一种服务,比如我们可以把服务 AB 部署在 1 个节点,BC 服务部署在 1 个节点,AC 再部署在一个节点,如下图所示

软件架构场景之—— 微服务的痛点

 

分布式事务

分布式事务这个痛点对于微服务来说,简直就是地狱。以下单流程为例

原本的下单流程是这样的:插入订单——>修改库存——>插入交易单——>插入财务应收款单——>返回结果给用户,让用户跳转

在单体式架构中,我们只需要把上面的下单流程包在一个事务里就行了,如果某个流程出错了,直接回滚数据,并通过业务代码告知用户出错了就行,让用户重试就好了

可是迁移到微服务后,因为这几个流程分别存放在不同的服务中,所以我们需要更新不同的数据库,也就需要纠结以下逻辑

  • 某个流程出错是否需要将数据全部回滚果需要的话,那么我们需要在每个流程中写上回滚代码。那万一回滚失败了呢们是不是还需要写回滚的回滚代码,回滚的回滚代码算回滚吗/li>
  • 要不就某些流程回滚,某些流程不回滚哪些流程回滚哪些流程不回滚呢/li>
  • 要不就统一不回滚,失败就重试样岂不是需要做成异步果做成异步,会不会出现时间超时果超时了,用户怎么办要回滚吗怎么又要回滚了

因此,针对这种情况,在大部分的场景下我们不考虑回滚和重试,只考虑写 Happy Path(完全不考虑异常,最简单的场景),如果报错就记个异常日志,再线下手工处理

 

服务之间的依赖

在设计类时,我们往往需要遵循类与类之间不可循环依赖的原则,因此最终设计出来的类关系类似下图所示的层次分明的结构

软件架构场景之—— 微服务的痛点

如果我们把依赖关系转移到微服务,结果会怎么样呢/p>

比如商品系统针对不同门店类型设置不同价格时需要调用门店系统中的类型,这时商品就依赖了门店;同时因门店中存在商品库存,门店也就依赖了商品系统的商品信息,从而形成了循环依赖,再比如最底层的财务系统,从理论上讲,它不需要依赖其他系统。而实际上刚好相反,它必须依赖订单信息,知道费用由什么订单产生,同时它还需要依赖会员信息和门店信息,知道是谁付的钱和谁收的钱

随着需求越来越多,服务之间的依赖就变成下面这种架构了

软件架构场景之—— 微服务的痛点

通过上图,发现服务之间的依赖可谓是你中有我,我中有你

 

联调的痛苦

以往,我们的需求排期是这样的:需求评审时间——>开发完成时间——>测试完成时间——>上线时间

迁移到微服务后,需求排期表活生生地增加了 2 个环节:需求评审时间——>接口设计时间——>开发完成时间——>联调完成时间——>测试完成时间——>上线时间

为什么我们这么在意联调/span>

因为在一个软件项目中,影响项目排期的往往不是技术问题而是第三方依赖问题,一旦涉及沟通、协调等问题就会特别耗时间

联调好接口后,在实际开发过程中接口还会修改吗/p>

答案是肯定会,而且增加、修改、删除接口都有可能。 但是对完接口后,至少可以保证我们在大概一致的方向上前进,如果确实需要调整,修改的也只是一些小细节,并不会影响开发进度

 

部署上的难题

使用单体式架构时,每个开发人员都想在本地把整个系统部署完后再调试,此时部署方式非常简单。可是迁移到微服务后,每个项目动不动就涉及十几个微服务。这时,如果让开发人员将这十几个微服务在本地部署完后再联调,根本无法实现。且不说内存不够,就算内存够,任何一个开发人员都不可能熟悉十几个微服务的部署

为此,专门弄了 1 套测试环境给开发人员进行联调,这样开发人员就可以将本地正在开发的服务接入联调环境,类似下图所示架构

软件架构场景之—— 微服务的痛点

可是,这种架构时不时会出现下面这三种问题

1. 联调环境的数据缺漏非常大

因为联调接入的服务是本地开发过程中的服务,即数据是开发数据,所以单个服务中的数据不具备完整性。而且因为是开发环境,上下游服务之间还没有调通,也就是说上下游的单据也不一致、不完整,不是出现订单少了收款单的情况,就是出现准入少了审批单的情况

2. 经常调用服务错误

A:“这个接口怎么有问题啊看,A 字段和 B 字段都缺失了。”
B:“怎么会呢明明加上去了啊
A:“你是不是忘记部署了是部署失败了
B:“我看看。”
A:“我去,你是不是调用了C的服务一下C。”
过了一会儿,B过来说:“还真是,他刚好在接入这个服务,我找他去。”

3. 联调环境极度不稳定

因为开发人员时不时需要对联调中的服务进行部署,或者将不稳定的开发服务接入联调环境,再加上前面提及单个服务中的数据不具备完整性,因此,如果想在联调环境下走完完整的流程,这根本不太可能。为此,只能将联调环境用作接口间的局部联调

文章知识点与官方知识档案匹配,可进一步学习相关知识云原生入门技能树服务网格(istio)ServiceMesh介绍8582 人正在系统学习中

来源:知识记录者-vincent

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年1月25日
下一篇 2021年1月25日

相关推荐