原文地址
摘要
最近有很多关于SOA(面向服务架构)的讨论,特别是关于微服务架构的缺点讨论。就在几年前,许多人还倾向于采用微服务架构,由于微服务体架构具备许多优点,比如灵活独立部署的形式、责任清晰、系统稳定性好和更好的关注点分离。近年来,人们开始吐槽微服务的复杂性,有时甚至很简单的功都能难以实现。
随着优步发展到大约有2200个核心微服务时,我们亲身体验了这些利弊的权衡。在过去的两年中,优步奋力保持微服务架构优势,同时降低微服务的复杂性。通过这篇博客,我们希望介绍关于构建微服务架构的通用方法,我们将其称为“面向领域的微服务架构”(DOMA-Domain-Oriented Microservice Architecture)。
虽然近年来因为微服务的这些缺点,大家吐槽微服务架构,但很少有人直接主张拒绝微服务架构。因运营效益太重要了,似乎没有或有限的其他选择。关于DOMA的目标是为那些希望在保持微服务体架构灵活性的同时降低系统复杂性的组织提供一种架构方式。
这篇文章介绍了DOMA,以及Uber为何采用这种架构,它对平台和产品团队的好处,最后是对希望采用这种架构的企业一些建议。
什么是微服务
微服务是面向服务架构(SOA)的扩展。与包含2000个功能的特大型“服务”相反,微服务是功能单一的服务集合。这些微服务被托管并通过网络访问,暴露定义好的接口。其他应用程序通过RPC/HTTP来调用这些接口。
微服务架构的核心特征是代码管理、服务调用和部署方式。考虑到大型单体应用,它们通常被分成具有良好定义的接口组件。这些接口将在进程内直接调用,而不是通过网络调用。通过这种方式,我们可以将微服务看作一个性能受限(因网络I/O和序列化/反序列化)的库,因为需要通过网路来访问微服务的功能(函数)。
当我们以这种方式考虑微服务时,我们可能会问为什么要采用微服务架构。答案通常是能独立部署和扩展。对于大型单体应用,一次修改,整个系统被迫重新部署或发布所有代码。系统的每个新版本都可能涉及许多更改。系统部署变得危险、耗时。任何人都能搞垮整个系统。
换句话说,团队采用微服务是以牺牲性能为代价换取高效地部署、扩展等优势。团队还必须承担支持微服务所需的基础设施成本。事实证明,在许多情况下,这种利益权衡是很有意义的,但这也是反对过早采用微服务体系结构的有力论据。
动机
在Uber,我们采用了微服务架构,因为我们(大约在2012-2013年)主要有两个独立的服务,而且微服务能解决许多运营问题。
- 可用性风险:单体应用代码的一次回归就可能导致整个系统(在本例中是整个Uber)崩溃。
- 部署代价大,风险高:在频繁回滚的情况下,执行这些操作是痛苦和耗时的。
- 关注点分离不佳:在庞大的代码库中很难保持良好的关注点分离。在指数增长的环境中,有时会导致逻辑和组件之间的边界不清晰。
- 执行效率低:这些问题结合在一起,使得团队难以自主或独立地执行开发。
换句话说,随着优步的工程师人数从10人增加到100人,多个团队拥有不同的技术栈,单体架构将团队的命运联系在了一起,使其难以独立运营。
因此,我们采用了微服务体系结构。最终,我们的系统变得更加灵活,这使得团队更加自治。 - 系统可靠性:微服务架构提高了系统的整体可靠性。单个服务可以关闭(回滚),而不需要关闭整个系统。
- 关注点分离:在架构上,微服务架构迫使您问这样一个问题:“这个服务为什么存在?”,更清楚地界定不同组件的功能。
- 责任清晰:谁负责的代码变得更加清楚。服务通常由个人、团队或企业级别负责,从而实现更快的增长。
- 自主执行:独立的部署+更清晰的所有权解锁了各种产品和平台团队的自主执行。
- 开发速度:团队可以独立部署他们的代码,这使他们能够按照自己的节奏开发。
可以毫不夸张地说,如果没有微服务架构,优步不可能实现我们今天所保持的规模和质量。
然而,随着公司规模的扩大,从100名工程师到1000名工程师,我们开始注意到一系列快速增长的系统复杂性相关的问题。在微服务架构中,我们可以将一个微服务看成是黑盒,但黑盒的功能可以随时更改,就很容易导致意外行为。例如,工程师必须通过12个不同团队的50个服务来查询问题的根本原因。
理解服务之间的依赖关系可能会变得相当困难,因为服务之间的调用可能会深入许多层。第n个依赖服务的延迟可能级联导致上游问题。如果没有正确的工具,就不可能可视化实际发生了什么,这使得调试变得困难。
为了构建一个简单的特性,工程师经常需要跨多个服务工作,所有这些服务都属于不同的个人和团队。这需要广泛的协作,并且在会议、设计和代码审查上花费大量的时间。当团队在彼此的服务中构建代码、修改彼此的数据模型,甚至替代服务所有者执行部署时,先前关于服务边界清晰的承诺就被破坏,会形成网络化的单体。为了安全地执行更改,其中看起来独立的服务都必须被部署在一起。
其结果是开发变慢、服务所有者不稳定、迁移痛苦等等。对于已经采用微服务架构的组织来说,已经没有回头路了。这就导致一种“不能耦合在一起,但又不能分离“的现象。
面向领域的微服务架构
如果我们可以把微服务看作是绑定I/O的库,而把“微服务架构”看作是一个大型的分布式应用,那么我们就可以使用易于理解的架构来考虑如何组织我们的代码。
因此,“面向领域的微服务架构”大量借鉴了已有的代码组织方式,如领域驱动设计、整洁架构、面向服务架构以及面向对象和面向接口设计模式。我们认为DOMA的创新之处在于,它在大型组织中开发大型分布式系统时使用了那些已有的设计原则。
DOMA的核心原则和术语如下:
1、我们不是面向单个微服务,而是面向相关微服务的集合。我们称之为领域(domain)。
2、进一步创建领域的集合,我们称之为层(layer)。领域所属的层确定了该域中的微服务允许承担的依赖关系。我们称之为分层设计。
3、为这些领域提供整洁的接口,作为访问这些领域集合的唯一入口。我们称为网关。
4、最后,明确每个领域对其他领域内部是不可知的,也就是说,一个领域不应该和另一个领域有逻辑关联,或者在其代码库或数据模型中有硬编码。由于团队经常需要在另一个团队的领域中包含逻辑(例如,自定义验证逻辑或数据模型上的一些上下文),我们提供了一个扩展架构来支持领域内定义良好的可扩展点。
换句话说,通过提供一个系统架构、领域网关和预定义的扩展点,DOMA将微服务架构由复杂转换为可理解的:一组灵活、可重用和分层的结构化组件。
这篇文章的其余部分将深入探讨Uber实施DOMA的方法,我们已经看到的好处,以及为可能想采用这种方法的公司提供实用建议。
Uber实现
Domains
优步的domains表示一个或多个功能相关的微服务集合。设计domain时的一个常见问题是“domain范围应该有多大?”这里我们不提供任何指导。有些域可以包含数十个服务,有些域只能包含一个服务。重要的是仔细考虑每个集合的逻辑角色。例如,我们的地图搜索服务构成了一个domain,票价服务是一个domain,匹配平台(匹配乘客和司机)是一个domain。这些也并不总是遵循公司的组织结构。优步地图团队本身被分成三个domain,在三个不同的网关后面有80个微服务。
分层设计
分层设计回答了在优步的微服务架构中“什么服务可以调用其他服务?”。因此,我们可以将分层设计看作是“规模化的关注点分离”。或者,我们可以把分层设计看作是“规模化的依赖管理”。
分层设计描述了一种机制,用于考虑Uber服务依赖关系中的故障影响范围和产品专用性。随着domain从底层迁移到顶层,它们在停机时影响的服务更少,并代表更具体的产品用例。相反,底层的功能具有更多的依赖关系,因此往往具有更大的故障影响范围,并代表一组更通用的业务功能。下图说明了这个概念。
我们可以将顶层看作是特定的用户体验(如移动功能),而底层则是通用的业务功能(如帐户管理或市场旅行)。特定层只依赖于它们下面的层,这为我们思考故障影响范围和domain集成等问题提供了有益的启发。
值得注意的是,功能通常会从这个图表的特定部分“向下”移动到更一般的部分。可以想象,随着需求的发展,一个简单的特性最终会越来越像一个平台。事实上,这种向下迁移是可预见的,优步的许多核心业务平台一开始是乘客或司机特定的功能,随着我们发展更多的业务线,它们变得更加一般化,并具有更多的依赖性(如Uber送餐或货运)。
在Uber内部,我们建立了以下五个层。
1、* 基础设施层。提供任何团队都可以使用的功能。这是优步对存储或网络等重大工程问题的答案。
2、* 业务层。提供优步作为一个组织可以使用的功能,但不只针对特定的产品类别或业务线(LOB),如乘车、餐饮或货运。
3、* 产品层。提供与特定产品类别或业务线相关的功能,但与移动应用程序无关,如“请求乘车”逻辑。
4、* 表示层。提供面向消费者的应用服务(移动/web)
5、* 边缘层。将优步服务安全地对外开放。这一层也是移动应用程序感知的。
正如您所看到的,每个后续层都代表了一个日益特定的功能分组,并且具有越来越小的故障影响范围(换句话说,依赖于该层功能的组件越来越少)
网关
术语“网关API”在微服务架构中已经是一个广泛的概念。我们的定义与已有的定义相差不大,只是我们倾向于将网关专门视为基础服务集合(我们称之为域)的唯一入口。网关的成功依赖于API设计的成功。
由于上游消费者只在单个服务上操作,上游服务只采用单个依赖关系,而不依赖某个domain中存在的多个下游服务,因此网关在未来迁移、可发现性和整体降低系统复杂性方面提供了许多好处。如果我们从面向对象设计的角度来考虑网关,那么它们就是接口定义,它使我们能够在底层“实现”(在本例中是底层微服务的集合)方面做我们想做的任何事情。
扩展
扩展:表示一种扩展领域的机制。扩展的基本定义是,它提供了一种机制,用于扩展基础服务的功能,而不会改变该服务的实际实现,也不会影响其总体可靠性。在Uber,我们提供两种不同的扩展模型:逻辑扩展和数据扩展。扩展的概念允许我们将架构扩展到多个能够相互独立工作的团队。
逻辑扩展
逻辑扩展为扩展服务的底层逻辑提供了一种机制。对于逻辑扩展,我们使用提供者或插件模式的变体,并在每个服务的基础上定义接口。这使得扩展团队可以以接口驱动的方式实现扩展逻辑,而无需修改底层平台的核心代码。
例如,一个司机上线。通常,我们会进行各种检查,以确保司机可以上线(安全检查、合规等)。每一个功能都属于一个独立的团队。实现这一点的一种方法是让每个团队在相同的入口编写逻辑,但这可能会引入复杂性。每个检查都需要自定义、完全不相关的逻辑。
在逻辑扩展的情况下,“上线”入口将定义成一个接口,它们希望每个扩展都符合预定义的请求类型和响应。每个团队将注册一个负责执行此逻辑的扩展。在这种情况下,它们可能只是获取有关司机的一些上下文,并返回一个bool值,表示司机是否可以上线。上线服务将简单地遍历这些响应,并确定其中是否有错误。
这将核心代码与每个扩展解耦,并在扩展之间提供隔离,而不知道正在执行的其他逻辑。很容易围绕这一点构建更多功能,比如可观察性或特性标记。
数据扩展
数据扩展提供了一种将任意数据附加到接口的机制,避免核心平台数据模型的膨胀。对于数据扩展,我们利用Protobuf的Any功能,这样团队就可以向请求中添加任意的数据。服务通常会存储这些数据或将其传递给逻辑扩展,这样核心平台就不负责反序列化这个任意上下文。Protobuf的Any实现带来了一些基础设施的开销,为换取更强的类型。对于简单的实现,可以很容易地使用JSON字符串来表示任意数据。
自定义扩展
除了逻辑和数据扩展,Uber的许多团队已经引入了适合自己领域的扩展模式。例如,与我们的表示层架构相关联的许多集成使用基于DAG的任务执行逻辑。
优点
几乎每个优步的主要domain都在一定程度上受到了DOMA的影响。在过去的一年里,我们主要专注于优步的业务层,它为我们不同的业务线提供了一般化的逻辑。
DOMA在Uber还很年轻,我们很高兴在未来分享更多关于架构的信息和深入例子。然而,早期的迹象表明在简化开发人员体验和整体系统复杂性的降低方面是非常积极的。
产品&平台
DOMA是优步产品和平台团队一致努力的结果。平台的支持成本通常会降低一个数量级。产品团队受益于隔离和快速开发。
例如,一个早期使用扩展架构的平台,能够较少在优先级上消耗时间并且集成一个特性的耗时从三天缩短到3小时,通过使用扩展接口减少使用者在代码review、规划和学习曲线的耗时。
减少复杂性
以前,产品团队必须调用大量下游服务来利用一个domain;他们现在只需调用一个服务。通过减少新功能的接触点数量,平台能够将上线时间减少25% -50%。此外,我们可以将2200个微服务划分为70个domain。其中大约50%已经实施,并且大多数未来也计划会采用这种方法。
迁移
在Uber,我们计算出微服务的半衰期为1.5年,这意味着每1.5年就有50%的微服务调整。如果没有网关,微服务架构很容易因此而陷入“迁移地狱”。不断变化的微服务经常需要上游迁移。网关使团队能够避免依赖于下游domain服务,意味着这些服务可以在不强制上游迁移的情况下更改。
去年,优步最大的两个平台重构就基于网关来实现的。这些平台拥有数百个依赖于它们的服务,这些服务必须迁移现有的调用服务。在这些情况下,迁移的成本将会非常高,使得一个完整的平台重写是不可行的。
新业务和产品
使用DOMA设计的平台已证明具有更强的可扩展性和更容易维护。大多数采用DOMA的Uber团队之所以这么做,是因为新增业务线变得过于昂贵。
实用建议
本节为希望采用DOMA的公司提供一些实用建议。这里的指导原则是,根据我们的经验,一个成熟且深思熟虑的微服务架构源自于在正确的时间向正确的方向推进。事实上,对整个微服务架构来说,真正的“重写”是不可能的。
因此,我们认为微服务架构的演进更像是“修剪篱笆”,为了最终正确地发展,而不是自顶向下或一次性的架构(或重构)工作。这是一个动态和渐进的过程。
初创企业
首要问题应该是“我们什么时候采用微服务架构?”以及“这对我们的企业有意义吗?”正如我们在上面所看到的,虽然微服务为拥有大量工程师的企业提供了操作上的优势,但这也会导致复杂性的增加,从而使功能更加难以构建。
在小型企业中,操作上的好处可能无法抵消架构复杂性的增加。此外,微服务架构通常需要专门的工程团队来支持,这可能超出了早期公司的预算,或者从优先级的角度来看不是最优的。
考虑到这一点,推迟微服务的使用并不是不合理的。如果一个企业确实选择采用微服务,它应该把“微服务看作大型分布式应用程序”,以及考虑它想要构建的微服务之间的关注点分离。此外,要认识到第一个微服务可能是最重要和最持久的,因为它们描述了业务的核心。
中型企业
一旦一个公司发展成拥有多个团队的中型公司,并且不同功能和平台之间的关注点分离变得模糊时,微服务架构就会更加有用。
在这个阶段,我们可以开始考虑微服务之间的层次结构。依赖关系管理可能变得更加重要,因为一些服务开始对业务操作变得更加重要,并且越来越多的团队依赖于它们。
对平台化的早期投入会在未来获得回报。如果一个人能够创建完全与产品无关的业务平台,并在核心平台服务中避免任意的产品逻辑,就有可能避免技术债务。此时采用扩展来实现这个目标可能是很明智的。
考虑到微服务的数量可能仍然很少,将它们聚集在一起可能没有意义。然而,值得注意的是,在Uber的DOMA实现上下文中,一个领域可以包含单个服务,所以考虑以“面向领域”的方式仍然是有用的。
大型企业
较大的工程团队可能有数百名工程师、微服务和若干依赖关系。在这一点上,DOMA完全适用。很可能会出现明显的微服务集群,这些微服务可以很容易地分组到domain中,并且有一个网关来暴露接口。已有的老服务通常需要重构或重写,然后迁移,这意味着网关将很快开始提供易于迁移的价值。
大型业务清晰的层次结构将变得越来越重要,一些服务将以特定功能或功能分组“产品”形式运行,而其他服务将越来越支持多种产品,并视为“平台”。在这个阶段,保持任意产品逻辑与平台分离是非常关键的,这样可以避免平台团队的沉重操作负担以及系统范围的不稳定性。
结束语
随着Uber越来越多的团队开始采用DOMA,我们仍在积极演进DOMA。DOMA的核心观点是,微服务架构实际上是一个大型的分布式应用,您可以将适用于任何软件的相同原则应用于它的发展。DOMA只是在实践中思考这些原则的一种方法。我们希望其他人也觉得有用,期待您的反馈!
DOMA本身是一项跨职能发展的结果,涉及优步各个团队的近60名工程师。特别感谢,那些在过去两年中投入大量精力的工程师。
这项工作引进行业中多个现有的设计模式来解决Uber的问题,同时也提出了新的模式,比如扩展。