最近我们开发团队在开发计划中有一个小停顿,技术部门认为现在是将应用从单体架构迁移到微服务的最佳时机。
图片来自 Pexels
经过一个月的准备和调查,我们取消了迁移,仍然使用单体模式。对我们而言,微服务不仅帮不上忙,反而会影响到开发计划。
我们了解微服务大约是在一年前,但是很惊讶地发现它并不适合我们。本篇文章把我们的经历写出来,可能会对大家有借鉴意义。
我们的应用是整合外部现有产品和业务规则给用户展现一个友好界面的 UI。客户是一家 UWP App,后台有相应服务在第三方域和我们之间交换数据。
对第三方的依赖严重影响我们选择微服务。例如,应用经常要在不同域之间转换功能,使得第三方域看起来像是完全不同的一个域,如果在我们之间有一个单一服务情况还不算太糟。
然而,整个域交换在分解成多个微服务过程中就看起来很怪异了。
是我们的微服务跟第三方的分解不同吗?我们复制了所有服务的前后端需求吗?还是我们分解了自己的微服务,仍然需要一个微服务从第三方获取信息?所有这些问题看起来都跟微服务指南相背离。
我们和第三方合作很紧密,经常一起协作发布版本。微服务的好处在于每个团队都可以不受影响独立发行服务,而跨公司合作则失去了这种好处。
微服务另外一个好处在于,每个团队只需要完整设计自己的业务问题。而我们,作为一个和第三方完全独立的公司团队,这种重构看起来不可行。
我们实在找不出应该从哪个单体应用下手。于是我们在域模型之间连线,以决定要创建哪些微服务。
然而,一旦这么开始做,发现很多共享业务逻辑影响着微服务域的划分。如果将微服务划分的更细小,只能带来更多的耦合关系,到处都需要消息总线,消息可能会出现大爆炸。
原因在于我们的单体式应用是为一个业务逻辑服务的。我们为用户方便创建了跨域和组的很多工作流,本质上,UI 在过去四年中就是将各种东西整合到了一起。
另外,我们还误解了微服务如何被隔离,以及低估了服务之间正确边界选择的重要性。
唯一能做到的就是为了实现一个标准功能,从而需要将所有相关微服务同时升级,由此要求每个微服务都不能被某个单独团队拥有。
我们大约有 12 个开发人员分布在两个功能团队和一个支持团队。工作波动性很大,没有专职负责团队。所有团队同时接触同一批代码很正常,不能将某个微服务指定给一个团队。
考虑架构时候一定要记得 Conway's Law,其意思是软件架构会模仿组织和团队架构增长。
微服务架构对于不同团队负责不同业务逻辑是比较有效的,然而,共享代码功能的工作模式最好采用单体式架构。
各种问题意味着至少六个月内,需要在 IIS 内并行运行微服务和单体式应用。
我们不会访问与微服务相关的工具,如容器、Kubernetes、服务总线、API 网管等,也就意味着与其他微服务之间通讯有很大障碍。
因此,我们决定每个微服务都复制与存储层转换相关读服务的共享逻辑。因为不能正常拆分服务,也就意味着必须承担大量复制工作量。
例如,对于某个复杂而且基础的业务逻辑,必须复制黏贴并维护至少 4 个计划中的微服务。
开发团队只有六个月内粗略的构想,而且业务更改也很频繁(业务更改需求也是司空见惯的事情),这些让微服务化更加充满不确定性,因为即使在短期也无法预知会出现什么链接。
微服务之间的复杂性会增加吗?花了几个月分离的服务会回到过去吗?尽管我们今年早些时候做过一些 PoC,但是因为业务需求的更改不得不放弃。
我们只有一个很窄的时间窗口,刚好能把单体式应用分解成列出来的微服务,而且没有冗余时间应付可能出现的改变,也没有 Plan B,我们被自己给卡住了。
因为我们在计划阶段就发现了很多问题和挑战,更别说实施阶段了,开发团队非常有压力。
考虑到风险和时间压力,而且架构师和实施专家也都没有任何特殊经验,加上没有标准工具能用,我们只能靠自己来实施,这些都更加恶化了情况。
和其他微服务大拿沟通后,他们也都发出了很多警告,并给出了不少以前并没有的架构建议,指出了我们在域模型之间画线的顺序。
到目前为止,由于时间紧任务重,我们的计划包括了很多不同于标准微服务的妥协方案。
因为缺乏专家,这看起来更像一条不归之路,开发团队越来越焦虑。
一旦前方布满了困难,就失去了目标,我们暂停下来,意识到我们并没有搞明白为什么要这样做。
我们没有列出痛点,而且不清楚这样做是否可以解决痛点。更甚,微服务有可能会给我们带来新的问题。
我们开始反思,我们从中会获得什么好处?能解决什么问题?我们召开了更多会议讨论它,但一直没有明确的答案。
最后,我们发现在讨论微服务过程中我们忽略了一个很重要的痛点,而且没有足够的时间来解决这个问题,也就是我们不能考虑微服务或者其他东西了。
这时候我们开始反思微服务一般意义上会带来什么好处?
微服务使得团队可以控制全栈提供一个功能。这种区隔会减少不同团队之间协同的次数,互相不影响对方的工作。
单体式应用中,团队可以专注于所有任务。而采用微服务后,则是某项业务流程的专家。
他们会理解自己区域内的业务规则和需求,知道软件栈如何搭建,当发生改变时会非常有信心完成。
有了微服务,可以根据性能需求扩展服务规模。而在单体式应用中,尽管也可以水平扩展服务,但是不能独立扩展单体应用的模块。
微服务粒度可以增加或者减少服务能力。也许当发现性能问题时,可以参与其他工作,或者稍微喘口气。
每个功能只需要修改单一微服务,而且可以不影响其他团队工作进行回滚。另外微服务还可以减少单一错误对整个系统的影响。
如果有一个扩充系统的,每个发布都很花时间并且有风险,那么就需要大量工作处理每次发行时的问题。
微服务可以减少沟通时间和成本,团队可以各自确定合适时间。
团队依赖微服务可以选择最佳技术方案。而单体式却很难升级。
大型应用升级都不是一件容易的事情。尤其是要在多个团队之间协作。相互隔离的微服务可以每次只升级一个服务,更容易控制风险。
微服务可以将频繁变化和很少变化的服务分隔开,减少意外发生的风险。
小型化服务更易于理解,而且可以保持设计一致。对比来看,单体式应用会因为不同团队的意见不统一带来不一致性。
微服务有很多优势。但是我们能从中获得什么?
最终,对于无法改变或者妥协的架构只能放弃这些优势。我们失去了微服务带来的隔离性。与第三方的合作减弱了从服务不相关性中带来的好处。
微服务也并不都是优点,有一长串需要考虑的问题。
例如,日志,监控,异常处理,容错,回滚,通讯,消息格式,容器,服务发现,备份,遥测,报警,跟踪,工具,共享,文档,扩展,时区,阶段,API 版本,网络延迟,健康检查,负载均衡,CDC测试,等等。
如果没有微服务平台,我们要自己面对所有上面列出来的问题。因为有痛点才要转向微服务,但是不但没法从中获益,还要面对一堆转向微服务带来的问题,我们是何苦来呢?
下图是我们现在单体式应用和微服务的对比。架构上来看,新架构很像单体式,各个组件还是紧耦合,也许我们只是用了微服务的标签。
好像“单体式”就意味着落后,“微服务”就意味着先进。但是从开发团队来看,我们的单体式应用运行良好,基本没有什么问题。
我们有很好的 CI/CD 配置,易于配置和回滚。分支和测试策略确保问题都被提前解决。
此时,似乎有些熟悉的感觉。在我以前从业经验也有过类似的经验,但从没称为微服务,尽管并不完全符合微服务的规则,但是的确解决了问题并带来类似的好处。
5 个人的小团队带领 200 人的开发者。这是一个巨大的 C# 应用,大概 5% 的工作通过单体式共享,其他的共享两节点服务。
我们也不喜欢单体式,工作起来很慢,编译,测试,架构改变的很快经常看到不同的同事出现。
有时候因为一个从未听过的同事辞职了从而延迟了整个项目,技术更新因为要和整个公司同步需要几个月,Pull 申请需要整个系统批复需又要几个星期。
同时,有两个服务规模很小,我们可以很好地控制、部署、架构它。一旦有性能问题,会怀疑实例数量直到解决底层问题,很少麻烦其他团队。
我们团队主导了前后端开发语言的选择。总之,我们很专注于一个很窄的业务逻辑,每个人都成为了专家。
越了解微服务,越发现其不太适合我们。我们是不是太为了技术而技术了?
还有很多其他问题没考虑:
迁移到微服务是个大爆炸,大家都停下功能开始想如何分解单体引用,即使前提还不具备。
后来证明这不是个好方法,有可能还是个倒退。首先创建所有微服务,然后设置架构并忽略了团队的架构。
假如我们开始时考虑团队和业务逻辑结合,等架构成熟,并等微服务自然显现,会更好。而且新的业务需求出现,可以直接被放在一个新服务中。
强制上微服务,也意味着需要选择每个微服务的大小。一些文章建议每个微服务至少按照一个团队的支撑设计。其他的则建议越小越好,一旦需要更改,很短时间内就可以完成。
领导层决定按照工作域划分,如果需要再细分。这个决定导致上面出现的问题。后来复盘时发现如果等待微服务自然出现,其大小其实最合适。
随着预定时间一天天临近,我们团队持续发现越来越多的问题。上线四天后,仍然看不到预期的效果。于是召开了一次会议,最终取消了微服务计划。
微服务的热度消散,也就意味着我们没有做好必要的调研。抛弃了微服务后,我们也有精力去调研其他方案。
最后,我们决定在单体应用内部划分成不同项目,更好地划分了架构和耦合关系。
另外这种架构使得域模型更加清晰,使得未来考虑微服务更容易。
领导层上微服务的仓促决定并没考虑清楚挑战和目前状态。评估后,发现微服务并不适合我们,需要更多的妥协。
这些折衷方案把微服务带来的好处都抵消掉了。微服务并不考虑非技术因素,例如团队结构和工作量。
几个月调研后,我们抛弃了微服务尝试,决定对已有的单体式应用做一些更适合的微调。