2 使用API网关(Using an API Gateway)
本书七个章节的第一章是关于微服务架构模式下的关于设计,构建,和部署微服务的介绍。它讨论了使用微服务的优点和缺点,然而,抛开微服务的复杂性,它通常是复杂应用程序理想的选择。这是这个系列中的第二篇文章,将会讨论使用 API 网关构建微服务。
当你选择用一组微服务构建你的应用时,你需要抉择你的应用程序客户端如何和微服务交互。在一个复杂的单体应用中,通常只有一组后端服务节点,这些节点通常是重复的,用负载均衡在它们之间分配流量。
但是,在微服务架构中,每个微服务公开一组通常是细粒度的端点(endpoints)。在这篇文章中,我们研究了这对客户端到应用程序通信的影响,并提出了一种使用API网关的方法。
介绍(Introduction)
我们假设一个场景,你需要开发一个原生的app手机客户端作为购物应用程序。您可能需要实现一个产品详细信息页面,该页面显示关于任何给定产品的信息。
例如,图2-1显示了当你在亚马逊的安卓手机应用程序的产品详情页上滑动时,你将看到的情况。
即使这是一个只能手机应用程序,产品详情页面显示了许多信息。例如,这儿不仅有基本的产品信息,例如名字,描述,价格,但这个页面也显示了:
- 购物车中物品的数量
- 订单历史
- 客户评价
- 低库存提醒
- 发货选项
- 各种各样的推荐,包括常和这个产品一起买的产品,其他客户购买了这个产品的同时也购买的其他产品,和其他客户购买这个产品时浏览的其他产品。
- 选择购买选项。
当使用一个单体应用架构时,一个手机客户端通过发送一个单独的 REST 调用到应用程序来获取这些数据。例如:
GET api.company.com/productdetails/productId
一个负载均衡路由这些请求到一些单独的应用程序实例上。这个应用然后查询各种数据库表,然后返回响应到客户端。与之相反,当使用微服务架构体系时,产品详情页的数据显示是通过多个微服务调用获取的。以下是一些潜在的微服务,它们拥有特定于例子中产品页面上显示的数据:
- 购物车服务: 购物车里面商品的数量
- 订单服务: 订单历史
- 目录服务:基本的产品信息,例如产品名称,图片,和价格
- 评价服务:客户的评价
- 库存服务:低库存告警
- 货运服务:货运选项,配送选项、截止日期和成本,分别从配送提供者的API中获取
- 推荐服务:推荐条目
我们需要决定手机客户端如何访问这些服务。让我们看一下这些选项。
客户端和微服务直接通信(Direct Client-to-Microservice Communication)
理论上,一个客户端需要直接发送请求到每个微服务。每个微服务应当有一个公共端点:
https://serviceName.api.company.name
这个URL将会映射到微服务的负载均衡器上,它在可用实例之间分发请求。为了检索特定于产品的页面信息,移动客户端将向上面列出的每个服务发出请求。
https://images.gitee.com/uploads/images/2021/1105/212004_ced25010_1303232.png
不幸的是,这种选择存在挑战和限制。一个问题是客户机的请求与每个微服务公开的细粒度api之间的不匹配。这个例子中的客户端需要发送七个单独的请求。在更复杂的应用中,他可能不得不发送更多的请求。例如,亚马逊描述了数以百计的服务是如何参与到呈现他们的产品页面的。虽然客户机可以通过局域网发出这么多请求,但它可能在公共互联网上也会这样做,导致效率低下,而在移动网络上肯定是不切实际的。这个方法也会导致客户端的代码更加的复杂。
客户直接调用微服务的另一个问题是,有些可能使用的协议对网络不友好。一个服务可能使用 Thrift binary RPC,而另一个服务可能使用AMQP消息传递协议。这两种协议对浏览器或防火墙都不是特别友好,最好在内部使用。应用程序应该在防火墙外使用HTTP和WebSocket等协议。
这种方法的另一个缺点是很难重构微服务。随着时间的推移,我们可能希望改变系统如何划分的服务,例如,我们可以合并两个服务,或者将一个服务拆分为两个或多个服务。但是,如果客户端直接与服务通信,那么执行这种重构就会非常困难。
由于这类问题,客户直接与微服务对话很少有意义。
使用API网关 Using an API Gateway
通常更好的方法是使用所谓的API网关(API Gateway)。API网关是服务器,它是进入系统的单一入口点。它类似于面向对象设计中的外观模式(Facade pattern)。API Gateway封装了内部系统架构,并提供了为每个客户定制的API.它可能还有其他职责,如身份验证(authentication)、监视(monitoring)、负载均衡(load balancing)、缓存(caching)、请求的响应和管理(request shaping and management)以及静态响应处理(static response handling)。图2-3显示了一个API网关通常如何适应体系结构。
API 网关负责请求的路由,合成,和协议的传输。所有来自客户端的请求首先通过API网关。API网关然后将这些请求路由到适当的微服务上。API网关通常将通过调用多个微服务,然后将结果合成在一起来处理一个请求。API网关可以在web协议(比如HTTP和WebSocket)和内部使用的对web不友好的协议之间传输。
API网关也会为每个客户端提供一个定制的API.通常是暴露给手机客户端一个粗粒度的API,例如,产品详情的场景,API网关可以提供一个后端端点(/productdetails?productid=xxx),让一个手机客户端能够通过一个请求获取到所有的产品详情。API网关通过调用各种服务来处理请求:产品信息,推荐,评论等。然后将结果合到一起。
API网关的一个很好的例子是 Netflix API网关。Netflix流媒体服务可以在数百种不同的设备上使用,包括电视、机顶盒、智能手机、游戏系统、平板电脑等。Netflix试图为他们的流媒体服务提供一个通用的API。然而,他们发现,由于设备的多样性和他们独特的需求,这种方式并没有很好地发挥作用。如今,他们使用API Gateway,通过运行特定于设备的适配器代码为每个设备提供定制的API。适配器通常通过调用平均6到7个后端服务来处理每个请求。Netflix API Gateway每天处理数十亿个请求。
API网关的优点和缺点(Benefits and Drawbacks of an API Gateway)
如您所料,使用API Gateway既有优点也有缺点。使用API Gateway的一个主要好处是它封装了应用程序的内部结构。客户端无需调用特定的服务,只需与网关对话即可。API网关为每种客户端提供特定的API。这减少了客户机和应用程序之间的往返次数。它还简化了客户端代码。
API网关也有一些缺点,它是另一个必须开发、部署和管理的高可用性组件。还有一个风险是API Gateway会成为开发瓶颈。开发人员必须更新API网关,以便公开每个微服务的端点。
重要的是,更新API网关的过程要尽可能轻量化。否则,开发人员将被迫排队等待更新网关。尽管有这些缺点,但是对于大多数实际应用程序来说,使用API网关是有意义的。
实现一个API网关(Implementing an API Gateway)
既然我们已经了解了使用API Gateway的动机和利弊,现在让我们看看需要考虑的各种设计问题。
性能和扩展性(Performance and Scalability)
只有少数公司的运营规模能达到Netflix的水平,每天需要处理数十亿个请求。然而,对于大多数应用程序来说,API Gateway的性能和可伸缩性通常是非常重要的。因此,在一个支持异步、非阻塞I/O的平台上构建API Gateway是有意义的。可以使用各种不同的技术来实现可伸缩的API网关。在JVM上,您可以使用基于NIO的框架(NIO-based frameworks)之一,如Netty、Vertx、Spring Reactor或JBoss Undertow。一个流行的非JVM选项是Node.js,这是一个基于Chrome的JavaScript引擎的平台。另一个选择是使用NGINX Plus。
NGINX Plus 提供了一个成熟的、可扩展的、高性能的web服务器和反向代理,易于部署、配置和编程。NGINX Plus可以管理身份验证(authentication)、访问控制(access control)、负载均衡请求(load balancing requests)、缓存响应(caching responses),并提供应用感知的健康检查和监控(health checks and monitoring)。
使用响应式编程模型(Using Reactive Programming Model)
API网关通过简单地将请求路由到适当的后端服务来处理一些请求。它通过调用多个后端服务并将结果合到一起来处理其他请求。对于一些请求,比如产品细节请求,后端服务的请求是相互独立的。为了最小化响应时间,API Gateway 应该并发地执行独立的请求。
有些时候,然而,请求之间存在依赖关系。API网关把请求路由到后端服务之前,可能需要先调用一个认证服务来验证这个请求。类似地,要获取关于客户希望列表中的产品的信息,API Gateway必须首先检索包含该信息的客户属性文件,然后检索每个产品的信息。另一个有趣的API组合例子是Netflix视频网格。
使用传统的异步回调方法编写API组合代码会很快让你陷入回调地狱。代码将是混乱的,难以理解,并且容易出错。更好的方法是使用响应式方法以声明式风格编写API Gateway代码。响应式抽象的例子包括Scala中的Future、Java 8中的CompletableFuture和JavaScript中的Promise。
还有响应式扩展(也称为Rx或ReactiveX),它最初是由微软为.net平台开发的。Netflix专门为JVM创建了RxJava用于他们的API网关。还有用于JavaScript的RxJS,它可以在浏览器和Node.js中运行。使用响应式方法将使您能够编写简单而高效的API网关代码。
服务调用(Service Invocation)
基于微服务的应用程序是分布式系统,必须使用一种跨进程通信机制。有两种风格的内部进程通信。其中一种是使用异步的,基于消息的机制。一些实现使用消息代理,如JMS或AMQP。其他的,比如Zeromq,是无代理的,服务直接通信。
另一种类型的进程间通信是同步机制,例如HTTP或Thrift。系统通常会同时使用异步和同步样式。它甚至可能使用每种风格的多个实现。因此,API网关将需要支持各种通信机制。
服务发现(Service Discovery)
API 网关需要知道与它通信的每个微服务的位置(IP地址和端口)。在传统应用程序中,你可能只需要代码写死这些地址。但在一个现代的、基于云的微服务应用程序中,找到所需的位置不是一个简单的问题。基础设施服务(如消息代理)通常有一个静态位置,可以通过操作系统环境变量指定。然而,确定应用程序服务的位置并不容易。
应用程序服务具有动态分配的位置。此外,由于自动扩展和升级,服务的实例集也会动态更改。因此,与系统中的任何其他服务客户端一样,API Gateway需要使用系统的服务发现机制:服务器端发现或客户端发现。现在,值得注意的是,如果系统使用客户端发现,那么API网关必须能够查询服务注册表,这是所有微服务实例及其位置的数据库。
处理部分失败(Handling Partial Failures)
当实现一个API网关的时候,另外一个你需要注意的问题是部分失败的问题。这个问题出现在所有的分布式系统中,每当一个服务调用其他服务是响应缓慢,或者服务不可用。API 网关应当永不阻塞无止境第等待下游服务的调用。然而,如何处理这些失败取决于特定的场景和什么样的服务失败。例如,如果在产品详情场景里面的推荐服务失败了,API网关应当返回其他的产品详情数据给客户端,因为产品详情数据对用户来说仍然是有用的。推荐服务可以返回空,或者被其他数据替换,例如,排名前十的硬件商品。如果是产品信息无法响应,那么API网关就应该返回一个错误给客户端。
如果缓存数据可用,API网关也可以返回缓存的数据。例如,由于产品的价格更新不是那么的频繁,在价格服务不可用的情况下,API网关可以返回缓存的价格数据。数据可以被API网关本书缓存,或者存储在一个外部的缓存中,例如Redis或者Memcached。通过返回默认的数据或者缓存的数据,API网关确保系统的失败给用户体验造成的影响最小化。
对于编写调用远程服务的代码,Netflix Hystrix是一个非常有用的库。Hystrix会将超过指定阈值的调用超时。当某个服务的错误率超过设定的阈值时,Hystrix会触发断路器跳闸,在指定的一段时间内,所有的请求都会立即失败.Hystrix让你定义一个回调action,当一个请求失败的时候,例如从缓存中读取数据,或者返回一个默认的值。如果你使用JVM,你当然要考虑使用 Hystrix。 如果你运行在一个非JVM环境,你应当使用一个等效的库。
总结(Summary)
对于绝大多数基于微服务的应用,实现API网关是有意义的,它从当了进入系统的一个单一入口。API网关负责请求的路由,合成和协议转换。它为每个应用程序的客户端提供了一个定制的API。API Gateway还可以通过返回缓存或默认数据来掩盖后端服务中的故障。在后面一章,我们将看看服务之间的通信。
Microservices in Action: NGINX Plus as an API Gateway
这个章节讨论了一个API网关是如何充当一个系统的入口点。API网关可以处理其他功能,例如负载均衡(load balancing), 缓存(caching),监控(monitoring),协议转换(protocol translation)和其他功能。而NGINX,当作为一个反向代理服务器实现时,作为一个进入系统的单一入口点,并支持API网关所提到的所有附加功能。因此,使用NGINX作为API网关的主机确实可以很好地工作。
把NGINX看作API网关并不是这本电子书的原创想法。NGINX Plus是管理和保护基于http的API流量的领先平台。你可以实现自己的API网关或使用现有的API管理平台,其中许多都是利用NGINX。
使用NGINX作为API网关的原因包括:
- 访问管理:你可以使用各种访问控制列表方法,也能容易的实现 SSL/TLS,通常是在web应用程序级别,或者是在每个单独的微服务级别。
- 可管理性和弹性: 您可以在服务器没有停机的情况下更新您的NGINX plus API,使用NGINX动态重新配置API,一个Lua模块,Perl,实时重启没有停机,或由Chef, Puppet, ZooKeeper,或DNS驱动的变化。
- 集成第三方的工具: NGINX Plus已经集成了前沿工具,如3scale、Kong和MuleSoft集成平台(仅提及NGINX网站上描述的工具)。
在NGINX微服务参考体系结构中,NGINX Plus被广泛用作API网关。请使用这里汇集的文章以及公开可用的MRA作为示例,了解如何在您自己的应用程序中实现这一点。