关于微服务的一些深度思考
我知道微服务这个话题已经被讨论的太多太多,但我还是想以我在Web应用设计的经验出发,发表一些我的个人观点:
很多人认为微服务架构解决的是与伸缩性和性能有关的软件问题。但其实他们解决的最重要的问题其实是:一个组织的问题。
康威定律永远都适用于此。当你考虑构建的软件应该是什么样子时,你需要先考虑一下你的组织架构应该是什么样子。它们总是相辅相生的。
如果只是一个独立团队,从这个角度出发,上来就设计多个可移动组件其实并没有什么太大意义。谁是每个组件的所有者?某个服务如何才能真正独立于其他服务运行?就因为这样更方便快捷,然后就做一个类似微服务架构的东西?这其实很容易让人混淆。
因为他实际上更像是一个“庞大的分布式单体”。从微服务架构起步来设计应用似乎是众多人的错误做法。软件的架构最终往往会和你的组织的架构是一样的。这是必然的。
如果你只有一个团队,千万不要从微服务架构开始。
随着组织规模的扩大,越来越多的人加入到团队中,此时才是真正提出重新审视当前架构设计问题的时候。
随着团队的成长,团队管理会变得越来越困难。将这个庞大的团队拆分成多个、更小、更独立的团队是团队管理的自然步骤。那么我们需要考虑的问题是:这些团队如何对他们负责的产品组件拥有完全的所有权? 如何让团队掌握自己的“命运”?
对于一个真正独立的团队来说,它需要在每一层都有充分的决策权:从UI/UX,到后端将要暴露的API,一直到支撑整个团队的基础架构。作为一个单体应用程序这样做当然是可行的,但是这样团队就需要在他们的开发过程中进行同步,特别是在部署阶段。他们经常会踩到对方的脚。因此,需要创建一个反映组织架构的架构。微服务恰好解决了这个痛点--团队规模的伸缩。
服务是需要可组合的,同时需要很好地相互配合,就像你在一个单体中创建可组合的模块一样。仅仅是把它拆开,然后再简单地在它前面配一个Web服务器并不能拯救你。
对于跨多个领域的特性,必须明确数据的所有权,以及清晰一致的API,否则就有可能把所涉及的服务之间的关系变得复杂化。定义这些边界是开发此特性的团队的责任。服务之间的通信应该能反映团队之间的通信。
以下为精彩评论节选。
acjohnson55:
微服务不是一个解决方案,它其实反应了一种能力。对于团队来说,能够以轻量的可视化的管道来部署微小的服务来满足业务需求,这是非常强大的。他们是否应该以这种方式打破他们的组织,可能还是需要具体情况具体分析。
在我过往的工作经历中,人们都忽视了微服务的成本,因为大家都陷入了这是一种去耦合的错觉。微服务肯定会使简单的功能充分包含在服务中变得更容易,但是一旦一个功能跨越了服务,甚至影响了服务的契约,你就会陷入比单体架构更痛苦的境地。微服务其实是以牺牲集成的复杂性为代价来换取局部的简单性。
微服务,作为一种哲学,在网络层编码你的组织架构。我希望你们一开始就能把它分解出来,因为改变它会很痛苦。
我非常支持有那些意义的小型服务。但就像“所有功能都应该是小的”的建议一样,微服务已经被视为一个设计原则,而不是一个有争议的决定。
satyrnein:
在我上家公司,我所在的一个团队选择重新构建一个单体应用,最终选择使用微服务来“包容”复杂性。他们构建好新的服务,然后用自动化测试来验证输入和输出,他们对此感到非常自豪。随后来到了集成,很明显此时的他们忘了考虑如何把数据从当前的单体应用来迁移数据,如果当前的服务不可用会发生什么,如何在单体应用到微服务迁移时生成报告,等等。
在这种情况下,使用微服务就像喝醉了一样:一种将你所有的问题暂时抛到脑后,只关注眼前的事情。但你眼前的问题并没有消失,实际上,只会让问题变得更糟。
geodel:
确实,我自己做了简单的计算。假设我们把一个服务分成5个微服务。现在,如果假设两个服务之间有5%的集成相关问题(显然这种情况不会在单体应用上存在),我们实际上看到的是端到端事务中会有20%的错误几率。
对我来说,这个比例相当高。在我们的场景里,有些报错还可能是很复杂的,所以可能会被忽略,从而导致缓慢的数据损坏。
amelius:
众所周知微服务已经经过了炒作周期的兴奋阶段,但并不是说它现在过时了。微服务架构算是我过往印象比较深的项目之一。并且,即使作为行业的最佳实践,但也能看到各种各样失败的案例。文章是对的,关于微服务最棘手的问题实际上是组织问题,而不是技术问题。
我的看法是,正在考虑采用微服务的开发团队应该认真考虑他们对组织结构、团队间和部门间沟通的影响程度。如果管理层非常在意发生在他们自身的各种微小变化,我不会建议给他们提供通过微服务实现可持续成功的几率是多少。也许是其他形式的SOA,但不是真正的微服务。
bsder:
“微服务,作为一种哲学,在网络层编码你的组织架构。”
我其实也并非全盘肯定,但,我非常希望人们能够把 “微服务哲学”采纳到以脚本语言为基础的应用当中。
例如,如果我想为OpenOffice编写脚本,我只能用OpenOffice自带的Python。但,它没有任何更新;老得不能再老了;还只兼容二进制的。这算是极其恼火的限制。
然而,如果他们能提供一个任何人都可以与之交谈的“微服务接口”,而不仅仅是指定的Python,那么你就可以使用一种完全不同的语言运行自己的Python或脚本。
这里的OpenOffice只是个例子,但这并不是针对他们的。
cjfd:
如果一个公司程序员少于100人,你甚至不应该考虑微服务。通常情况下,微服务被引入不是因为它是否真的有价值,而是因为当下流行或被程序员的简历描述所引领的。相对来说,我到是都更喜欢重构一个糟糕的单体应用,而不是重构一个糟糕的微服务。
belter:
整个事情的问题点就是从它们称为微服务开始。倒不如就叫它面向服务的架构吧。不要把麻烦都扯到微观世界上,单体架构此时需要些尊重。
cjfd:
面向服务的架构是一个比微服务更普遍的类别,而且出现的时间要长得多。在许多情况下,它在技术上更有说服力。举个例子,你有一个API,它不是世界上最稳定的东西,当它不正常时,你不希望它把整个应用程序都搞垮。
因此,你可以将其放在一个单独的服务中,以便于重新启动。这是分离可执行文件的一个很好的技术原因。当人们毫无理由地引入这些区分时,问题就出现了。这样就会遇到RPC的所有问题,而没有任何好处。也就是说,一个人毫无理由地把事情弄得更复杂。
codyb:
之前有个同事,我们当时在一个有4人规模的创业公司,那会的他一直提倡微服务。我们甚至没有真正懂DevOps的人,连一个真正意义上的VPC用来提供CIDR块以隔离我们的数据库和公共网络也没有,就这样,就能搞微服务?
真不懂这是为了什么!我们连用户是谁都没搞定。
然后我们就开始自己测试应用的使用体验,因为除了创始人,我们并没有实际的用户,这感觉就像拔牙似的。
这些完全和我的想法背道而驰。
BoorishBears:
对我来说,微服务不应该是一个架构。
你可以在一个单体应用中拥有可以独立伸缩的功能模块,这些功能块不应该是微型的,它们应该是有意义的功能模块,以证明剥离它们的开销是合理的。
在某种程度上,你的评论反映了这一点,很多地方证明了微服务的合理性,他们的“微服务”提供的请求比一般公司的整个代码库还要多。
这就是“大数据”,有着10GB的日志。
solmag:
我支持你所说的模块化,消息队列,异步事件。我个人理解除了最后一个其他都对。
cjfd:
像消息队列和异步事件这样的东西对于你喜欢的编程语言来说都是可执行的。然后,你可以在具有与机器内核相同线程数的线程池中运行它们。
jayd16:
所以你的意思不仅是单体应用,还有单一的实例?仅在进程和非共享队列中?那么,在各个单体之间没有负载平衡么?
cjfd:
不一定。我们可以在应用程序中使用这些工具,但这并不意味着我们不能将其与某种形式的RPC结合起来。特别是,是否需要负载平衡貌似完全是另外一回事。
另一方面,为什么不能用单体应用,单体实例?如果一个人使用一种性能语言,想想看,一台机器上能运行多少。如果他的需求增长,那么就应该有一些扩展计划,这一点是肯定的,但如果不是乱搞,比如在使用某种形式的RCP的应用程序之间运行队列等,那么你可能会惊讶于你的单体应用实际上可以做多少事。要知道,搞一套支撑微服务的配套组件完全不是免费的。
papito:
首先,我们把这些都统称为“分布式系统”。那些将伸缩作为需求的场景都放到这个系统里。
这个世界最终并没有“打破摧毁”分布式系统。大多数公司创建了多个微服务,将他们的小部分研发放在风口浪尖,因为“这就是FAANG的方式”(Facebook,Apple,Amazon,Netflix,Google)。
所有的DRY原则都被抛弃了,现在必须在生产环境中进行调试——新的术语是OBSERVABILITY(可观测性)。
更不用说分布式系统的实际原因是要独立地扩展系统的多个部分,但是在进行扩展之前,你需要知道需要分别扩展哪些部分。当然,我看到的是拓扑结构确实反映了公司的结构。这不是“需要单独扩展的东西”,而是“Y团队正在做X,而Y团队不想和Z团队交谈,所以他们将创建一个服务,以确保他们不必与人交谈”。
人都是自我的。可我们还是得互相交流,而且是大量的交流,因为事情总是不停地出错,却没人知道为什么。
Dropbox、Instagram、StackOverflow——这些公司至今基本上都是单体应用。你认为你的应用需要像谷歌一样,我觉得这实在有些过于夸张。
所以,我也不会太深入讨论一个,能去解决这个大多数人都没有遇到的问题,而浪费金钱、人力、CPU和二氧化碳排放。
recursivedoubts:
微服务的意思就是:独立部署和扩展的,且小型、单一目的的服务。在讨论微服务时,我经常被康威定律所左右,但团队其实不会以这种方式组织自己。相反,团队在宏观服务上工作:给交付团队发邮件,或监督团队,或其他。在大多数情况下,最好将这些宏服务作为一个整体来实现和部署,然后通过一个合理的API向外界展示。
throwaway984393:
微服务之所以被引入,是因为它们似乎能够很好地解耦和扩展,但它们却犯了一些错误,这让一切变得更加困难。通常情况下,他们不会计划如何协调他们的工作,这就在设计中留下了空白,并给业务带来了更多风险。
Team A:
↑ Email product:
__|_____________ ↑ Service C <----------
/ How do we \ | Service D <-- \
| work together | --------------------- \ ------\--------------
| on overall | | How do we manage | | | How do we |
| system design? | | stakeholder risk? | | | coordinate changes? |
\_______________/ --------------------- | \--------/----------- /
| | | |
↓ | \ |
Team B: ↓ | |
"Data" team: | /
Service A <---|-------
Service B <--/
最重要的是,他们甚至没有真正的微服务。它们开始直接调用彼此的数据,而不是在API层进行接口,它们对彼此的工作方式停留在假设的层面上,它们不做负载测试或做限制或配额…… 因为他们都不了解这个系统的其他部分,所以他们不知道他们彼此之间缺乏认知其实才是导致他们出问题的原因。
即便是在跨团队合作中,如果他们在一个单体应用的项目里,他们其实很有可能在某个偶然的机会中理解到这个系统的其他部分。
bob1029:
从一个单体应用开始,只有在你被服务器硬件瓶颈的驱使下才会把它分解掉(例如,这个进程内存不够或者吃掉了所有的CPU/IO)。一旦你打破了你的单体应用,你就失去了它最强大的特性——直接调用。我看到开发人员花费在JSON连接协议、CORS问题、API端点设计等方面的时间真的开始让我担心了。有时我会想,是否有人想做这种工作,或者这对某些人来说只是个游戏而非实际工作。
我给这个微服务做了个旅程图:单体应用 => 微服务 => 单体应用。
我曾经强烈提倡使用微服务,因为它可以很容易地将所有的关注划分为每个人可以拥有的快乐的小桶。我们曾经把我们的产品卖给我们的客户,认为它有一个“面向微服务的架构”,就好像它能神奇地解决我们所有的问题,或者我们认为这才是客户本应该关注的点。
然而,现实是,所有这一切对我们造成的后果是,我们现有的所有客户都离开了我们,随后,我们不得不重新评估在这个市场开展业务的愿景。所有有趣/闪亮的技术对话和想法瞬间蒸发成现实的乌云。
最终我们使用了单体架构,我们回到了正轨上。我们再次专注于业务和客户。当我们弃用最终的服务到服务的JSON API/控制器时,我们感到如释重负。不用再检查一堆不同的日志,也不用再拉出wireshark来弄清楚任意版本的2段不同的代码之间到底发生了什么。
jspash:
我马上就要完成一个项目了这个项目就是把一个面向服务的系统还原成一个单体应用。每一步都减少了代码行数,修复了bug,节省了金钱和时间。我是如此的快乐,同时打算把这个作为我的副业。要知道,我“拯救”了那些打算被卖掉的,有着复杂梦想的公司。
bob1029:
最近我经常想起这个问题。我认为我们正直接面对的是科技领域最大的新兴市场之一。我们认为,到2025年,TAM (Technology Acceptance Model,技术接受模型)将如何解决网络流量问题?并不是所有的企业都会因为糟糕的技术选择而走向失败,同时还能支付可观的咨询费来为他们解决问题。
我现在已经有了一个处理这个问题的流程。它通常从excel的领域建模开始,所有的业务相关人都在同一个讨论回环里,直到每个人都同意为止。我发现如果你把这部分做对了,那么使用C#还是Python,或者使用AWS还是本地数据中心来构建实际的产品都没有什么关系。当你的生产环境仅仅涉及了一个100MB的ZIP文件和3行Powershell,你很难说到底放在那里更优。
wayoutthere:
我还发现了另一个例子:有关限制运维爆炸范围的和部署问题相关的。我曾经与一个大型电信客户合作,他们的AAA(Authentication,Authorization,Accounting)Web服务(是的,我们不需要单独给他们配一套认证系统,因为这个客户足够强大,并且有足够的专业知识)作为他们的帐户管理Web服务,他们将这几个组件放在了同一个单体中。帐户管理服务频繁地进行开发,以支持新功能,而AAA代码一年只能更新几次。
如果没必要,为什么要触摸每一个产品都依赖的关键Web服务呢?如果帐户管理离线,你的业务仍在运行,但当身份验证离线时就不一样了,即使是对认证的短暂中断也是不可接受的(数以百万计的客户),由于服务的重要性,任何更新都必须在受限的窗口中执行。
因此,我们将任务关键部分分离出来,将它们放在各自的代码库中进行独立的版本管理,这让我们能够更快地进行帐户管理工作,因为我们可以放心地部署那些并不一定百分百确定的代码,这归功于我们不需要等待维护窗口。
FinanceAnon:
基本上,我觉得单体和微服务没什么区别。
单体应用中,可以调用另一个功能或者类,但在微服务这些都叫做http调用。我个人理解服务的好处是能够独立地扩展不同的微服务,能够为不同的微服务选择不同的语言,并且随着越来越多的人在做它,代码库里的冲突也减少了,但你仍然需要处理向后兼容性和终端的版本控制。
当你这样看的时候,我觉得很有趣。微服务本质上是一组作为一个不断部署的单元函数。但在Lambdas中,每个函数都是可以独立伸缩的单个单元。
trixie_:
http和功能调用是有本质上的区别的。功能调用基本上是没机会出现“无法连接”的错误,然而,对于服务来说,一个服务无法与另一个服务通信的原因是无穷无尽的。构建一个系统来处理服务间通信和只调用同一个二进制文件中的函数,这两点相比,无疑前者是一个巨大的开销。
bluGill:
我关注微服务是出于不同的原因:安全。我已经放弃了我们的代码将永远是完全的想法。然而,微服务意味着,如果有人闯入一个服务,他们无法看到属于另一个服务的数据。(每个服务使用不同的用户运行,因此OS保护意味着文件命令不能打开这些数据)。
这只能在非安全代码相关的一些威胁起到保护作用,但保护层以内才是威胁的关键。
choeger:
如果我是一家公司的首席技术官,微服务会让我做噩梦。你如何对使用过的自由软件(许可证和安全更新)进行尽职调查?如果每个开发人员都可以添加一个新的自动伸缩服务,你如何规划整个资源使用配置?是谁在跟踪部署以防我们不小心让系统过载?如何一致地重构跨服务特性?最糟糕的是:谁来跟踪服务之间的n*n个合约?
我的意思是,是的,我知道每一个问题都可以解决,有时以一种相对直接的方式。但是,谁真正涵盖了所有这些方面,那么谁又停掉了仅仅运行几个月就跑偏了的服务呢?
roberthahn:
我发现人们对微服务的反应很有意思。我总是用Unix用户空间里的工具的相同框架来看待它们——许多小型、集中的应用程序做了一些非常好的事情,加上一个超级通用的IPC(Inter-Process Communication,进程间通信)机制——在Unix的情况下,使用管道或消息队列。
但是Unix并不都是小工具。我们有服务器来处理繁重的工作,比如数据库。
然后挑战就变成了,你如何设计IPC机制?也许这个问题其他人也有。但我现在还不知道答案。但这是我经常思考的问题,我还没有看到令人信服的证据证明“微服务总是不好的”。
管道对于小型处理任务非常好,非常有用。“我想知道单词‘Parameter’在这个代码库中的源文件中出现的频率。”这是伟大的!管道是你的朋友。它们是人类已知的解决这个问题最快的方法。但它们也很脆弱你可能没有想过这个事实,NoParameter也出现在代码库中,你不想再计算它。
现在,当写一些大而复杂的东西时,管道根本跟不上。没有人想去浏览一个包含着100个通过管道进行通信的可执行文件。这将是可怕的。你正在寻找的IPC机制,它实际上可以处理浏览器所需要的东西,叫做“函数调用”,当C被引入时,它已经非常流行了。
服务之间的交互机制,例如REST调用,介于管道和函数调用之间。它们比管道可靠一点,但跟函数调用相比就不那么可靠了。它们可以用于比管道更复杂的任务,但不应该用于复杂到需要调用函数的任务。
KronisLV:
“如果你只有一个团队,千万不要从微服务架构开始。”
这可能是一个很好的观点,但这并不是故事的全部。
就我个人而言,我同意大多数团队不应该从微服务开始,单一服务可以完全足够,而且单体更容易运行和验证。否则,最终你可能会面对繁重的运营复杂度,以至于根本没有足够的能力做实际的开发软件,并确保它实际上是好的。
但是,你还需要考虑单体应用中的耦合,以便在需要时可以轻松地将其分解。实际上,我在我的博客中写了更多关于这方面的内容,在一篇名为“模块化:因为我们需要扩展,但我们也负担不起微服务”的文章中:https://blog.kronis.dev/articles/modulith-because-we-need-to-scale-but-we-also-cannot-afford-micro-services
有一种想法是错误的:他们觉得他们的代码是能工作就不用考虑再多,所以他们生成的PDF报告(PDF Report is a code first reporting engine, PDF Report是一款代码报告引擎)同整个代码库紧密耦合,这与他们的文件上传和处理逻辑是一样的。
所以当你突然需要前端和后端分离,或提取某个组件,因为它阻止了更新某个新技术(例如,Java 8到Java 11。一切都可以工作,只有那一个部件不行,所以就得为这一个部件保持旧版本或者不更新版本),然而,单体应用的情况下你就不能这么实现。
容器迟早也会出现,因为它们可以以一种可管理的方式处理大量应用程序,但同时也很容易出错,这可能是由于不了解技术或一些潜在的问题引起的。
许多人认为“做容器”就是把他们的传统的单体应用放在一个容器中,然后就结束了。但事实并非如此,如果这样做,你仍将面临许多运营挑战。要“正确地”处理容器,你需要实际了解应用程序是如何配置的,如何处理日志记录、外部服务以及如何处理持久化数据。
这也不是“没有真正的苏格兰人”的谬论(appeal to purity,诉诸纯洁,是一种非形式谬误,是指在原来的普遍宣称遇到反例时,提出一个理想、纯静的标准以为其辩护的论证方式。),他们试图收集一些更有用的可行步骤的建议,例如:https://12factor.net/
尽管这些建议与容器本身没有直接关系,但在容器部署之外,它们自己也能很好地工作。
最后,我还看到Kubernetes几乎被用作容器的同义词,你不可能在谈论容器的时候不提到它。然后,实际上我也看到有些项目失败了,因为人选择它是因为它的受欢迎程度,却无法应对它们的复杂性(“哦,嘿,现在我们还需要Istio Kiali、Helm、哦,还需要一个Nexus去存储Helm的charts,而且我们还得编写它们,然后还有一个服务网格和一些键值存储服务”),而像Docker Swarm或Hashicorp Nomad这样的简单产品就足够了。
事实上,我还有另一个关于这个主题的博客主题,“Docker Swarm over Kubernetes”:https://blog.kronis.dev/articles/docker-swarm-over-kubernetes
老实说,这个观点也适用于容器以外的环境,例如,选择像Apache Kafka而不是RabbitMQ这样的东西,然后被其复杂性所困。
总而言之,在为任何软件选择架构时,都得考虑很多因素,同时也要考虑正确使用的技术。在某些方面,这比仅仅将一些文件推送到运行PHP的FTP服务器更慢、更麻烦,但从长远来看,它也更安全、更高效。遗憾的是,如果一开始就选错了,那么糟糕的设计决策将随着时间的推移而恶化。
maxdo:
这又是一个“基于我的经验”的观点。根据我的经验,我们的服务会因为性能而失败,因为我们的nodejs仍然是单线程。考虑到这一点,我们要么按照角色重复部署大型服务,让你有相同的编排复杂度,要么用不同的语言重写,这意味着拥抱微服务。(PS:我们的产品最初是由非技术的联合创始人编写的,他们从一开始就使用heroku和微服务。他们的使用范围很小,所以一直保持免费的等级。所以在这个用例中,微服务更便宜。)是的,如果你的后端是一个简单的服务,那么对于没有经验的开发人员来说,跟上它的速度会更简单。
SomeCallMeTim:
Node可以使用Cluster模块运行多进程。将节点写成一个单体,但在100个实例中运行它也是一种选择。角色可以在软件中实现。当你部署单个服务时,“编排的复杂性”要小得多。
因此,单线程Node本身并不是使用微服务的理由。
我倾向于为服务的核心API编写一个整体,然后为需要独立伸缩的任务(或者需要在高内存/高性能实例上运行)编写微服务。所以我并不完全反对使用微服务。但只有当它们对我们有利时,我们才选择使用它们,而不仅仅是“因为它们已经被这样写了”。
maxdo:
如果你在不同的角色下部署相同的二进制文件,那么复杂性方面的问题是相同的。
使用现代工具部署和托管存储根本不是问题,你可以使用模板或构建,甚至Lambda与GitLab Github CI功能相结合。最新的产品能够让你无需运维考虑的使用微服务。在我的团队中,我们没有专门的运维人员,从开发人员到产品部的部署都是由开发人员通过Git运行的。
Redis 实现分布式锁真的安全吗?
如何成为一名拖垮团队的程序员?别模仿...
我的前老板绝对是个二货!