面向领域的微服务架构-来自优步的实践

面向领域的微服务架构-来自优步的实践。

源文: Introduce Microservice Architecture (Adam Gluck/Uber)

目录

简介

最近,业界围绕微服务架构,特别是微服务架构的缺陷展开了大量的讨论。仅仅几年前,正是由于微服务架构具有独立部署的灵活性,权属清晰,系统稳定性的提升,更好的关注点隔离等众多优势,很多人都欣然采用了微服务架构。但近年来,业界开始抨击微服务有大大增加系统复杂性的趋势, 有时一个微小的功能都难于构建。

由于优步发展到约2200个核心微服务,我们采用微服务架构也经历了这些优点和缺陷之间的取舍平衡(trade off)。在过去的2年里,优步一直试图降低微服务的复杂性,同时保持微服务架构的优势。 我们希望通过这篇博文介绍我们对微服务架构的一般方法,我们称之为 "面向领域的微服务架构"(DOMA)的方法。

虽然近些年由于这些缺陷人们对微服务架构的批评日益增多,但还是很少有人提倡完全拒绝微服务。它给运营上带来的效益太重要了,在这方面似乎还没有或部分可替代的架构。我们使用DOMA的目的是为那些想要降低整个系统复杂性的同时又能维持微服务优势的组织提供一种前进的道路。

这篇文章解释了 DOMA 的概念, 是哪些因素导致优步会采用这种架构,它对平台和产品团队带来了哪些好处,最后是针对那些想采用这种架构的团队的一些实用建议。

什么是微服务

微服务是面向服务架构的延伸。与2000年代规模较大的 "服务 "相比,微服务是代表一组更小范围的功能的应用程序。这些应用程序通过网络托管和提供,并暴露出一个定义良好的接口。其他应用程序通过 "远程过程调用"(RPC)来调用这个接口。

微服务架构的关键特征是代码托管、调用和部署的方式。如果我们思考大型的单体应用,它们一般会被分割成具有明确定义接口的封装组件。这些接口就会直接在进程中调用,而不是通过网络。通过这种方式,我们也可以将微服务看作一个库,为了调用库中的所有功能有些性能影响(这些影响来自网络I/O和序列化/反序列化的开销)。

当我们这样思考微服务的时候,可能会质疑自己为什么要采用微服务架构。答案通常是微服务可以独立部署且易于扩展。对于一个大型的单体应用,一个组织被迫一次性部署或发布所有的代码。应用程序的每一个新版本都可能涉及许多更改。部署变得风险大、耗时长。任何人都可以使整个系统瘫痪

换句话说,组织采用微服务是为了运营效率而牺牲了性能。组织也必须承担支撑微服务所必需的维护基础设施的费用。事实证明,在多数情况下,这种取舍权衡是很有意义的,但是也强烈反对过早地采用微服务架构。

动机

在优步,我们采用了微服务架构,因为当时(在大约2012-2013年)主要有两个单体服务,并且遇到以下诸多问题(这些问题正是微服务能解决的)。

  • 可用性风险。 在整个单体代码库中,一次回归就可以让整个系统(本例指优步所有系统)崩溃。

  • 风险大,昂贵的部署。 部署是痛苦的,耗时的,而且需要经常回滚。

  • 关注点分离欠佳。 很难在一个庞大的代码库中保持良好的关注点分离。在指数级增长的环境中,权宜之计有时会导致逻辑和组件之间的边界不清。

  • 执行效率低下。 这些问题加在一起,使团队难以自治或独立执行。

换句话说,由于优步从10多个工程师发展到100多个工程师且拥有自己技术栈的团队时,单体架构将团队的命运捆绑在了一起并使其难以独立运作。 因此,我们采用了微服务架构。 最终结果,我们的系统变得更加灵活,这使得团队更加自治。

  • 系统可靠性。总体系统可靠性在微服务体架构中得到提高。单个服务可以关闭(并回滚),而不需要关闭整个系统。

  • 关注点分离。从架构上讲,微服务架构迫使您问这样一个问题:“这个服务为什么存在?”,能更清晰地定义不同组件的角色。

  • 明确所有权。 谁拥有什么代码变得更加清晰。 服务通常在个人、团队或组织级别拥有,从而实现更快的增长。

  • 自主执行。独立的部署+更清晰的所有权,解锁了不同产品和平台团队的自主执行。

  • 开发速度。团队可以独立部署他们的代码,这使他们能够按照自己的节奏执行。

可以毫不夸张地说,如果没有微服务架构,优步不可能实现今天保持的规模和服务质量。

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

例如,工程师必须通过12个不同团队的约50项服务来调查问题的根本原因。

理解服务之间的依赖关系会变得相当困难,因为服务之间的调用层次很深,通常跨越很多层。第n个依赖关系的延迟峰值可能会导致上游的一连串问题。 如果没有合适的工具,就不可能看到实际发生的事情,这使得调试变得困难。

2018年年中优步微服务架构在Jaeger中的调用依赖关系图

为了构建一个简单的功能,工程师往往需要跨多个服务工作,所有这些服务都由不同的个人和团队拥有。 这就需要广泛的合作,在会议、设计和代码审查上花费更多的时间。 由于团队在彼此的服务中构建代码,修改彼此的数据模型,甚至代表服务所有者执行部署,早期对服务所有权的明确界限的承诺受到了影响。 网络化的单体可能会形成,看似独立的服务都必须部署在一起,才能安全地执行任何变更。

其结果是开发者体验变慢、服务所有者不稳定、迁移更痛苦等。对于已经采用微服务架构的企业来说,已经没有回头路了。这就变成了 "有了它们不能活,没有它们也不能活"。

2018年左右优步的一个复杂流程的例子,在DOMA之前,一个简单的整合需要10个服务。

面向领域的微服务架构

如果我们能把微服务看成是I/O绑定的库,把 "微服务架构 "看成是一个大型的、分布式的应用,那么我们就可以使用更好理解的架构去思考如何组织我们的代码。

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

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

我们不是围绕单一的微服务,而是围绕相关微服务的集合。我们将这些称为。我们进一步创建域的集合,我们称之为。域所属的层建立了该域内的微服务允许承担哪些依赖关系。 我们为域提供了清晰的接口,我们将其视为进入集合的入口。我们称这些为网关。最后,我们确定每个领域对其他领域来说都是不可知的,也就是说,一个域不应该在其代码库或数据模型里面硬编码与另一个域相关的逻辑。 因为很多时候,团队确实需要在另一个团队的域中加入逻辑(例如,自定义的验证逻辑或数据模型上的一些元上下文)。 我们提供了一个扩展架构来支持领域内定义良好的扩展点。换句话说,我们提供一个系统化的架构、领域网关和预定义的扩展点。DOMA的目的是将微服务架构从复杂的东西转变为可理解的东西:一套结构化的、灵活的、可重用的、分层的组件

这篇文章的其余部分将挖掘优步DOMA的实施,我们看到的好处,以及对可能想要采用这种架构的公司的实用建议。

优步的实现

领域

优步域代表了一个或多个微服务的集合,这些微服务与功能的逻辑分组相关联。在设计域的过程中,一个常见的问题是 "一个域应该有多大?" 我们在这里不给出指导。有些域可以包含几十个服务,有些域只包含一个服务。重要的任务是仔细思考每个集合的逻辑作用。 例如,我们的地图搜索服务构成了一个域,票价服务是一个域,匹配平台(匹配乘客和司机)是一个域。这些也不一定按照公司的组织结构来。 例如优步地图组织本身分为3个域,3个不同的网关,网关背后有80个微服务。

分层设计

在优步的微服务架构中,分层设计回答了 "什么服务可以调用其他什么服务?"的问题。因此,我们可以将层设计视为 "规模化的关注点分离"。 另外,我们也可以把分层设计看作是 "规模化的依赖管理"。

分层设计描述了一种机制,用于思考优步跨服务依赖性的故障爆炸半径和产品特异性。随着域从底层到顶层。 它们在中断时对服务的影响较小,而且代表了更具体的产品使用场景。反之。 底层的功能有更多的依赖性,因此往往有更大的障爆炸半径范围,也代表了更普遍的业务功能。 下图说明了这个概念。

我们可以把上层看作是具体的用户体验(比如移动功能),而下层则是通用的业务功能(比如账户管理)。 层只依赖于其下的层,这给我们提供了一个有用的启发式方法来思考故障爆炸半径和领域整合等问题。

值得注意的是,功能经常会从这个图表中 "向下 "移动,从具体到更普遍。可以想象,一个简单的功能,随着需求的发展,最终会变成越来越多的平台。 事实上,这种向下迁移是意料之中的,优步的许多核心业务平台都是从骑手或司机的特定功能开始的。 随着我们开展了更多的业务线,它们也有了更多的依赖性(如Uber Eats或Uber Freight),这些业务就变得更加普遍了。

在优步内部,我们建立了以下五层。

  1. 基础设施层。 提供任何工程组织都能使用的功能。这是优步对诸如存储或网络等大的工程问题的解决方案层。

  2. 业务层。 提供了优步作为一个组织可以使用的功能,但这些功能并不针对特定的产品类别或业务线(LOB),如乘车、外卖或货运。

  3. 产品层。 提供与特定产品类别或业务线(LOB)相关的功能,但与移动应用不可知。 如 "请求搭车 "的逻辑,这是由多个面向Rides的应用(Rider,Rider "Lite",m.uber.com等)所组成的。

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

  5. 边缘层。 将优步服务安全地暴露给外界。这一层也是移动应用感知的。

正如你所看到的,后续的每一层都代表着越来越具体的功能分组,而且爆炸半径越来越小(或换句话说,较少的组件依赖于该层内的功能)。

网关

在微服务架构中,"网关API"这个词已经是一个广为人知的概念。除了我们倾向于把网关仅仅看作是进入底层服务集合(我们称之为域)的一个单一入口点外,我们的定义与既定的定义差别不大。一个网关的成功取决于API设计的成功。

上图对网关进行了说明。它抽象出了域的内部细节--多个服务、数据表、ETL管道等。只有接口、RPC API、消息事件和查询暴露给其他域。

由于上游消费者只在一个服务上运行,因此网关在未来的迁移、可发现性和整体系统复杂度方面提供了许多好处,上游服务只采取单一的依赖性,而不是依赖一个领域内可能存在的多个下游服务。 如果我们从面向对象(OO)设计的意义上考虑网关,那么它们就是接口定义,使我们能够在底层的 "实现"(这里是底层微服务的集合)方面为所欲为。

扩展

扩展代表了一种扩展域的机制。扩展的基本定义是,它提供了一种机制,即 用于扩展底层服务的功能,而不改变该服务的实际实现,也不影响其整体可靠性。 在优步,我们提供两种不同的扩展模式:逻辑扩展和数据扩展。 扩展的概念使我们能够将我们的架构扩展到多个团队能够相互独立地工作。

逻辑扩展

逻辑扩展提供了一种机制来扩展服务的底层逻辑。对于逻辑扩展,我们使用提供者的一个变体或插件模式,其接口是以服务为基础定义的。这使得扩展团队可以在不修改底层平台核心代码的情况下,以接口驱动的方式实现扩展逻辑。

比如,一个司机上线。通常情况下,我们会进行各种检查,以确保司机被允许上线(安全检查、合规性等)。 其中的每一项都是由各个团队拥有的。一种实现方式是让每个团队在同一个端点中编写逻辑,但这可能会引入复杂性。 每项检查都需要自定义的、完全不相关的逻辑。

在逻辑扩展的情况下,"上线 "端点将定义一个接口,他们希望每个扩展都能符合预定义的请求类型和响应。 每个团队会注册一个扩展,负责执行这个逻辑。 在这种情况下,他们可能会简单地获取一些关于司机的上下文,然后返回一个布尔值,说这个司机是否可以上线。 上线端点将简单地迭代这些响应,并确定其中是否有假。

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

数据扩展

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

自定义

在逻辑和数据扩展之外,优步的很多团队都推出了自己适合自己领域的扩展模式。 例如,与我们的展示架构绑定的很多集成都使用了基于DAG的任务执行逻辑。

收益

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

DOMA在优步还很年轻,我们很高兴能在未来分享更多数据和深入的架构案例。 不过,在简化开发者体验和降低整体系统复杂度方面,早期的影响非常积极。

产品和平台

DOMA是优步整个产品和平台团队达成共识的结果。平台支持成本往往下降了一个数量级。产品团队从明确的权责边界和加快地开发中受益。

例如,我们扩展架构的一个早期平台服务消费者能够将一个新功能的优先级和集成时间从三天下降到三个小时。 通过采用扩展架构,减少代码审查、规划的时间,以及服务消费者的学习曲线。

降低复杂度

以前产品团队需要调用许多下游服务来利用一个领域,现在他们只需要调用一个服务。通过减少上线一个新功能的服务数量。 平台能够减少25-50%的上线时间。此外,我们能够将2200个微服务划分为70个领域。其中大约50%的服务已经实现。 并且大部分已经计划在未来采用。

未来的迁移

在优步,我们计算过微服务的半衰期是1.5年,也就是说每1.5年我们就有50%的微服务流失。 如果没有网关,微服务架构很容易因为这种流失而陷入 "迁移地狱"。不断变化的微服务需要不断进行上游迁移。 网关使团队能够避免对底层领域服务的依赖,这意味着这些服务可以在不强制进行上游迁移的情况下发生变化。

优步在去年最大的两次平台改版都发生在网关背后。这些平台上有数百个依赖于它们的服务,它们将不得不迁移现有的服务消费者。 在这些情况下,迁移的成本会非常高,使得重写整个平台不可行。

新业务线和产品

事实证明,使用DOMA设计的平台可扩展性更强,也更容易维护。 优步的大多数团队采用DOMA是因为支持新业务线的成本太高。

实用建议

本节为可能想采用DOMA的公司提供一些实用的建议。 这里的指导原则是,根据我们的经验,一个成熟的、经过深思熟虑的微服务架构源于在正确的时间向正确的方向不断推进。 现实情况是,对于一整个微服务架构来说,真正的 "重写 "是永远不可能的。

因此,我们认为发展微服务架构更像是 "修剪篱笆",使其最终正确地生长。 而不是自上而下或一次性的架构(或重新架构)工作。这是一个动态和渐进的过程。

创业公司

正如我们在上面看到的那样,首先要提出这样的问题, "我们应该在什么时候采用微服务架构?"和 "它对我们的组织有意义吗?" 虽然微服务为拥有大量工程师的组织提供了操作上的好处,但这也带来了复杂性的增加,使功能的构建更加困难。

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

考虑到这一点,在一段时间内完全暂缓采用微服务也不是没有道理的。如果一个组织真的选择采用微服务。 它应该思考 "微服务作为大型分布式应用 "的类比,以及它想要构建的微服务之间的关注点分离。 另外,要认识到第一批微服务很可能是最重要的,也是持续时间最长的,因为它们真正描述了核心业务。

中等规模公司

一旦公司发展到中等规模,拥有多个团队,不同的功能和平台之间的关注点明确划分就会变得模糊。这时微服务架构的作用就更加明显。

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

早期对平台化的投资可能会在未来得到回报。 如果能建立产品完全不可知的业务平台,避免核心平台服务中的任意产品逻辑,才可能避免技术债务。 此时采用扩展来实现这一目标或许是有意义的。

鉴于微服务的数量可能还相当少,将它们集中在一起可能没有意义。 不过,这里值得注意的是,在优步的DOMA实现背景下,一个领域可以包含一个服务,所以用 "面向领域 "的方式来思考可能还是有用的。

大公司

大公司一般都有规模较大的工程团队,团队可能有数百名工程师和数百个微服务以及众多的依赖关系。这时DOMA就能起到全部作用。 这时会存在众多的微服务集群,这些集群可以很容易地归为域,在它们前面有一个网关。 之前遗留的服务往往开始需要重构或重写,然后进行迁移,这时网关将很快会在服务的迁移便利性方面提供应有的作用。

一些特定功能或分组的功能会作为“产品”服务来运行,被看作“平台”的其它服务会越来越多地支持多个产品, 此时明确的层次结构将变得越来越重要。 现阶段,保持任意产品逻辑与平台脱钩至关重要。以免给平台团队带来沉重的运营负担以及造成全系统的不稳定。

最后的感想

随着优步越来越多的团队采用DOMA,我们仍在积极地发展DOMA。DOMA理解微服务架构的关键点在于它把该架构也只是看作一个大的分布式应用程序而已,同样的原则也可以应用于其它架构的进化,就像你应用到其它任何软件一样。 DOMA只是一种在实践中思考这些原则的方法。我们希望其他人觉得它有用,我们也期待着反馈!

DOMA本身是一个跨职能部门合作的成果,有近60位工程师参与,他们来自优步的各个部门。特别要感谢的是,在过去2年里为这项工作投入大量精力的人......。

Alex Zylman, Alexandre Wilhelm, Allen Lu, Ankit Srivastava, Anthony Tran, Anupam Dikshit, Anurag Biyani, Daniel Wolf, Davide D’Agostino, Deepti Chedda, Dmitriy Bryndin, Gaurav Tungatkar, Jacob Greenleaf, Jaikumar Ganesh, Jennie Ngyuen, Joe McCabe, Joshua Shinavier, Julia Law, Kusha Kapoor, Linda Fu, Madan Thangavelu, Nimish Sheth, Parth Shah, Shawn Burke, Simon Newton, Steve Sherwood, Uday Kiran Medisetty, and Waleed Kadous

#鸣谢:

这项工作将业界现有的多种设计模式用来解决优步的问题,同时也提出了类似扩展的新模式。 我们感谢业界做出的工作。我们也感谢领英的工程师们在“Superblocks”的工作,他们向我们讲述了他们的相关经验。

(全文完)