在Node.JS的巨石中呆了9年

来自https://reneaigner.deviantart.com的整体


一周前,我在Node.JS会议上发表讲话,并答应许多人发布性能记录。 后来我意识到,在规定的半小时内我无法容纳一些有趣的事实。 是的,我自己更喜欢阅读,而不是看和听,所以我决定以文章的形式发布我的演示文稿。 但是,该视频也将在链接部分的帖子结尾。


我决定告诉您一个痛点-独身生活。 枢纽上已经有数百篇关于此的文章,评论中有成千上万的副本被打破,真相长期以来一直在争议中消亡,但是...事实是,我们在OneTwoTrip中拥有非常特殊的经验,这与许多人在其中撰写某些架构模式不同真空度:


  • 首先,我们的整体已经有9年的历史了。
  • 其次,他一生都在高负载下工作(现在每小时有2300万个请求)。
  • 在NaN中,我们在Node.JS上编写了我们的巨著,在过去的9年中,这种巨变已经变得面目全非。 是的,我们从2010年开始在节点上写作,我们为勇敢的狂人们演唱一首歌!

因此,我们有很多特殊性和实际经验。 有意思吗 走吧


免责声明时间


本演示文稿仅反映其作者的私人意见。 它可能与OneTwoTrip的位置一致,也可能不一致。 那真是太幸运了。 我是公司其中一个团队的技术专家,除了我自己之外,不假装客观或表达别人的意见。

免责声明二


本文介绍了历史事件,目前一切都错了,因此请不要惊慌。

0.怎么发生的


Google中“微服务”一词的查询趋势

一切都很简单-九年前,没人知道微服务。 因此,我们像其他所有人一样开始写作。


1.感到痛苦


在这里,我将描述这9年中我们遇到的问题情况。 其中一些已解决,一些已被黑客规避,有些根本失去了相关性。 但是对他们的记忆,就像战斗的伤痕,永远不会离开我。


1.1更新连接的组件



当协同作用是邪恶的时候就是这种情况。 因为任何组件都可以重复使用数百次,并且如果有可能歪曲使用它,那么它就不会丢失。 任何动作都可能导致完全无法预测的效果,并且并非所有的动作都可以通过单元测试和集成测试来跟踪。 还记得拖把,风扇和气球的故事吗? 如果没有,用谷歌搜索。 她是整体代码中最好的例证。


1.2向新技术的迁移


您要快递吗? 林特? 另一个测试或骚扰的框架? 更新验证器或至少lodash? 更新Node.js? 对不起 为此,您必须编辑数千行代码。


许多人谈论整体的优势,那就是任何修订都是原子提交 。 这些人对一件事保持沉默- 此修订将永远不会完成


您知道有关语义版本控制的老玩笑吗?


语义版本控制的真正语义:

主要=重大变化
小=重大更改
补丁=一点零钱的重大变化

现在想象一下,几乎可以肯定的是,任何小小的重大更改都会在您的代码中弹出。 不,有可能忍受它,我们确实定期收集力量并迁移,但这确实非常困难。 很好


1.3版本


在这里我必须说一些我们产品的细节。 我们有大量的外部集成,很少有单独出现的各种业务逻辑分支。 我真的很羡慕产品在生产中会在10分钟内真正执行其代码的所有分支的产品,但事实并非如此。 通过反复试验,我们为自己找到了一个最佳的发布周期,该周期最大程度地减少了最终用户遇到的错误数量:


  1. 该版本即将发布,集成测试将在半天内通过
  2. 然后在舞台上受到精心监督一天(针对10%的用户)
  3. 然后,在更加仔细的监督下,生产又有了一天。
  4. 而且只有在那之后,我们才给他大师的绿灯。

由于我们爱我们的同事,并且不在周五放映,因此最终这意味着每周大约有1.5-2次向大师发布。 这导致该发行版可以包含60个或更多任务。 这样的数量会导致合并冲突,突然的协同效应,对日志分析进行质量检查的全部工作量以及其他麻烦。 总的来说,我们很难释放出整体。


1.4只是很多代码


似乎代码的数量不应具有根本的重要性。 但是...不是真的。 在现实世界中是:


  • 更高的进入门槛
  • 每个任务的巨大构建工件
  • 较长的CI流程,包括集成测试,单元测试,甚至代码lint
  • IDE工作缓慢(在Jetbrains开发初期,我们多次用日志震惊了他们)
  • 复杂的上下文搜索(别忘了,我们没有静态输入)
  • 难以找到和删除未使用的代码

1.5缺少代码所有者


通常,任务的职责范围难以理解,例如在相关的库中。 最初的开发人员可能已经转移到另一个团队,甚至离开了公司。 在这种情况下,找到负责任的唯一方法是行政管理的任意性-任命和任命一个人。 对于开发人员和执行该操作的人员来说,这并不总是令人满意的。


1.6调试难度


记忆正在流动吗? 增加CPU消耗? 想建立火焰图吗? 对不起 在一个整体中,许多事情同时发生,以致于难以定位问题。 例如,要了解60个任务中的哪一个在投入生产时会导致资源消耗增加(尽管在测试和登台环境中不会局部复制),这几乎是不现实的。


1.7一叠


一方面,当所有开发人员“说”相同的语言时,这是很好的。 对于JS,事实证明,即使是与Frontend开发人员一起使用的Backend也可以相互理解。 但是...


  • 没有灵丹妙药,对于某些任务,您有时想使用其他东西。 但是我们有一个整体,我们无处可挡其他开发人员。
  • 我们不能仅仅依靠一个好的团队,就根据朋友的建议来到我们的建议-我们无处可放。
  • 随着时间的流逝,我们依靠这样的事实,即市场上没有足够的开发人员。

1.8许多团队对幸福有不同的想法



如果您有两个开发人员,那么您已经有两个不同的想法,即哪个是最佳框架,应遵循哪些标准,使用库等等。
如果您有十个团队,每个团队都有几个开发人员,那么这简直是一场灾难。
解决它的方法只有两种:“民主”(每个人都按照他的意愿去做)或极权主义(从上面强加标准)。 在第一种情况下,质量和标准化受到损害,在第二种情况下-不允许人们实现幸福感的人受到损害。


2.整体的优势


当然,对于不同的堆栈,产品和团队,整体中有一些优势可能会有所不同。 当然,数量不止三个,但我将不对所有可能的事情负责,仅对与我们相关的事情负责。


2.1易于部署


当您拥有一项服务时,进行升级和测试比一打服务要容易得多。 的确,此加号仅在初始阶段才有意义-例如,您可以提升测试环境,并使用从中获得的所有服务(开发的服务除外)。 或从容器中取出。 或其他。


2.2没有数据传输开销


如果您没有高负荷的话,这是一个可疑的加分。 但是我们就是这种情况-因此,微服务之间的传输成本对我们来说很明显。 无论您如何尝试快速完成此操作,都比其他任何事物都更快地将所有内容存储和传输到RAM中-这是显而易见的。


2.2一个组件


如果您需要在历史上的某个时刻回滚,那么用一个整体来做到这一点真的很简单-采取并回滚。 对于微服务,有必要选择在特定时间点彼此使用的服务的兼容版本,但这并不总是那么简单。 没错,这也可以借助基础架构来解决。


3.整体的想象优势


在这里,我接受了所有通常认为是加号的内容,但从我的角度来看,它们不是。


3.1代码-这是文档


经常听到这种意见。 但是通常情况下,新手开发人员会紧随其后,他们从未看到过几年前离开的人们编写的成千上万行代码中的文件。 嗯,由于某种原因,这一点通常是由整体支持者提出的-他们说我们不需要任何文档,我们没有任何运输工具或api-一切都在代码中,简单明了。 我不会反对这一说法,只是说我不相信。


3.2没有不同版本的库,服务和API。 没有不同的存储库。


是的 但是没有 乍一看,因为您了解该服务并非凭空存在的。 在与之集成的其他众多代码和产品中-从第三方库开始,继续到服务器软件版本,而不是结束于外部集成,IDE版本,CI工具等等。 一旦您了解了您的服务间接包含了多少个不同版本的内容,就可以立即清楚地知道,此附加功能只是一种糊涂。


3.3更容易监控


更容易 因为您大约只有一个仪表板,而不是几十个。 但这更加复杂,有时甚至是不可能的,因为您无法将图形分解为代码的不同部分,并且只有医院的平均温度。 总的来说,我已经在本段中谈到了调试的复杂性,我只想澄清一下,同样的复杂性也适用于监视。


3.4更容易遵守通用标准


是的 但是,正如我在段落中已经写过的,关于许多拥有幸福感的团队一样,这些标准要么是极权主义强加的,要么是由于缺乏他们而被削弱的。


3.5减少重复代码的机会


奇怪的意见是,代码没有在整体中重复。 但是经常见到他。 在我的实践中,事实证明,代码重复完全取决于公司的开发文化。 如果是,则将通用代码分配给所有类型的库,模块和微服务。 如果不存在,则在整体中将复制粘贴20次。


4.微服务的优点


现在,我将介绍迁移后的工作。 同样,这些都是根据实际情况得出的真实结论。


4.1您可以构建异构基础架构


现在,我们可以在最适合解决特定问题的堆栈上编写代码。 使用任何来找我们的优秀开发人员都是合理的。 例如,以下是我们当前拥有的技术的示例列表:


4.2您可以进行多次频繁发布


现在,我们可以制作许多小的独立发行版,它们更简单,更快,更轻松。 曾经只有一个团队,但现在已经有18个团队了。 或对此负责的人...


4.3更容易进行独立测试


我们减少了集成测试的时间,集成测试现在仅测试真正改变的时间,同时我们不担心突然的协同效应。 当然,我不得不四处走走-例如,学习如何制作向后兼容的API-但随着时间的流逝,一切都安定下来。


4.4易于实施和测试新功能


现在我们可以进行实验了。 任何框架,堆栈,库-您都可以尝试一切,如果成功,请继续进行。


4.5您可以更新任何内容


您可以更新引擎,库的版本,但是什么都可以! 作为小型服务的一部分,查找和修复所有重大更改仅需几分钟。 而不是像以前那样是几周。


4.6您无法更新


奇怪的是,这是微服务最酷的功能之一。 如果您有稳定的工作代码,则只需冻结它,然后就不用管它了。 而且,您将不必更新它,例如,以便在新引擎上运行产品代码。 该产品本身可以在新引擎上运行,并且微服务可以继续使用。 带有炸肉排的苍蝇最终可以单独食用。


微服务的5个缺点


当然,美中不足的是不完整的,仅靠坐下来并获得报酬的完美解决方案是行不通的。 我们遇到了什么:


5.1需要总线进行数据交换和清除日志记录。


通过HTTP进行的服务交互是一种经典模型,并且即使它们之间存在日志记录和平衡层,通常也是一种有效的模型。 但是最好有一个更独特的轮胎。 另外,您应该考虑如何在彼此之间收集和合并日志-否则您手上将只有粥。


5.2跟踪开发人员的工作。


通常,应该总是这样做,但是在微服务中,开发人员显然拥有更大的自由度,有时这可以引发Stephen King的事。 即使从外观上看,该服务正在运行-不要忘记应该有人监视他的内在状况。


5.3您需要一个好的DevOps团队来管理所有这一切。


几乎所有开发人员都可以以一种或另一种方式部署整体并上传其发行版(例如,通过FTP或SSH,我看到了这一点)。 但是对于微服务而言,有各种各样的集中式服务,用于收集日志,指标,仪表板,用于管理配置的厨师,伏特,詹金斯和其他好东西,而没有它们,您通常可以生存-但是这是不好的,而且难以理解。 因此,要管理微服务,您需要有一个良好的DevOps团队。


5.4您可以尝试大肆宣传,并用脚射击自己。


也许这是建筑的主要缺点及其危险。 人们经常盲目地跟随趋势,并开始在不了解架构和技术的情况下对其进行介绍。 之后,一切都陷入了混乱,他们对由此产生的混乱感到困惑,并例如在Habr上写了一篇文章“我们如何从微服务转变为整体”。 通常,只有在知道执行此操作的原因以及要解决的问题时,才可以移动。 还有你得到的。


6卡其色in Monolith


一些让我们生活在整体中的骇客稍微好一点,也更长一些。


6.1棉绒


乍看之下,在整体中引入棉绒并不是一件容易的事。 当然,您可以制定严格的规则,添加配置,然后...一切都不会改变,每个人都只是关闭了linter,因为一半的代码变成了红色。


为了逐步引入linting,我们为eslint编写了一个简单的附件-slowlint ,它使您可以做一件简单的事情-包含一系列临时忽略的文件。 结果:


  • IDE中突出显示了所有不正确的代码
  • 新文件是根据删除规则创建的,否则CI不会丢失它们
  • 老家伙逐渐统治并摆脱例外

在过去的一年中,有可能将大约一半的整体代码合并为一种样式,即几乎所有主动添加的代码。


6.2单元测试的改进


一旦与我们进行了单元测试三分钟。 开发人员不想等待那么多时间,因此仅在服务器上的CI中检查了所有内容。 一段时间后,开发人员发现测试失败,被诅咒,打开了一个分支,返回了代码……总的来说,他很痛苦。 我们对此做了什么:


  1. 首先,我们开始运行多线程测试。 Yandex有一个多线程摩卡变种,但是与我们合作并没有成功,因此他们自己编写了一个简单的包装。 测试开始快了一半半。
  2. 然后,我们从0.12个节点移至第8个节点(是的,过程本身将绘制到单独的报告中)。 看起来很奇怪,但是它并没有从根本上提高生产效率,但是测试开始快了20%。
  3. 然后我们坐下来调试测试并分别优化它们。 这给了最大的速度增长。

通常,目前,单元测试在prep挂钩中运行,并在10秒内完成,这非常舒适,使您可以在不中断生产的情况下运行它们。


6.3减轻神器重量


整体工件最终占用了400兆字节。 考虑到它是为每个提交创建的,因此,总的体积非常大。 腹泻模块( modclean模块的一个分支)为我们提供了帮助。 我们从工件中删除了单元测试,并清除了各种垃圾,例如自述文件,程序包中的测试等。 增重约为重量的30%!


6.4依赖缓存


以前,使用npm安装依赖项需要花费大量时间,您不仅可以喝咖啡,还可以例如烤披萨。 因此,首先,我们使用了npm-cache模块,该模块已分叉并完成了一些工作。 它使您可以将依赖项存储在共享的网络驱动器上,然后所有其他构建都可以从中获取它。


然后,我们考虑了组件的可复制性。 当你拥有一个整体时,传递依赖的改变就是上帝的祸害。 考虑到当时我们远远落后于引擎版本,更改某些第五级依赖关系很容易破坏我们的整个程序集。 因此,我们开始使用npm-shrinkwrap。 与他在一起已经很容易,尽管合并他的更改对于坚强的精神是一种享受。


然后,终于出现了package-lock和出色的npm ci命令-其运行速度仅比从文件缓存中安装依赖项慢。 因此,我们仅开始使用它,并停止存储依赖程序集。 在这一天,我带了几盒甜甜圈。


6.5发布顺序的分配。


而且这更多是一种行政手段,而不是技术手段。 最初,我反对他,但时间证明第二位技术专家是正确的,而且通常做得很好。 当这些发布在多个团队之间轮流分发时,很清楚错误在哪里出现,更重要的是,每个团队都对速度负责,并设法解决问题并尽快推出。


6.6删除无效代码


整体而言,删除代码非常令人恐惧-您永远不知道自己会被卡在哪里。 因此,大多数情况下,它只是躺在一边。 多年来。 甚至必须支持死代码,更不用说它带来的混乱了。 因此,随着时间的流逝,我们开始使用需求分析对死代码进行表面搜索,并进行集成测试以及在覆盖率检查模式下启动以进行更深入的搜索。


7整体切割


出于某种原因,许多人认为,要切换到微服务,您需要放弃您的独占,在草稿中写一堆微服务,一次运行所有这些-才会有幸福。 但是这个模型……嗯……您将无所事事,只花大量时间和金钱来编写必须扔掉的代码,这实在令人感到困惑。


我提出了另一个选择,对我来说似乎更有效,并且已与我们一起实施:


  1. 我们开始在微服务中编写新服务。 我们运行这项技术,跃跃欲试,了解我们是否愿意这样做。
  2. 我们将代码提取到模块,库或其中使用的任何内容中。
  3. 我们将服务与整体服务区分开。
  4. 我们将微服务与服务区分开来。 没有仓促和一次。

8最后


图片来自https://fvl1-01.livejournal.com/


最后,我决定离开最重要的事情。


请记住:


  • 你不是谷歌
  • 你不是微软
  • 你不是Facebook
  • 你不是Yandex
  • 您不是netflix
  • 你不是OneTwoTrip

如果其他公司也能奏效,那绝对不会使您受益。 如果您尝试用“对他们有用”一词来盲目复制其他公司的经验,那么很可能会以惨败告终。 每个公司,每个产品和每个团队都是独一无二的。 对某些人有效的方法对其他人无效。 我不想说明显的话,但太多的人开始在其他公司周围建立货真价实的组织,盲目模仿方法,然后将自己埋在假的圣诞树装饰下。 不要这样做。 实验,尝试并开发最适合您的解决方案。 只有这样,一切都会奏效。


有用的链接:


Source: https://habr.com/ru/post/zh-CN459206/


All Articles