微服务入门系列:使用 API 网关

这是下面七篇系列文章的第二篇:

  1. 微服务入门系列:微服务介绍
  2. 微服务入门系列:使用 API 网关(本文)
  3. 微服务入门系列:微服务架构的进程间通信
  4. 微服务入门系列:微服务架构的服务发现
  5. 微服务入门系列:基于事件驱动的数据管理
  6. 微服务入门系列:微服务的部署策略选择
  7. 微服务入门系列:单体应用的微服务重构

出自微服务部落:https://blog.idevfun.io/building-microservices-using-an-api-gateway/
原文链接:https://www.nginx.com/blog/building-microservices-using-an-api-gateway/
作者:Chris Richardson
翻译:董干

这 7 篇文章是很不错的微服务入门系列文章,介绍了微服务所需要的主要技术,是当年(本系列文章写于2015年,但现在仍然不过时)带领译者入门微服务的文章,强烈建议深入微服务架构之前先了解本系列。


微服务七篇入门系列的第一篇讲述了微服务的设计、构建和部署,介绍了微服务架构模式。文中讨论了使用微服务的优缺点,以及为何尽管微服务具有如此的复杂性,它仍然是复杂应用的理想选择。本文是该系列的第二篇,介绍使用 API 网关构建微服务。

当你选择使用一组微服务构建应用时,你需要决定客户端如何与这些微服务交互。对于单体应用来说,只有一组(通常是多个实例、带有负载均衡的)请求端点。然而在微服务架构中,每个服务都暴露了通常来说粒度更细的一组端点。我们将在本文讨论这种形式将如何影响客户端应用的通信,并提出 API 网关的方式。

介绍

假设你正在开发一个原生的购物移动应用。你很可能需要实现一个商品详情页,用来展示任何给定的商品。

例如,下图展示了当你在 Amazon 的安卓版应用上显示的商品详情页向下滚动时能看到的区域。

Richardson-microservices-part2-1_amazon-apps

即使是手机应用,这个商品详情页也显示了大量信息。例如,这个页面不仅有商品的基本信息(如名称、描述和价格),也包含了:

  • 购物车中的商品数量
  • 订单历史
  • 买家评价
  • 低库存提示
  • 配送可选项
  • 各种各样的推荐,包括与该商品经常一起购买的商品、购买了本商品的买家买的其他商品、购买了本商品的买家浏览过的商品等等
  • 其他购买选项

当使用单体应用架构时,移动客户端会通过调用一个 REST 请求 (GET api.company.com/productdetails/productId) 到应用服务端获得这些数据。负载均衡将该请求路由到 N 个等同的服务实例之一。应用程序会查询各种数据库的表并且将结果返回给客户端。

与此相对地,当使用微服务架构时,商品详情页所展示的数据属于多个不同的微服务。这里列出了一些上面的例子中展示商品详情页所需要的数据可能所属的微服务:

  • Shopping Cart Service —— 购物车中的商品数量
  • Order Service —— 订单历史
  • Catalog Service —— 商品基本信息,例如名称、图片和价格
  • Review Service —— 买家评论
  • Inventory Service —— 低库存提示
  • Shipping Service —— 配送选项,预计送达时间,以及单独从配送服务提供商拉到的配送费用
  • Recommendation Service —— 推荐商品

Richardson-microservices-part2-2_microservices-client

我们需要决定移动端如何接入这些服务。我们来看一些可选的方式。

客户端和微服务直接通信

理论上讲,客户端可以直接对微服务发起请求。每个微服务可以有一个公开的接口 (https://serviceName.api.company.name)。这个 URL 将对应微服务的负载均衡,由负载均衡将请求分发到可用的实例上。若想获得商品的详情,移动客户端将会分别请求上述列表中的每一个服务。

不幸的是,这种方式会遇到一些挑战和限制。一个问题是客户端的需求和暴露的细粒度的微服务 API 之间的不匹配。在这个例子中,客户端需要发出7个单独的请求。在更复杂的应用中,可能需要更多的请求数。例如,Amazon 曾提到在展示他们的商品页面时是如何涉及到上百的服务的。如果是在局域网中,客户端还有可能请求这么多次,但在公网下这将会非常的低效,而且在移动网络中这也完全不实际。这种方式也会使客户端代码更复杂。

客户端直接调用微服务的另一个问题是一些微服务可能使用并非 web 友好的协议。一些服务可能会使用像 Thrift 这样的二进制 RPC 协议,还有一些服务可能使用 AMQP 消息协议。不管哪种协议都对浏览器和防火墙不是很友好,因此最好在内网使用。在防火墙外,应用程序应该使用像 HTTP 和 WebSocket 这样的协议。

这种方式的另一个缺陷是,它不利于微服务的重构。经过一定时间后,我们可能会对系统的服务做重新划分。例如,我们可能将两个服务合并成一个,或者将一个服务分成两个或更多的服务。但是,如果客户端直接与服务进行通信,那么实现这类重构将会无比困难。

正因为上述这些问题,客户端直接和微服务通信很少能适用。

使用 API 网关

通常更好的方式是使用 API 网关这种方式。API 网关是系统的唯一入口服务器。它有些类似于面向对象设计中的门面模式。API 网关将内部系统架构封装成适合每种客户端调用的 API。它也有可能负责其他职责例如鉴权、监控、负载均衡、请求缓存、请求重塑和管理,以及静态资源处理等。

下图描述了 API 网关通常是如何适用于这个架构的:

Richardson-microservices-part2-3_api-gateway

API 网关负责请求路由、组合以及协议转换。所有客户端的请求都需要先经过 API 网关。然后它将这些请求路由到相应的微服务。API 网关通常会请求多个微服务并将这些结果聚合后返回给客户端。它可以在 HTTP 和 WebSocket 这样的 web 协议和内部使用的非 web 友好的协议之间进行协议转换。

API 网关也可以针对每个客户端返回定制的 API。它通常会给客户端暴露较粗粒度的 API。例如我们考虑商品详情这个场景,API 网关可以提供一个接口(/productdetails?productid=xxx)使得客户端可以通过一次请求就获得所有商品的详情数据。API 网关通过调用各个服务来处理这个请求 —— 包括商品信息、推荐、评论等服务 —— 然后将各服务的结果合并起来。

API 网关的一个很好的例子是 Netflix 的 API 网关。Netflix 的视频点播服务运行在上百的不同设备中,包括电视、机顶盒、智能手机、游戏系统、平板电脑等等。一开始,Netflix 尝试着想提供一个统一的流媒体服务 API 给所有设备用。然而,他们发现这并不是很合适,因为各种不同的设备都有着各自不同的特殊需求。如今,他们使用 API 网关结合一些设备专门的适配代码,给各种不同的设备提供特殊裁剪的 API。每个适配器通常将一个请求分发给平均六到七个后端的服务。Netflix 的 API 网关像这样每天处理几十亿的请求量。

API 网关的优点和缺点

正如你期望的,使用 API 网关有它的优势和缺陷。使用它的一个主要好处是它封装了应用系统的内部结构。客户端并非调用具体的服务,而是同网关通信即可。API 网关可以为每一类客户端提供定制的 API。这减少了客户端和应用程序之间的请求次数。也使得客户端代码得到简化。

API 网关也有缺陷。它是另一个需要保证高可用的组件,它本身也需要开发、部署和管理。还有个风险是 API 网关可能成为开发的瓶颈。开发者为了暴露每个微服务的接口,必须在 API 网关上更新代码。因此,保证更新 API 网关的过程越轻量越好。否则,开发者将会被迫排队等着更新网关。然而,尽管有这些缺点,对现实世界中的应用程序来说,使用 API 网关仍然是有意义的。

实现 API 网关

讨论完使用 API 网关的动机和代价,我们再看看可能需要考虑的各种设计问题。

性能和可扩容

能像 Netflix 这样每天需要处理几十亿请求的公司就那么几个。然而,对于大部分应用来说,API 网关的性能和可扩容性通常也非常重要。因此,在一个支持异步非阻塞 I/O 的平台上构建 API 网关非常重要。有很多可选择的技术可以用来实现高可扩容性 API 网关。在 JVM 上,你可以使用基于 NIO 的框架,例如 Netty、Vertx、Spring Reactor 或者 JBoss Undertow。还有个流行的非 JVM 的选择是基于 Chrome 的 JavaScript 引擎构建的平台 Node.js。另外一个选择是使用 NGINX Plus。NGINX Plus 是提供成熟、可扩容、高性能的 web 服务器、反向代理,它的部署、配置和基于它的编程很简单。NGINX Plus 也能够管理鉴权、访问控制、请求负载均衡、缓存响应结果,并且提供程序可感知的健康检查和监控。

使用响应式编程模型

API 网关有时处理请求仅仅需要简单地把它路由到相应的后端服务。其他的请求调用需要通过调用多个后端服务,并将它们的结果聚合起来返回给客户端。有些请求,例如商品详情,到底层服务的多个请求之间是互相独立的。为了减少响应时间,API 网关应该并行地调用互相独立的请求。然而,有时请求之间是有依赖关系的。API 网关可能需要先通过调用某鉴权服务验证请求合法性,然后再将请求路由到后端服务。类似地,为了获取买家心愿单中的商品信息,API 网关必须先获取包含这些数据的买家个人信息,然后再获取每个商品的信息。Netflix 的视频网格是 API 组合的另一个有趣的例子。

使用传统的异步回调方式编写 API 聚合代码会迅速导致回调地狱。代码将变得混乱、难于理解并容易出错。一种更优的方式是使用声明式风格和响应式编程的方式编写 API 网关的代码。响应式编程抽象的例子包括 Scala 的 Future,Java 8 的 CompletableFuture以及 JavaScript 的 Promise。还有由微软原本为 .NET 平台开发的 Reactive 扩展(也叫做 Rx 或者 ReactiveX)。Netflix 为了在他们的 API 网关中使用它创建了针对 JVM 的 RxJava。还有既能运行在浏览器也能运行在 Node.js 中的 JavaScript 版,RxJS。使用响应式的方式使得编写 API 网关代码更简单高效。

服务调用

基于微服务的应用程序是一种分布式系统,因此必须使用某种进程间通信的机制。进程间通信有两种方式。一种方式是使用异步的,基于消息的机制。一些实现使用像 JMS 和 AMQP 这样的消息代理。还有的像 Zeromq 这种无代理的实现,服务之间则直接通信。另外一种进程间通信方式是使用像 HTTP 或者 Thrift 这样的同步机制。一个系统通常既使用异步的方式也使用同步的方式,甚至每种方式可能使用多种不同的实现。因此,API 网关需要支持多种多样的通信机制。

服务发现

API 网关需要知道与它通信的每个服务的位置(IP 地址和端口号)。在传统的应用中,你可能会将这些位置硬编码进去,但是在现代的、基于云的微服务应用中,这并不是个简单的问题。基础设施服务,例如消息代理,通常有静态的位置,因此可以通过操作系统环境变量指定。然而,确定应用服务的位置并不是很容易。应用服务的位置是动态分配的。并且,由于自动扩容和服务升级,每个服务的实例集合也是动态变化的。因此,API 网关同系统中其他的服务客户端一样,需要使用系统的服务发现机制,要么是服务端发现,要么是客户端发现后续文章将会详细介绍服务发现。目前,值得记住的就是如果系统使用客户端发现方式,那么 API 网关必须能够查询服务注册中心,它是所有服务实例和它们位置的数据库。

处理局部故障

实现 API 网关需要解决的另一个问题是局部故障问题。这是所有分布式系统中当一个服务需要调用另一个服务时响应过慢或不可用时都会遇到的问题。API 网关永远不应该无限阻塞等待下游服务响应。然而,如何处理局部故障取决于具体的场景和故障的服务。例如,在商品详情的场景,如果推荐服务没有响应,API 网关应该返回其余的商品详情给客户端,因为其余数据对用户来说仍然是有用的。推荐的商品可以为空或者替换为一个写死的排名前十列表。然而,如果商品基本信息服务没有响应,那么 API 网关应该给客户端返回错误。

API 网关也可以返回缓存的数据,如果缓存可用的话。例如,由于商品价格变化并不是很频繁,当商品价格服务不可用时,API 网关可以返回缓存的价格数据。这些数据可以由 API 网关自身缓存起来,也可以存储在像 Redis 或者 Memcached 这样的外部缓存中。通过返回默认数据或者缓存数据,API 网关确保了系统故障不至于影响用户体验。

Netflix Hystrix 是个非常有用的编写请求远程服务的库。Hystrix 对超过某一阈值的请求进行超时。它实现了熔断模式,避免客户端不必要地等待一个没有响应的服务。如果某服务的错误率超过了设置的阈值,Hystrix 触发熔断,一段时间内所有的请求会立即返回失败。Hystrix 还允许你定义一个当请求失败时的降级行为,例如读取缓存或者返回默认值等。如果你使用 JVM,那么你一定要考虑使用 Hystrix。如果你运行在非 JVM 环境中,你也应该使用一个等价的库。

总结

对于大部分基于微服务的应用来说,实现一个 API 网关作为系统的唯一入口是有意义的。API 网关负责请求路由、聚合以及协议转换。它可以为应用的每类客户端提供定制的 API。API 网关还可以通过返回缓存的或默认的数据隐藏后端服务的故障。在本系列文章的下一篇当中,我们将看一下服务之间的通信机制。


这是下面七篇系列文章的第二篇:

  1. 微服务入门系列:微服务介绍
  2. 微服务入门系列:使用 API 网关(本文)
  3. 微服务入门系列:微服务架构的进程间通信
  4. 微服务入门系列:微服务架构的服务发现
  5. 微服务入门系列:基于事件驱动的数据管理
  6. 微服务入门系列:微服务的部署策略选择
  7. 微服务入门系列:单体应用的微服务重构

出自微服务部落:https://blog.idevfun.io/building-microservices-using-an-api-gateway/
原文链接:https://www.nginx.com/blog/building-microservices-using-an-api-gateway/
作者:Chris Richardson
翻译:董干

这 7 篇文章是很不错的微服务入门系列文章,介绍了微服务所需要的主要技术,是当年(本系列文章写于2015年,但现在仍然不过时)带领译者入门微服务的文章,强烈建议深入微服务架构之前先了解本系列。

Show Comments

Get the latest posts delivered right to your inbox.