02-API网关

原文链接

这是本系列的第二篇文章,将讨论使用API网关构建微服务。当选择将应用程序构建为一组微服务时,需要确定应用程序的客户端如何与微服务进行交互。

  • 使用单体应用,只有一组(通常是复制的,负载均衡的)端点。
  • 在微服务架构中,每个微服务都暴露出一组更细粒度的端点。

在本文中,我们将研究它如何影响客户端到应用程序的通信,并提出一种使用API网关的方法。

简介

让我们假设正在为购物应用程序开发原生移动客户端,需要实现商品详细信息页面,该页面显示有关给定产品的所有信息。

例如,下图显示了在亚马逊Android移动应用程序中滚动浏览商品详细信息时看到的内容。

image

即使这是智能手机应用程序,商品详细信息页面也会显示很多信息。例如,不仅有基本的产品信息(如名称,描述和价格),而且此页面还显示:

  • 购物车中的商品数量
  • 订单历史记录
  • 客户评论
  • 低库存警告
  • 运输选项
  • 各种建议,包括在购买此商品是经常一起购买的商品,以及购买此产品的客户查看的其他产品
  • 替代购买选项

使用单体应用架构时,移动客户端将通过单个REST调用来检索此数据(GET api.company.com/productdetails/productId)到应用程序。负载均衡器将请求路由到N个相同的应用程序实例之一。然后,应用程序将查询各种数据库表并将响应返回给客户端。

当使用微服务架构时,商品详细信息页面上显示的数据由多个微服务拥有。以下是一些潜在的微服务,它们拥有示例商品详细信息页面上显示的数据:

  • 购物车服务:购物车中的商品数量
  • 订购服务:订单历史
  • 目录服务:产品基本信息,例如名称,图片和价格
  • 审核服务:客户评论
  • 库存服务:低库存警告
  • 送货服务:运输选项,截止日期、成本、运输提供商的API
  • 推荐服务:推荐项目

image

我们需要决定移动客户端如何访问这些服务。

客户端与微服务直接通信

理论上,客户端可以直接向每个微服务发出请求。每个微服务都有一个公共端点(https://serviceName.api.company.name)。此URL将映射到微服务的负载均衡器,该负载均衡器跨可用实例分发请求。要检索商品详细信息,移动客户端将向上述每个微服务发出请求。

不幸的是,这种方式存在挑战和局限:

  1. 客户端的需求与每个微服务公开的细粒度API之间的不匹配

    此示例中的客户端必须发出七个单独的请求,在更复杂的应用程序中,它可能会发出更多的请求(例如,亚马逊描述了在渲染产品页面时如何涉及数百种服务)。虽然客户端可以通过局域网发出这么多请求,但是这在公共互联网上效率太低,而且在移动网络上肯定是行不通的。这种方法也使客户端代码更加复杂。

  2. 可能会使用非Web友好的协议

    • 一个服务可能使用Thrift二进制RPC
    • 而另一个服务可能使用AMQP消息传递协议

    这两种协议都不适合浏览器或防火墙,最好在内部网络中使用,应用程序应在防火墙之外使用HTTP和WebSocket等协议。

  3. 重构微服务变得困难。随着时间的推移,我们可能想要修改系统拆分为微服务的划分方式,重新定义微服务模块的边界。

    例如,我们可能合并两个服务或将服务拆分为两个或更多服务。但是,如果客户端直接与服务通信,那么执行这种重构可能非常困难。

由于这些问题,客户直接与微服务通信变的没有意义。

使用API网关

通常,更好的方法是使用API网关。API网关是进入系统的单一入口点服务。它类似于面向对象设计的外观模式(Facade Pattern)。API网关封装了内部系统体系结构,并提供了为每个客户端量身定制的API。它可能还有其他职责,例如:

  • 身份验证
  • 监视
  • 负载均衡
  • 缓存
  • 请求转换和管理
  • 静态响应处理

下图显示了API网关通常如何适合该体系结构:

image

  1. API网关负责请求路由,组合和协议转换

    客户的所有请求首先通过API网关。然后它将请求路由到适当的微服务。API网关通常通过调用多个微服务并聚合结果来处理请求。它可以在Web协议(如HTTP和WebSocket)和内部使用的Web友好协议之间进行转换。

  2. API网关为每个客户端提供自定义API,通常为移动客户端公开粗粒度API。

    例如,展示商品详细信息的方案。API网关可以提供端点(/productdetails?productid=xxx),使端点能够通过单个请求检索所有商品详细信息。API网关通过调用各种服务(目录服务,推荐服务,审核服务等)来处理请求,并将结果组合在一起。

API网关示例Netflix API网关

Netflix流媒体服务可用于数百种不同类型的设备,包括电视,机顶盒,智能手机,游戏系统,平板电脑等。最初,Netflix试图为其流媒体服务提供一种通用的API。然而,他们发现由于各种设备和它们的独特需求,通用API不能很好地工作。现在,他们使用API​​网关,通过运行特定设备的适配器代码为每个设备提供量身定制的API。通常,适配器平均调用六到七个后端服务来处理每一个请求。Netflix API Gateway每天处理数十亿个请求。

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的主流选择是Node.js,基于Chrome的JavaScript引擎构建的平台
  • 另一种选择是使用NGINX Plus

    NGINX Plus提供了一个成熟,可扩展,高性能的Web服务器和反向代理,可轻松部署,配置和编程。NGINX Plus可以管理身份验证,访问控制,负载平衡请求,缓存响应,并提供应用程序感知的运行状况检查和监视。

使用反应式编程模型

API网关通过简单地将它们路由到适当的后端服务来处理某些请求。它通过调用多个后端服务并聚合结果来处理其他请求。

  • 对于某些请求(例如,显示商品详细信息的请求),对后端服务的请求彼此独立。为了最大限度地缩短响应时间,API网关应同时执行独立请求
  • 有时请求之间存在依赖关系。在将请求路由到后端服务之前,API网关可能首先需要通过调用身份验证服务来验证请求,例如:

    1. 要获取有关客户愿望清单中商品的详细信息
    2. API网关必须首先检索包含该商品信息的客户信息文件
    3. 然后检索每个商品的信息

    另一个有趣的API组合示例是Netflix Video Grid

使用传统的异步回调方法编写API组合代码很快就会导致回调地狱。代码将纠结、难以理解,并且容易出错。更好的方法是以声明式风格使用反应式编程模型编写API网关代码。反应式编程的抽象概念示例,包括:

  • Scala中的Future
  • Java 8中的CompletableFuture:Netflix为JVM创建了RxJava,专门用于其API网关
  • JavaScript中的Promise:用于JavaScript的RxJS,它可以在浏览器和Node.js中运行
  • Reactive Extensions(也称为Rx或ReactiveX),最初由Microsoft为.NET平台开发

使用反应式编程模型将能够编写简单而有效的API网关代码。

服务调用

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

  1. 基于消息传递的异步机制。某些实现使用消息代理(例如JMS或AMQP)。如Zeromq,是无代理的,服务直接沟通。
  2. HTTP或Thrift等同步机制。系统通常同时使用异步和同步两种风格,甚至可能使用每种风格的多个实现。因此,API网关将需要支持各种通信机制。

服务发现

API网关需要知道与之通信的每个微服务的位置(IP地址和端口):

  • 在传统的应用程序中,可能需要硬连接来定位服务位置
  • 在现代的基于云的微服务应用程序中,这也是一个非常重要的问题

基础结构服务(例如,消息代理)通常具有静态位置,可以通过OS环境变量指定。但是,确定应用程序服务的位置并不容易,应用服务具有动态分配的位置。由于自动扩展和升级,服务实例集会动态更改。因此,与网络中的任何其他服务客户端一样,API网关需要使用系统的服务发现机制:服务器端发现客户端发现

稍后的文章将更详细地描述服务发现。现在,值得注意的是,如果系统使用客户端发现,那么API网关必须能够查询服务注册表(Service Registry),它是所有微服务实例及其位置的数据库。

处理部分失败

实现API网关时必须解决的另一个问题是部分失败的问题。一个服务调用另一个响应缓慢或不可用的服务,所有分布式系统都会出现此问题。API网关永远不应无限期地阻塞等待下游服务,如何处理故障取决于具体方案和哪个服务失败。

  • 如果推荐服务在商品详细信息方案中没有响应,则API网关应将剩余的商品详细信息返回给客户端,因为它们对用户仍然有用。推荐项目可以是空的,也可以替换为Top10的商品名单。
  • 如果目录服务没有响应,那么API网关应该向客户端返回错误,或者,API网关也可以返回缓存的数据(例如,由于商品价格不会经常变化,API网关可以返回缓存的定价数据)。

    数据可以由API网关本身缓存,也可以存储在外部缓存中,例如Redis或Memcached。通过返回默认数据或缓存数据,API网关可确保系统故障不会影响用户体验。

Netflix Hystrix是一个非常有用的库,用于编写调用远程服务的代码

Hystrix的超时调用次数可以设定阈值,它实现了断路器模式,阻止客户端不必要地等待无响应的服务。

  • 如果服务的错误率超过指定的阈值,Hystrix将使断路器跳闸,并且所有请求将在指定的时间段内立即失败
  • Hystrix允许在请求失败时定义回退操作(例如,从缓存读取或返回默认值)
    • 如果使用的是JVM,那么一定要考虑使用Hystrix
    • 如果在非JVM环境中运行,则应使用其他等效的库

总结

对于大多数基于微服务的应用程序,实现API网关是有意义的,它充当系统的单一入口点。 API网关负责请求路由,组合和协议转换,它为每个应用程序的客户端提供自定义API。API网关还可以通过返回缓存或默认数据来屏蔽后端服务中的故障。在本系列的下一篇文章中,我们将介绍服务之间的通信。

上次修改: 14 April 2020