其他

关于放弃微服务的杂谈

转载:东哥 Java面试那些事

背景


谷歌、亚马逊°、Uber都开始放弃微服务了?说实话,刚刷到这个消息的时候,我还有点懵—这不就是互联网圈的“政治正确”吗?微服务都火了快十年了,怎么突然说不行就不行了?难不成,这是大型企业集体在反思“微服务的原罪”?

我这些年项目里也踩过不少微服务的坑,一看到这种讨论,自然忍不住想深挖一下,背后到底是微服务真的过时了,还是这帮大厂太能折腾?今天咱们就聊聊微服务这件事,顺便聊聊我自己的踩坑经历,给还在微服务一线战斗的朋友们提个醒

长期以来,不管大厂还是小厂,微服务都被认为是云原生服务应用程序架构的事实标准,然而2023,不止那位37signals的DHH决心下云,放弃微服务,就连亚马逊和谷歌等这些云巨头,正在带头开始革了微服务的命。

谷歌坐不住了:我们做的微服务都错了!

“在编写分布式应用程序时,传统观点认为将应用程序拆分为可以独立推出的独立服务。这种方法的初衷是好的,但像这样基于微服务的体系结构往往会适得其反,带来的挑战抵消了体系结构试图实现的好处。

今年6月,一群谷歌员工(由谷歌软件工程师Michael Whittaker领导)发表了一篇名为“Towards Modern Development of Cloud Applications”的论文,开篇就对当下的微服务架构开怼。

文章认为,从架构上讲,微服务本身设置就有问题,它是一个没有边界的结构:“从根本上说,这是因为微服务将逻辑边界(如何编写代码)与物理边界(如何部署代码)混为一谈。”

因此,谷歌的工程师们提出了一种堪称“微服务2.0”的方法。将应用程序构建为逻辑整体,但将其交给自动化运行时,后者可以根据应用程序所需的内容和可用的内容来决定在哪里运行工作负载。

亚马逊Prime Video团队:放弃微服务,改用单体

无独有偶,同样是在6月,亚马逊流媒体平台 Prime Video发布的一则案例研究似乎改变了风向:“我们放弃了无服务器、微服务架构,改用单体架构取而代之,此举为客户节省90%的运营成本,还简化了系统复杂度”。

单体应用对微服务的“反戈一击”,还是亚马逊团队提出来的,再次让这个话题迅速引爆技术圈。

整个案例看下来,微服务跟降本增效似乎也扯不到一起去。问题出在哪里?

Prime Video 团队需要一个监控视频流质量问题的工具,由于视频数量太大,就要求该工具有很强的可扩展性。

最初这项工作是由一组分布式组件完成的,这些组件由AWS Step Functions(一种无服务器编排服务,AWS Lambda无服务器服务)编排,分分钟就能搭出一个有模有样的监控系统。但谁能想到,Step Function 伸缩问题竟然成为最大的绊脚石。

具体来看,一是对于视频流的每一秒,需要很多并发的 AWS Step Function,所以很快就达到了账户限制;二是 AWS Step Function 是按照状态转换向用户收费的,太贵了实在用不起。

无奈之下,Prime Video开始考虑用单体的解决方案以降低成本和增加应用的扩展能力,在经历了反复试验后,团队最终决定重建Prime Video的整个基础设施。

亚马逊在博客文章总结道:“微服务和无服务器组件是可以大规模工作的工具,但是否在整体上使用它们必须根据具体情况而定……将服务迁移成单体让我们的基础设施成本降低了 90%以上,还提升了我们的伸缩能力。”

这就说明,至少在视频监控领域,单体架构比微服务、无服务器主导的方法产生了更高的性能、更能降本增效。

始终鼓吹下云和反对微服务化的DHH( Ruby on Rails创始人,David Heinemeier Hansson)一针见血地指出:连亚马逊自个都觉得微服务或无服务器“扯淡”了。

放弃微服务的,不止谷歌、亚马逊

最近几年,无数的中小团队在权衡利弊后选择放弃微服务。

Uber 就是其中一家,此前 Uber 通过构建微服务来完成很小的需求或功能,甚至出现很多由一个人构建维护的微服务。这些微服务的存在给Uber带来了更多的挑战,比如监控、测试、持续集成 / 持续交付(CI/CD)、服务级别协议(SLA)等。

踩了微服务的“坑”之后,Uber 团队对新服务进行了更加深思熟虑的规划:不再只是完成一件事,而是使其服务于一项业务功能,由 5-10 个工程师负责维护,还总结出了血泪教训:要在正确的时间选择正确的解决方案来构建产品。

办公管理软件公司 Managed by Q 的应用程序是一个部署在 ECS 上的 Django 单体。为了赶上现代化开发实践的步伐,他们转向微服务架构。但他们很快发现,每多一个新服务,就会增加一些基础设施,而且开发一个跨多个服务的功能需要做更多的工作。

结果,在转向微服务两年之后,他们开始合并微服务。一些微服务被合到了单体中,其他的则合并成较大的服务。他们也在实践中得出经验:不能理所当然地认为微服务就是正确的选择。

本来想把微服务当银弹,结果工程开销太大,得不偿失。以上提到的企业最大的问题是在只有20位工程师的环境中实现了几十个微服务,有种杀鸡焉用牛刀的错位感。

微服务的虚假繁荣:从单体变成“分布式单体”

随着越来越多“逃离微服务”的案例发生,人们对于2005年就提出的“微服务”再度审视,甚至批评。

比如开头提到的谷歌工程师们,就在他们的论文中列出了目前微服务方法的缺陷,包括:

  • 性能: 通过网络将数据序列化并发送到远程服务会损害性能,如果应用程序变得足够复杂,甚至可能导致瓶颈。
  • 理解追踪 :众所周知,在分布式系统中,考虑到微服务之间的许多交互,很难追踪Bug。
  • 管理问题 :应用程序的不同部分可以按照自己的时间表进行更新,这被认为是一个优势。但这导致开发人员不得不管理大量的二进制文件,每个二进制文件都有自己的发布时间表。祝您好运,使用本地运行的服务运行端到端测试。
  • API变得脆弱 :微服务互操作性的关键是,一旦建立了微服务,API就不能改变,让它们破坏任何其他依赖API的微服务。因此,API只能用更多的API进行扩展,从而产生膨胀。

看起来跟之前提到的“过度设计”的概念不谋而合。

事实上有些团队在将集中式单体应用拆分为微服务时,首先进行的往往不是建立领域模型,而只是按照业务功能将原来单体应用的一个软件包拆分成多个所谓的“微服务”软件包,而这些“微服务”内的代码高度耦合,逻辑边界不清晰,本质上还是单体架构模式 ,所以只是实现了“表面繁荣”,并没有实现想要的结果。

正如Sam Newman在《构建微服务》一书中提到的那样,架构需要满足一定的前提条件,否则就可能过度设计。

微服务怎么突然被嫌弃了


先说结论:微服务这个架构,本身没错,错的是我们用它的方式和用它的时机。
谷歌、亚马逊这些巨头最近反思微服务,不是因为微服务天生有原罪,而是因为它们在实际操作中,发现微服务带来的复杂度、成本,越来越成了一笔糊涂账。
谷歌的工程师Michael WhittakerQ直接在论文里开怼,说微服务把“逻辑边界”和“物理边界”混在了一起。啥意思?逻辑边界说的是代码的职责划分,物理边界是部署层面的事,结果很多项目拆微服务时,逻辑上分不清,物理上还拆得稀碎,服务之间一堆HTTP、RPC来回扯,最后搞出来一个“分布式单体”,性能差、成本高,连bug都难追踪。

亚马逊Prime Video团队就踩了这个坑。他们要做一个视频流监控系统,本来想着用AWS Step
FunctiontI Lambda,分分钟搞个弹性伸缩、无服务器的微服务架构,听起来是不是特别高级?结果,伸缩是伸缩了,账单也爆炸了——每个状态转换都收费,视频流量一大,直接上限告急,最后不得不回头用单体架构重写,运营成本降了90%。
说到这里,我脑子里立马就闪回起之前的项目。我还记得,当年有个后台管理系统,搞了一堆微服务,用户、订单、支付、商品,拆得那叫一个彻底,结果开发一个“用户下单”的功能,前后端要对接五六个服务,每次联调简直是噩梦。最崩溃的是,服务一多,版本一不一致,整个测试环境全挂,最后不得不搞一堆Mock和兼容方案,折腾得心态都快炸了。
这大概就是谷歌说的,微服务拆得太细,逻辑边界和物理边界混乱,结果越拆越糟糕,性能没提升多少,复杂度飙升,开发测试一地鸡毛。

微服务不是银弹,选错时机才是坑


我现在越来越认同一句话:架构是服务业务的,别为了技术而技术。
微服务一开始是为了解决大规模系统的灵活扩展问题,但问题是,不是每个项目都需要灵活扩展。像
Uber、Managed by Q这样的公司,早期人少、服务少,非得搞几十个微服务,每个微服务还配套CI/CD、监控、日志、SLA,结果,服务数翻倍,复杂度也翻倍。一个团队十几个人,光是维护微服务的基础设施就得三四个专人,开发新功能反倒成了次要的事

我以前一个项目组就干过类似的事,人不多,业务不复杂,非得拆微服务,搞了半天,服务跑起来倒是好看,Jenkins、Prometheus、ELK,全都配上了,真到上线新功能,几个人忙得焦头烂额,后面直接砍掉一半服务,合并成一个大点的模块,日子才好过点。
所以,微服务到底适合谁?适合那些业务复杂、团队规模大、需要高可用、可扩展的大型系统。像阿里、腾讯这种级别,用户多、业务杂,拆微服务确实有必要,单体跑不动。但你要是一个二三十人的团队,业务刚起步,非得搞个二三十个服务,那真是杀鸡用牛刀,成本高、收益低。

谷歌 亚马逊 新思路:模块化单体


谷歌在新论文里提出的这个“微服务2.0”,其实就是把应用写成逻辑模块(比如不同的业务模块分开写),但部署的时候,还是一个整体,具体的分布式、部署层面,交给运行时自动去处理。
这跟传统的Spring Boot单体有点像,但更智能一些。谷歌的想法是,开发别太操心部署,逻辑上你把代码写好,运行时来决定哪些模块分开部署、哪些模块放一起,既能保持代码的清晰,又能兼顾性能和成本。

亚马逊Prime Video那个案例,也有点类似。他们重构后,其实是用单体架构做视频监控,里面可能还是分模块、分层,但部署成一个整体,避免了微服务那一堆服务间通信的开销,性能提升了,成本也降下来了。

微服务的幻觉:分布式单体


其实微服务的最大陷阱,就是让人产生一种“我把系统拆小了,复杂度就降了”的幻觉。殊不知,服务拆小了,整体复杂度反而提升了。
为什么?因为服务之间的通信变复杂了,RPC、消息队列、缓存、数据库一致性,这些原来在单体里是本地调用、事务搞定的,拆成微服务之后,全成了分布式系统的问题。
这不,谷歌论文里也提到几个典型的痛点:

  1. 性能问题:服务间数据传输,序列化、反序列化,网络延迟,不管你用HTTP还是gRPC,都避免不了,复杂度上来了,性能下去了。
  2. 微服务一多,问题出在哪儿,得靠链路追踪工具去查,Jaeger、Zipkin整起来,还是不一定找得到。
  3. 每个服务一套部署计划,一不留神,服务版本不一致,接口变更,联调就得跪。
  4. 服务之间依赖多,API改不了,越来越臃肿,最后加一个新功能,得扩展五六个接口。

这不就是所谓的“分布式单体”嘛,看着是微服务,实际还是一坨糊在一起的代码,拆也拆不动,合也合不回来,活脱脱一个四不像。

怎样选架构才靠谱


经历了这么多,我的感受是,架构没对错,只有适合不适合。
如果你业务还在发展初期,团队人少,功能简单,单体架构才是你的好朋友。像Spring Boot,模块分清楚,业务逻辑清晰,代码可控,出问题也好排查。哪怕你用Spring Cloud Alibaba这种微服务套件,也别一上来就搞服务拆分,先单体起步,业务成熟了,再慢慢拆。

等你业务上量了,用户多了,瓶颈出来了,再考虑微服务拆分。但拆也别太碎,先拆流量大、变化快、独立性强的模块,比如用户、订单、支付,别上来就按部门拆,结果一堆服务互相依赖,最后拆上来就按部门拆,结果一堆服务互相依赖,最后拆出个“大一统”。
像谷歌、亚马逊提倡的“模块化单体”,也是个值得参考的思路。开发阶段,代码模块化,部署阶段,视情况调整,能整体部署就整体,确实有需求再拆。
简单点说,别为了微服务而微服务,技术是为业务服务的,不是为了秀肌肉。
写到这里,忍不住想起一句话:“软件开发就是在复杂和简单之间做权衡。”微服务也一样,别让自己陷入复杂的沼泽,能简单的时候,别硬拗。