架构有时候挺难捉摸的——人们不断提出新想法,这些想法在没有任何背景或者细微差别的情况下快速成为主流“实现方式”;而迫于寻求架构改进之法的行业则盲目跟进,不假思索地全盘接受。
在这股大有问题的风潮中,微服务正是最新一代弄潮儿。可微服务到底哪里好,我们又为什么要使用微服务?是时候对这个问题追根溯源了。
— 1 —
究其根本,微服务号称能带来……
很多好东西!
这些听着都挺耳熟,但这六条说法其实各有来历:两条来自微服务文献 (博文、论文等),两条来自20年前的EJB文献,两条来自Oracle Tuxedo——一项四十多年前的技术。各位朋友分得清它们的真实归属吗?
换句话说,咱们这个行业其实一直在用同样的炒作方法,同样的宣传伎俩。
不记得过去的人,注定要重蹈覆辙。
——George Santanyana,《理性的生活》(1905年)[1]
说起微服务这边的炒作,一篇企业博文[2]列出了选择微服务的十大理由:
宣传材料看得够多了,下面我们一一对这些说法进行甄别,只是这回要从真实技术的角度出发:
而且聪明的朋友可能已经注意到,上面大概一半的观点都在强调共通的主题——创建和维护更小、更独立的代码和数据“块”,让各块间相互版本化,使用共同的输入和输出以实现整体系统集成。这就好像……
— 2 —
究其根本,我们在微服务中发现的是……
模块。
没错,大量的低级“模块”。这个诞生自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[4]》,文章是在50多年前的1971年写成的。这里提出的明确定义“独立的、不同于其他的程序模块”已经涵盖了微服务的约半数优势,就是说同样的方法我们已经用了50年之久。
所以,何必要为微服务而激动不已?
毕竟微服务的实质跟微服务、服务甚至是分布式系统,根本就没有关系。
— 3 —
微服务的实质,在于……
组织清晰度。
亚马逊是最早公开讨论微服务概念的企业之一,但他们自己并没有特别积极地运用这种新架构。毕竟为什么要瞎折腾呢,DBA团队得做相应的架构变更、QA需要开发新测试来发现bug、基础设施团队得采购新一批服务器、UX团队则要为演示创建原型。这些都是额外的负担,也代表着现实阻力。
在这种情况下,开发团队根本就没法顺利推进自己预想的架构调整。而以上提到的各个部门,只是普通IT团队中各类职能(分析、开发、设计、测试、数据管理、部署、管控等)的小缩影。
要想实际推动,则要么保证现有团队能找到不同的技术组合,要么硬性要求每位团队成员都具备完整的技能集(即所谓「全栈开发者」),可这无疑会大大增加招聘难度。另外,团队还需要对自己的生产中断事件负责,每位成员都得随叫随到——这些都会体现在薪酬待遇和法律影响上。当然,如果真能打通这些关节要害,那各个团队确实能够独立构建自己的工件,于是生产力水平将一路飙升、再无阻碍。
可这种只有理论可行性的事情,真能实现吗?
— 4 —
在微服务当中,我们还发现了……
分布式计算谬误。
有些朋友可能不太熟悉,这里的谬误来自Peter Deutsch在80年代给Sun的同事们做的演讲。1994年,Ann Wolrath和Jim Waldo的开创性论文《A Note on Distributed Computing[5]》则再次提及。
“无论如何定义「正确」,分布式系统都很难在性能、可靠性和可扩展性方面真正正确。”(松散解释)
当我们将系统拆分成运行在单一操作系统节点上的内存模块时,即使是在五十年前,跨进程或库边界进行数据传递的成本也可以忽略不计。但当我们开始跨网络线路传递数据时(也是目前大多数微服务的通行作法),则通信的延迟会瞬间提升五到七个数量级。这不仅仅要求我们向网络添加更多节点来“扩展”,同时也令整个系统快速恶化。
没错,通过将微服务托管在同一台机器上、加载到运行各独立微服务容器化镜像的虚拟机集群内,确实能降低不同服务间的相互影响。(例如使用Docker Compose或Kubernetes托管一组Docker容器。)但这种方式会增加虚拟机进程边界间的延迟(因为我们必须遵循七层模型规则,即使其中某些层可以完全模拟,产生的延迟仍无法忽视),而且会在各独立节点上引发运行可靠性问题。
更糟糕的是,还没等我们切实解决分布式计算谬误,那边又冒出了与之相关但又独立存在的新麻烦:企业计算谬误[6]。
— 5 —
探究微服务的核心,我们最好能……
重新思考自己的需求。
真有必要把问题拆分成一个个独立实体吗?用托管在Docker容器里的独立进程就能做到这一点,或者也可以在遵循标准化API约定或者其他选项的应用程序服务器中引入独立模块。
这是个有成熟解的问题,过去20年来已经有无数种技术能够实现,包括servlet、ASP.NET、Ruby、Python、C++,甚至是闻者头痛的Perl。其中的关键,是如何建立起易于理解的集成和通信约定的通用架构基础。
我们要不要减少开发团队面对的依赖项?认真观察这些依赖项,再跟合作伙伴共同确定可以把其中哪些做做整合。如果组织不想打破结构体系中“以技能为中心”的指导思路(就是把员工明确划分成「数据库组」、「基础设施组」、「QA组」和「开发组」等),那也至少要尝试建立一个边界相对模糊的报告结构,把每位成员视为团队“矩阵”里的一个元素。
而且最重要的是,一定要保证团队对自己正在构建的内容拥有清晰认知,保证他们能轻松向他人解释自己服务/微服务/模块的核心意义。关键是给团队以方向和目标、达成目标的自主权,同时辅以必要的鼓励和引导。
这就是事情的真相。你想要微服务?不,你想要的只是模块。
相关链接: