API网关是近年来微服务体系结构中不可或缺的一部分。API网关为我们所有的应用程序提供了一个单一的入口点,并提供了一个从后端微服务访问数据、逻辑或功能的接口。它还提供了一个集中的地方来实现许多高级职责,包括路由、协议转换、速率限制、负载削减、头充实和传播、数据中心亲和性强制、安全审计、用户访问阻塞、移动客户端生成等。
在我们的上一篇文章,我们走过多代优步的API Gateway演变和我们在每个阶段的设计选择。在本文中,我们将深入潜入自助API网关系统的技术组件。
在最摘要的水平,网关尚未通过API服务数据。网关在许多味道中出现并涵盖了一种广泛的频谱,从充当API网关的低级负载均衡器,到非常具有功能丰富的应用程序级负载均衡器,这些负载均衡器在这些API中的请求和响应有效载荷上运行。在Uber,我们开发了一种功能丰富的API网关,其能够跨多个协议对传入和传出数据有效载荷进行复杂的操作。
API管理
一个功能丰富的应用程序是通过与众多提供不同功能的后台服务交互来实现的。所有这些交互都要经过一个通用的应用网关层。API管理指的是这些网关API的创建、编辑、删除和版本控制。
工程师在UI中配置其API的参数,并将功能API发布到Internet以获取所有UBER应用程序。该配置管理API的行为:PATH,Request数据类型,响应类型,允许的最大呼叫,应用程序协议,通信协议,调用的特定微服务,允许的标题,可观察性,现场映射验证等。
发布配置后,网关基础架构将这些配置转换为有效和功能API,可以从我们的应用服务中服务流量。网关基础架构还生成客户端SDK,以用于消耗这些API。
与网关系统的所有交互通过UI发生,该UI通过用于创建端点的逐步处理来散步用户。UI简化了该过程,以及强制执行API的各方面的各种验证。此外,这是配置请求超时,监视和警报的位置。
管理系统在发布新配置更改和存储会话之前提供辅助功能,如审核门,以共享或恢复API管理。以下是屏幕截图,显示允许添加中间件的UI步骤的高级概述:
请求生命周期中的组件
为了说明网关的各种组件,重要的是要理解单个请求如何流过网关运行时。传入请求包含映射到服务器的处理程序的路径。在请求的生命周期内,它流过以下组件:协议管理器,中间件,数据验证,处理程序和后端客户端。请求生命周期中的所有组件都以堆栈实现。
下面详细介绍了每个组件,它们在进入请求对象时对其进行操作,而相同的组件在退出响应对象时以相反的顺序运行。
协议经理是堆栈的第一层。它包含用于网关支持的每个协议的Deserializer和串行器。此图层提供了实现可以摄取任何类型相关协议有效载荷的API的能力,包括JSON,TEVIFT或PROTOBUF。它还方便地能够接收传入的JSON请求并以PROTO编码的响应响应。
中间件图层是一种抽象,它在调用端点处理程序之前实现可编译的逻辑。中间件实现交叉切割问题,例如认证,授权,速率限制等。每个端点都可以选择配置一个或多个中间件。除了可选的中间件之外,该平台还包括一组始终为每个请求执行的强制中间件。单个中间件不需要实现RequestMiddleWare.也RespectEmiddleware.方法。如果中间件失败执行,则呼叫短路堆栈的其余部分和来自中间件的响应将返回给呼叫者。在某些情况下,中间件可以是NO-OP,具体取决于请求上下文。
端点处理程序是负责请求验证,有效载荷转换和将端点请求对象转换为客户端请求对象的图层。在响应对象上运行时,endpointhandler.将后端服务响应转换为端点响应在响应对象上执行任何转换,响应验证基于模式和序列化。
客户对后端服务执行请求。客户端是协议感知的,根据配置过程中选择的协议生成。用户可以配置客户机的内部功能,如请求和响应转换、模式验证、断路和重试、超时和截止日期管理以及错误处理。
配置组件
协议管理器、中间件、处理程序和客户端有许多可以使用配置控制的行为。管理API的用户不修改任何代码,而只是修改配置,以确定网关上预期的端点行为。为了便于配置,它们通过UI进行管理,并由Git存储库支持。
每个组件的配置都在节俭和/或yaml文件中捕获。yaml文件为组件提供信息,并充当它们之间的一种胶水。节俭文件定义有效载荷和协议语义。
网关节约文件大量使用节俭idl中的注释为了为各种特征和协议提供单一的真理来源。在下面的部分中,我们将潜入每个组件的配置。
协议管理器
协议管理器需要了解请求的协议的上下文中的数据的形状和类型。应为响应时尚的类似参数。
下面的三行YAML配置提供了协议类型、Thrift文件路径和协议管理器用于处理传入请求的方法:
上面的配置说明了新API的类型是“HTTP”协议,关于模式和协议的所有其他细节都在Apisample.Thrift.文件下面。
节俭的文件Apisample.Thrift.功能丰富,并描述了JSON请求和响应有效载荷,HTTP路径和HTTP动词的数据类型。使用节俭的注释功能在节俭架构中定义HTTP协议。
并非所有API调用都会导致成功。以下示例模式提供了从处理程序到适当的HTTP协议的错误响应。这是使用注释完成的,如下所示:
还有许多其他注释可以帮助协议管理器使用thrift注释管理HTTP请求的行为。
中间件
中间件是堆栈中最灵活且功能丰富的组件。它允许网关平台将更高阶的特征暴露给API网关用户。我们将介绍由中间件供电的详细功能解锁功能部分。在这里,我们将专注于yaml文件中的中间件配置。
在上面的配置中,身份验证中间件被添加到API中。身份验证中间件将从值中接收配置的路径参数header.x-user-uuid.上面配置的第二个中间件是变换Quest.中间件,它被指示将区域从传入请求复制到RegionId.在对后端服务的调用中。开发新中间件时,它定义了API开发人员需要提供的所有可配置参数的模式。
处理程序
支持处理程序的主要配置围绕着验证和将传入请求映射到后端客户机请求参数。
上面的配置提供了处理程序需要的输入,以便理解请求应该映射到的后端客户端。如果传入请求字段与后端服务完全匹配,那么上面的配置就足够了。如果字段的名称不同,则必须使用变换Quest.中间件。
客户
后端客户机的配置分为YAML文件和thrift文件。在下面的示例中,一个新的后端服务TChannel.协议配置了在中定义的请求和响应定义配置beastendsample.thrift.文件,其中有两个可以调用的方法。
再次注意方法后端::方法也可以真正的是HTTP API,有一条路径/后端/方法在注释的帮助下等效地表示在节俭规范中。
可运行的工件
上一节中描述的所有组件的yaml和节俭配置是完全描述一个单个API配置所必需的。自址网关负责确保这些组件配置汇集在一起以提供网关运行时。
有两个网关的味道:一个人采用配置并基于它们(非常喜欢的孔,TYK和Enverse代理,如Envoy,Nginx)动态服务API;另一个使用基于输入配置的代码生成步骤生成构建工件。在优步,我们选择后者,代码生成方法来创建一个可追加的构建工件。
生成模式对象:所有模式文件都通过处理器运行,以输出本机GOLANG代码thriftrw和protoc。这是序列化/解除串化和客户端接口的代码生成所需的。
生成自定义序列化:具有移动应用程序的API合同需要与I64,ENUM类型和多个协议相关的自定义序列化。
副名应力:端点,后端客户端和中间件的代码是静态生成的。代码几代内具有固有的依赖项。客户端是独立的,可以立即生成。中间件可以取决于零或更多客户端的功能。端点可以取决于零个或多个中间件和零或一个客户端。该DAG(定向非循环图)在构建时已解决。
由于客户端从端点自主生成,因此端点可以是HTTP,而后端服务可以是GRPC。在边缘网关的构建的此步骤中完成了绑定。
API生成:在最后一步中,对DAG进行迭代以生成所有端点。单个生成步骤如下:加载模板,将端点请求生成到客户机请求映射,反之亦然,注入依赖关系,并使用请求-响应转换来水合物idl对象。
代码生成的整个过程被抽象为一个Uber OSS库,桑给巴尔.
解锁功能
集中式系统的优势是构建可以使所有在板上的用户受益的功能。使用像边缘类似的功能丰富的网关,有多个途径可以构建功能,可以通过API的所有API杠杆杠杆化。
以下是已经开发的功能的一些示例,以及一些仍在管道中的一些示例。
审计管道
EDGE Gateway发出富裕元数据的访问日志,持续审核。维护所有产品的所有API访问模式的审计记录至关重要。当恶意演员尝试使用自动化系统访问我们的API时,它允许安全审核,并有助于在版本,地理位置和应用程序中构建各种产品的配置文件。
该管道有助于在特定SDK版本、应用程序、地理位置或互联网提供商之间快速捕获bug、问题和异常。我们的所有应用程序都启用了审计管道。
验证
每个外部API请求都被认证(Authn)和/或授权(authz)。该平台为AuthX提供了几种可重用实现,作为用户可以从其端点中选择的中间件。这消除了对如何实现的验证/ authz的担忧以及强制执行端点使用所提供的实现中的至少一个。这些实现的更新可以通过自动应用于所有端点的平台所有者无缝制作。
电路破碎
用于调用后端服务的每个客户端都用断路器包装。随时回后端服务经历增加延迟或错误率(可配置),断路器将启动,防止任何级联中断。这也提供了恢复已经恶化的服务的空间。
速率限制
端点所有者可以选择速率限制API。提供的实现的一些示例是基于UserID,用户代理,IP的速率限制,用户Agent,IP的组合等于请求的某些属性等。可以基于来自路径/查询参数,标题或主体的特定字段来强制实施限制。这允许灵活地提供比简单的用户级API访问更粒度的应用程序感知率限制策略。每个端点都可以在不需要重新部署的情况下独立地分配配额。
文件
yaml和节俭中的所有配置完全描述了API。这为以一致的方式自动生成所有网关API的文档。
移动客户端代
所有Uber的移动应用程序都会根据节俭IDL生成服务和模型以与服务器进行交互。CI作业从网关中获取所有端点IDL,并为各种型号运行自定义代码生成。移动代码Gen还取决于各种自定义节俭注释,例如异常状态代码,URL路径和HTTP方法。通过针对生成的代码审查运行的CI作业,防止了对端点模式的任何后向不兼容的更改。
响应场修剪
由于API的创建很容易,并且可以通过相同的基础客户端服务返回多个端点。我们有能力创建粒度选择用户体验所需的特定字段的API,而不是响应后端响应的全尺寸。
数据中心亲和力
具有冗余数据中心和区域是大型Web公司的当前架构。属于不同业务单位或域的API在网关上托管,每个业务部门都可以在多个数据中心定义其工作负载分片。Edge Gateway提供了业务单元可以写入配置用户,区域或版本关联的缓存到适当的数据中心。然后,网关将通过尊重数据中心亲和信息来确保从特定用户,设备或应用程序重新路由传入的API。
短期用户禁令
帐户级禁止是处理恶意演员的锤子。对于临时滥用系统的用户,网关提供了一个中央处,可以在短时间内从特定用户免于API访问。这种方法类似于数据中心亲和力,其中网关可以提供外部缓存以将阻塞用户用TTL存储。欺诈和安全系统可以提供用户,应用程序版本或其他标识符来阻止。Edge Gateway将确保执行这些短期禁令以保护用户。
挑战和经验教训
在网关的开发期间,我们必须在设计的多个方面进行选择。一些选择导致我们非常令人兴奋的结果,而有些人没有提供预期的投资回报。我们将简要谈谈一些挑战。
语
在网关开发时,我们的语言选择是Go和Java。我们之前的一代是Node.js.虽然这是一个非常合适的构建IO-沉重的网关层的语言,但我们决定与Uber语言平台团队支持的语言保持一致。提供显着的性能改进。在构建时间期间,缺乏普遍导致大量生成的代码到我们正在击中Go Linker的限制的时间。我们必须在二进制编译期间关闭符号表和调试信息。语言命名惯例,如id,http和go的保留关键字(但不是在节俭中)创建了将内部实现详细信息暴露给最终用户的失败。
序列化格式
我们的网关的协议管理器能够实现多个协议。此功能将自身暴露于复杂的兼容性问题,例如表示联合,集合,列表和JSON架构VS节俭架构中的映射的数据类型不匹配。我们不得不提出本土约定的那个映射。
配置存储
如前所述,用户配置存储在Git中。但是,其中一些配置在性质上是动态的,如API速率限制。以前,改变需要代码生成和部署。这是耗时的,因此我们现在将用户配置的动态部分存储在配置存储中。
网关UI.
在网关UI中,单个API的开发很容易,但在开发批处理编辑流中可以更难管理。当旧文件引用其他节俭文件并且嵌套可以任意深度时,尤其如此。一旦用户提供配置并且构建系统接管,就可以在UI独立演变时,对UI的浮出构建故障可能会充满挑战。在它们之间保持一致的合同至关重要。
了解有效载荷
可以开发大多数网关功能,而无需将传入或传出有效载荷进行反序列化。我们的协议互操作性的用例迫使我们将有效载荷进行反序列化。这增加了构建系统的复杂性以及运行时的性能。如果后端和移动协议相同,则可能有益于限制网关仅访问协议动词和标题而不导出身体。然而,它会限制一些复杂的网关功能。
一个丰富的网关,就像我们描述的那样,是一个复杂的事业。如果您有兴趣遵循相同的路径,桑给巴尔可以提供可扩展的模块,可从其中引导。在Uber,我们正在开发一种API网关运行时,用于从我们的应用到后端服务的gRPC请求特使,没有重要的用户体验更改为我们的自助式UI。如果你感兴趣和热情,请和我们谈谈!
承认
没有这么多人参与网关的重要贡献,这项工作是不可能的。一些关键致谢 - Abhishek Panda,Alex Hauser,Aleavind Gopalan,Chao Li,Chuntao Lu,Gregory Trowbridge,Jake Vercaten,Karthik Karuppaiya,Makimiliano Benedetto,Michael Sindler,Olivia Zhang,Pavel Astakhov,Rena Ren,Steven Bauer,Tejaswi Agarwal,Uday Medisetty,王辉王。
















