作者 | 核子可乐、Tina
忘记历史之人,注定将重蹈覆辙。
(资料图)
最近,关于微服务的讨论又多了起来,包括马斯克、GitHub 前 CTO 都加入了批判微服务的阵营。昨天,传奇程序员 Ted Neward 也在他博客上发了一篇文章,作为一位 IT 行业的“老年人”,他用溯源的方式,挑选文献进行了“考古”,以此分析微服务想解决的问题,以及由此带来的利弊。
Ted Neward 在 IT 行业工作了 30 多年,在这期间他多数时间都在担任初创企业的架构师或 CTO。同时他还在各种技术会议上发表主题演讲,撰写了很多书籍(下图为他撰写的部分书籍)和文章。多年来,他积累了至少 10 种编程语言相关的应用知识。
由于成就斐然,他从 2015 年开始就接受华盛顿大学信息学院的邀请,任职客座教授。但他更为传奇的地方是没有计算机学位,“事实上,我认为我的文科学位(国际关系)比我见过的任何计算机科学学位都更适合我从事开发和编程的职业。”
Ted Neward 在这篇文章中认为,微服务旨在解决两个技术问题:模块化(关注点分离、隐藏实现、文档接口等)和可扩展性(能够为特定模块增加计算、内存和 IO 的数量)。
第一个问题,模块化,可以在语言层面解决。通过“考古”计算机科学里开创性的论文,他认为使用“模块(Modules)”可以达到微服务想要的效果,这就是这篇博文的重点。第二个问题,即可伸缩性,解决可伸缩性的同时,也带来了一些“分布式”上的难题,然而这几十年来,我们得到的经验教训是“分布式系统很难被「做对」”。这听起来像是在制造新问题,然后再花时间和金钱来修复它。
Ted Neward 分析角度与众不同,也因此在一天之内给 Hacker News 带来了 500 多条评论。有网友回忆说,“拿 Twitter 举例来说,他们似乎是在 2014 年采用了微服务。2013 年的时候,他们应该是使用单体架构,当时的 MAU 达到了 2 亿的水平。而如今大多数公司永远也达不到 2013 年 Twitter 的规模。”
这让人感慨如果在 IT 行业拥有足够长的工作经历、了解一些历史知识,也会带来一些意想不到的收获,网友纷纷评论:
“现在的团队很‘年轻’,他们渴望尝试和学习使用微服务。”
“这是我不喜欢在缺乏‘老人’的团队中工作的原因之一。年轻的开发者仍然会犯很多老开发者已经犯过或见过的错误。年轻人认为‘老人’们行动缓慢,但他们也创造了稳定性和控制力。在初创企业中,由年轻人组成的团队能够快速行动和交付。不过,企业通常得到的是一座由意大利面条建造的高塔,他们就用这样的代码为实际用户提供服务。”
“我作为团队中的老家伙(也比经理年长)的经历是,年轻人只想尝试新技术,喜欢推动企业采用新技术,无论老人的观点如何,而年轻的经理也对老人的疑心很大。”
…
人人都在讨论微服务,有这个必要吗?
架构变迁就如同一股流行之风,总有人提出新想法,迫切希望改进现有架构的行业,在没搞清背景和细微差别的情况下迅速把它们推向主流。然后一夜之间,这东西就火起来了。微服务无疑就是目前最火的议题,所以咱们就好好说道说道这背后的根源。
…获得一大堆收益!
“可扩展性”:“可以把代码拆分成更小的部分,独立加以开发、测试、部署和更新。” “更专注”:“……让开发者专注于解决业务问题和业务逻辑。” “可用性”:“后端数据必须始终可用于多种设备……” “简单性”:“简化了大型企业级应用程序的开发流程。” “响应性”:“……让分布式应用程序通过扩展,应对不断变化的事务负载……” “可靠性”:“复制的服务器组能在发生故障时继续运行,避免引发单点故障。即使发生故障,所运行的应用程序也能恢复至良好状态。”但我想提醒大家,这六条好处其实各有来历:两条来自微服务文献 (博文、论文之类)、两条来自 20 年前的 EJB 文献,还有两条来自四十多年前的老技术 Oracle Tuxedo。怎么样,各位能分得清楚吗?
总之,技术行业其实是在一遍又一遍地重复那些炒作说辞。
“忘记历史之人,注定将重蹈覆辙。”George Santanyana,《理性生活》(1905 年)
说起微服务的炒作宣传,咱们看看某篇企业博文给出的十条总结:
有助于推广大数据最佳实践 。微服务天然适应数据管道架构,符合大数据的收集、摄取、处理和交付方式。数据管道中的各个步骤,都会以微服务的形式处理一项小任务。 相对更易构建和维护 。由于用途单一,所以微服务可由较小团队进行构建和维护。各团队可以跨职能组建,同时专注于解决方案中的特定微服务子集。 有助于提高代码质量 。将整体解决方案模块化成一个个离散组件,有助于应用开发团队每次只关注一小部分工作,从而简化编码和测试流程。 简化跨团队协调过程 。与以往涉及重量级进程间通信协议的传统面向服务架构(SOA)不同,微服务通过事件流方法降低了集成难度。 可支持实时处理 。微服务架构的核心是发布 - 订阅框架,可支持实时数据处理以提供即时输出与洞察。 能适应快速增长 。微服务让代码和数据能够复用模块化架构,降低多数据驱动型用例和解决方案的部署难度,从而快速增加业务价值。 能提升产出规模 。数据集往往以不同方式呈现给不同受众,微服务则简化了为不同最终用户提取数据的方式。 易于评估应用程序生命周期中的更新 。各种高级分析环境,包括用于机器学习的分析环境,需要一些方法来根据新创建的模型评估现有计算模型。微服务架构中的 A-B 及多变量测试,让用户能够轻松验证更新后的模型。 可实现规模伸缩 。这种可伸缩性不仅能提供更高容量,也能轻松发现扩展瓶颈,立足微服务层级解决这些瓶颈。 有大量流行工具可供选****择 。大数据及开源社区的各种技术,都能在微服务架构中运行良好。Apache Hadoop、Apache Spark、NoSQL 数据库和各种流分析工具都跟微服务很搭调。话说得倒是漂亮,但事实真是如此吗?下面咱们就一条条来分析:
有助于推广大数据最佳实践 。从 70 年代以来,管道加过滤器架构就一直在软件领域发挥作用,当时的各类 Unix 系统提出了以下原则: 每个程序只做好一件事。如果需要完成新任务,别添加新“功能”来提高旧程序的复杂度,而应重新构建新程序。 将每个程序的输出,都看作另一程序的输入。不要用无关信息混淆输出。严格避免使用列式或二进制输入格式。不要总想着使用交互式输入。 相对更易构建和维护 。人家 Unix 早就实现过了。 有助于提高代码质量 。如果说每次只关注一小部分有助于提高代码质量,那 Unix 之前也实现过了。 简化跨团队协调过程 。这话说得就很可笑,什么“面向服务架构(SOA)……往往涉及重量级进程间通信协议”——这说的是 JSON over HTTP 呗?或者说,这是指一切 SOA 都需要 SOAP、WSDL、XML Schema 和 WS-* 的完整规范集?可微服务并没有用任何方式阻止使用这些“重量级”协议,某些微服务甚至建议使用 gRPC——这是一种跟 IIOP 很相似的二进制协议,而后者来自 CORBA,正是各种“重量级”协议的前身。 可支持实时处理 。实时处理哪里是什么新鲜事物了。之前就有很多系统在使用发布 - 订阅或者“事件总线”模型来实现,根本就不需要劳微服务的大驾。 能适应快速增长 。“复用模块化架构”……大家还记得有多少东西在以可复用为卖点吗?编程语言是一种(OOP、函数式语言、过程语言等),库和框架也是。总有一天,大家会看透这种宣传伎俩,不再理会什么“可复用性”。 能提升产出规模 。“数据集往往以不同方式呈现给不同受众”,SAP Crystal Reports 的主页上就写着差不多的话,这哪是微服务的原创? 易于评估应用程序生命周期中的更新 。机器学习和高级分析环境需要“根据新创建的模型评估现有计算模型”……净是空话,一点实际的都没有。可实现规模伸缩。那么问题来了——EJB、事务性中间件(比如 Tuxedo)和大型机,谁又不能实现规模伸缩了? 有大量流行工具可供选择 。每次技术炒作进入“没活可整”的尴尬期后,都喜欢拿工具选项说事。当初的 CASE 是如此,后来的 UML 也是如此,没什么新鲜的。而且敏锐的读者朋友可能已经注意到,以上这些观点有差不多一半都可以总结成:创建并维护更小、相互独立的代码和数据“块”,通过彼此版本化,让它们使用共通的输入和输出以实现进一步系统集成。
模块。
没错,这里说的就是低级“模块”。模块的概念从 1970 年代起就是大部分编程语言的核心,只是早期语言还不这么说。CLR(C#、F#、Visual Basic 等)中将其称为“程序集”,JVM(Java、Kotlin、Clojure、Scala、Groovy 等)称其为“JAR”或“包”,各种操作系统的动态链接库里也有模块的身影(Windows 上叫 DLL,Unix 中叫 so 或者 a,MacOS 则是 /Library 目录下的 Frameworks)。它们的内部格式有异,但基本目标相同:实现一个独立构建、管理、版本控制和部署的代码单元,以供重复使用。
咱们来看看计算机科学论文中是怎么给模块下定义的:
“项目工作的明确定义与细分,保障了系统的模块化。每个任务都将形成一个独立的、不同于其他的程序模块。在实现时,每个模块及其输入 / 输出都经过明确定义,与其他系统的预期接口不会混淆。在检查开始前,先对各个任务进行同步以避免调度问题。最终,系统会以模块化形式进行维护;系统错误和缺陷可被追溯至特定的系统模块,从而限制错误搜索的具体范围。”
上述论断来自 David Parnas 的开创性论文《On the Criteria To Be Used in Decomposing Systems into Modules》(https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf),这可是篇 50 多年前的 1971 年写成的文章。其中提出的明确定义出“独立的、不同于其他的程序模块”已经涵盖了半数所谓微服务优势,并证明咱们 50 年前就在这么做了。
所以,何必为了微服务而过分激动?毕竟我们想要的微服务优势,其实跟微服务、服务甚至是分布式系统都没啥关系。
组织清晰度。
亚马逊一早就在公开讨论微服务概念,但却没法像独立开发团队那样真正把这套架构用起来。理由很简单,大厂面对的阻力更多——DBA 团队得做相应的架构变更,QA 需要构建新的 bug 测试,基础设施要额外采购服务器,UX 团队则得为演示创建新原型。
这些元素相加,导致架构变更难以推进。而这其实是一个个普通 IT 团队中各类职能的缩影(涵盖分析、开发、设计、测试、数据管理、部署、管控等)。换言之,要想拥抱微服务架构,那现有团队要么想办法集中起各种不同的技能组合,要么是要求每位成员都具备完整的技能集(也就是所谓「全栈开发者」),这明显会大大增加招聘难度。另外,大家还得为生产中断负责,成员们得承担起随叫随到的责任,这些新要求肯定会在薪酬和法律条款上有所体现。只有解决掉所有这些问题,各个团队才能独立构建工件,让打字速度成为开发效率的唯一瓶颈……
但这可能吗?当然不可能。
分布式计算谬误。
分布式计算谬误最早由 Peter Deutsch 在 80 年代的 Sun 公司内部演讲中提出,随后又出现在 1994 年由 Ann Wolrath 和 Jim Waldo 撰写的开创性论文《A Note on Distributed Computing》(https://scholar.harvard.edu/files/waldo/files/waldo-94.pdf)当中。
“概括来讲,分布式系统很难被「做对」——无论这个「做对」是指性能、可靠性还是可扩展性。”
即使是在五十年前,在把系统拆分成运行在单个操作系统节点上的内存模块时,由跨进程或库边界数据传递带来的成本都可以忽略不计。然而,当跨网络线路进行数据传递时,如今的大部分微服务会把通信延迟提升五到七个数量级。而且这个问题没法通过向网络添加更多节点来“扩展”解决——反而是节点越多,情况越糟。
确实,把微服务托管在同一台机器上、加载到运行独立微服务容器镜像的虚拟机集群内,确实能降低一些相互影响(例如使用 Docker Compose 或 Kubernetes 托管一组 Docker 容器)。但这样会增加虚拟机进程边界间的延迟(根据七层模型,即使其中某些层可以完全模拟,也必然拉高延迟),而且单一节点还会带来运行可靠性问题。
而且还没等解决掉分布式计算谬误,另一个相关问题又跳了出来:企业计算谬误。
认真考虑自己在谈论什么。
有必要把问题拆分成一个个独立实体吗 ?如果需要,其实用托管在 Docker 容器中的独立进程就够了,或者可以在遵循标准化 API 约定或其他选项的应用程序服务器中引入独立模块。总之,这是个非常成熟的应用需求,过去 20 年来的任何技术都能解决,包括 servlet、ASP.NET、Ruby、Python、C++,甚至是最不好用的 Perl。关键是在动手之前,先为集成和通信约定建立一套易于理解的通用架构底板。
是不是想减少团队面对的依赖项 ?那最靠谱的办法应该是整理各依赖项,再跟合作伙伴一同确认可以消解掉其中哪些。如果想要继续坚持“以技能为中心”的组织结构设计(也就是把数据库、基础设施、QA 和开发等职能按小组进行明确划分),那就跟公司高层合作,尝试将每位成员以“矩阵元素”的形式填充进团队报告体系。总之,最重要的是确保团队对自己所构建的内容拥有清晰认知,而且可以明确向他人解释自己的服务 / 微服务 / 模块到底有什么用。 关键是给团队以方向和目标、达成目标的自主权,同时辅以必要的鼓励和引导 。
也许这两大需求才是我们追求微服务的真实意义。谁能解决问题,就选择谁——真的没必要一定是微服务。
参考链接:
http://blogs.newardassociates.com/blog/2023/you-want-modules-not-microservices.html
https://news.ycombinator.com/item?id=34230641