查看原文
其他

Uber面向领域的微服务体系架构实践

技术琐话 2021-08-08

The following article is from 常柱 Author 常柱


介绍

最近,人们对面向服务的系统架构和微服务系统架构的缺点进行了大量的讨论。尽管仅仅在几年前,由于微服务体系架构提供了许多好处,如独立部署的灵活性、明确的所有权、提高系统稳定性以及更好地分离关注点等,但近年来,人们开始谴责微服务的倾向极大地增加了复杂性,有时甚至使微小的特性也难以构建。

随着 Uber 已经发展到大约 2200 个关键微服务,我们亲身体验了这些折衷。在过去两年里,Uber 试图降低微服务的复杂性,同时仍然保持微服务架构的优势。通过这篇博文,我们希望介绍我们对微服务体系结构的通用方法,我们称之为“面向领域的微服务体系架构”(Domain Oriented microservice Architecture,DOMA)。

由于这些缺点,近年来批评微服务架构变的很流行,但很少有人主张彻底拒绝微服务架构。系统的运营效益(可维护性)太重要了,而且似乎没有或极其有限的替代方案。我们使用 DOMA 的目标是为那些希望在保持与微服务体系结构相关的灵活性的同时降低总体系统复杂性的组织提供一条前进的道路。

这篇文章解释了 DOMA,导致 Uber 采用这种架构的原因,它对平台和产品团队的好处,最后,为希望采用这种架构的团队提供了一些建议。

什么是微服务

微服务是面向服务体系架构的扩展。与 2000 年代相当大的“服务”不同,微服务是代表一组范围狭窄的功能的应用程序。这些应用程序在网络上托管和可用,并公开定义良好的接口。其他应用程序通过进行“远程过程调用”(RPC)来调用此接口。

微服务体系结构的关键特征是代码的托管、调用和部署方式。如果我们考虑大型的、单片的应用程序,它们通常被分成具有定义良好的接口的封装组件。这些接口将直接在进程内调用,而不是通过网络调用。通过这种方式,我们可以开始将微服务视为一个性能受到影响的库(网络 I/O 和序列化/反序列化),以便调用它的任何函数。

当我们以这种方式考虑微服务时,我们可能会质疑为什么要采用微服务架构。答案通常是独立部署和扩展。对于大型的单片应用程序,组织必须一次部署或发布所有代码。应用程序的每个新版本都可能涉及到许多更改。部署变得有风险而且耗时。任何人都可以搞垮整个系统。

换言之,组织采用微服务是为了获得运营效益而牺牲性能。组织还必须承担维护支持微服务所需的基础设施的成本。事实证明,在许多情况下,这种权衡是有意义的,但它也是反对过早采用微服务体系结构的有力理由。

动机

在 Uber,我们采用了微服务架构,因为我们(大约在 2012-2013 年)主要有两个单一服务,并遇到了微服务解决的许多运营问题:

  • 可用性风险高。在一个单一的代码库中进行一次回归可以使整个系统(在本例中是 Uber 的所有部分)崩溃。
  • 部署高风险高成本。在频繁需要回滚的情况下执行这些操作既痛苦又耗时。
  • 关注点分离差。用庞大的代码库很难保持关注点的良好分离。在指数增长的环境中,权宜之计有时会导致逻辑和组件之间的边界差。
  • 执行效率低。这些问题的结合使得团队很难自主或独立地执行任务。

换言之,随着 Uber 从 10 岁的工程师成长到 100 岁的工程师,多个团队拥有技术堆栈的一部分,这种单一的架构将团队的命运捆绑在一起,使得独立运营变得困难。

因此,我们采用了微服务架构。最终,我们的系统变得更加灵活,这使得团队更加自主。

  • 系统可靠性。在微服务体系结构中,系统的整体可靠性提高了。单个服务可以停止(并回滚),而不必关闭整个系统。
  • 关注点分离。从体系结构上讲,微服务体系结构迫使您提出这样一个问题:“为什么存在这种服务?“更清楚地定义不同组成部分的作用。
  • 所有权明确。谁拥有什么代码变得更加清楚了。服务通常在个人、团队或组织级别拥有,以实现更快的增长。
  • 自主执行强。独立部署+更清晰的所有权线将不同产品和平台团队的自主执行分离解锁。
  • 开发迭代快。团队可以独立地部署他们的代码,这使他们能够以自己的速度执行。

毫不夸张地说,如果没有微服务架构,Uber 将无法实现我们今天保持的规模和质量。

然而,随着公司规模的扩大,从 100 名工程师到 1000 名工程师,我们开始注意到一系列与大大增加的系统复杂性相关的问题。在微服务体系结构中,人们用一个单片代码库来交换黑盒,黑盒的功能随时可能改变,很容易导致意外行为。

例如,为了调查问题的根本原因,工程师们不得不在 12 个不同的团队中处理大约 50 个服务。

理解服务之间的依赖关系会变得相当困难,因为服务之间的调用可以深入到许多层。第 n 个依赖项中的延迟峰值可能会导致上游问题的级联。如果没有正确的工具,就不可能看到实际发生的情况,这使得调试变得困难。

(优步微服务架构(Microservice Architecture),约在 2018 年年中由 Jaeger 提供)

为了构建一个简单的特性,工程师通常必须跨多个服务工作,这些服务都由不同的个人和团队拥有。这就需要在会议、设计和代码评审方面进行广泛的协作。当团队在彼此的服务中构建代码、修改彼此的数据模型,甚至代表服务所有者执行部署时,早期明确的服务所有权的承诺将受到损害。可以形成网络化的整体,其中看起来独立的服务都必须部署在一起才能安全地执行任何更改。

(Uber 在 2018 年左右的一个复杂流程示例,在 DOMA 之前,需要 10 个接触点进行简单集成。)

结果是开发人员体验变慢,服务所有者不稳定,迁移更痛苦等等。对于已经采用微服务架构的组织来说,没有回头路可走。这就成了“不能和他们一起生活,不能没有他们就不能生活”的例子。

面向领域的微服务体系架构(DOMA)

如果我们可以把微服务看作是 I/O 绑定的库,把“微服务体系架构”看作一个大型的、分布式的应用程序,那么我们就可以使用理解良好的体系结构来思考如何组织代码。

因此,“面向领域的微服务体系架构”大量借鉴了组织代码的既定方法,如领域驱动设计、干净的体系架构、面向服务的体系架构以及面向对象和接口的设计模式。我们认为 DOMA 是创新的,因为它是一种相对新颖的方法,可以在大型组织中的大型分布式系统中利用既定的设计原则。

与 DOMA 相关的核心原则和术语如下:

  • 我们不是以单个微服务为导向,而是以相关微服务的集合为导向。我们称之为域。

  • 我们进一步创建我们称之为层的域集合。域所属的层建立了允许该域中的微服务承担的依赖关系。我们称之为层设计。

  • 我们为域提供干净的接口,这些域被视为集合的单入口点。我们称之为“门”。

  • 最后,我们确定每个域对其他域应该是不可知的,也就是说,一个域不应该在其代码库或数据模型中有与另一个域硬编码相关的逻辑。由于团队经常需要在另一个团队的域中包含逻辑(例如,自定义验证逻辑或数据模型上的一些元上下文),我们提供了一个扩展架构来支持域中定义良好的扩展点。

换句话说,通过提供一个系统化的体系结构、域网关和预定义的扩展点,DOMA 打算将微服务体系结构从复杂的东西转换成可以理解的东西:一组灵活、可重用和分层的组件。

本文的其余部分将深入探讨 Uber 对 DOMA 的实现、我们所看到的好处,以及对可能希望采用这种方法的公司的实用建议。

Uber 的实践

领域

Uber 域表示一个或多个微服务的集合,这些服务与功能的逻辑分组相关联。在设计域时,一个常见的问题是“域应该有多大?”?“我们这里没有指导。有些域可以包含几十个服务,有些域只有一个服务。重要的任务是仔细考虑每个集合的逻辑角色。例如,我们的地图搜索服务构成一个域,票价服务是一个域,匹配平台(匹配车手和司机)是一个域。这些也不总是遵循公司的组织结构。Uber 地图组织本身分为三个域,在 3 个不同的网关后面有 80 个微服务。

分层设计

分层设计回答了“什么服务可以调用什么其他服务”的问题?“在 Uber 的微服务架构中。因此,我们可以将层设计视为“大规模的关注点分离”,或者,我们可以将层设计视为“规模上的依赖管理”

分层设计描述了一种机制,用于考虑 Uber 服务依赖性中的故障爆炸半径和产品特定性。当域从底层移动到顶层时,它们在中断情况下影响的服务更少,并且代表更具体的产品用例。相反,底层的功能有更多的依赖项,因此往往具有更大的爆炸半径,并代表更一般的业务功能集。下图说明了这一概念。

我们可以将顶层视为特定的用户体验(如移动功能),将底层视为通用的业务功能(如帐户管理或市场旅行)。层只依赖于它们下面的层,这给我们提供了一个有用的启发来考虑像爆炸半径和域集成这样的问题。

值得注意的是,功能通常会将图表从特定的“向下”移动到更一般的位置。可以想象,随着需求的发展,一个简单的特性最终会越来越成为一个平台。事实上,这种向下迁移是意料之中的,Uber 的许多核心业务平台一开始都是针对骑手或司机的功能,随着我们开发更多的业务线,这些功能变得更加普遍(比如 Uber Eats 或 Uber Freight)。

在 Uber 内部,我们建立了以下五个层次:

  • 基础设施层。提供任何工程组织都可以使用的功能。这是 Uber 对存储或网络等重大工程问题的解答。

  • 业务层。提供 Uber 作为一个组织可以使用的功能,但这些功能并不特定于特定的产品类别或业务线(LOB),如乘车、就餐或货运。

  • 产品层。提供与特定产品类别或 LOB 相关的功能,但对移动应用程序是不可知的,例如多个面向骑乘的应用程序(Rider、Rider“Lite”、m。优步网等等)。

  • 表现层。提供与面向消费者的应用程序(移动/网络)中存在的功能直接相关的功能。

  • 边缘层。向外部世界安全地提供 Uber 服务。这一层也是移动应用感知的。

如您所见,每个后续层代表一组越来越具体的功能,并且具有越来越小的爆炸半径(或者,换句话说,越来越少的组件依赖于该层中的功能)。

网关

术语“网关 API”在微服务架构中已经是一个广泛确立的概念。我们的定义与已建立的定义差别不大,只是我们倾向于将网关视为底层服务集合的单一入口点,我们称之为域。网关的成功依赖于 API 设计的成功。

上图说明了网关的高级关系图。它抽象了域的内部细节-多个服务、数据表、ETL 管道等。只有接口-rpcapis、消息传递事件和查询才公开给其他域。

由于上游消费者只对单个服务进行操作,因此网关在未来的迁移、可发现性和系统复杂性的总体降低方面提供了许多好处,因为上游服务只采用一个依赖项,而不是依赖于一个域中可能存在的多个下游服务。如果我们从面向对象设计的角度来考虑网关,它们就是接口定义,它使我们能够在底层“实现”(在本例中是底层微服务的集合)方面做任何我们想做的事情。

扩展

扩展表示一种扩展域的机制。扩展的基本定义是,它提供了一种机制来扩展底层服务的功能,而不会更改该服务的实际实现,也不会影响其整体可靠性。在 Uber,我们提供两种不同的扩展模型:逻辑扩展和数据扩展。扩展的概念允许我们将架构扩展到多个能够独立工作的团队。

逻辑扩展

逻辑扩展提供了一种扩展服务底层逻辑的机制。对于逻辑扩展,我们使用提供程序或插件模式的变体,接口是按服务定义的。这使得扩展团队能够以接口驱动的方式实现扩展逻辑,而无需修改底层平台的核心代码。

例如,一个司机上网。通常,我们会进行各种检查,以确保司机可以上网(安全检查、合规性检查等)。每一个都由一个单独的团队拥有。实现这一点的一种方法是让每个团队在同一个端点中编写逻辑,但这会带来复杂性。每次检查都需要定制的、完全无关的逻辑。

在逻辑扩展的情况下,“联机”端点将定义一个接口,它们希望每个扩展都符合预定义的请求类型和响应。每个团队将注册一个负责执行此逻辑的扩展。在这种情况下,他们可以简单地获取驱动程序的一些上下文并返回一个 bool,说明驱动程序是否可以联机。go-online 端点将简单地遍历这些响应,并确定其中是否有任何响应为 false。

这将核心代码与每个扩展解耦,并在扩展之间提供隔离,扩展不知道其他逻辑在执行什么。围绕这一点很容易建立更多的功能,比如可观测性或特征标记。

数据扩展

数据扩展提供了一种将任意数据附加到接口的机制,以避免核心平台数据模型膨胀。对于数据扩展,我们利用 Protobuf 的任何功能,以便团队可以向请求添加任意数据。服务通常会存储这些数据或将其传递给逻辑扩展,这样核心平台就不会负责反序列化(从而“了解”)这个任意上下文。Protobuf 的任何实现都会带来一些基础设施开销,以换取更强的类型。为了更简单的实现,可以很容易地使用 JSON 字符串来表示任意数据。

定制

除了逻辑和数据扩展之外,Uber 的许多团队已经引入了适合自己领域的扩展模式。例如,与我们的表示体系结构相关的许多集成都使用基于 DAG 的任务执行逻辑。

好处效益

Uber 几乎每个主要领域都受到 DOMA 的某种程度的影响。在过去的一年里,我们主要关注 Uber 的业务层,它为我们的各个业务线提供了通用逻辑。

DOMA 在 Uber 还很年轻,我们很高兴将来能分享更多的数据和深入的架构示例。然而,在简化开发人员体验和降低总体系统复杂性方面,早期迹象是非常积极的。

产品和平台

DOMA 是 Uber 产品和平台团队一致努力的结果。平台支持成本通常下降一个数量级。产品团队从护栏和加速开发中获益。

例如,我们的扩展体系结构的早期平台消费者能够通过采用一种扩展体系结构,将划分优先级和集成新功能的时间从三天缩短到三小时,从而缩短了消费者的代码审查、规划和学习曲线时间。

降低复杂性

以前,产品团队需要调用许多下游服务来利用一个域;现在他们只需要调用一个。通过减少搭载新功能的接触点数量,平台能够将登录时间减少 25-50%。此外,我们能够将 2200 个微服务分为 70 个域。其中大约有 50%已经实施,而且大多数都有一些未来采用的计划。

未来的迁移

在 Uber,我们计算出微服务的半衰期是 1.5 年,也就是说,每 1.5 年,我们的微服务就有 50%流失。没有网关,微服务体系结构很容易因为这种混乱而陷入“迁移地狱”。不断变化的微服务不断需要上游迁移。网关使团队能够避免对底层域服务的依赖,这意味着这些服务可以在不强制进行上游迁移的情况下进行更改。

去年 Uber 最大的两次平台重写都发生在 gateways 之后。这些平台有数百个依赖于它们的服务,这些服务必须迁移现有的消费者。在这些情况下,迁移的成本将非常高,使得完全重写平台变得不可行。

新的业务线和产品

使用 DOMA 设计的平台已经被证明具有更高的可扩展性和更易于维护。大多数采用 DOMA 的 Uber 团队之所以这么做,是因为支持新的业务线变得过于昂贵。

实用建议

本节为可能希望采用 DOMA 的公司提供了一些实用的建议。这里的指导原则是,在我们的经验中,一个成熟和深思熟虑的微服务架构源于在正确的时间朝着正确的方向轻轻推动。事实是,对于一个人的整个微服务架构来说,真正的“重写”是不可能的。

因此,我们认为演化微服务架构更像是“修剪树篱”,以便它最终正确增长,而不是自上而下或一次性的架构(或重新架构)工作。这是一个动态的、渐进的过程。

创业公司

驱动问题应该是“我们什么时候应该采用微服务架构?“这对我们的组织有意义吗?”?“正如我们在上面看到的,虽然微服务为拥有大量工程师的组织带来了运营上的好处,但这会带来复杂性的增加,从而使功能更难构建。

在小型组织中,运营收益可能无法抵消架构复杂性的增加。此外,微服务体系结构通常需要专用的工程资源来支持,这可能超出了早期公司的预算,或者从优先级的角度来看是次优的。

考虑到这一点,将微服务完全推迟一段时间也不无道理。如果一个组织选择采用微服务,它应该考虑“微服务作为大型分布式应用程序”的类比,以及它希望构建的微服务之间的关注点分离。另外,要认识到,第一批微服务很可能是最重要、持续时间最长的,因为它们真实地描述了业务的核心。

中型公司

一旦一家公司成为拥有多个团队的中型企业,并且不同功能和平台之间的关注点的明确分离变得模糊,微服务架构就变得更加有用。

在这个阶段,人们可以开始考虑微服务之间的层次结构。依赖性管理可能变得更加重要,因为一些服务开始变得对业务运营更加重要,并且越来越多的团队依赖它们。

对平台化的早期投资可能会带来回报。如果一个人能够创建完全不依赖产品的业务平台,并避免核心平台服务中的任意产品逻辑,那么就有可能避免技术债务。在这一点上采用扩展来实现这个目标可能是有意义的。

鉴于微服务的数量可能仍然很低,将它们聚集在一起可能没有意义。然而,这里值得注意的是,在 Uber 的 DOMA 实现环境中,一个域可以包含一个服务,因此以“面向域”的方式思考可能仍然有用。

大型公司

更大的工程组织可能有数百名工程师和微服务以及几个依赖关系。在这一点上,DOMA 达到了它的全部用途。很可能会有明显的微服务集群,这些集群可以很容易地组合到具有网关的域中。遗留服务通常开始需要重构或重写,然后再进行迁移,这意味着如果网关已经就位,它们将很快开始提供易于迁移的价值。

清晰的层次结构也将变得越来越重要,一些服务作为针对特定功能或功能分组的“产品”服务运行,而其他服务将越来越多地支持多个产品并被视为“平台”。在现阶段,保持任意产品逻辑与平台的解耦是至关重要的,从而避免了平台团队的沉重运营负担和全系统的不稳定。

最后的想法

随着 Uber 越来越多的团队开始采用 DOMA,我们仍在积极发展 DOMA。DOMA 的关键观点是,微服务体系结构实际上只是一个大型的分布式程序,您可以将同样的原则应用到它的发展过程中,就像应用于任何软件一样。DOMA 只是在实践中思考这些原则的一种方法。我们希望其他人觉得有用,我们期待反馈!

DOMA 本身就是跨职能部门努力的结果,Uber 的每个组织都有近 60 名工程师参与其中。

致谢

这项工作带来了业界现有的多种设计模式来解决 Uber 的问题,同时也提出了一些新的模式,比如扩展。我们感谢业界在这方面所做的努力。我们也要感谢 Linkedin 的工程师们,他们为我们讲述了他们的经历。

作者

亚当·格鲁克

亚当·格鲁克是优步的高级软件工程师。他在优步的前 3.5 年里,充实了我们的司机平台团队,并帮助扩大了我们的司机产品。最近,他还是 Uber 工程战略团队的一员,专注于高层系统架构和 Uber 平台化工作。

原文: https://eng.uber.com/microservice-architecture/


 往期推荐:



技术琐话 


以分布式设计、架构、体系思想为基础,兼论研发相关的点点滴滴,不限于代码、质量体系和研发管理。本号由坐馆老司机技术团队维护。



长按扫码关注



点亮在看是认可,分享转发是支持


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存