Chris Richardson微服务翻译:构建微服务之使用API网关

Chris Richardson 微服务系列翻译全7篇链接:

原文链接:Building Microservices: Using an API Gateway


介绍

假设我们为一个商品应用开发一个移动APP,我们应该提供一个产品详情页来展示指定产品的信息。Amazon Android 应用在商品详情页展示的内容,如下图所示:

尽管只是移动APP,商品详情页依然展示给我们很多信息,不仅包括基本信息(名称、描述、价格),还包含如下内容:

  • 购物车中的商品数
  • 历史订单记录
  • 买家评价
  • 低库存预警
  • 送货选项
  • 推荐:包括与此商品一起购买的其他商品、购买该商品的顾客还买的其他商品、购买该商品的顾客还看过其他的商品
  • 其他购物选项

使用单体应用架构时,移动APP通过单一的 REST 请求(GET api.company.com/productdetails/productId)来获取展示的数据。负载均衡会将请求路由到多个相同实例的其中一个,然后程序查询各种数据库表,返回数据给客户端。

对应如果采用微服务架构,展示在产品详情页的数据会来自不同的微服务上。下面列举一些微服务对应的展示数据:

  • 购物车服务:购物车中的商品数
  • 订单服务:订单记录
  • 目录服务:商品基本信息,如名称、图片和价格
  • 评论服务:用户评价
  • 库存服务:低库存预警
  • 配送服务:送货选项、期限和费用
  • 推荐服务:推荐商品

客户端如何访问这些服务,让我们看看下面的方法。

客户端与服务端直接通信

理论上客户端可以直接请求每个微服务,每个微服务都有一个公开的节点(https://serviceName.api.company.com),该 URL 映射到负载均衡,然后被分发到可用的实例上处理,为了获取产品详情,移动客户端需要向上述每个服务发送请求。

遗憾的是,这种方法存在挑战和局限:

1)客户端需求和每个微服务暴露出的细粒度 API 不匹配。本例中客户端需要发送7个不同的请求,在一个复杂的应用中请求数甚至还要更多,例如亚马逊在显示他们的产品页面时会调用数百个服务。这种方法还使得客户端代码非常复杂。

2)一些服务使用的协议对 web 并不友好。一个服务可能使用 Thrift 的二进制 RPC,而另一个服务可能使用 AMQP 消息协议。这些协议不是浏览器和防火墙友好的,最好在内部使用。而防火墙之外,应用程序最好使用 HTTP 或 WebSocket 之类的协议。

3)这种方法会使得微服务难以重构。随着时间的推移,我们可能要重新规划、合并或拆分微服务,如果客户端直接与微服务通信的话,对这些微服务进行重构变得异常困难。

正是这些原因,采用客户端直接调用微服务的方式并不明智。

使用 API 网关

通常更好的方式是使用 API 网关,API 网关是提供系统唯一入口的服务器,他和设计模式中的 外观模式 类似:API 网关封装内部系统架构,并向客户端提供 API。它还可能负责诸如 用户验证、监控、负载均衡、缓存、请求管理、静态响应处理等功能。下图展示了适应 API 网关的架构:

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 网关有优势也有不足。

最大优点是:
1)它封装了应用的内部结构,客户端只需要简单的与 API 网关交互即可,而不用直接调用每个服务。
2)API 网关为不同的客户端提供定制的 API,减少了客户端和应用间的交互次数,并大大简化了客户端的代码。

API 网关也有其不足:
1)它本身增加了一个需要开发、部署和维护的高可用组件。
2)API 网关有时候变成了开发的瓶颈。开发者为了暴露新的微服务必须更新 API 网关。API 网关的更新流程要尽可能的简单,否则,开发人员不得不排队等待。尽管它有这些不足,但对于大部分的应用程序而言,使用 API 网关是合理的。

实现一个 API 网关

目前我们讨论了 API 网关的动机和一些优缺点。下面我们看一些需要考虑的一些设计问题。

性能与扩展性

只有少数公司拥有 Netflix 这样的规模,每天需要处理数十亿的请求。对于大多数程序而言,API 网关的性能和可扩展性是非常重要的。因此,API 网关构建在一个支持异步、IO 非阻塞的平台上是合理的。有多种不同的技术可参考:JVM 上,基于 NIO 的框架,比如 Netty、Vertx、 Spring Reactor 或 JBoss Undertow 等。一个流行的选项是 Node.js,他是构建于 Chrome JS 引擎的平台。另一个选项是 NGINX Plus,他提供了成熟、可扩展、高性能的 web 服务器和一个易于部署的、可配置可编程的反向代理。NGINX Plus 可以管理身份验证、权限控制、负载均衡、缓存以及应用级别的健康检查及监控。

使用响应式编程模型

API 网关将请求路由到相应的多个微服务并合并处理结果,对于一些请求,例如产品详情页,后端对应的服务是彼此独立的,为了减少响应时间,API 网关应并行处理这些请求。然而有时候,请求之间是有依赖关系的,在请求微服务之前,API 网关需要调用身份验证服务来验证请求的合法性。类似的,获取用户心愿单上的产品信息时,API 网关需要先获取包含这些信息的客户资料,然后再去获取信息的详情。另一个有趣的例子就是:Netflix Video Grid

使用传统的异步回调方式来写 API 组合代码会把你带入地狱。代码会变的混乱、难以理解且容易出错。更好的方式是使用响应式方法,以一种声明式样式编写 API 网关代码。例如:Scala中的 Future 、Java 8中的 CompletableFuture 以及JavaScript中的 Promise,还有微软为.NET开发的 Reactive Extensions。Netflix 为他们的 API 网关创建了 RxJava for JVM,此外还有 RxJS for JavaScript,既可以在浏览器中运行,也可以在Node.js 中运行。响应式风格将帮助你写出简洁高效的 API 网关代码。

服务调用

微服务架构是一个进程间通信的分布式系统, 有两种进程间通信方式:一种是采用异步、基于消息传递的机制。例如:JMS 或 AMQP 这样的消息组件;另一种是使用 HTTP 或 Thrfit 这种同步机制进行通信。通常,一个系统会同时使用同步和异步方式,甚至还会使用同一类型的多种实现。总之,API 网关需要支持不同的通信机制。

服务发现

API 网关需要知道和他通信的每个微服务的地址(IP和端口号)。传统应用中,可以使用硬编码的方式,但是在现代基于云的微服务应用中,这不是一个容易解决的问题。基础架构服务(例如:消息组件)通常会有一个静态地址,可以在环境变量中指定。然而,获取一个微服务地址就不是简单的事了,微服务的地址是动态分配的,一组服务实例可能因为自动扩展或升级而动态的变化。因此 API 网关需要服务发现机制,可以是服务器端发现,或者是客户端发现(后面的文章会详细介绍服务发现的问题)。如果使用客户端发现的话,API 网关应该能查询服务注册中心,这是一个包含所有微服务实例及其地址的数据库。

处理局部故障

局部故障在分布式系统中很常见,当一个服务调用另一个服务,当后者响应慢或不可用时就出现了这个问题。API 网关不能因为无限期的等待下游服务而阻塞。不过,如何处理失败取决于应用场景或具体哪个服务失败,例如:产品详情页,推荐服务挂了,那么 API 网关应该返回其他产品信息,保障产品对用户仍然可用,推荐内容可为空或使用固定的 Top 10 列表取代。不过,如果产品服务挂了,那么 API 网关应该返回客户端错误信息。

API 网关也可以缓存返回的数据。例如:产品价格变动不频繁,当价格服务不可用时,API 网关可以返回缓存的价格数据。数据可以是 API 网关自己缓存,也可以使用 Redis 或 Memcached之类的外部缓存。通过返回默认值或缓存值,API 网关保证局部故障不会影响用户体验。

Netflix Hystrix 在编写代码调用远程服务时是非常有用的,Hystrix 会标记超过特定阀值的调用为超时,他还实现了『熔断器』模式来防止对无响应服务的更多请求,如果一个服务的出错率超过了特定阀值,那么 Hystrix 会触发熔断器,所有请求会快速失败并持续一段时间。Hystrix 允许用户定义请求失败时的 fallback 操作,例如:读取缓存或返回默认值。如果你在使用 JVM ,那么应该考虑使用 Hystrix,如果使用的是非 JVM 环境,那么可以使用一个功能相同的库。

总结

对于大部分基于微服务的应用,实现 API 网关作为应用的单一入口是明智的。API 网关负责请求路由、聚合、协议转换。为每个客户端提供特定的 API,还可以通过返回默认值或缓存值来处理后端服务的调用失败。下篇文章,我们将讨论服务间的通信。

posted @ 2017-11-13 18:54 butterfly100 阅读(...) 评论(...) 编辑 收藏