第二篇:使用API网关

写在前面的话,这些文章是在NGINX的官方博客中发现的。是关于微服务的一系列的文章,本着好东西共享一下,同时也丰富一下自己,把这些翻译成中文,但是后来发现国内已经有很多人翻译了,我只能说我的品位还不差,和各位大牛步调还算一致,虽然已经有人翻译了,但是本着“一千个读者就有一千个哈姆雷特”的想法继续了下去。

本文出自Using an API Gateway,作者 Chris Richardson, 写于2015年5月19日


第一篇文章介绍了如何设计、构建和部署微服务。其中讨论了使用微服务的优点和缺点,即使由于微服务的复杂性,对于复杂的应用来说,它们通常情况下也是理想的选择。这篇文章,也是本系列文章的第二篇,将会讨论使用API网关构建微服务。

当你选择将你的应用构建成一系列的微服务的时候,你就需要考虑你的应用客户端如何与微服务进行交互。对于一个单体应用来说,只要有一系列的endpoint即可,然后可以通过负载均衡将流量分布到完全相同的应用中即可。但是在微服务架构中,每个服务都会有很多细粒度的endpoint。本文将要讨论的就是这种情况如何影响客户端与应用的通信,并且提出使用API网关的方法。

一、介绍

假如你正在开发一个购物的移动应用客户端,很可能要实现一个产品详细信息的页面,它会展示一个产品的详细信息。例如图2-1显示了在Amazon的Android客户端浏览产品信息时看到的页面:

这里写图片描述

图2-1 购物应用

虽然这是一个智能手机应用,但是产品信息页面仍然要展现很多的信息。例如,基本的产品信息,比如名称、描述、价格,该页也会展示:

  1. 购物车中的产品数量;
  2. 历史订单;
  3. 顾客评论;
  4. 低库存预警;
  5. 运送选项;
  6. 各种推荐,包括与此产品时同时购买的其他产品,购买该产品的顾客也购买的产品,购买该产品的顾客浏览的产品;
  7. 可替代的购买选项;

当使用单体应用架构时,移动客户端通过给应用发送一个REST请求来获取数据,比如:

GET api.company.com/productdetails/productId

负载均衡器路由这个请求到几个相同应用实例其中的一个。单体应用接着查询多个数据库表并返回响应给客户端。

比较而言,当使用微服务架构的时候,显示在产品详细信息页面的数据来自于多个微服务。如下是一些潜在的服务,它们也有可以展示在特定产品信息页面的数据:

  • 购物车服务—购物车中产品的数量
  • 订单服务—历史订单
  • 目录服务—产品的基本信息,比如产品名称、图片和价格
  • 评论服务—客户的评论
  • 库存服务—低库存预警
  • 运送服务—从物流提供商的API得到的运送选项、期限和价格
  • 推荐服务—建议的商品

这里写图片描述

图2-2 映射移动客户端到相应的微服务

我们需要考虑移动客户端如何访问这些服务,了解可能的选项。

二、客户端和微服务直接通信

理论上,客户端可以直接请求每个微服务。每个微服务需要有一个公共的endpoint

https://serviceName.api.company.name

这个URL将会映射到微服务的负载均衡器,这个负载均衡器会将请求分发到可用的实例上,为了获取特定产品的页面信息,移动客户端会向上述列出的每个服务发送请求。

不幸的是,对于这种选择也有很大的限制和挑战。一个问题是客户端的需求和每个微服务暴露出来的细粒度的API不相符。在本例中,客户端必须发送7次单独的请求。在更复杂的应用中,可能发送更多的请求。例如,Amazon描述了上百的服务是如何参与到它们的产品页面的渲染的。即使客户端通过局域网发送很多的请求,当通过公网的时候也会变得效率低下,通过移动网络更是不切实际。这种方法也使得客户端代码更复杂。

直接调用客户端的另外一个问题是,一些服务可能使用了web不友好的协议。一个服务可能使用了Thrift 二进制RPC ,另外的服务可能使用AMQP消息协议。这些协议对浏览器和防火墙都不是友好的,比较适合内部使用。应用应该在防火墙外部使用类似于HTTPWebSocket的协议。

使用这种方法的另外一个缺点是会使重构微服务变得非常困难。随着时间的发展,我们可能改变系统划分服务的方法。例如,我们可能融合两个服务或者将一个服务分成两个或者多个服务。但是如果客户端直接和服务进行通信,那么完成这种重构会变得异常困难。

因为这些问题,很少会让客户端直接和微服务通信。

三、使用API网关

通常情况下一个更好的方法是使用API网关。API网关是一个服务器,可以作为访问系统的唯一入口。与面向对象设计中的门面设计模式类似。API网关封装了内部系统架构并为每个客户端量身定制了API。它也可能担负其他的责任,比如认证、监控、负载均衡、缓存、流量整形、管理和静态资源处理。

图2-3显示了API网关如何融入微服务架构中。

这里写图片描述

API网关负责请求路由、组装以及协议翻译。所有来自客户端的请求首先进入API网关。接着网关会将该请求路由到合适的微服务中。API网关也可以通过调用多个微服务以及合并结果来处理请求。它可以在web协议比如HTTPWebSocket、内部使用的对web不友好的协议之间转换。

API网关也能为每个客户端提供定制的API。它通常向移动客户端暴露出一个粗粒度的API。例如产品详细信息场景中。API网关可以提供一个endpoint(/productdetails?productid=xxx),可以使移动客户端在一次调用中获取所有的产品信息。API网关通过调用多个服务—产品服务、推荐服务、评论服务等来处理请求,并封装结果。

API网关的一个优秀的实例是Netflix API Gateway。Netflix的流服务在上百种设备上,比如电视、机顶盒、手机、游戏系统、平板电脑等,都能够流畅运行。开始的时候,Netflix尝试为他们的流服务提供one-size-fts-all API。然而,他们发现由于设备种类繁多,需求各异,所以这种方式无法正常工作。现在,它们使用API网关通过运行特定设备适配器的代码来给每个设备提供定制的API。一个适配器可以通过调用,平均情况下6-7个后端的服务来处理每个请求。Netflix的API网关每天处理数十亿的请求。

四、API网关的优点和缺点

正如你所料,API网关的优点和缺点同时存在。一个主要的优点是它封装了应用的内部结构。客户端直接和网关通信,而不必调用特定的服务。这种方式提供给每种客户端特定的API,减少了客户端和应用之间的往返次数,同时也简化了客户端的代码。

API网关也有一些缺点。它必须被开发、部署和管理成一个高度可用的组件,也有成为开发瓶颈的危险。为了暴露出每个微服务的endpoint开发者必须更新API网关

尽可能轻量级地更新API网关是很重要的。否则,开发者将会被迫排队等待网关更新。即使存在这些缺点,对于现实世界的应用,使用API网关仍然是行之有效的方法。

五、实现API网关

既然我们已经了解了使用API网关的动机和要进行的取舍,就需要考虑一下各种设计上的问题。

5.1 性能与扩展性

很少公司能达到Netflix需要每天处理数以十亿的请求的规模。但是对于大多数的应用来说,API网关的扩展性仍然是非常重要的。因此在一个支持异步、非阻塞IO的平台上构建API网关是很必要的。有很多技术可以用来实现可扩展的API网关。在JVM上你可以选择基于NIO的框架,比如Netty、Vertx、Spring Reactor或者JBoss Undertow。一个流行的非JVM选项是Node.js,它是构建在Chrome的JavaScript引擎之上。另一个选择是使用NGINX Plus。NGINX Plus提供了成熟的、可扩展的、高性能的web服务器和易于部署、配置和编程的反向代理服务器。NGINX Plus可以管理认证、访问控制、负载均衡、缓存响应,提供应用级别的健康检查和监控。

5.2 使用响应式编程模型

API网关通过将请求简单地路由到合适的后端服务上来处理。通过调用多个后端的服务并组装结果来处理其他请求。因为这些请求,比如产品信息请求,到后端的时候是独立于其他的请求的。为了将响应时间减少到最短,API网关应该并行地执行独立的请求。

然而有时,请求之间存在依赖。在路由请求到后端服务前,API网关可能需要先通过权限服务验证。类似的,为了获取顾客愿望单中的产品信息,API网关必须首先获取顾客的愿望单信息,接着获取每个产品的信息。另外有关组织API的一个有趣的例子是Netflix Video Grid

使用传统的异步回调方法来写API组合代码会使你很快进入回调的地狱。代码会变得彼此纠缠、理解困难、易于出错。写API网关代码的较好的办法是通过声明的方式使用响应式方法。响应式的抽象例子包括Scala中的FutureJava 8中的CompletableFutureJavaScript中的Promise。也存在一些响应式的扩展(也叫做Rx或者ReactiveX),这些本来是微软为.NET平台开发的。Netflix为基于JVM的环境创造了RxJava以便在它们的API网关中使用。JavaScript中的RxJS可以在浏览器和Node.js中运行。使用响应式的方法可以让你的API网关的代码写得更加简单而高效。

5.3 服务调用

基于微服务的应用是一个分布式系统,必须使用进程间通信机制。两种进程间通信方式如下:

  • 一种是使用异步的,基于消息的机制。一些实现使用了消息代理,比如JMS或者AMQP。其他的比如Zeromq,是无代理的,服务之间直接通信。
  • 另外一种方式是同步机制,比如HTTP或者Thrift

一个系统可以结合使用同步和异步,甚至可以使用每种方式的多种实现。结果,API网关会需要支持多种通信机制。

5.4 服务发现

API网关需要知道要与之进行通信的每个微服务的位置(IP地址和端口)。在传统的应用中,你可能硬编码这个地址,但是在现在、基于云的微服务应用中,发现需要服务的位置不是简单的事情。

基础服务,比如消息代理,通常情况下都会有一个静态地址,可以通过系统环境变量指定。然而确定应用服务的位置却是不容易的。

应用服务具有动态分配的地址。由于动态伸缩和更新,一个服务的实例可能动态改变。API网关会像系统中的其他客户端,需要使用系统的服务发现机制,或者是服务端发现或者是客户端发现。后面的文章会对服务发现进行详细的说明。到目前为止,值得注意的是如果系统使用了客户端发现机制,API网关必须能够查询服务注册表,这是一个保存有所有微服务实例和它们的位置的数据库。

5.5 处理局部故障

另外一个你需要考虑的问题是局部故障。这个问题在所有的分布式应用中,当一个服务调用其他的服务时响应很慢或者不可用时,都有可能发生。API网关不应该无限期阻塞在等待下游服务的状态中。然而,如何处理故障取决于特定的场景和不可用的服务。例如,如果推荐服务在产品详细信息场景下无响应,API网关应该返回其余的产品信息,因为它们对于客户来说仍然是有用的。这些推荐可以是空的,或者是可以被,例如,固定的前十名代替。然而如果产品信息服务没有响应,API网关服务应该返回一个错误给客户端。

如果可用的话,API网关也可以返回缓存的数据。例如,如果产品价格不经常变化,当价格服务不可用的时候,API网关就可以返回缓存的价格数据。这个数据可以被API网关缓存,也可以被外部的缓存系统存储,例如Redis或者Memcached。通过返回默认的数据或者缓存的数据,API网关确保系统故障对用户体验的影响最小。

当编码调用远程服务的时候,Netflix Hystrix是一个令人难以置信的库。Hystrix 将超过指定阈值的调用设置为超时。它实现了断路器模式circuit breaker pattern),这终止了客户端对无响应服务的不必要等待。如果针对某个服务的错误率超过了指定的阈值,Hystrix会触发断路器,此时所有的请求在经历了一段时间后会迅速失败。当请求失败的时候,Hystrix让你定义了备选项,比如从缓存中读取数据或者返回默认值。如果你使用JVM,你当然应该考虑使用Hystrix。如果你的应用运行在一个非JVM的环境中,你也应该使用类似的库。

六、总结

对于大多数基于微服务的应用,实现API网关是很重要的,它作为进入系统的唯一入口,负责请求路由、组装、协议翻译。提供给每个应用客户端特定的API。通过返回缓存或者默认值来隐藏后端服务的故障。在后面的文章中,我们将继续了解服务间的通信。

猜你喜欢

转载自blog.csdn.net/lmy86263/article/details/74937488
今日推荐