我们在前面已经说了微服务的六个痛点:(1)微服务的职责划分之痛;(2)微服务的粒度拆分之痛;(3)没人知道系统整体架构全貌之痛;(4)重复代码过多之痛;(5)服务器资源耗费更多之痛;(6)分布式事务之痛。
在这里我们继续来聊其他的痛点:
一、服务之间的依赖
在设计类时,我们往往需要遵循类与类之间不可循环依赖的原则,因此最终设计出来的类关系类似下图所示的层次分明的结构。
如果我们把依赖关系转移到微服务,结果会怎么样呢?我们先举个例子看看。
比如商品系统针对不同门店类型设置不同价格时需要调用门店系统中的类型,这时商品就依赖了门店;同时因门店中存在商品库存,门店也就依赖了商品系统的商品信息,从而形成了循环依赖。
再比如最底层的财务系统,从理论上讲,它不需要依赖其他系统。而实际上刚好相反,它必须依赖订单信息,知道费用由什么订单产生,同时它还需要依赖会员信息和门店信息,知道是谁付的钱和谁收的钱。
因此,随着需求越来越多,服务之间的依赖就变成下面这种架构了。 通过上图,我们发现服务之间的依赖可谓是你中有我,我中有你。
那这种地狱般的依赖一般会出现什么问题呢?
场景1:
重构 2 个服务后,我们就需要在测试过程中评估哪些服务会受影响?
因为前一段时间线上环境已经出现了 2 次一级故障,所以 CTO 强烈要求我们此次务必认真评估影响面,不能再出现类似问题。
于是一个 leader 提出方案:先根据重构的代码找到受影响的接口,然后根据接口找到所有调用这些接口的上游代码,再找到那些调用上游接口的接口,以此类推。
由于该方案分析成本过高,且一旦出现任何遗漏就会前功尽弃,因此直接被 CEO 否决了。
最终我们提出了一个较合理的方案:根据全链路日记系统中的服务间依赖,找到这 2 个服务的所有上游服务及上游的上游服务。
通过这个方案评估后,我们发现服务重构后,大半的微服务受到了影响,于是一堆人不得不陪着通宵达旦做回归测试,那几天重构服务的 Leader 也都低着头走。
场景2:
有了之前的教训,后续遇到新的重构需求时,重构的人就学乖了,直接把原来的服务 abcService V1 写成新服务 abcService V2。此时新的代码直接调用 V2 版本,而旧的代码还是继续调用 V1 版本,等有时间再下架 abcService V1 ,这样就不用一堆人陪着加班了。
后面大家纷纷照搬这个方案,使得V1、V2 的形式越来越流行,服务数量出现暴涨。而且在实际开发工作中,开发人员很少在后期下架旧版服务,最终导致服务数量越来越多且新旧版本并存,维护起来更痛苦了。
以上就是服务之间的依赖导致的问题了,而关于这类问题的解决思路我们放在后面专门来说
二、联调的痛苦
以往,我们的需求排期是这样的:需求评审时间——>开发完成时间——>测试完成时间——>上线时间。
迁移到微服务后,需求排期表活生生地增加了 2 个环节:需求评审时间——>接口设计时间——>开发完成时间——>联调完成时间——>测试完成时间——>上线时间。
在这种变化下,每次遇到比较紧急的需求时,我们都会额外问一句:接口文档好了吗?联调怎么样了?
为什么我们这么在意联调?因为在一个软件项目中,影响项目排期的往往不是技术问题而是第三方依赖问题,一旦涉及沟通、协调等问题就会特别耗时间。
这里,我们举一个例子简单说明下。
有一次,我们正对门店系统进行小的需求改动,此时需要商品系统研发人员配合提供一个简单的接口,而商品系统的开发人员说,“我们正在忙另外一个项目,周二抽空提供这个接口。”
听了商品系统开发人员的番话,我们简单评估了一下上线时间,周二拿到接口,周三进行联调,周四、周五测试 2 天,应该周五晚上就可以上线了。于是,我们与业务人员进行了相关反馈。
可是等我们把门店的功能设计好后,因商品系统的开发人员开展的项目临时修改了一个紧急需求,要求周二务必通宵搞定,为此,他们无法在周二这天给到我们接口,最终门店周五上线的计划也就被延误了。
而这种事情在实际开发过程中发生的频率比较高,并不是单个事件。
下面,我们再举一个例子说明下。
有一次,我们正在做一个涉及 30 个服务的大项目。周五完成所有需求评审后,我们的首要目标是对接口文档。
因为接口文档是由各个项目组根据实际需求汇总各自需要提供的接口数,总计 300 多个接口,以至于这个过程我们花了整整两周时间。
对完接口文档后,十几个项目组之间又开始对接口联调时间,这个过程又整整花了三天时间。
不过,对完接口后,各自开发功能的速度就很快了,那次我们用了 2 周就把功能开发完了。
说到这可能你想问,对完接口后,在实际开发过程中接口还会修改吗?我的答案是肯定会,而且增加、修改、删除接口都有可能。 但是对完接口后,至少可以保证我们在大概一致的方向上前进,如果确实需要调整,修改的也只是一些小细节,并不会影响开发进度。
功能设计完后,就需要进行联调了,而这个过程往往最耗时,因为我们需要耗费大量的时间在沟通上,先通过下面这段对话感受下。
调用方:“addXXX 的接口怎么样了?” 被调用方:“好了,你可以调调看。” 调用方:“不行啊,你看返回了 404。” 被调用方:“哎呀,环境部署错了,稍等一下。” 调用方:“赶紧的。” 被调用方:“好的。” …… 被调用方:“好了。” 这时,调用方在联调时发现需要增加一个字段,就说,“addXXX 的接口需要增加一个修改时间字段,你帮我加一下。” “可以,不过我正忙着另外一个项目,要不明天给你?” “别啊,今天必须联调完。” “那我晚上赶一赶,9 点给你成不?” “好吧。”
所以,在做项目时我们最怕的就是协调时间,因为它不可控。毕竟每个开发人员的需求优先级都不一样,除非所有相关项目组的第一优先级都相同,不然协调时间会是一件很头疼的事情。
而且这个大项目共包含 300 多个接口,也就是说 300 多个接口都需要协调,这就使得联调的时间一点不比开发功能的时间少。
三、部署上的难题
使用单体式架构时,每个开发人员都想在本地把整个系统部署完后再调试,此时部署方式非常简单。可是迁移到微服务后,每个项目动不动就涉及十几个微服务。这时,如果让开发人员将这十几个微服务在本地部署完后再联调,根本无法实现。且不说内存不够,就算内存够,任何一个开发人员都不可能熟悉十几个微服务的部署。
为此,我们专门弄了 1 套测试环境给开发人员进行联调,这样开发人员就可以将本地正在开发的服务接入联调环境,类似下图所示架构:
可是,这种架构时不时会出现下面这三种问题。
1. 联调环境的数据缺漏非常大
因为联调接入的服务是本地开发过程中的服务,即数据是开发数据,所以单个服务中的数据不具备完整性。
而且因为是开发环境,上下游服务之间还没有调通,也就是说上下游的单据也不一致、不完整,不是出现订单少了收款单的情况,就是出现准入少了审批单的情况。
2.经常调用服务错误
时不时会有人发出这样的抱怨:
甲:“这个接口怎么有问题啊?你看,A 字段和 B 字段都缺失了。” 乙:“怎么会呢?我明明加上去了啊?” 甲:“你是不是忘记部署了?还是部署失败了?” 乙:“我看看。” 甲:“我去,你是不是调用了六子的服务?问一下六子。” 过了一会儿,乙过来说:“还真是,他刚好在接入这个服务,我找他去。”
3.联调环境极度不稳定
因为开发人员时不时需要对联调中的服务进行部署,或者将不稳定的开发服务接入联调环境,再加上前面提及单个服务中的数据不具备完整性,因此,如果想在联调环境下走完完整的流程,这根本不太可能。为此,我们只能将联调环境用作接口间的局部联调。
这就是联调环境难以部署带来的痛点,使得我们花了太多时间在协调问题上。于是我们在想,有没有一个办法可以简单地创建一套相对独立的测试环境呢?那后面我们就根据这些痛点来对应聊聊解决方案。
四、总结
可能你想问,关于微服务的优势我们只讲了 5 点,而微服务的痛点足足讲了 9 点,我们为什么还要使用微服务?
如果使用单体式架构的话,随着业务的复杂化,将会出现无论怎么加人都无法迭代的情况。 而如果使用微服务,虽然它存在一堆问题,但是至少可以通过加人的方式保持迭代。