头图

(本篇文章原文来自Uber Engineering, 原文链接,作者为Madan Thangavelu, Kiran Medisetty, Pavel Astakhov。 无奈能力有限,会在翻译不通顺处放出原文供大伙自行理解🦉。)

高可用的扩展性的自助网关并可进行配置 管理并监控所有业务平台的接口

Uber的接口网关发展历程

在2014年的十月份,Uber开始了业务的扩张,并在日后成为了整个企业历史中最辉煌的一个增长阶段。之后,我们扩大了的团队并且获得了数以百万的来自全球各地的用户。
在这篇文章中,我们将随作者一同经历支撑Uber产品的接口平台发展的各个阶段,我们也将和当时的Uber的工程师们一同思考怎样的设计方案才能够使企业度过这段凶猛的业务增长期。我们会分为三个阶段去讲述这个网关系统,明确设计上的挑战和需要承担的责任。

第一代:有机体(the organic evolution)

2014年的调查显示Uber的架构方式迫切的需要两个关键性的改革:调度与接口。

调度服务将负责与司机的设备进行长连接(原文如下:The dispatch service was responsible for connecting a rider with a driver),api服务则是负责用户信息的持久化存储。除此之外将会有一系列微服务支持用户的业务流程。骑手与司机的APP通过根目录'/'连接到调度层。请求的内容包含一个特殊的字段为messageType,用于确定调用特定的处理程序的rpc指令。

pic

在这组RPC命令中,有15个被用于关键的实时操作,例如允许司机接受行程订单,拒绝行程以及司机发起行程请求。一个特殊的messageType名为“ ApiCommand”,它使用调度服务的一些附加上下文对api服务的所有请求进行代理。

总体看来“ ApiCommand”似乎是我们进入系统的网关。第一代是单个整体服务有机演进的结果,该整体服务开始为用户提供实际服务,并找到了一种扩展其他微服务的方式。调度服务用作面向公众的API的移动接口,但需要匹配逻辑的调度和将所有其他流量将路由到Uber内其他微服务来处理。(此处可能会有歧义,原文: but included a dispatch system with matching logic and a proxy to route all other traffic to other microservices within Uber)

此后第一代系统的辉煌时期并没有持续很长时间,因为方案已经投入生产太长时间。

到2015年1月,一个全新的API网关(可以说是第一个真正的网关)的蓝图被启动,这是朝着正确方向迈出的第一步。

第二代:全包含网关(the all-encompassing gateway)

Uber 在其早期就采用了微服务架构,这个架构最终导致系统中的微服务增长到2200多个,对Ube所有产品提供支撑

pic2

API网关层被命名为RTAPI,是Real-Time-API的缩写。

它始于2015年初的单个RESTful API,并逐渐成为许多面向公众的API的网关,这些API为用户不断增长的移动端App和Web客户端提供支持。该服务曾是一个单一的存储库,但由于它以指数速度持续增长,因此将其拆分部署。

该API网关是Uber最大的NodeJS应用程序之一,具有一些令人印象深刻的统计数据:

  • 110个逻辑端点组
  • 40%的工程人员已将代码提交到该层
  • 高峰时为800,000 rps
  • 120万种翻译服务可为客户本地化数据
  • 在5分钟内对每个差异执行50,000个集成测试
  • 在很长的一段时间里,几乎每天都需要部署
  • 约100万行代码可处理最核心业务流程
  • 大约20%的移动版本是从此层中定义的模式生成的代码
  • 与Uber的百余个团队拥有的约400多个下游服务进行通信

第二代目标

公司内部的每个基础架构都有一组预定的特性需要满足。有一些特性是在初始设计期间就计划的,而某些特性则需要一直沿用。

  • 去耦
    每时每刻都有数百个团队并行的构建并提交新的需求代码。由后端团队开发的提供基本功能的微服务数量呈爆炸式增长。前端和移动端团队正在以同样快的速度构建新的产品功能。网关提供了所需的解耦,并允许我们的应用程序继续依赖稳定的API网关。
  • 协议转换
    所有移动端到服务器的通信主要使用HTTP / JSON。在内部,Uber还推出了新的内部协议,该协议旨在提供多路双向传输协议。在某个时候,Uber的每个新服务都将采用了这个新协议。这将后端系统与两个协议之间的服务分散在一起。这些服务的某些子集也使我们只能通过对等网络来解决它们。?当时的网络技术还处于早期阶段,网关使我们的产品团队免受了潜在的网络变化的影响。
  • 横向关注点
    公司使用的所有API都需要包含一定的功能集,这些功能集应同时保持通用性和稳定性。当时的我们需要专注于身份验证,监控(网络延迟,错误信息,服务负载),数据验证,日志记录,按需调试日志,基线警报,SLA测量,数据中心耦合度,CORS配置,本地化策略,缓存,网络传输速率限制,服务减载和服务混淆。
  • 流式传输
    在这段时间内,许多应用程序功能都采用了将数据从服务器推送到APP的功能。这些有效数据被建模为API和上面讨论的相同的“横向关注点”。该应用程序的最终推送由我们的流媒体基础架构管理。
  • 后端与前端(原文:Backend for the frontend)
    开发速度是任何成功产品的关键点。在整个2016年,我们的新硬件基础设施并未进行容器化,提供新服务很容易,但是硬件资源分配却复杂多了。该网关为团队提供了一点,使他们可以在一天内开始和完成其功能。这是因为这是我们的应用程序所调用的系统,该服务具有灵活的开发空间来编写代码,并且可以访问公司内的数百个微服务客户端。第一代Uber Eats完全是在网关内开发的。随着产品的成熟,一些功能被移出门户。Uber有许多功能,这些功能可以完全由在网关层其他现有的微服务构建。
  • 技术挑战:
    我们的网关最初的目标主要是解决IO问题和构建一个Node.js团队。 工程师内部经过多轮审查,Node.js成为该网关的首选语言。随着时间的流逝,使用如此动态的语言并为Uber架构的关键层的1,500名工程师提供自由格式的编码空间,使得项目的维护出现困难。
    在更新的时候,每个新的API更新都会运行超过50,000个测试,因此可靠地构建具有某些动态加载机制的、需要较大数量的依赖包或库的测试框架变得非常复杂。随着Uber的其他部分功能逐渐选用Golang和Java作为主要支持的语言,新的后端工程师加入了网关团队,Node.js的异步开发模式逐渐减慢了我们的工程师的工作。
    最终网关项目变得相当巨大。它带有monorepo的标签(网关被部署为40多个独立服务),并且将2500 npm库升级到较新版本的Node.js,这继续使工作量成倍增加。这意味着我们无法采用众多库的最新版本。此时,Uber开始考虑使用gRPC作为首选协议。
    在代码检查期间,团队无法避免发生空指针异常(NPE)持续的出现,最终导致重要的网关项目部署停顿了几天,直到NPE固定在一些不相关的未使用API​​上为止。这进一步降低了我们的工程速度。
    网关中代码的复杂性不同于IO绑定。一些API引入的性能下降可能会导致网关速度变慢。
  • 非技术挑战:
    网关的两个特定目标导致对该系统的压力很大。“减少服务间不必要的通信往返”和“前端与后端”(backend for the frontend) 容易导致大量业务逻辑代码泄漏内部网关中的设计。有时,该泄漏是由选择导致的,而其他时候则是没有原因的。拥有超过一百万行代码,很难区分“减少往返”和繁重的业务逻辑。
    由于网关是确保我们的客户继续增长的的关键基础设施,因此网关团队开始成为Uber产品开发的瓶颈。我们通过API的分片部署和分散审核来缓解这种情况,但瓶颈问题并没有得到令人满意的解决。
    这是我们不得不重新考虑API网关的下一代策略的时候。
    第三代:自助服务,分散式和分层式
    到2018年初,Uber已经拥有更多的业务线和新应用程序。而且业务线的数量仍然随时时间在不停增长。在不同的业务部门中,团队都管理着各自的后端系统和应用。所以我们需要使系统垂直独立以实现快速产品迭代。综上所述,网关必须提供一组合适的功能,避免上述的技术和非技术挑战并加速我们的开发效率。

第三代目标

与上次设计第二代网关的公司截然不同。回顾所有技术和非技术挑战,我们开始设计具有新目标的第三代产品。

关注点分离

这种新的体系结构鼓励该公司采用分层的方法进行产品开发。

  • 边缘层:真正的网关系统,提供第二代系统网关部分目标中描述的所有功能,但不包括“后端后端”(backend for the frontend)和“往返行程减少”。
  • 表示层:微服务,经过专门的标记来为其功能和产品的前端服务提供后端支持。这种设计可以使产品团队来管理和编排服务,这些服务可以满足绝大部分消费类APP所需的API。服务中的代码适用于视图生成和许多来自下游服务的数据聚合。有单独的API可以修改接口的反馈以适应特定用户。例如,与标准Uber Driver相比,Uber Lite应用程序则需要较少的与地图相关的信息。这些服务中的每一个都可能涉及不同数量的下游调用,所以我们使用可读性更高的视图逻辑来显示服务所需的有效负载。
  • 产品层: 改层的微服务经过专门标记,以提供描述其产品/功能的功能性,可重用的API。这些可能会被其他团队重复使用,以构成和建立新的产品体验。
  • 主域层:包含微服务,这些微服务是叶节点,为产品团队提供单一的完善功能。
    pic3

减少边缘层的目标

导致复杂性的关键因素之一是第二代中的临时代码,其中包括视图生成和业务逻辑。借助新架构,这两个功能已移至其他微服务中,这些微服务由标准Uber库和框架上的独立团队拥有和运营。边缘层作为纯边缘层运行,没有自定义代码。
需要注意的是,一些刚创建好的团队建议只使用一个单一服务来支撑相关的业务、产品或服务。随着业务的增长,之后可以将其分解为不同的层。
这种架构提供了极大的灵活性,可以从小处着手,并形成一个北极星架构,该架构在我们所有产品团队中都是一致的。

技术构建块

在努力过渡到新构想的体系结构中,我们需要关键的技术组件。

边缘网关

pic5
最初由我们的第二代网关系统提供服务的边缘层已由与UI界面配对的单个独立Golang服务所取代。内部开发了“边缘网关”作为API生命周期管理层。现在,所有Uber工程师都可以访问UI界面来配置,创建和修改我们面向产品的API。UI具有简单的配置(如身份验证)以及高级配置(如请求转换和标头传播)。

服务框架

鉴于所有产品团队都需要维护并管理一组微服务(可能在其功能/产品的此体系结构的每一层),边缘层团队与语言平台团队合作,就一个名为“Glue”将在整个公司中中使用。Glue框架提供了一个基于Fx依赖项注入构建的MVCS框架。

服务库

网关中的代码类别属于“减少的不必要的往返行程”和“前端的后端”,需要Golang中的轻量级DAG执行系统。我们在Golang中建立了一个称为Control Flow Framework CFF)的内部系统,该系统可使工程师为了编排服务的处理逻辑开发复杂的无状态工作流程
pic6

  • 组织一致性
    将公司过去几年使用的业务的转移到新技术系统中始终是一个巨大的挑战。它关乎公司接近40%工程项目能否正常运转。进行此类工作的最佳方法是不同团队间建立明确的共识和对该任务目标的理解。除此之外有几个方面需要关注。
  • 建立信任
    集中团队将一些高扩展性的API和关键业务逻辑迁移到了新的堆栈中,以验证尽可能多的用例,并由此我们可以开始考虑让其余的团队迁移他们的业务代码。
  • 明确责任
    由于存在许多API,我们必须清楚地确定所有权。这并不简单,因为大量的API可能会具有跨团队的代码。对于明确映射到某个产品/功能的API,我们会自动为其分配,而对于复杂的API,我们会逐案处理并协商所有权。
  • 承诺
    将其分解为团队后,我们将团队分割为若干组(通常按较大的公司组织结构,例如Rider,Driver,Payments,Safety等),并与部门领导联系,以找到合适的工程和程序管理者来领导不同的团队
  • 培训
    集中团队对工程师进行了培训,以了解“如何”迁移,“要注意什么”,“何时”进行迁移。我们创建了技术支持小组,其他团队的开发人员可以在迁移中向他们提出问题或寻求帮助。我们甚至部署了一个自动化的问题跟踪系统,明确问题的解决进度。
  • 迭代策略
    随着迁移的进行,我们遇到了一些极端情况,并对原有的架构提出了挑战。我们原有的设计上多次引入了新功能,而在其他时候,我们选择不污染或不改动原有的架构。
    与此同时,我们的团队也在不断地思考技术前进的方向。最终,我们选择朝着自助API网关和分层服务体系结构迈进。

结论

在Uber花费了一段时间开发和管理三代网关系统之后,这里为大家介绍是有关API网关的一些心得。

如果有机会,请为您的移动应用程序和内部服务统一一个协议。采用多种协议和序列化数据格式最终会导致网关系统的巨大开销。统一一个协议可以让网关层的功能更加丰富。它可以像代理一样简单,也可以是极其复杂且功能丰富的网关,可以使用自定义DSL来实现graphQL。如果有多个协议要转换,则网关层将被迫变得十分复杂用以实现将HTTP请求路由到另一个协议中。

设计网关系统以进行水平扩展非常关键。在像我们的第二代和第三代这样的复杂网关系统的情况下尤甚。为API组构建独立二进制文件的能力是一项关键功能,它使我们的第二代网关可以水平扩展。一个二进制文件太大了,无法运行1600个复杂的API。
基于UI的API配置非常适合已有API的增量更改,但是创建新API通常是一个多步骤的过程。作为工程师,有时UI可能会比直接在代码层面上工作要慢。

从第二代到第三代的开发和迁移时间表长达2年。随着工程师加入项目到退出团队中我们需要确保工程师可以在很长的时间里都为公司效力。保持可持续发展的势头对于如此长期运行的项目取得成功至关重要。

最后,每个新系统都没有义务偿还旧系统中的技术债务。有意识地选择放弃支持对于长期可持续性至关重要。

回顾我们网关的发展历程,人们或许会想知道我们能否可以跳过一代而到达当前的体系结构。任何尚未在公司中开始此旅程的人可能还会想知道,是否应该直接从自助API网关开始。我要说的是决定不会是独立的决定,因此很难做出决定。很大程度上取决于整个公司系统的发展,例如基础架构,语言和平台,产品团队,业务增长速度等等。

我们在第三代系统中日常API的更改已经超过了第二代和第一代。这与加快产品开发周期的速度直接相关。迁移到基于golang的系统大大改善了我们的资源利用率。我们大多数API的访问延迟已大大减少。随着新体系结构的成熟和旧技术被不断取代,我们还有很长的路要走。


YaSin
1 声望0 粉丝