使用 API 网关构建微服务

本期内容

微服务系列文章的第一篇介绍了微服务架构模式,讨论了使用微服务的优缺点,以及为何微服务虽然复杂度高倒是复杂应用程序的理想选择。nginx

在决定以一组微服务来构建本身的应用时,你须要肯定应用客户端如何与微服务交互。web

在单体式程序中,一般只有一组冗余的或者负载均衡的服务提供点。在微服务架构中,每个微服务暴露一组细粒度的服务提供点。在本篇文章中,咱们来看它如何影响客户端到服务端通讯,并提出一种使用 API 网关的方法。数据库

简要概述

让咱们想象一下,你要为一个购物应用程序开发一个原生移动客户端。你极可能须要实现一个产品详情页面,展现任何指定商品的信息。编程

下图展现了 Amazon Android 应用在商品详情页显示的内容。后端

Richardson-microservices-part2-1_amazon-apps

 

即便只是个智能手机应用,产品详情页面也显示了大量的信息。该页面不只包含基本的产品信息(如名称、描述、价格),并且还显示了以下内容:设计模式

  • 购物车中的商品数量
  • 历史订单
  • 客户评论
  • 低库存预警
  • 送货选项
  • 各类推荐,包括常常与该商品一块儿购买的其它商品、购买该商品的客户购买的其它商品、购买该商品的客户看过的其它商品
  • 其它的购物选择

使用单体应用程序架构时,移动客户端经过向应用程序发起一次 REST 调用(GET api.company.com/productdetails/)来获取这些数据。负载均衡器将请求路由给 N 个相同的应用程序实例中的其中之一。而后,应用程序会查询各类数据库表,并将响应返回给客户端。api

相反,如果采用微服务架构,显示在产品页上的数据会分布在不一样的微服务上。下面列举了可能与产品详情页数据有关的一些微服务:浏览器

  • 购物车服务——购物车中的件数
  • 订单服务——历史订单
  • 目录服务——商品基本信息,如名称、图片和价格
  • 评论服务——客户的评论
  • 库存服务——低库存预警
  • 送货服务——送货选项、期限和费用,这些信息单独从送货方 API 获取
  • 推荐服务——推荐商品

Richardson-microservices-part2-2_microservices-client

咱们须要决定移动客户端如何访问这些服务。让咱们看看有哪些方法。缓存

客户端与微服务直接通讯

从理论上讲,客户端能够直接向每一个微服务发送请求。每一个微服务都有一个公开的端点(https ://.api.company.name)。该 URL 映射到微服务的负载均衡器,由后者负责在可用实例之间分发请求。为了获取产品详情,移动客户端将逐一贯上文列出的 N 个服务发送请求。服务器

遗憾的是,这种方法存在挑战和局限。问题之一是客户端需求和每一个微服务暴露的细粒度 API 不匹配。在这个例子中,客户端须要发送 7 个独立请求。在更复杂的应用程序中,可能要发送更多的请求;按照 Amazon 的说法,他们在显示他们的产品页面时就调用了数百个服务。然而,客户端经过 LAN 发送许多请求,这在公网上可能会很低效,在移动网络上就根本不可行。这种方法还使得客户端代码很是复杂。

客户端直接调用微服务的另外一个问题是,部分服务使用的协议对 web 并不友好。一个服务可能使用 Thrift 二进制 RPC,而另外一个服务可能使用 AMQP 消息传递协议。无论哪一种协议对于浏览器或防火墙都不够友好,最好是内部使用。在防火墙以外,应用程序应该使用诸如 HTTP 和 WebSocket 之类的协议。

这种方法的另外一个缺点是,它会使得微服务难以重构。随着时间推移,咱们可能想要更改系统拆分红服务的方式。例如,咱们可能合并两个服务,或者将一个服务拆分红两个或更多服务。然而,若是客户端与微服务直接通讯,那么执行这类重构就很是困难了。

因为上述三种问题的缘由,客户端直接与服务器端通讯的方式不多在实际中使用。

使用 API 网关构建微服务

一般来讲,使用 API 网关是更好的解决方式。API 网关是一个服务器,也能够说是进入系统的惟一节点。这与面向对象设计模式中的 Facade 模式很像。API 网关封装内部系统的架构,而且提供 API 给各个客户端。它还可能还具有受权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等功能。下图展现了一个适应当前架构的 API 网关。

Richardson-microservices-part2-3_api-gateway

API 网关负责服务请求路由、组合及协议转换。客户端的全部请求都首先通过 API 网关,而后由它将请求路由到合适的微服务。API 网关常常会经过调用多个微服务并合并结果来处理一个请求。它能够在 web 协议(如 HTTP 与 WebSocket)与内部使用的非 web 友好协议之间转换。

API 网关还能为每一个客户端提供一个定制的 API。一般,它会向移动客户端暴露一个粗粒度的 API。以产品详情的场景为例,API 网关能够提供一个端点(/productdetails?productid=xxx),使移动客户端能够经过一个请求获取全部的产品详情。API 网关经过调用各个服务(产品信息、推荐、评论等等)并合并结果来处理请求。

Netflix API 网关是一个很好的 API 网关实例。Netflix 流媒体服务提供给成百上千种类型的设备使用,包括电视、机顶盒、智能手机、游戏系统、平板电脑等等。

最初,Netflix 试图为他们的流媒体服务提供一个通用的 API。然而他们发现,因为各类各样的设备都有本身独特的需求,这种方式并不能很好地工做。现在,他们使用一个 API 网关,经过运行与针对特定设备的适配器代码,来为每种设备提供定制的 API。一般,一个适配器经过调用平均 6 到 7 个后端服务来处理每一个请求。Netflix API 网关天天处理数十亿请求。

API 网关的优势和缺点

如你所料,使用 API 网关有优势也有不足。使用 API 网关的最大优势是,它封装了应用程序的内部结构。客户端只须要同网关交互,而没必要调用特定的服务。API 网关为每一类客户端提供了特定的 API,这减小了客户端与应用程序间的交互次数,还简化了客户端代码。

API 网关也有一些不足。它增长了一个咱们必须开发、部署和维护的高可用组件。还有一个风险是,API 网关变成了开发瓶颈。为了暴露每一个微服务的端点,开发人员必须更新 API 网关。API网关的更新过程要尽量地简单,这很重要;不然,为了更新网关,开发人员将不得不排队等待。不过,虽然有这些不足,但对于大多数现实世界的应用程序而言,使用 API 网关是合理的。

实现 API 网关

到目前为止,咱们已经探讨了使用 API 网关的动力及其优缺点。下面让咱们看一下须要考虑的各类设计问题。

性能和可扩展性

只有少数公司拥有 Netflix 这样的规模,须要天天处理天天须要处理数十亿请求。无论怎样,对于大多数应用程序而言,API 网关的性能和可扩展性都很是重要。所以,将 API 网关构建在一个支持异步、I/O 非阻塞的平台上是合理的。有多种不一样的技术能够实现一个可扩展的 API 网关。在 JVM 上,可使用一种基于 NIO 的框架,好比 Netty、Vertx、Spring Reactor 或 JBoss Undertow 中的一种。一个很是流行的非 JVM 选项是 Node.js,它是一个基于 Chrome JavaScript 引擎构建的平台。

另外一个方法是使用 NGINX Plus。NGINX Plus 提供了一个成熟的、可扩展的、高性能 web 服务器和一个易于部署的、可配置可编程的反向代理。NGINX Plus 能够管理身份验证、访问控制、负载均衡请求、缓存响应,并提供应用程序可感知的健康检查和监控。

使用响应式编程模型

API 网关经过简单地将请求路由给合适的后端服务来处理部分请求,而经过调用多个后端服务并合并结果来处理其它请求。对于部分请求,好比产品详情相关的多个请求,它们对后端服务的请求是独立于其它请求的。为了最小化响应时间,API 网关应该并发执行独立请求。

然而,有时候,请求之间存在依赖。在将请求路由到后端服务以前,API 网关可能首先须要调用身份验证服务验证请求的合法性。相似地,为了获取客户心愿单中的产品信息,API 网关必须首先获取包含这些信息的客户资料,而后再获取每一个产品的信息。关于 API 组合,另外一个有趣的例子是 Netflix Video Grid。

使用传统的异步回调方法编写 API 组合代码会让你迅速坠入回调地狱。代码会变得混乱、难以理解且容易出错。一个更好的方法是使用响应式方法,以一种声明式样式编写 API 网关代码。响应式抽象概念的例子有 Scala 中的 Future、Java 8 中的 CompletableFuture 和 JavaScript 中的P romise,还有最初微软为 .NET 平台开发的 Reactive Extensions(RX)。Netflix 建立了 RxJava for JVM,专门用于他们的 API 网关。此外,还有 RxJS for JavaScript,它既能够在浏览器中运行,也能够在 Node.js 中运行。使用响应式方法能让你编写简单但高效的 API 网关代码。

服务调用

基于微服务的应用程序是一个分布式系统,必须使用一种进程间通讯机制。有两种类型的进程间通讯机制可供选择。一种是使用异步的、基于消息传递的机制。有些实现使用诸如 JMS 或 AMQP 那样的消息代理,而其它的实现(如 Zeromq)则没有代理,服务间直接通讯。

另外一种进程间通讯类型是诸如 HTTP 或 Thrift 那样的同步机制。一般,一个系统会同时使用异步和同步两种类型。它甚至还可能使用同一类型的多种实现。总之,API 网关须要支持多种通讯机制。

服务发现

API 网关须要知道它与之通讯的每一个微服务的位置(IP 地址和端口)。在传统的应用程序中,或许能够硬连线这个位置,但在现代的、基于云的微服务应用程序中,这并非一个容易解决的问题。基础设施服务(如消息代理)一般会有一个静态位置,能够经过 OS 环境变量指定。可是,肯定一个应用程序服务的位置没有这么简单。应用程序服务的位置是动态分配的,并且,单个服务的一组实例也会随着自动扩展或升级而动态变化。

总之,像系统中的其它服务客户端同样,API 网关须要使用系统的服务发现机制,能够是服务器端发现,也能够是客户端发现。下一篇文章将更详细地描述服务发现。如今,须要注意的是,若是系统使用客户端发现,那么 API 网关必须可以查询服务注册中心,这是一个包含全部微服务实例及其位置的数据库。

处理局部失败

在实现 API 网关时,还须要处理局部失败的问题。该问题出如今全部的分布式系统中。当一个服务调用另外一个服务,然后者响应慢或不可用的时候,就会出现这个问题。API 网关不能由于无限期地等待下游服务而阻塞。不过,如何处理失败取决于特定的场景以及哪一个服务失败。例如,在产品详情场景下,若是推荐服务无响应,那么 API 网关应该向客户端返回产品详情的其它内容,由于它们对用户依然有用。推荐内容能够为空,也能够用一个固定的 TOP 10 列表取代。不过,若是产品信息服务无响应,那么 API 网关应该向客户端返回一个错误信息。

若是缓存数据可用,那么 API 网关还能够返回缓存数据。例如,鉴于产品价格不会频繁变更,若是价格服务不可用,API 网关能够返回缓存的价格数据。数据能够由 API 网关本身缓存,也能够存储在像 Redis 或 Memcached 之类的外部缓存中。经过返回默认数据或者缓存数据,API 网关能够确保系统故障不影响用户体验。

在编写代码调用远程服务方面,Netflix Hystrix 是一个格外有用的库。Hystrix 会暂停超出特定阈限的调用。它实现了一个“断路器(circuit breaker)”模式,能够防止客户端对无响应的服务进行没必要要的等待。若是服务的错误率超出了设定的阈值,那么 Hystrix 会启动断路器,全部请求会当即失败并持续必定时间。Hystrix 容许用户定义一个请求失败后的后援操做,好比从缓存读取数据,或者返回一个默认值。若是你正在使用 JVM,那么你应该考虑使用 Hystrix;若是你正在使用一个非 JVM 环境,那么可使用一个功能相同的库。

总结

对于大多数基于微服务的应用程序而言,实现 API 网关,将其做为系统的惟一入口颇有必要。API 网关负责服务请求路由、组合及协议转换。它为每一个应用程序客户端提供一个定制的 API。API 网关还能够经过返回缓存数据或默认数据屏蔽后端服务失败。在本系列的下一篇文章中,咱们将探讨服务间通讯。

下期题目:深刻微服务架构的进程间通讯 ,敬请期待!

点击“Building Microservices: Using an API Gateway”,查看英文原文