构建微服务:使用 API 网关
作者 |MicroStone
来源 | 今日头条
当您选择将应用程序构建为一组微服务时,您需要决定应用程序的客户端将如何与微服务交互。对于单体应用程序,只有一组(通常是复制的、负载平衡的)端点。然而,在微服务架构中,每个微服务都暴露了一组通常是细粒度的端点。在本文中,我们研究了这如何影响客户端到应用程序的通信,并提出了一种使用API 网关的方法。
介绍
假设您正在为购物应用程序开发本机移动客户端。您可能需要实现一个产品详细信息页面,该页面显示任何给定产品的信息。
例如,下图显示了您在亚马逊的Android 移动应用程序中滚动浏览产品详细信息时看到的内容。
打开百度APP看高清图片
即使这是一个智能手机应用程序,产品详细信息页面也会显示大量信息。例如,不仅有基本的产品信息(如名称、描述和价格),而且此页面还显示:
- 购物车中的商品数量
- 订单历史
- 顾客评论
- 低库存警告
- 送货选项
- 各种推荐,包括经常购买该产品的其他产品,购买该产品的客户购买的其他产品,以及购买该产品的客户查看的其他产品
- 替代购买选项
当使用单体应用程序架构时,移动客户端将通过对应用程序进行单个 REST 调用 ( ) 来检索此数据。负载均衡器将请求路由到 N 个相同的应用程序实例之一。然后应用程序查询各种数据库表并将响应返回给客户端。GET
api.company.com/productdetails/*productId*
相比之下,当使用微服务架构时,产品详情页面上显示的数据由多个微服务拥有。以下是示例产品详细信息页面上显示的拥有数据的一些潜在微服务:
- 购物车服务 - 购物车中的商品数量
- 订单服务 – 订单历史
- 目录服务 - 基本产品信息,例如其名称、图像和价格
- 评论服务 – 客户评论
- 库存服务 - 低库存警告
- 运输服务 – 运输选项、截止日期和费用,独立于运输提供商的 API
- 推荐服务——推荐项目
我们需要决定移动客户端如何访问这些服务。让我们看看选项。
直接的客户端到微服务通信
理论上,客户端可以直接向每个微服务发出请求。每个微服务都有一个公共端点。该 URL 将映射到微服务的负载均衡器,该负载均衡器在可用实例之间分配请求。为了检索产品详细信息,移动客户端将向上面列出的每个服务发出请求。
不幸的是,此选项存在挑战和限制。一个问题是客户端的需求与每个微服务公开的细粒度 API 之间的不匹配。此示例中的客户端必须发出七个单独的请求。在更复杂的应用程序中,它可能需要做更多的事情。例如,亚马逊描述了数百种服务如何参与呈现他们的产品页面。虽然客户端可以通过 LAN 发出这么多请求,但在公共 Internet 上可能效率太低,而且在移动网络上肯定不切实际。这种方法还使客户端代码更加复杂。
客户端直接调用微服务的另一个问题是,有些可能使用不适合 Web 的协议。一项服务可能使用 Thrift 二进制 RPC,而另一项服务可能使用 AMQP 消息传递协议。这两种协议对浏览器或防火墙都不是特别友好,最好在内部使用。应用程序应在防火墙外使用 HTTP 和 WebSocket 等协议。
这种方法的另一个缺点是难以重构微服务。随着时间的推移,我们可能想要改变系统划分为服务的方式。例如,我们可能会合并两个服务或将一个服务拆分为两个或多个服务。但是,如果客户端直接与服务通信,那么执行这种重构可能会非常困难。
由于这些类型的问题,客户直接与微服务对话几乎没有意义。
使用 API 网关
通常更好的方法是使用所谓的API Gateway。API 网关是一个服务器,它是系统的单一入口点。它类似于面向对象设计的外观模式。API 网关封装了内部系统架构,并提供了为每个客户端量身定制的 API。它可能具有其他职责,例如身份验证、监控、负载平衡、缓存、请求整形和管理以及静态响应处理。
下图显示了 API 网关通常如何适应架构:
API 网关负责路由、组合和协议转换。来自客户端的所有请求首先通过 API 网关。然后它将请求路由到适当的微服务。API 网关通常会通过调用多个微服务并聚合结果来处理请求。它可以在 HTTP 和 WebSocket 等 Web 协议与内部使用的 Web 不友好协议之间进行转换。
API 网关还可以为每个客户端提供自定义 API。它通常为移动客户端公开一个粗粒度的 API。例如,考虑产品详细信息场景。API 网关可以提供一个端点,使移动客户端能够通过单个请求检索所有产品详细信息。API 网关通过调用各种服务(产品信息、推荐、评论等)并组合结果来处理请求。
API 网关的一个很好的例子是Netflix API 网关。Netflix 流媒体服务可在数百种不同类型的设备上使用,包括电视、机顶盒、智能手机、游戏系统、平板电脑等。最初,Netflix 试图为其流媒体服务提供一刀切的API。然而,他们发现由于设备种类繁多及其独特的需求,它并不能很好地工作。今天,他们使用 API 网关,通过运行特定于设备的适配器代码为每个设备提供定制的 API。适配器通常通过平均调用六到七个后端服务来处理每个请求。Netflix API 网关每天处理数十亿个请求。
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 选项是 Node.js,它是一个基于 Chrome 的 JavaScript 引擎构建的平台。另一种选择是使用NGINX Plus, NGINX Plus 提供成熟、可扩展、高性能的 Web 服务器和反向代理,易于部署、配置和编程。NGINX Plus 可以管理身份验证、访问控制、负载平衡请求、缓存响应,并提供应用程序感知的健康检查和监控。
使用反应式编程模型
API 网关通过简略地将它们路由到恰当的后端服务来处理一些请求。它通过调用多个后端服务并聚合结果来处理其他请求。对于某些清求,例如产品详细信息请求,对后端服务的请求是相互独立的。为了最小化响应时间,API 网关应当同时履行独立的请求。但是,有时清求之间存在依赖关联。API 网关可能首先需要通过调用身份验证服务来验证恳求,然后再将恳求路由到后端服务。同样,如果获得有关客户愿望清单中产品的信息,API 网关必须首先检索包含该信息的客户配置文件,然后检索每个产品的信息。
使用传统的异步回调方法编写 API 组合代码会很快将您带入回调地狱。代码会很纠结,难以理解,而且容易出错。更好的方法是使用响应式方法以声明式风格编写 API Gateway 代码。响应式抽象的示例包括Scala 中的Future、Java 8 中的[CompletableFuture和 JavaScript 中的Promise。还有反应式扩展(也称为 Rx 或 ReactiveX),最初由 Microsoft 为 .NET 平台开发。Netflix 为 JVM 创建了 RxJava,专门用于他们的 API 网关。还有用于 JavaScript 的 RxJS,它可以在浏览器和 Node.js 中运行。使用反应式方法将使您能够编写简单而高效的 API 网关代码。
服务调用
基于微服务的应用程序是一个分布式系统,必须使用进程间通信机制。进程间通信有两种风格。一种选择是使用基于消息传递的异步机制。一些实现使用消息代理,例如 JMS 或 AMQP。其他的,例如 Zeromq,是无代理的,服务直接通信。另一种进程间通信方式是同步机制,例如 HTTP 或 Thrift。系统通常会同时使用异步和同步样式。它甚至可能使用每种样式的多个实现。因此,API 网关需要支持多种通信机制。
服务发现
API 网关需要晓得与之通信的每个微服务的位置(IP 地点和端口)。在传统应用程序中,您可能可以硬连接位置,但在现代、基于云的微服务应用程序中,这是一个不平常的难题。基础设施服务,例如消息代理,通常有一个静态位置,可以通过操作系统环境变量指定。然而,确定应用服务的位置并不容易。应用程序服务具备动态分配的位置。此外,由于自动缩放和升级,服务的实例会议动态变化。因此,API 网关与体系中的任何其他服务客户端一样,需要应用体系的服务发现机制:服务器端发现或客户端发现。现在,值得注意的是,如果系统应用客户端发现,那么 API 网关必须能够查询服务注册表,这是一个包含所有微服务实例及其位置的数据库。
处理部分故障
实现 API 网关时必须解决的另一个问题是部分失败的问题。每当一个服务调用另一个响应缓慢或不可用的服务时,所有分布式系统都会出现此问题。API 网关不应该无限期地
实现 API 网关时必须解决的另一个题目是局部失败的问题。每当一个服务调用另一个响应迟缓或不可用的服务时,所有分布式体系城市出现此问题。API 网关不应当无限期地阻塞等待下游服务。但是,它如何处理失败取决于具体的场景和失败的服务。例如,如果推举服务在产品详情场景中没有响应,API 网关应当将其余的产品详情返回给客户端,因为它们对用户仍然有效。这些提议可以是空的,也可以由例如硬连线的前十名列表调换。但是,如果产品信息服务没有响应,那么 API Gateway 该当向客户端返回一个错误。
如果可用,API 网关也可以返回缓存数据。例如,由于产品价钱不经常变化,如果定价服务不可用,API 网关可能会返回缓存的定价数据。数据可以由 API Gateway 本身缓存,也可以存储在 Redis 或 Memcached 等外部缓存中。通过返回默认数据或缓存数据,API Gateway 确保体系故障不会影响用户体验。
Netflix Hystrix是一个非常有用的库,用于编写调用远程服务的代码。Hystrix 超时调用超过指定阈值。它实现了一个断路器模式,阻止客户端不必要地等待无响应的服务。如果服务的错误率超过指定的阈值,Hystrix 会触发断路器,并且所有请求将在指定的时间段内立即失败。Hystrix 允许您在请求失败时定义回退操作,例如从缓存中读取或返回默认值。如果您使用的是 JVM,则绝对应该考虑使用 Hystrix。而且,如果您在非 JVM 环境中运行,则应该使用等效的库。
概括
对于大多数基于微服务的应用程序,实现 API 网关是有意义的,它充当系统的单个入口点。API 网关负责请求路由、组合和协议转换。它为每个应用程序的客户端提供自定义 API。API 网关还可以通过返回缓存或默认数据来屏蔽后端服务中的故障。