当你选择采用微服务构建自己的程序,则你需要考虑客户端怎样与后端服务交互。对于一个单体应用,仅有一个服务群提供服务(通过负载均衡器实现)。在微服务架构里面,每一个服务都暴漏了一个服务器集群。本篇文章我们讨论它对于客户端通讯的影响和提出通过API网关的解决办法。
背景介绍
首先让我们想象一下一个购物的移动程序。它需要实现产品详情页展示,例如产品信息、库存信息、订单信息、购物车等。例如,下图显示了你将看到的产品信息:
尽管它仅仅是一个手机程序的详情页,它也显示了产品的一些信息。它不仅展示了一些产品的基本信息(名称、描述和价格),也显示以下信息:
1)购物车中的产品数
2)订单历史
3)客户浏览历史
4)低库存预警
5)物流信息
6)推荐信息,包括经常购买的新、购买过之后的推荐和浏览历史推荐;
7)不同的购买选项
如果采用单体程序,客户端仅仅需要一个链接就能够获取所需要的所有信息。负载均衡器返回任何一个应用程序的反馈数据即可。应用程序将会查询数据表,并向客户返回数据。
相反,微服务架构下,产品详情页数据则需要好几个服务群才能够提供。这里是几个拥有产品详情页信息数据的几个服务器服务:
1)购物车服务-负责显示购物车中的产品数量
2)订单服务-订单历史
3)类目服务-提供产品的名字、描述和价格
4)历史服务-用户的浏览历史
5)库存服务-低库存预警
6)物流服务-从物流提供商返回物流花费、到达时间和物流选项
7)推荐服务-建议选项
我们需要决定,客户端怎样获取这些服务。下面让我们讨论一下可能的方案。
客户端直接连接微服务
理论上微服务可以直接连接微服务。每一个微服务都有一个地址,这个地址映射向服务均衡(提供服务的负载)地址。为了获取产品信息,客户端可以直接连接上述的微服务地址。
不幸的,这种方案存在挑战和限制。其一,其存在客户端和微服务之间的错配问题。客户端不得不发起其次独立的请求。在复杂的系统,它需要返回更多的请求。例如,亚马逊在选择页面的时候描述了数百个微服务。如果客户端发起了过多的请求,会导致 互联网的低效,这也不是移动网络的最佳实践。这也会导致客户端的过度复杂。(个人觉得,如果微服务页面较少,这会提升开发效率和网络请求时间)
直连的另一个问题是微服务对外的协议可能不是web友好的。例如,有些服务采用Thrift binary RPC,而有些则采用AMQP 。这两种协议都不是浏览器和防火墙所友好的协议。一个友好的web协议应该采用WebSocket 或者HTTP协议。
另一个缺陷是这种解决方案使得重构微服务变得困难。随着时间改变,我们想改变微服务划分的标准。例如,我们可能将两个微服务合并,或者拆分一个微服务。这种类型的重构将会使得这种方案变得非常困难。
采用API网关
通常,一个较好的方案采用API网关。API网关是一种从前端到后端的唯一路径服务。它类似于面向对象设计的 Facade 模式。API网关将后端的服务进行了包装,同时提供了客户端所需要的API。它还提供其他职责,像权限、监控、负载均衡、缓存、请求转发和管理、静态请求处理等。
以下图展示了API网关适应的架构:
API网关负责请求的路由转发、组合和协议解释。所有来自客户端的请求都需要经过API网关。它将请求匹配到合适的微服务。它通过请求多个微服务或者聚合结果处理请求。它能够在HTTP、WebSocket和不友好的协议之间转换。
API网关也为客户端提供个性化的接口。它为移动客户端提供了粗粒度的API。例如,产品的详情页。例如,他能够为移动客户端提供一个连接,通过此可以获得所有的所需数据。API网关处理请求分为两步:1)向不同的服务请求数据 2)合并数据。
API网关的一个成功例子是Netflix API Gateway。 Netflix流服务包含数百种终端设备,像电视、移动电话、游戏等。Netflix向提供一种统一形式的API。但是,他们发现由于涵盖的设备太多和个性化需求导致这种方案太难实现。现在,他们为每种类型的设备实现一种设备适配,通过API网关做具体适配。一种适配通过调用六、七种服务处理请求。The Netflix API Gateway每天处理数十亿请求。
API网关的优缺点
正如期望的那样,API网关也存在优点和缺点。最大的优势是API网关屏蔽了后端服务的结构。客户可以简单的通信,而不用处理服务之间的关系。API网关为每种客户端提供单独的接口。它减少了客户端和应用程序之间的往返次数。它也简化了客户端的代码。
API网关也有缺陷。它必须是一个开发、设计和部署的高可用组建。API网关成为开发的瓶颈是一种风险。研发必须升级网关服务以便暴露所有的服务。尽可能的轻量化部署API网关非常重要。开发者发版时必须要线性等待。尽管有这些缺点,程序采用API网关将会是有意义的。
个人认为:在程序框架的前期当服务并不是很多的时候,可以尝试采用客户端直连的形式实现,但是前端必须创建前端路由。如果后期服务较多,则尽量采用API路由网关的形式,因为这种情况下可以缩短请求次数、缩短请求路径,此种问题的请求不包含静态文件的处理。
实现API网关
现在我们已经了解了创建API网关的目的和意义了。让我们看一下创建时需要考虑的事情:
性能和伸缩性
仅有很少的公司如Netflix一样伸缩操作和单日处理数十亿请求。但是,API网关的性能和伸缩性非常重要。在一个平台上创建一个异步、非阻塞的IO API网关是由意义的。有多重技术实现API网关的自由伸缩。在JVM上,你可以实现基于NIO框架的程序,类似于 Netty, Vertx, Spring Reactor, or JBoss Undertow。一个流行的费JVM技术是Node.js,它建立在Chrome的javascr上.另一个解决方案是 NGINX Plus.,它提供了生出的伸缩、高性能Web服务和易于创建、配置和编程的反向代理。
NGINX Plus.能够提供管理权限、获得控制、负载请求、缓存处理结果和提供心跳感知和监控。
采用自适应编程模型
API网关通过简单的路由请求到不同的服务处理客户端请求。它通过引用多个后端服务和聚合结果来处理其他服务。一些请求像请求产品详情的请求独立与其他请求。为了最小化请求,API网关需要并行处理请求。有时,不同请求之间存在依赖。AIP网关在处理请求之前,可能首先验证服务请求。类似的,为了获取与客户相关的产品列表信息,可能首先获取用户信息,其次获取相关的产品信息。API组件的例子是 Netflix Video Grid.。
如果采用传统的异步请求代码变下API网关,可能会进行请求地狱。这些代码非常难写、难于理解和易错。写这种代码的一种较好办法是采用反应法以一种清晰的样式书写。适应抽象的例子包括 Future in Scala, CompletableFuture in Java 8, and Promise in JavaScript. There is also Reactive Extensions (also called Rx or ReactiveX)。
服务调用
微服务程序是一个分布式的程序,但是必须提供进程间通信的机制。在进程间通信有两种形式。一种是异步的、基于消息的通信。一些实现采用消息代理例如JMS or AMQP。还有不采用代理的,如 Zeromq(这种消息直接通信)。另一种是直接通信,像HTTP or Thrift。一个系统会用同步或者异步通信。一种形式会有多种的实现形式。因此,API网关需要实现多种通信机制。
服务发现
API网关知道相互通信服务的地址。在传统系统,你可能硬编码地址,但是在现代云服务平台这将是一个巨大的问题。基础服务,像消息代理,可以通过系统变量设置,但是固定一个程序服务地址并不可行。因为一个应用程序服务的地址是动态指定的,同时服务随着伸缩或者更新,地址也可能会发生改变。因此,API网关就像其他客户端程序一样,需要应用系统的服务发现功能:服务端发现和客户端发现。
处理局部报错
另一个你需要关注的问题是局部报错问题。这个问题发生在一个服务调用另一个服务时,导致的原因是服务慢或者服务不可用。API网关永远不要无限制的阻塞服务调用。具体怎么处理依赖于具体的场景和哪个服务失败。例如,如果推荐服务报错,则API网关需要将产品的其他信息返回给客户端,因为这些信息依然对于客户有用。如果产品信息不再服务,则需要向用户返回报错信息。
API网关应该返回在条件允许的情况下,应该返回缓存信息。例如,产品价格不频繁改动,如果产品价格服务不可用,则应该返回缓存中的产品价格。数据应该缓存在API服务器内存或者第三方内容中如Reddis或者memcached。通过返回默认信息或者缓存信息,API网关保证系统错误不影响用户体验。
Netflix Hystrix 是一个有用远程服务引用库。Hystrix 采用调用超时策略。它采用断路模式,即阻止客户端等待调用不反应的服务。如果某个服务发生错误的次数超过阈值,则Hystrix 启动断路并不允许客户端继续调用该服务。Hystrix定义了一个包括返回调用机制,报错后允许你获取缓存或者默认数据。
总结
对于大多数的微服务程序,采用API网关(作为程序的单一访问点)是有意义的。API网关负责请求路由、组合请求结果和协议解析。它为每种程序的客户端提供的单独的接口。它通过获取内存或者默认值可以掩盖后端错误。但是其路由的构建、结果的合并将是核心。