优步:面向“域”的微服务架构,满足2200个关键微服务的扩展
最近,业界围绕面向服务的架构,尤其是微服务架构的弊端进行了大量讨论。几年前,由于许多用户关注微服务架构的众多优势,例如独立部署形式的灵活性,明确的所有权,系统稳定性的改进,以及关注点的更好分离,许多企业很快采用了微服务架构。而现在关于微服务会大大增加其复杂性的趋势,有时甚至使琐碎的功能也难以构建的话题,引发了不少用户的讨论。
Uber近年来一直在使用微服务,现在Uber已经增长到大约2200个关键微服务,这个过程中Uber做了不少的权衡。Uber表示,在过去的两年中,他们尝试降低微服务的复杂性,同时仍保持微服务架构的优势。这篇文章详细介绍了Uber对微服务架构的通用方法,Uber将其称为“面向域的微服务架构”(简称:DOMA)。
面对微服务的不足,批评微服务架构的话题喧嚣尘上,但很少有用户主张完全拒绝微服务架构。因为运营收益太重要了,似乎还有针对微服务的更好替代品。Uber使用DOMA通用方法的目标是为降低总体系统复杂性,同时保持与微服务架构相关联的灵活性的企业,提供微服务向前发展的道路。
什么是微服务?
微服务是面向服务的架构的扩展。与过去大型的整体“服务”相反,微服务代表一组范围狭窄的功能的应用程序。这些应用程序是托管的,并且可以通过网络使用,并公开定义明确的接口。其他应用程序通过进行“ 远程过程调用 ”(RPC)来调用这个接口。
微服务架构的关键特征是代码的托管,调用和部署方式。如果我们考虑大型的整体应用程序,通常会将它们分为具有明确定义的接口的封装组件。这些接口将被直接称为进程内接口,而不是通过网络。通过这种方式,我们可以开始将微服务当做具有性能问题(网络I/O和序列化/反序列化)的库,以便调用其任何功能。
当我们以这种方式考虑微服务时,我们可能会思考为什么我们会完全采用微服务架构?答案通常是独立部署和扩展。对于大型的整体应用程序,企业不得不一次部署或释放所有代码。应用程序的每个新版本都可能涉及许多更改,而且部署变得既危险又费时。任何差池都可能使整个系统瘫痪。
换句话说,企业采用微服务来获得运营的价值,而以性能为代价。企业还必须承担维护支持微服务所需的基础架构的成本。事实证明,在很多情况下,这种权衡是有道理的,但这也是反对过早采用微服务架构的理由之一。
缘起
Uber采用微服务架构,因为大约在2012年至2013年,Uber拥有两个整体服务,并且微服务的使用解决了许多运营问题。
可用性风险。单一代码库中的单个回归可以使整个系统瘫痪。
风险高昂的部署。由于频繁需要回滚,因此执行这些操作很痛苦且耗时。
关注点分离差。使用庞大的代码库,很难很好地保持关注点的分离。在指数增长的环境中,权宜有时会导致逻辑和组件之间的边界不清晰。
执行效率低下。这些问题加在一起使团队难以自主或独立执行。
换句话说,随着Uber的工程师人数从10增长到100,拥有多个团队,拥有部分技术栈的整体式架构将团队的命运束缚在一起,使其难以独立运营。
Uber采用了微服务架构之后。最终,系统变得更加灵活,从而使团队更加自治。
系统可靠性。在微服务架构中,总体系统可靠性得到提高。单个服务可以关闭(并回滚),而无需关闭整个系统。
关注点分离。在架构上,微服务架构迫使Uber提出以下问题:“为什么存在这项服务?”,从而更清楚地定义不同组件的角色。
清除所有权。谁拥有什么代码变得更加清楚。服务通常在个人,团队或企业级别拥有,从而实现更快的增长。
自主执行。独立的部署和更清晰的所有权界限,可释放各个产品和平台团队的自主执行权。
开发人员速度。团队可以独立部署代码,这使他们能够按照自己的节奏执行。
毫不夸张地说,没有微服务架构,Uber将无法实现今天维持的执行规模和执行质量。
但是,随着Uber规模的扩大,从100名工程师增加到1000名工程师,Uber开始注意到与系统复杂性大大增加相关的一系列问题。使用微服务架构,可以将单个整体的代码库换成“黑盒子”,“黑盒子”的功能可以随时更改,并且很容易导致意外的发生。
要调试pick-up问题,工程师必须在12个不同的团队中完成约50项服务。
由于服务之间的调用可能会深入很多层,因此了解服务之间的依赖性可能会变得非常困难。第n个依赖项中的延迟峰值可能会导致上游问题的级联。如果没有合适的工具,就不可能看到实际发生的事情,从而使调试变得困难。
Jaeger于2018年中发布的Uber微服务架构
为了构建简单的功能,工程师经常必须跨多个服务工作,所有这些服务均由不同的个人和团队拥有。这就需要大量的协作以及花在会议,设计和代码审查上的时间。当团队在彼此的服务中构建代码,修改彼此的数据模型,甚至代表服务所有者执行部署时,先前明确的服务所有权承诺将受到损害。可以形成网络整体,其中必须将似乎独立的所有服务一起部署以安全地执行任何更改。
大约在2018年Uber的复杂流程示例,在DOMA之前需要10个接触点才能进行简单集成。
结果是开发人员体验变慢,服务所有者不稳定,迁移更加痛苦等等。对于已经采用微服务架构的企业而言,没有回头路。需要找到解决方案来克服这些挑战
面向“域”的微服务架构
如果可以将微服务视为I/O绑定库,而将“微服务架构”视为大型的分布式应用程序,则可以使用众所周知的架构来思考如何组织代码。
因此,“面向域的微服务架构”大量借鉴了已建立的组织代码的方式,例如域驱动设计,干净架构,面向服务的架构,以及面向对象和面向接口的设计模式。Uber将DOMA视为创新,因为它是在大型组织的大型分布式系统中,利用既定设计原则的相对新颖的方法。
与DOMA相关的核心原理和术语如下:
- Uber不是围绕单个微服务,而是围绕相关微服务的集合——称为域。
- Uber进一步创建称为图层的域的集合。域所属的层确定了允许该域内的微服务承担什么依赖性——称为层设计。
- Uber为域提供干净的接口,这些域被视为集合的单个入口点——称为网关。
- 最后,Uber确定每个域都应该与其他域不可知,也就是说,一个域不应该具有与在其代码库或数据模型内部进行硬编码的另一个域相关的逻辑。由于团队经常需要在另一个团队的域中,包括逻辑(如,自定义验证逻辑或数据模型上的某些元上下文),因此我们提供了一种扩展架构,以支持该域中定义明确的扩展点。
换句话说,通过提供系统的架构,域网关和预定义的扩展点,DOMA打算将微服务脚骨从复杂的东西转变为可理解的东西:结构化的一组灵活,可重用和分层的组件。
以下将深入研究Uber在DOMA中的实施,已经看到的价值,以及为可能希望采用这种方法的企业提供的实用建议。
DOMA的实施
域
Uber域代表一个或多个与逻辑功能分组绑定的微服务的集合。设计域时常见的问题是“域应该有多大?” 在此Uber不提供任何指导。有些域可以包含数十个服务,有些域只能包含一个服务。
重要的任务是仔细考虑每个集合的逻辑角色。例如,Uber的地图搜索服务构成一个域,票价服务是一个领域,匹配平台(匹配驾驶员)是一个域。这些也不总是遵循企业的组织结构。Uber Maps组织本身分为三个域,在三个不同的网关后面有80个微服务。
层设计
层设计回答了“什么服务可以调用什么其他服务?”的问题。在Uber的微服务架构中,可以将层设计视为“按比例分离关注点”。另外,可以将层设计视为“大规模依赖管理”。
层设计描述了一种机制,用于考虑Uber跨服务依赖项的失败故障面(原文为失败爆炸半径, failure blast radius)和产品特异性。当域从底层移到顶层时,它们在中断的情况下会影响较少的服务,并代表更多特定的产品使用案例。相反,底层的功能具有更多的依存关系,因此趋向于具有更大的故障面,并代表了更通用的业务功能集。下图说明了此概念。
可以将顶层看作是特定的用户体验(比如移动功能),而底层看作是通用的业务功能(比如账户管理)。这为我们思考故障面和域集成等问题提供了有益的启发。
值得注意的是,在这个图表中,功能常常从特定的“向下”到更普遍的“向下”。可以想象,随着需求的发展,一个简单的特性最终会越来越像一个平台。事实上,这种向下的迁移是意料之中的,而且Uber的许多核心业务平台一开始只是针对乘客或司机的特定功能,随着我们发展了更多的业务线,它们变得越来越普遍(比如Uber Eats或Uber Freight)。
在Uber内部,建立了以下五个层次。
- 基础设施层。提供任何工程团队可以使用的功能。这是Uber对诸如存储或网络等重大工程问题的解答。
- 业务层。提供Uber作为一个组织可以使用的功能,但这并不特定于特定的产品类别或业务(LOB),如乘车、餐饮或货运。
- 产品层。提供与特定产品类别或LOB相关的功能,但与移动应用程序无关,比如“请求搭车”逻辑,它被多个搭车应用程序(Rider、Rider“Lite”、m.uber.com等)利用。
- 演示层。提供与面向消费者的应用程序(移动/web)中存在的特性直接相关的功能。
- 边缘层。安全向外界开放优步服务。这一层也支持移动应用程序。
如您所见,后续的每一层都代表了越来越具体的功能分组,并且具有越来越小的故障面(换句话说,更少的组件依赖于该层内的功能)。
网关
术语“网关API”在微服务架构中已经是一个广泛建立的概念。Uber的定义与已建立的定义差别不大,除了倾向于将网关专门看作底层服务集合(称之为域)的单个入口点之外。网关的成功依赖于API设计的成功。
上图说明了网关的高级图。它抽象出域的内部细节——多个服务、数据表、ETL管道等。只有接口—RPC api、消息传递事件和查询被公开给其他域。
由于上游使用者只在单个服务上操作,因此通过上游服务只接受单个依赖项(而不是依赖于某个域中可能存在的多个下游服务),网关在未来的迁移、可发现性和系统复杂性的整体降低方面提供了许多好处。如果从面向对象设计的角度来考虑网关,那么它们就是接口定义,它使Uber能够根据底层“实现”(在本例中是底层微服务的集合)来做任何想做的事情。
扩展
扩展表示一种扩展域的机制。扩展的基本定义是,它提供了一种机制来扩展基础服务的功能,而不改变该服务的实际实现,也不影响其总体可靠性。在Uber,提供两种不同的扩展模型:逻辑扩展和数据扩展。扩展的概念允许Uber将架构扩展到多个团队,从而能够彼此独立地工作。
逻辑扩展
逻辑扩展为扩展服务的底层逻辑提供了一种机制。对于逻辑扩展,Uber使用provider or plugin模式的变体,并在逐个服务的基础上定义接口。这使得扩展团队能够以接口驱动的方式实现扩展逻辑,而无需修改底层平台的核心代码。
如,一个司机上线(go online)。通常,我们会进行各种检查,来确保司机可以运营(安全检查、合规等)。每一个都属于一个单独的团队。实现这一点的一种方法是让每个团队在相同的端点编写逻辑,但这可能会引入复杂性。每个检查都需要定制的、完全不相关的逻辑。
对于逻辑扩展,“go online”端点将定义一个接口,希望每个扩展都符合预定义的请求类型和响应。每个团队将注册一个负责执行此逻辑的扩展。在这种情况下,它们可能只是取一些关于驱动程序的上下文,然后返回一个bool,说明驱动程序是否可以上线。go online端点将简单地遍历这些响应,并确定其中是否有错误。
这将核心代码与每个扩展解耦,并提供扩展之间的隔离,而不知道其他逻辑正在执行。围绕它构建更多的功能很容易,比如可观察性或特性标记。
数据扩展
数据扩展提供了将任意数据附加到接口,来避免核心平台数据模型膨胀的机制。对于数据扩展,Uber利用了Protobuf的Any功能,让团队可以向请求添加任意数据。服务通常会存储这些数据或将其传递给逻辑扩展,便于核心平台永远不会对这个任意上下文进行反序列化(从而“knowing about”)。Protobuf的Any实现带来了一些基础设施开销,以换取更强的类型。对于更简单的实现,可以简单地使用JSON字符串表示任意数据。
自定义
除了逻辑和数据扩展,Uber的许多团队都引入了适合自己领域的扩展模式。例如,与presentation架构绑定的许多集成使用基于DAG的任务执行逻辑。
价值所在
Uber的几乎每个主要域都在某种程度上受到了DOMA的影响。在过去的一年里,Uber主要关注业务层,它为不同的业务线提供了通用的逻辑。
DOMA在Uber还很年轻,然而从简化开发人员体验和降低整个系统复杂性的角度来看,它的早期表现非常积极。
产品与平台
DOMA是Uber跨产品和平台团队一致努力的结果。平台支持成本通常会下降一个数量级。产品团队受益于guard rails和加速的开发。
例如,我们的扩展架构的早期平台使用者能够通过采用扩展架构减少代码审查、规划和用户学习曲线的时间,将优先级和集成新特性的时间从三天减少到三小时。
降低复杂性
以前的产品团队必须调用大量的下游服务来利用一个域;现在只需要调用一个。通过减少加载新功能的接触点数量,平台能够减少25-50%的登陆时间。此外,能够将2200个微服务划分为70个域。其中大约有50%已经实现,而且大多数都有未来采用的计划。
未来的迁移
在Uber,计算出微服务的半衰期是1.5年,这意味着每1.5年我们的微服务就会有50%的变动。如果没有网关,微服务架构很容易因此陷入“迁移的噩梦”。不断变化的微服务需要不断进行上游迁移。
网关使团队能够避免对基础域服务的依赖,这意味着这些服务可以在不强制上游迁移的情况下进行更改。
这些平台有数百个依赖于它们的服务,而这些服务将不得不迁移现有的消费者。在这些情况下,迁移的成本会非常高,使得重写一个完整的平台变得不可行。
新的业务和产品线
实践证明,使用DOMA设计的平台具有更高的可扩展性和易于维护性。Uber采纳DOMA的大多数团队之所以这样做,是因为支持新的业务线变得太昂贵了。
实用建议
Uber为希望采用DOMA的公司提供一些实用建议。指导原则是,根据Uber的经验,一个成熟的、深思熟虑的微服务架构来自于在正确的时间朝着正确的方向缓慢推进。现实情况是,对于整个微服务架构而言,真正的“重写”是不可能的。
因此,Uber认为发展微服务架构更像是“修剪园艺”,一步步上线正确增长,而不是自上而下或一次性架构(或重新架构)。这是一个动态的,渐进的过程。
初创企业
驱动问题应该是“何时应采用微服务架构?” 以及“这对企业有意义吗?” 如上所述,尽管微服务为拥有大量工程师的企业带来了运营优势,但这种做法的代价是复杂性的增加,复杂性的增加使功能的构建更加困难。
在初创企业中,运营收益可能无法抵消架构复杂性的增加。此外,微服务架构通常需要专用的工程资源来支持,这对于早期公司来说可能超出预算,或者从优先级的角度来看不是优秀选择。
考虑到这一点,完全搁置一段时间的微服务并非没有道理。如果企业确实选择采用微服务,则应考虑“将微服务视为大型分布式应用程序”的类比,并考虑要构建的微服务之间的关注点分离。此外,请注意第一个微服务可能真正地描述了业务的核心,因此可能是最重要的。
中型企业
对于中型企业,已经有了成熟的团队,而关注点之间的明确区分变得模糊不清,那么不同功能和平台之间的微服务架构就变得更加有用。
在这个阶段,企业可以开始考虑微服务之间的层次结构。依赖管理可能变得更加重要,因为某些服务对于业务运营变得越来越至关重要,并且越来越多的团队依赖于它们。
平台化的早期投资可能会在未来带来回报。如果一个人能够创造出完全与产品无关的商业平台,并且在核心平台服务中避免任意的产品逻辑,那么就有可能避免技术债务。此时采用扩展来实现这个目标是有意义的。
鉴于微服务的数量可能仍然很低,将它们聚在一起可能没有意义。然而,这里值得注意的是,Uber的DOMA实现上下文中的域可以包含单个服务,因此以“面向域”的方式思考仍然是有用的。
大型企业
较大的企业组织可能拥有数百名工程师和微服务以及数个依赖项。至此,DOMA完全发挥了作用。可能会有明显的微服务集群,可以很容易地将它们组合在一起,并在它们前面放置网关。传统服务通常需要开始进行重构或重写,然后再进行迁移。这意味着,如果网关已经存在,网关将很快开始提供易于迁移方面的价值。