1. 介绍
当我们使用微服务构建应用时, 需要考虑客户端如何和应用通信. 对于单体应用而言, 只有一组实例(一般是负载均衡加上多个应用副本); 而在微服务架构下, 每个服务都可能有一组实例. 在本篇文章中, 我们将看到这如何影响客户端和服务的通信, 将看到如何应用 API 网关解决问题.
假设我们要开发一个原生的手机购物应用, 有一个页面要展示详细的商品信息, 展示效果如下图所示:
除了展示商品的基本信息(像名称, 描述和价格等), 还包含以下信息:
A. 购物车中的商品数
B. 历史订单
C. 用户评价
D. 低库存告警
E. 物流选项
F. 智能推荐
G. 替代产品
对于单体应用而言, 手机客户端只需要发送一次请求(像: GET API.company.com/productdetails/productId), 负载均衡模块将请求分发到应用实例, 应用查询不同的数据表获取数据, 再将响应返回给客户端.
对于微服务应用而言, 商品详情页需要的数据由多个微服务拥有, 例如:
购物车服务, 提供购物车中的商品数量;
订单服务, 提供历史订单;
商品分类服务, 提供商品名称, 图片, 价格等商品基本信息;
评价服务, 提供用户评价;
库存服务, 提供低库存警告;
物流服务, 提供物流选择, 到货日期以及物流价格等;
推荐服务, 提供智能推荐的关联商品.
我们需要考虑手机客户端如何访问这些服务.
2. 客户端直接访问微服务
理论上说, 客户端可以直接访问微服务. 每个微服务都会发布一个公共访问入口(像: https://serviceName.api.company.name), 这个链接指向微服务的负载均衡模块. 为了获取商品详情的各种数据, 手机客户端需要访问多个相关的微服务.
不幸的是, 由客户端直接访问微服务有很多限制和挑战. 其中一个问题是客户端的需求和微服务提供的接口不匹配. 在商品详情的例子中, 客户端需要访问多个微服务, 多个微服务组合的才是客户端想要的. 应用越复杂, 需要访问的微服务就越多, Amazon 展示商品详情时需要访问上百个服务. 客户端通过互联网和移动网络访问这么多服务显得效率低下, 还会造成客户端代码特别复杂.
客户端直接访问微服务的另外一个问题是, 有些微服务提供的接口并不是 web 友好的, 一个服务可能使用 Thrift 的 RPC, 而另外一个服务可能使用 AMQP 的消息机制; 这些服务都不是防火墙友好的, 并不是互联网上的最佳实践. 一个应用应该在防火墙外使用 HTTP 和 WebSocket 这些对 Web 友好的通信协议.
最后一个问题是, 客户端直接访问微服务会导致微服务重构困难. 随着时间的推移, 我们可能会对原有服务进行拆分或合并等重构工作, 而如果客户端直接访问微服务, 这些重构工作会不可避免的影响到客户端, 从而使得重构变得异常困难.
由于以上原因, 客户端直接访问微服务不可行.
3. 使用 API 网关
API 网关是一种较好的客户端访问微服务的方式. API 网关是系统的单一访问入口, 就像面向对象设计模式中的 Façade 模式, 它隐藏了应用的内部结构, 为不同类型的客户端定制不同接口. API 网关还能提供鉴权, 监视, 负载均衡, 缓存, 协议转换和管理等功能. 下图展示了本例中 API 网关的结构:
API 网关负责请求路由, 响应组装和协议转换. 所有的客户端请求先到 API 网关, 网关将请求路由到对应的微服务; 在客户端的一次请求中, 网关可能调用多个微服务, 再将多个返回结果组装后返回给客户端. API 网关还能实现 Web 协议 (Http,WebSocket) 与其他协议的转换.
API 网关能为每一类客户端定制接口. 比如它能为手机客户端定制商品详情的接口(/productdetails?productid=xxx), 手机客户端调用这个接口获取商品详情页的所有信息.
Netflix 的 API 网关是个典型的例子, Netflix 的流视频服务在电视, 电视盒子, 智能手机, 游戏机等上百种不同的设备上都能使用. 最初, Netflix 努力设计能适用于所有设备的统一接口, 后来发现不同设备对接口的需求差异太大. 今天, Netflix 通过 API 网关使用不同的设备适配器为每种设备提供不同的接口, 每种设备适配器调用 6 到 7 个微服务. 每天 Netflix 的 API 网关处理上百万次的客户端请求.
4.API 网关的优点和缺点
最大的优点是 API 网关封装了应用的具体实现, 简化了客户端与应用之间的通信, 极大减少了客户端的代码量.
缺点是你需要开发, 部署, 管理一个高可靠的 API 网关. 由于所有的请求都先到 API 网关, 它很可能变成整个系统的瓶颈; 另外 API 网关要足够轻量级, 要方便升级以反映微服务的变化. 虽然有上述缺点, API 网关对大多数现实中的应用来说还是一种可行的微服务实现方案.
5. 实现 API 网关
现在我们来看实现 API 网关时需要重点考虑的几个问题:
性能和伸缩性
全世界只有大约 100 个像 Netflix 那样规模的应用, 每天需要处理百万级的服务请求. 你的应用规模可能没有那么大, 可 API 网关的性能和伸缩性依然很重要. 因此, 在支持异步调用, 非阻塞 I/O 等高性能特性的平台上构建 API 网关就很重要. 在 JVM 之上, 你可以使用基于 NIO 的框架, 像 Netty,Vertx,Spring Reactor 或者 JBoss Undertow; 另外一种非 JVM 的选项是基于 Chrome 的 JS 引擎 Node.JS; 还有一种选择是 Nginx Plus.Nginx Plus 可作为成熟的, 可伸缩的, 高性能的 Web 服务器和反向代理服务器, 部署简单, 配置和编程方便, 并能支持身份鉴别, 访问控制, 负载均衡, 服务健康检查等功能.
使用响应式编程模型
对有些服务请求, API 网关简单转发; 对另外一些服务请求, API 网关需要调用几个服务, 并组装多个服务的响应. 对于某些服务请求, 像之前讲到的商品详情, 需要调用多个互相独立的服务; 为了缩短服务响应时间, API 网关应该支持并发多个服务请求; 而有些时候, 服务之间可能有依赖, 比如需要先进行身份鉴别再调用服务. 类似的, 要展示用户心愿列表中的商品详情, 需要先获取用户的心愿列表, 再根据心愿列表获取每件商品的详细信息. 另外一个有趣的服务组合示例是 Netflix 的视频网格.
使用传统异步调用方式编写服务组合的代码会是一场灾难, 代码会变得杂乱, 不易理解, 容易出错. 一个更好的方式是使用响应式编程编写声明式代码, 现在很多语言都支持响应式的抽象, 像 Scala 中的 Future,Java8 中的 CompletableFuture,JaveScript 中的 Promise. 还有一些响应式的扩展(也叫 Rx 或者 ReactiveX), 这最早是微软为. Net 平台开发的, 后来 Netflix 为他们的 API 网关开发了 RxJava, 现在针对浏览器和 Node.JS 有了 RxJS. 使用响应式编程可以让你书写简单高效的 API 网关代码.
服务调用
基于微服务的应用是分布式系统, 必须使用进程间通信的机制. 进程间通信主要有两大类, 其中一种基于消息的异步调用, 使用类似 JMS,AMQP 等消息中间件, 或者像 Zeromq 这种不需要消息中间件的服务间直接通信; 另外一种是像 Http 和 Thrift 之类的同步调用. 这两种通信机制, 在应用中一般都会用到, 甚至会组合使用. 因此, 使用 API 网关实现微服务架构时, 需要支持多种服务间通信机制.
服务发现
API 网关需要知道跟它通信的所有微服务的访问入口 (IP 地址加端口号). 在传统的单体应用中, 直接将 IP 地址和端口号硬编码到应用中就可以, 而在基于微服务的应用中就不行了. 提供基础设施的服务(像消息中间件) 还可以通过配置环境变量事先确定, 而应用的其他服务的访问入口是动态变化的. 另外, 由于自动伸缩特性和服务实时更新, 构成服务的实例的集合也是动态变化的. 因此, API 网关像其他服务客户端一样, 需要使用系统的服务发现机制(服务端发现或者客户端发现), 后续会有文章专门描述服务发现. 需要指出的是, 如果系统采用客户端服务发现机制, 那 API 网关必须能查询服务注册表(包含所有服务实例和访问入口的数据库).
处理部分失败
另外一个必须考虑的问题是部分失败. 在分布式系统中, 一个服务调用另外一个反应迟钝或者不可达的服务时, 就会出现部分失败的情况. 如何处理服务调用失败要看具体情况, 比如说, 在展示商品详情时调用智能推荐服务失败, API 网关就应该展示其他商品信息, 因为这些信息对用户依然有价值, 智能推荐可以是空, 或者是默认的 top10 最受欢迎商品; 但是, 如果调用商品信息服务失败, API 网关就应该返回错误.
API 网关也能返回缓存数据. 比方说, 既然商品价格不会经常变化, 就可以在调用价格服务失败时返回之前缓存的价格数据; 可以使用 API 网关自身缓存, 也可以使用像 Redis 或 Memcached 等第三方缓存. 通过返回默认数据或者缓存数据, API 网关可以确保部分系统失败不影响用户体验.
NetflixHystrix 对编写远程服务调用的代码而言是一个特别有用的库, 它会终止超过阈值的服务调用. 它实现了一种断路器模式, 停止客户端对没有响应的服务做无意义的等待; 如果某一个服务出错的概率达到阈值, Hystrix 会将该服务阻断, 这样在一定时间内, 所有对该服务的请求会立即返回错误. Hystrix 允许你为错误设置回调操作, 在回调操作中可以读取缓存或者是返回默认值. 如果你的应用使用 JVM, 一定要考虑使用 Hystrix; 如果你的应用使用没有 JVM 的环境, 你也应该考虑找一个类似的库.
6, 总结
对于大多数基于微服务的架构而言, 使用 API 网关是有意义的, 它可以作为应用的单一访问入口. API 网关负责请求路由, 响应组装和协议转换, 它能为每一类客户端定制接口; API 网关还能标注出错的服务, 并返回默认值或者缓存数据. 在接下来的章节中, 我们将详细讨论服务间通信.
来源: http://www.jianshu.com/p/1c07fda2a5ab