• 把简单问题复杂化为何成为了当今技术圈的“主流文化”?
  • 发布于 2个月前
  • 190 热度
    0 评论
  • 浅歌
  • 0 粉丝 27 篇博客
  •   
复杂的说教
有一个非常著名的草图,其中一位工程师正在向项目经理解释一个过于复杂的微服务迷宫是如何为了获取用户生日而工作的,但最后还是未能成功。这一幕准确地描述了当前技术文化的荒谬状态。我们虽然笑了,但在正式场合提及这一点却可能会被视为职业上的异端,使得你几乎难以被雇佣。我们是如何走到这一步的?为何我们的目标不再是解决眼前的问题,而是通过解决那些并不存在的问题来浪费金钱?

完美风暴
近些年的发展可能导致了当前的这种状况。

首先,大批为浏览器编写 JavaScript 的开发者开始自诩为 “全栈” 开发者,深入研究服务器开发和异步代码。JavaScript 毕竟是 JavaScript, 对吧?使用它创建什么有什么区别 —— 用户界面、服务器、游戏或嵌入式系统,都一样吗?

当时的 Node.js 实际上还是某个人的学习项目,并且当时使用 JavaScript 进行服务器开发存在许多问题。但当你向那些刚入门的服务器端开发者指出这一问题时,通常会引起很大的反感。因为这是这是他们所知道的全部。除了 Node.js 之外,他们几乎不认识其他的,认为 Node.js 是唯一的方法,这也是我们至今仍然要面对的那种固执和教条主义的起源。

然后,大量的 FAANG(Facebook、Apple、Amazon、Netflix、Google)资深工程师开始加入初创公司的行列,指导那些初出茅庐、极易受影响的 JavaScript 服务器端工程师。复杂性教堂的信徒们坚决声称,“Google 是怎么做的” 是不容质疑且正确的 —— 即使在当前的背景和公司规模下这并没有意义。你告诉我你没有独立的用户偏好服务?兄弟,那绝对不行!

但是,简单地责怪这些资深工程师和新手是不公平的。还有其他什么因素促成了这种现象呢?对,大把的资金。

当你手头有大量的风险投资时,你的目标难道是盈利吗?有好几次,我收到管理层的邮件,要求所有人都呆在办公室,收拾好桌面,并忙碌地工作,因为一批穿着 Patagonia 背心的投资者即将参观办公室。投资者想看的是公司的爆炸式增长,而不是盈利,是要看公司多快地雇佣了高薪的软件工程师来做…… 某些事。

现在你有了这么多开发者,你打算让他们做什么呢?他们当然可以建立一个简单易于维护的系统,或者,他们也可以构建一个复杂到没人能完全理解的 “微服务” 体系。微服务 —— 编写可扩展软件的新方法!我们是不是就这样忽视 “分布式系统” 这个概念从未存在过?(我们先不提微服务其实并不等同于真正的分布式系统这个议题)。

回想以前,当技术行业还没有这么浮躁,分布式系统被视为一种需要敬畏和避免的技术 —— 仅用作解决特别棘手问题的最后选择。与分布式系统相关的任何事情,无论是开发、调试、部署、测试还是恢复,都变得更为复杂和耗时。但是,也许现在因为有了更先进的工具,一切都变得很简单了。

对于基于微服务的开发,并没有标准的工具或通用的框架。到了 2020 年代,分布式系统的开发仅略有简化。Docker 和 Kubernetes 并没有像人们想象的那样消除分布式配置的复杂性。

我经常引用这篇总结五年初创公司审计的文章,因为它根据确凿的证据得出了充满智慧的结论(并带有深入的见解):

…… 在我们审计过的初创公司中,现在表现最好的那些公司往往有一种近乎大胆的 “保持简单” 的工程方法。他们深恶痛绝地排斥仅为了显得聪明而复杂化的策略。相反,我们之前觉得 “哇,这群人真聪明” 的公司,大多数逐渐黯淡下去了。

简单地说,“复杂性是致命的”。

审计揭示了一个有趣的现象:在建设直观、简单、高性能的系统时,许多初创公司似乎都经历了一种集体的冒名顶替者综合症。有一种固执的观念,那就是一开始就不采用微服务 —— 无论面对什么问题。“所有人都在使用微服务,但我们只有一个由少数工程师维护的 Django 单体应用和一个 MySQL 实例,我们是不是做错了?” 答案几乎总是 “没有”。

同样,经验丰富的工程师在今天的技术环境中常常感到犹豫和不自信,好消息是,可能并不是你的问题。很多团队装作自己在处理大规模的 “网络” 级任务,背后藏有各种库、ORM 和缓存,自信地展现他们的专业知识(他们在 Leetcode 上做得很好!),但他们可能对数据库索引的基本概念一窍不通。你正身处一个过度的自信、浪费和 Dunning-Kruger 效应的环境中,那么,真正的冒名顶替者是谁?

单体架构并没有错
相信只有采用一个类似于那个臭名昭著的阿富汗战争策略图的系统才能成长,这在很大程度上只是一个误区。

Dropbox、Twitter、Facebook、Instagram、Shopify、Stack Overflow,这些公司在创业初期都采用了单体代码库。直到今天,许多公司仍然保持其核心业务为单体架构。Stack Overflow 以运行大型网站所需的硬件极少为傲。Shopify 仍是一个基于 Rails 的单体应用,依靠经过时间考验的 Resque 来处理数十亿的任务。

.WhatsApp 则通过他们的 Erlang 单体应用和只有 50 名工程师实现了极速扩展。怎么做到的呢?
.WhatsApp 有意识地将工程人员规模控制在 50 名左右。

.每个工程团队的规模也很小,通常由 1-3 名工程师组成,并且每个团队都被给予了极大的自主权。


在服务器选择上,WhatsApp 更偏向于使用数量较少的服务器,并尽最大可能进行垂直扩展。而 Instagram 在被收购时估值数十亿,却只是一个 12 人的团队。你能想象 Threads 是整个 Meta 园区共同努力的结果吗?其实不是,它们遵循了 Instagram 的模式,这就是整个 Threads 团队:

当你声称你的特定问题领域需要一个超级复杂的分布式系统和一个充满了 “超能天才” 的开放式办公室,这难道不是更多地显得傲慢,而不是真正的才华?

不要为不存在的问题烦恼
这是一个很直接的问题:你要解决什么问题?是规模性问题吗?你如何知道要如何为了规模和性能来拆分你的系统?你是否有足够的数据来确定哪些应该是单独的服务及其原因?

分布式系统是为了规模和弹性而设计的。你的系统能够同时实现规模化和弹性吗?如果某个服务出现问题或运行缓慢会怎样?只是简单地增加规模吗?那么其他会被大量请求淹没的服务又如何?你有没有考虑到所有可能出错的情况?有没有逆压机制?有断路器吗?队列怎样?有网络抖动吗?每个连接点都设置了合理的超时时间吗?是否有完备的措施确保一个小改动不会让整个系统崩溃?你需要注意和调整的细节是无穷无尽的,它们全都与你的系统的具体使用和流量模式有关。

事实上,大部分的公司可能永远不会达到需要构建一个真正的分布式系统的规模。如果你只是在模仿亚马逊和谷歌,但又没有他们的规模、经验和资源,那你很可能只是在浪费金钱和时间。

比分布式系统更难的唯一事情是一个糟糕的分布式系统。


每个团队单独有 API
在公司结构中融入分布式系统是值得尝试的,但这经常会产生意想不到的后果。常见的方法是将大问题分解为小问题,然后逐一解决。那么,如果你把一个大服务拆分为多个小服务,事情不就变得简单了吗?

这个理论听起来非常美好 —— 每个微服务都有一个专门的团队在细心维护,通过一个精美、向后兼容、并且有版本控制的 API 与外界隔离。而且,这种设置如此稳定,以至于你甚至很少需要与维护团队沟通 —— 就好像这个微服务是由第三方提供商维护的。真的很简单!

但如果你觉得这听起来很陌生,那是因为这种情况在实际中很少发生。实际上,我们的 Slack 频道充满了团队间关于发布、缺陷、配置更新、不兼容的更改和公告的信息。每个人都需要随时随地了解所有的更新。更糟糕的是,一个已经很忙的团队可能只是随便地维护多个微服务,而不是全心全意地做好一个,而这些微服务的所有权随着团队成员的变动而变动。

为了 “胜出”,我们并没有建造一辆出色的赛车,而是建造了一系列性能不佳的高尔夫车。


你失去的东西
构建微服务有很多坑,很多时候,这些问题要么没有被发现,要么被简单地忽略。团队花费数月的时间编写高度定制的工具,并学习与核心产品完全无关的经验教训。以下是一些常被忽视的方面。

告别 DRY 原则
尽管多年来我们一直教导开发者编写 “不要重复自己” 的代码,但如今似乎我们已经不再强调它。默认情况下,微服务并不遵循 DRY 原则,每个服务中都充满了重复的模板代码。常常由于这种 “管道工作” 的开销太大,微服务的实际规模变得很小,所以一个服务的实例中 “服务” 部分比 “产品” 部分还要多。那么该如何处理可以被分离出来的公共代码呢?
.使用公共库吗?
.这个公共库如何进行更新?是否在各处都维护不同的版本?
.是定期强制更新,同时在所有仓库中创建大量拉取请求吗?
.全部保存在一个大的代码仓库中吗?但这同样伴随着一系列问题。
.允许代码重复吗?
.忘了吧,每个团队都会不断地重新发明轮子。

选择这条路径的每一家公司都会面临这些选择,而真正的好的选择是很少的 —— 你必须选择自己能够接受的困境。

开发者的操作体验将大大降低
“开发者的操作体验” 是指开发者在进行任务(如开发新功能或解决 bug)时所遇到的阻碍和所需的努力。

对于微服务,工程师必须理解整个系统,这样才能知道为了完成某个特定任务需要启动哪些服务,需要与哪些团队沟通,讨论哪些问题。也就是说,“在做任何事之前,你必须了解一切”。如何才能做到这一点?Spotify 这样的亿万富翁公司都花费了大量的资源来构建 Backstage—— 一款用于记录其庞大系统和服务的软件。

这至少应该让你明白,这种游戏并不是每个人都可以参与的,入场的代价非常高。那么工具呢?像 Spotify 这样的巨头以外的公司通常都需要自己去构建解决方案,而这些方案的稳定性和适应性自然是大家所熟知的。

那么有多少团队真正地简化了开启一个新的微服务的流程呢?这涉及到:
.在 GitHub/GitLab 中的开发者权限
.默认环境变量和配置
.持续集成 / 持续部署
.代码质量检查
.代码审查设置
.分支规则及其保护
.监控和可观测性
.测试工具
.基础设施即代码

当然,你还需要考虑公司中使用的编程语言的数量。也许你已经有了一个可以使用的模板或指南?或者你有一个一键式的系统可以从零开始创建新的服务?要调整这种自动化的所有细节可能需要数月的时间。所以,你要么致力于产品,要么致力于制造工具。

集成测试
如果日常微服务的复杂性还不够,你还要放弃由强大的集成测试带来的安心。你的单服务和单元测试都通过了,但在每次提交后,你的关键逻辑是否仍然稳固?谁负责整体的集成测试,例如在 Postman 中?是否存在这样的测试套件?

分布式设置的集成测试几乎是个无法解决的难题,因此我们转向了另一个方向 - 可观测性。正如 “微服务” 是新型的 “分布式系统”,“可观测性” 则是新型的 “生产调试”。显然,如果你不涉及可观测性,那你可能并不是在真正开发软件。

可观测性已经形成一个完整的领域,并且你将为它支付大量时间和金钱。它并不简单 —— 你需要理解并实施金丝雀发布、功能标志等。谁来完成这些?一个已经超负荷工作的工程师?

你可以看到,将问题拆分并不会使解决问题变得更简单。你得到的只是更多更复杂的问题。

为何仅仅是 “服务”?
为什么你的服务必须是 “微” 的?普通的服务有什么问题?一些创业公司走到极端,为每个功能创建一个服务,确实,“这难道不就像 Lambda 吗?” 给出了一个多么荒唐的实例。

那么我们该怎么做?从单体开始显然是一个不错的选择。还有一个可能在许多场合下工作的模式是 “主干与分支”,主体单体由辅助的 “分支” 服务支持。例如,一个处理大量 CPU 计算的图像调整服务比用户注册服务更有意义。或者说,你真的有每秒收到如此之多的注册,需要独立进行水平扩展吗?

附注: 在版本控制中,在 CVS 和 Subversion 的日子里,我们很少使用 “master” 分支。我们使用 “主干和分支”,因为,你懂的,是树的形态。“Master” 分支后来才出现,当 GitHub 决定不再使用这种不太恰当的命名方式时,大多数工程师已经不记得主干/分支的模式了,因此现在的默认选择变成了 “main”。

摆动的钟摆正在回归
然而,这种过度炒作的现象似乎正在逐渐消退。风险资本的资金流正在收紧,因此,市场正在进行自我纠正,使得企业重新开始进行有逻辑的决策。他们认识到在没有大规模网络问题的情况下,过度投资于大规模网络架构是不明智的。

最终,当你需要从纽约前往费城时,你其实只有两个选择。你可以尝试构建一个复杂的宇宙飞船,然后从轨道上降落到你的目的地;或者,你可以简单地买一张 Amtrak 的火车票,乘坐 90 分钟的列车。这正是我们面临的问题。
用户评论