DDD+微服务大型案例:Uber如何从复杂的RPC微服务转向面向业务领域的微服务架构DOMA? -优步工程博客


最近,围绕面向服务的体系结构,尤其是微服务体系结构的弊端进行了大量讨论。仅仅几年前,由于许多人宣传微服务架构的好处,例如独立部署形式的灵活性,明确的所有权,系统稳定性的改进以及更好的关注点分离,很多人很快采用了微服务架构,近年来人们开始谴责微服务会大大增加其复杂性。
随着Uber增长到大约2,200个关键微服务,我们亲身经历了这些折衷。在过去的两年中,Uber尝试降低微服务的复杂性,同时仍保持微服务架构的优势。我们希望通过这篇博客文章介绍我们对微服务架构的通用方法,我们将其称为“面向领域的微服务架构”(DOMA)。
由于这些缺点,近年来批评微服务体系结构变得很普遍,但很少有人主张完全拒绝微服务体系结构。运营收益太重要了,似乎没有替代品或替代品很有限。我们与DOMA的目标是为希望降低总体系统复杂性同时保持与微服务架构相关联的灵活性的组织提供前进的道路。
这篇文章解释了DOMA,导致Uber采用此架构的担忧,其对平台和产品团队的好处,以及最后给想要采用此架构的团队的一些建议。 

Uber从单体到微服务转变
在Uber,我们采用了微服务架构,因为我们(大约在2012年至2013年)拥有两个整体服务,并且遇到了微服务解决的许多运营问题。

  • 可用性风险。单一代码库中的单个回归可以使整个系统(在这种情况下,是整个Uber)瘫痪。
  • 风险高昂的部署。由于频繁需要回滚,因此执行这些操作很痛苦且耗时。
  • 关注点分离差。使用庞大的代码库很难很好地保持关注点的分离。在指数增长的环境中,权宜有时会导致逻辑和组件之间的边界不佳。
  • 执行效率低下。这些问题加在一起使团队难以自主或独立执行。

换句话说,随着Uber的工程师人数从10增长到100,而拥有多个团队拥有部分技术堆栈的整体架构,将团队的命运束缚在一起,使其难以独立运营。
结果,我们采用了微服务架构。最终,我们的系统变得更加灵活,从而使团队更加自治。
  • 系统可靠性。在微服务架构中,总体系统可靠性得到提高。单个服务可以关闭(并回滚),而无需关闭整个系统。
  • 关注点分离。在架构上,微服务架构迫使您提出以下问题:“为什么存在此服务?” 更清楚地定义不同组件的角色。
  • 清除所有权。谁拥有什么代码变得更加清楚。服务通常在个人,团队或组织级别拥有,从而实现更快的增长。
  • 自主执行。独立的部署+明确的所有权范围可释放各种产品和平台团队的自主执行权。
  • 开发人员速度。团队可以独立部署代码,这使他们能够按照自己的节奏执行。

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

微服务问题
但是,随着公司规模的扩大(从100名工程师增加到1000名工程师),我们开始注意到与系统复杂性大大增加相关的一系列问题。使用微服务架构,人们可以将单个整体的代码库换成黑匣子,黑匣子的功能可以随时更改,并且很容易引起意外行为。
由于服务之间的调用可能会深入很多层,因此了解服务之间的依赖性可能会变得非常困难。第n个依赖项中的延迟峰值可能会导致上游问题的级联。如果没有合适的工具,就不可能看到实际发生的事情,从而使调试变得困难。

为了构建简单的功能,工程师经常必须跨多个服务工作,所有这些服务都由不同的个人和团队拥有。这就需要大量的协作以及花在会议,设计和代码审查上的时间。当团队在彼此的服务中构建代码,修改彼此的数据模型甚至代表服务所有者执行部署时,先前明确的服务所有权界限的承诺就受到了损害。可以形成联网的整体,其中必须将似乎独立的所有服务一起部署以安全地执行任何更改。
结果是开发人员体验变慢,服务所有者不稳定,迁移更加痛苦等。对于已经采用微服务架构的组织而言,这是没有回头路的。这变成了“ 不能与他们同住,没有他们就不能生活”的情况。 

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

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

  1. 我们不是围绕单个微服务,而是围绕相关微服务的集合。我们称这些域。
  2. 我们进一步创建我们称为图层的域的集合。域所属的层确定了允许该域内的微服务承担什么依赖性。我们称这种层设计。
  3. 我们为域提供干净的接口,这些域被视为集合的单个入口点。我们称这些网关。
  4. 最后,我们确定每个域都应该与其他域不可知,也就是说,一个域不应该具有与在其代码库或数据模型内部进行硬编码的另一个域相关的逻辑。由于团队经常需要在另一个团队的域中包括逻辑(例如,自定义验证逻辑或数据模型上的某些元上下文),因此我们提供了一种扩展架构,以支持该域中定义明确的扩展点。

换句话说,通过提供系统的体系结构,域网关和预定义的扩展点,DOMA打算将微服务体系结构从复杂的东西转变为可理解的东西:结构化的一组灵活,可重用和分层的组件。 
这篇文章的其余部分将深入研究Uber在DOMA中的实施,我们已经看到的好处以及为可能希望采用这种方法的公司提供的实用建议。

1.领域
Uber域代表一个或多个与逻辑功能分组绑定的微服务的集合。设计域时常见的问题是“域应该有多大?” 在此我们不提供任何指导。有些域可以包含数十个服务,有些域只能包含一个服务。重要的任务是仔细考虑每个集合的逻辑角色。例如,我们的地图搜索服务构成一个领域,票价服务是一个领域,匹配平台(匹配骑手和驾驶员)是一个领域。这些也不总是遵循公司的组织结构。Uber Maps组织本身分为三个域,在三个不同的网关后面有80个微服务。

2.层设计
层设计回答了“什么服务可以调用什么其他服务?”的问题。在Uber的微服务架构中。结果,我们可以将层设计视为“按比例分离关注点”。另外,我们可以将层设计视为“大规模依赖管理”。
层设计描述了一种机制,用于考虑Uber跨服务依赖项的失败爆炸半径和产品特异性。当域从底层移到顶层时,它们在中断的情况下会影响较少的服务,并代表更多特定的产品使用案例。相反,底层的功能具有更多的依存关系,因此趋向于具有更大的爆炸半径,并代表了更通用的业务功能集。下图说明了此概念。

可以将顶层视为特定的用户体验(例如移动功能),并将底层视为通用的业务功能(例如帐户管理或市场旅行)。图层仅取决于其下的图层,这为我们提供了一种有用的启发式方法,可以考虑爆炸半径和区域集成之类的问题。
值得注意的是,功能通常会使此图表从特定的“下降”到更一般的水平。可以想象一个简单的功能,随着需求的发展,该功能最终将越来越成为一种平台。实际上,这种向下迁移是可以预料的,而且Uber的许多核心业务平台都是从特定于车手或驾驶员的功能开始的,随着我们开发更多的业务线并且它们具有更多的依赖性(例如Uber Eats或Uber Freight),其功能变得更加普遍。 )。
在Uber内部,我们建立了以下五个层次。

  1. 基础设施层。提供任何工程组织可以使用的功能。这是Uber对诸如存储或网络等重大工程问题的解答。
  2. 业务层。提供组织可以使用的Uber功能,但并非特定于特定产品类别或业务线(LOB)的功能,例如乘车,进餐或货运。
  3. 产品层。提供与特定产品类别或LOB相关但与移动应用程序无关的功能,例如由多个面向乘车应用程序使用的“请求乘车”逻辑(Rider,Rider“ Lite”,m.uber.com)等)。
  4. 表现层。提供直接与面向消费者的应用程序(移动/网络)中存在的功能相关的功能。 
  5. 边缘层。将Uber服务安全地暴露给外界。该层也支持移动应用程序。

如您所见,每个随后的层代表着越来越具体的功能分组,并且爆炸半径越来越小(或者换句话说,更少的组件取决于该层中的功能)。 

3.网关
术语“网关API”已经是微服务架构中广泛建立的概念。我们的定义与已建立的定义没有太大不同,只是我们倾向于将网关专门视为基础服务集合(我们称为域)单个入口点网关的成功取决于API设计的成功。

上图说明了网关的高级示意图。它抽象出了域的内部细节–多个服务,数据表,ETL管道等。只有接口– RPC API,消息传递事件和查询才暴露给其他域。
 
由于上游使用者仅在单一服务上运行,因此网关在将来的迁移,可 发现性以及系统复杂性的整体降低方面提供了许多好处,上游服务仅需依赖一个依赖关系,而不是对域中可能存在的几个下游服务的依赖关系。如果我们从面向对象设计的角度考虑网关,那么它们就是接口定义,它使我们能够根据底层“实现”(在本例中为底层微服务的集合)做我们想做的任何事情。

4.扩展
扩展表示一种扩展域的机制。扩展的基本定义是,它提供了一种扩展基础服务功能的机制,而无需更改该服务的实际实现,也不会影响其整体可靠性。在Uber,我们提供了两种不同的扩展模型:逻辑扩展数据扩展。扩展的概念使我们能够将体系结构扩展为能够独立工作的多个团队。
(1)逻辑扩展
逻辑扩展提供了一种扩展服务基础逻辑的机制。对于逻辑扩展,我们使用提供程序插件模式的变体,以及基于逐个服务定义的接口。这样一来,扩展团队就可以以接口驱动的方式实现扩展逻辑,而无需修改基础平台的核心代码。
例如,驾驶员上线。通常,我们会进行各种检查以确保允许驾驶员上线(安全检查,合规性等)。这些都由一个单独的团队拥有。一种实现方法是让每个团队在同一端点编写逻辑,但这会带来复杂性。每次检查都需要自定义且完全不相关的逻辑。 
对于逻辑扩展,“上线”端点将定义一个接口,他们希望每个扩展都符合预定义的请求类型和响应。每个团队都将注册一个负责执行此逻辑的扩展。在这种情况下,他们可能只是考虑了有关驾驶员的一些情况,然后返回布尔值,说明驾驶员是否可以上网。在线端点将简单地遍历这些响应,并确定它们是否为假。
这将核心代码与每个扩展解耦,并提供了扩展之间的隔离,这些扩展不知道正在执行什么其他逻辑。围绕此可以轻松构建更多功能,例如可观察性或功能标记。 

(2)数据扩展
数据扩展提供了一种将任意数据附加到接口的机制,以避免核心平台数据模型中的膨胀。对于数据扩展,我们利用Protobuf的Any功能,以便团队可以向请求添加任意数据。服务通常会存储此数据或将其传递给逻辑扩展,以便核心平台永远不会负责反序列化(并因此“了解”)此任意上下文。Protobuf的Any实现带有一些基础架构开销,以换取更强大的键入。对于更简单的实现,可以同样轻松地使用JSON字符串表示任意数据。


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

好处
UMA几乎在每个主要领域都在一定程度上受到了DOMA的影响。在过去的一年中,我们主要关注Uber的业务层 ,该业务层为我们的各个业务领域提供了通用逻辑。 
DOMA在Uber仍很年轻,我们很高兴在将来分享更多数据和有关我们架构的深入示例。但是,就简化的开发人员体验和降低整体系统复杂性而言,早期迹象非常积极。

1. 产品与平台
DOMA是Uber产品和平台团队共同努力的结果。平台支持成本通常下降了一个数量级。产品团队受益于护栏和加速的开发。例如,我们扩展架构的早期平台使用者能够通过采用扩展架构来缩短优先级和集成新功能的时间,从三天缩短到了三个小时,从而减少了用于消费者的代码审阅,规划和学习曲线的时间。

2.降低复杂度
以前,产品团队必须调用众多下游服务才能利用域。他们现在只简单调用一个即可。通过减少接触点的数量来支持新功能,平台能够将投入时间减少25-50%。此外,我们能够将2200个微服务分类为70个域。大约有50%已实施,并且大多数都有未来采用的计划。

3.未来的迁移 
在Uber,我们计算出微服务的半衰期为1.5年,这意味着每1.5年,我们微服务的50%流失。如果没有网关,微服务架构很容易因这种混乱而陷入“迁移地狱”。不断变化的微服务不断需要上游迁移。网关使团队可以避免对基础域服务的依赖,这意味着可以更改这些服务而无需强制进行上游迁移。 
去年,Uber最大的两次平台重写发生在网关后面。这些平台具有数百个依赖于它们的服务,这些服务将不得不迁移现有的消费者。在这种情况下,迁移的成本将非常高,因此无法进行完整的平台重写。

4.新的业务和产品线
实践证明,使用DOMA设计的平台具有更高的可扩展性和易于维护性。Uber采纳DOMA的大多数团队之所以这样做,是因为支持新的业务线变得太昂贵了。

5.实用建议
本节为可能希望采用DOMA的公司提供一些实用建议。这里的指导原则是,根据我们的经验,成熟,周到的微服务架构源于在正确的时间,正确的方向悄悄进行。现实情况是,对于一个人的整个微服务架构而言,真正的“重写”是不可能的。
因此,我们认为发展微服务架构更像是“修剪树篱”,以便最终能够正确增长,而不是自上而下或一次性架构(或重新架构)。这是一个动态的,渐进的过程。
(1)初创企业
驱动问题应该是“我们何时应采用微服务架构?” 和“这对我们的组织有意义吗?” 如上所述,虽然微服务为拥有大量工程师的组织提供了运营优势,但是这却与复杂性的增加权衡,而复杂性的增加会使功能的构建更加困难。
在小型组织中,运营收益可能无法抵消架构复杂性的增加。此外,微服务架构通常需要专用的工程资源来支持,这对于早期公司来说可能超出预算,或者从优先次序的角度来看不是最佳的。 
考虑到这一点,完全搁置一段时间的微服务并非没有道理。如果组织确实选择采用微服务,则应考虑“将微服务视为大型分布式应用程序”的类比,并考虑要构建的微服务之间的关注点分离。此外,请认识到,第一个微服务将真正描述业务的核心,因此可能是最重要和最长的。

(2)中型企业
一旦一家公司由多个团队组成的中型公司,而关注点之间的明确区分变得模糊不清,那么不同功能和平台之间的微服务架构就会变得更加有用。
在这个阶段,人们可以开始考虑微服务之间的层次结构。依赖管理可能变得更加重要,因为某些服务开始对业务运营变得越来越重要,并且越来越多的团队依赖于它们。 
对平台化的早期投资可能会带来红利。如果可以完全创建与产品无关的业务平台,并避免核心平台服务中的任意产品逻辑,则可以避免技术债务。在这一点上采用扩展来实现该目标可能是有意义的。
鉴于微服务的数量可能仍然很低,因此将它们集群在一起可能没有任何意义。但是,在这里值得注意的是,Uber的DOMA实现上下文中的域可以包含单个服务,因此以“面向域”的方式进行思考可能仍然有用。

(3)大型企业
较大的工程组织可能拥有数百名工程师和微服务以及一些依赖项。至此,DOMA完全发挥了作用。可能会有明显的微服务集群,可以很容易地将它们组合在一起,并在它们前面放置网关。传统服务通常需要开始进行重构或重写,然后再进行迁移,这意味着,如果网关已经存在,网关将很快开始提供易于迁移方面的价值。
清晰的层次结构也将变得越来越重要,因为某些服务作为特定功能或功能分组的“产品”服务运行,而其他服务将越来越多地支持多种产品,并被视为“平台”。在此阶段,保持任意产品逻辑与平台的分离至关重要,这样可以避免对平台团队造成沉重的运营负担以及整个系统的不稳定。
最后的想法
随着Uber越来越多的团队采用DOMA,我们仍在积极发展DOMA。DOMA的关键见解是,微服务架构实际上只是一个大型的分布式程序,您可以将其应用于任何软件的原理应用于其演变。DOMA只是在实践中考虑这些原则的一种方法。我们希望其他人觉得它有用,我们期待您的反馈!
DOMA本身是跨职能工作的结果,该工作涉及Uber每个组织的近60名工程师。对于在过去两年中投入大量精力进行这项工作的人们来说,这是一些特别的认可……


banq注:DDD+microservice+hexagon六角形端口适配器+鲍勃大叔clean架构=Uber的DOMA