微服务正在统治世界,甚至有可能正在成为新的默认选项。
O'Reilly 调查了 1283 个企业,有 52%的受访者表示他们正在使用微服务进行软件开发。其中超过 28%使用微服务超过三年,超过 55%使用微服务的时间为一到三年。O'Reilly 还指出企业对微服务的兴趣可能达到或接近顶峰。
这几年,有无数的中小团队在微服务上陷入了挣扎。微服务有好处但也存在弊端和风险,业务不断发展,微服务也更加复杂,一些企业权衡利弊后甚至选择了退回单体架构。今年有好几个公司总结了他们放弃微服务实践的事情。
Uber 支付体验平台放弃了微服务,转而使用了合理规模服务。
4 月 6 日,Uber 支付体验平台的工程经理 Gergely Orosz 发布推文表示其团队的架构方向已经发生了变化,放弃微服务,转而使用宏服务。
为什么会做出这样的选择呢?Gergely Orosz 表示:“最早,Uber 通过构建微服务来完成很小的需求或功能,以至于出现了很多由一个人构建维护的微服务。这些微服务的存在给我们带来了新的复杂性和挑战,例如监控、测试、持续集成 / 持续交付(CI/CD)、服务级别协议(SLA)、跨所有微服务的库版本(安全和时区问题)等等。”
因此,在创建新平台的时候,Uber 支付体验团队对新服务进行了更加深思熟虑的规划:不再只是完成一件事,而是使其服务于一项业务功能,由 5-10 个工程师负责维护。Gergely Orosz 把这样的服务规划称之为宏服务。
要在正确的时间选择正确的解决方案来构建产品。
Botify 是一家从事 SEO 优化的公司,其平台于 2012 年在 Python/Django 技术栈上创建。2016 年初,整个 Botify 平台都是通过 Django 应用程序的负载均衡集群提供服务。
2016 年年底,Botify 工程团队想让工程师和产品经理拥有更多的局部所有权,从而可以快速轻松地将他们的产品和技术栈投入使用。为此,他们决定将他们的 Django 应用程序拆分为微服务。当时,他们的团队大约为 15 人。他们从身份验证和授权入手实现第一个微服务,将 Django 应用程序当前的一部分功能转移到微服务中。微服务模块也需要和其他的 Django/Python 单体模块进行通讯。
Botify 的平台是一个企业级 B2B 服务,帮助网络上最大的网站改善他们的 SEO,主要挑战在于对客户数据的分析。处理用户相关数据的微服务架构旨在服务于高流量的 B2C 平台,而 Botify 的挑战在于动态地聚合数以 GB 的 SEO 数据,使其在几秒钟内可用。对大约一万名客户的元数据以毫秒为单位进行响应,这项任务不需要高度可伸缩的微服务架构。恰恰相反,Botify 的后端到后端通信减慢了这些简单的检索过程,花费了更多的时间。
鉴于每天都要在 JavaScript 身份验证后端和 Django 模块之间频繁地来回切换,权衡架构的优缺点以及潜在的迁移成本后,他们做出了大胆的选择,将身份验证后端重新加入到 Django 单体中。
Botify 于今年 2 月停用了微服务。其团队负责人 David Wobrock 表示:“每一种技术都自有其用途,但我们相信,要在正确的时间选择正确的解决方案来构建我们的产品。如今,我们不必来回切换了,也不用维护两个后端了,显然,我们已经从中获益。“
“我们公司也从单体转向了微服务,但最后在二者之间找到了一个平衡点。”
在 2017 年的时候,办公管理软件公司 Managed by Q 的技术团队大概有 20 名工程师,应用程序是一个部署在 ECS 上的 Django 单体。为了赶上现代化开发实践的步伐,他们开始转向了微服务架构。
但随着微服务数量的增长,事情不像之前那么顺利了。
每多一个新服务,就会增加一些基础设施。比如,一个 ECS 服务、一个 Postgres 实例或一个 RabbitMQ 实例。CI/CD 的配置也会增加,还需要进行第三方服务(比如 Rollbar/Sentry)配置。依赖也需要更新,而且需要更新依赖的地方越来越多。基础设施团队在项目上花了很多时间,为每一个服务重复着枯燥无味的工作。而且小型的服务容易被人忽略,在运行起来之后,基本上会被搁在一边,最后就会过时。
如果服务边界没有搞清楚,还会显著降低功能的开发速度,这是微服务的一个很大的风险点。开发一个跨多个服务的功能需要做更多的工作,而重构一个跨多个服务的功能是一个噩梦。如果服务边界很清晰,大部分项目只会影响到一个服务。但是,对于初创公司来说,它们的发展方向是不可预测的。一个产品的两个部分在一开始可能是完全独立的,但一年之后可能会变得紧密耦合起来。所以,要完全清晰地定义服务边界不是件容易的事。
最后,在转向微服务两年之后,他们开始合并微服务。一些微服务被合到了单体中,其他的则合并成较大的服务。在一年中总共移除了 9 个微服务。大小合理的服务承担着相当大的责任,大多数功能开发都可以在单个服务中完成。
不能当然地认为微服务就是正确的选择。
微服务已经被这代人当成银弹,不过,上面提到的这些企业已开始用更挑剔的眼光来看待它。这些例子中,他们认为转向微服务的工程开销太大,不值得。而这样的案例还有很多。
每个系统实现都会有一些错误的选择。但是,他们所犯的最大的错误与他们的微服务实现无关。他们所犯的最大错误是在只有 20 名工程师的环境中实现了几十个微服务。
如果你认为,“在一个只有 20 名工程师的团队中实现大量的微服务简直是疯了!”那么,我同意你的看法。但这种情况随处可见,这至少表明没有一种单一的架构模式适合所有的人。
我从三年前就开始写这篇博文了,期间,我无数次听说有中小型团队在微服务上陷入了挣扎,现在,我终于准备好发表了。我写这篇文章不是因为我讨厌微服务,而是因为我担心微服务正在成为新的默认选项。“你不用微服务吗?那么显然,你没把软件工程当回事。”人们不再做决定,他们只是想当然地认为微服务就是正确的选择,我认为这是有问题的。
规模决定一切
你的系统有多少人在开发?五个?十个?五十?一百?还是一千?如果你的应用程序、平台、应用程序套件或其他任何东西,有超过 500 人在开发,那么我认为,你大可以放心地关掉这篇博文并继续前进。你们的问题不是我们这里要讨论的问题。但是,如果你的应用程序开发工程师不足 100 人,那么请别走,我们聊一会儿。如果你的情况介于两者之间,那么这就要看实际情况了,也请不要走开,看看是否能发现一些有用的东西。
我职业生涯的大部分时间都服务于做小东西的公司。我在大公司做过不少咨询工作,但我工作过的大多数公司(或者是部门)都总共只有不到 100 名软件工程师。通常,这类工程组织有一个共同的关注点:创建可靠且稳定的系统,为业务交付价值——所有这些都需要借助紧张的工程资源。
我所处理的问题都不是在谷歌、Facebook 或 Uber 那种规模上。在有些人看来,我似乎不知道自己在做什么,但就我个人而言,我认为这是一种资产。我认识的在这类机构中工作的人都很聪明,但他们不是魔法师。
在一个大型工程环境中工作,意味着用几乎无法估量的工程资源大规模地解决问题。通常,他们有大量的内部工具和库可以使用,这使他们能够编写大规模的软件。与来自大型工程环境背景的人交流得越多,我就越意识到,对于我们这些人来说,在一家初创企业或中小型企业中,与由 5 名、10 名甚至 50 名工程师组成的团队打交道是多么困难。
建议是要看上下文的
在如此大的规模下,沟通和协调是迄今为止最大的挑战之一,这是有道理的。减少团队之间的依赖关系,或者让应用程序扩展到每秒处理数百万个请求,规模这么大,这样的需求完全值得付出如此巨大的工程开销。
但是,对于中小型企业,我经常听到这样的建议:
你需要重新构建一个更现代化的软件栈;
你应该使用扩展性更好的数据存储;
你的数据工程团队可以切换到 Kafka 吗?
你应该重构成一系列的微服务;
你需要用 Go、Rust 或其他高可扩展的语言进行重写。
通常,这些中小型团队发现,仅仅是满足特性请求和提供支持就很困难,更不用说考虑重写整个应用程序了。这个建议合适吗?当然,在很少一些情况下可能是合适的。但是,有小企业和创业公司一再告诉我,他们从导师或顾问那里听说过这些。
是不是有的建议听着很熟悉?
当你需要应对一个小型团队和一个大型应用程序时,这可能会让人胆怯。你会感觉到各种压力,协调特性发布,随着系统复杂性的增长开发速度下降,你开始阅读很多文章,了解如何将应用程序分解成很多小块,从而帮助你降低复杂性,提高部署频率,简化开发工作。
所以,你决定从只有 20 人的工程师团队里抽出一大部分人,和少量的工程承包人员一起,在接下来的两年里重新规划设计你的应用程序,拆分你的系统,并构建出几十个微服务,可能还有一些微前端。在新构建的应用程序上线后,你很快就会发现,部署确实越来越频繁。每个微服务的代码库都很小,推断也比较容易。小型的更新和 Bug 修复可以更快地完成、测试和上线。你对自己说:“太棒了!这肯定会提高开发团队的开发速度!”
新挑战
但是,接下来的几个月里,你开始遇到一些挑战。
协调
每当需要进行较大的更改时,你就会发现自己需要更新许多服务,并且要协调这些服务的发布。当你向同事提起这件事时,他们总是说:“你弄错了。你的服务在逻辑上应该是分离的。”逻辑上分离,听起来很好,但是,你已经将系统拆分成许多小块,所以这些小块之间有很多依赖关系。有人曾经说过,如果你的服务之间有一堆依赖关系,那么你的拆分边界就有问题,但是,除了大幅减少微服务的数量外,你并不知道其他的拆分方法。
数据一致性
你还会注意到,出现了一些数据一致性的新问题。看起来,应用程序中一些过去在单个操作中完成的操作现在被拆分到几个不同的服务中,其中一个服务有一些写入失败。同样,你的同事会告诉你,这样做是不对的,但是,将库存服务与订单服务拆分成单独的服务似乎是正确的,是符合微服务的精神的。你告诉自己,“也许,我们需要着手设计跨所有这些操作的多阶段提交,或者我们需要构建工具来确保数据一致性。”再一次,这让你觉得会明显增加一些工程复杂性和开销。
性能挑战
性能问题也开始悄然出现——应用程序中的一些页面需要调用六个服务来呈现,加载时间很长。看起来,你需要为其中一些服务实现一个内部缓存层,以加快页面呈现速度。简单的缓存层不会特别麻烦,只是需要再添加一层。
分布式跟踪
随着时间的推移,你还会开始注意到,团队记录的处理某些 Bug 的时间显著增加。当你与团队讨论这个问题时,他们会说,某些问题在系统中很难定位。即使他们找到了,也很难再现它们,因为在工程师的机器上让多个服务进入同样的状态会是一项巨大的挑战。你默默地记了下来,你需要从整体上做个规划,以便提供更好的系统级可跟踪性,这样,你就可以看到请求在整个系统中的流转过程。又多了一个待办事项。
重复
现在,设计和构建某些比较大的特性需要花费更长的时间了。它们需要多个不同的服务来实现不同的功能,并在不同的数据存储中协调更改。你会发现,自己在不同的服务中复制了某些业务操作的逻辑,尽管你已经尽了最大的努力来保证每个服务在逻辑上是独立的,但是,你没法完全做到这一点。这给表带来了各种不同的风险,因为现在需要在服务之间保持业务逻辑的一致性。这是否意味着创建共享服务?手动保持逻辑同步?一声叹息。
安全面
另一个关键点 CVE 呢?谢天谢地,你花时间在构建过程中引入了工具,使你能够知道哪个服务受了影响,因为为所有这些服务打补丁是非常困难的。手工审计每一个服务将是一项艰巨的任务。尽管如此,因为一个 CVE 版本而部署 24 个服务是一种你没有真正考虑到的痛苦。
报表挑战
如果将数据拆分到多个数据存储,一些查询就会变得非常复杂。因此,你聘请了一些顾问来帮助你构建一个专用的数据仓库并创建一个 ETL 过程,将其转换为让你的团队可以轻松生成报告的形式。这种设置提供了一些预期的优点,但是,现在每次进行重要的模式更改都必须同时维护 ETL 过程。又多了一件事要处理。
稳定性
最后,稳定性开始出现问题。其中一个服务有点不稳定,在访问它时会导致系统的其他部分挂起。现在,你看到的不是抛出的错误,而是请求静静地阻塞在那里,直到失去响应。你知道,你的团队没有花足够的时间来确保服务之间良好的容错能力,因此你意识到,可能需要使某些交互异步进行,这将进一步增加工程开销。
这非常令人沮丧,因为你的团队做了大量的研究,并试图遵循所有可接受的模式来实现良好的微服务,但这种转变带来的开销似乎并不值得。是的,单个服务的开发和部署更容易,但是,系统整体的复杂性高了许多。其中一些痛点可以通过额外的工程和一些更好的模式来缓解,但是,这就违背你最初的目标了——你应该提高速度,用更少的资源做更多的事情,而不是增加系统的工程开销。也许你在寻找灵丹妙药,并做出了一些轻率的决定。
考虑下你面临什么问题?
也许你已经经历过一些这样的痛苦,并且深有感触,或者你转到了微服务,这对你的团队来说是一个巨大的进步。在很多情况下,都有必要拆分出一些服务,但这完全取决于你面临的问题。
你的痛苦是为了有效地协调多个团队对同一个单体应用程序所做的更改吗?你的痛苦是否无法通过其他策略(如基于主干的开发)来解决?你可能需要将其拆分,创建一些服务,并考虑微服务的意义所在。
你是否感到非常痛苦,因为你那个有着数百万行代码的庞大单体需要做出一些牺牲并需要几天的时间来部署?你需要把它拆分并开发成服务。
在你的应用程序中,是否有大量不相关的功能混杂在一个系统中?你可能需要一些服务,或者更小的应用程序。
你那拥有 30 名工程师的团队是否深陷复杂性,开发速度很低,而大家都把原因归咎于单体应用程序?你可能需要首先关注一个更好的单体,然后再考虑迁移到一些服务或一组较小的应用程序。
正如 Simon Brown 曾经说过的那样:“如果你不能构建一个结构良好的单体,你凭什么认为微服务就是答案?”
复杂性被转移,但并未被消除
通过采用微服务或微前端,你是在转移复杂性,而不是消除复杂性。在一些地方,这种转变是值得的,但是,如果你的系统不够大,你的团队成员不够多,而你面临的问题不是来自于协调大量的开发人员,那么将应用程序拆分成很多小块可能会弊大于利。
我并不是说,小型团队不应该把业务分解为大小合理的服务,也不是说你不应该将那些功能过多的应用程序拆分。但多年来,我看到太多的人提出这样的建议:把你的系统分解成很多很多的小块,就能神奇地解决你所有的问题。
你需要清楚地了解你的系统所面临的挑战,看看整个团队不断增加的速度是否可以抵消构建分布式系统所带来的额外复杂性,然后再做出决策。如果你有证据证明这种取舍是值得的,那么根据你的业务领域和团队规模来拆分出合理的服务。务必要非常关注微服务的最佳实践,否则你最终将构建一个分布式的单体,这将是最糟糕的结果。
请注意,本文还没有涉及从单体迁移到微服务的复杂性,不要低估了这种复杂性。这是一个需要非常谨慎的过程,并且应该增量地完成。
严格考察,谨慎行事
我建议你严格地考察权衡。考虑一下,你的团队是否会从中受益,然后从小规模实验入手,拆分出一些服务,看看效果如何。克服挑战,觉得有意义再继续前进。然后,重复上述过程。我知道,这可能与如今科技行业“快速行动,改变一切”的理念不符,但你最终会为自己采取了慎重的做法而高兴。