当选择将应用程序构建为一组微服务时,需要确定应用程序的客户端如何与微服务进行交互。 对于单体应用程序而言,只有一组(通常是负载平衡的)endpoint。 但是,在微服务架构中,每个微服务都公开了一组通常是细粒度的endpoint。 这会如何影响客户端到应用程序的通信呢。
假设我们正在为购物应用程序开发本机移动客户端。 我们可能需要实现一个产品详细信息页面,该页面显示有关给定产品的信息。例如,下图显示了在应用程序中滚动产品详细信息时将看到的内容。
即使这是智能手机应用程序,“产品详细信息”页面也会显示很多信息。例如,不仅有基本的产品信息(例如名称,描述和价格),而且此页面还显示:
使用整体应用程序体系结构时,移动客户端将通过对应用程序进行单个REST调用(GET api.company.com/productdetails/productId)来检索此数据。负载平衡器将请求路由到N个相同的应用程序实例之一。然后,应用程序将查询各种数据库表,并将响应返回给客户端。
相反,在使用微服务架构时,产品详细信息页面上显示的数据由多个微服务拥有。以下是一些可能的微服务,这些微服务拥有在示例产品详细信息页面上显示的数据:
如下图所示:
我们需要确定移动客户端如何访问这些服务。 怎么做呢?我们可以用下面这些选项:
客户端到微服务的直接通信
从理论上讲,客户端可以直接向每个微服务发出请求。每个微服务都有一个public endpoint(https://serviceName.api.company.name)。该URL将映射到微服务的负载平衡器,该负载平衡器在可用实例之间分配请求。要检索产品详细信息,移动客户端将向上面列出的每个服务发出请求。
不幸的是,此选项存在挑战和局限性。一个问题是客户端需求与每个微服务公开的细粒度API之间的不匹配。在此示例中,客户端必须发出七个单独的请求。在更复杂的应用程序中,可能需要做更多的工作。例如,亚马逊描述了如何在呈现其产品页面时涉及数百种服务。尽管客户端可以通过LAN发出许多请求,但在公共Internet上可能效率太低,并且在移动网络上肯定是不切实际的。这种方法还使客户端代码更加复杂。
客户端直接调用微服务的另一个问题是,有些服务可能使用不支持Web的协议。一个服务可能使用Thrift二进制RPC,而另一服务可能使用AMQP消息传递协议。两种协议都不是特别适合浏览器或防火墙的协议,并且最好在内部使用。应用程序应在防火墙外部使用HTTP和WebSocket之类的协议。
这种方法的另一个缺点是很难重构微服务。随着时间的流逝,我们可能想更改将系统划分为服务的方式。例如,我们可以合并两个服务或将一个服务拆分为两个或多个服务。但是,如果客户直接与服务进行通信,那么执行这种重构可能会非常困难。
由于这些问题,客户端很少直接与微服务进行通信。
使用API Gateway
通常,更好的方法是使用所谓的API gateway即api网关。 API网关是服务器,是系统的单个入口点。 它与面向对象设计中的Facade模式相似。 API网关封装了内部系统架构,并提供了针对每个客户端量身定制的API。 它可能还具有其他职责,例如身份验证,监视,负载平衡,缓存,请求重构和管理以及静态响应处理。
下图显示了API网关通常如何适应体系结构
API网关负责请求路由,组合和协议转换。来自客户端的所有请求都首先经过API网关。然后,它将请求路由到适当的微服务。 API网关通常会通过调用多个微服务并汇总结果来处理请求。它可以在内部使用的HTTP和WebSocket之类的Web协议与Web不友好的协议之间转换。
API网关还可以为每个客户端提供自定义API。它通常为移动客户端提供粗粒度的API。例如,考虑产品详细信息场景。 API网关可以提供一个端点(/ productdetails?productid = xxx),该端点使移动客户端可以通过单个请求检索所有产品详细信息。 API网关通过调用各种服务(产品信息,建议,评论等)并组合结果来处理请求。
Netflix API Gateway是API网关的一个很好的例子。 Netflix流媒体服务可在数百种不同的设备上使用,包括电视,机顶盒,智能手机,游戏系统,平板电脑等。最初,Netflix尝试为其流媒体服务提供一种通用的API。但是,他们发现,由于设备种类繁多以及其独特的需求,它不能很好地工作。后来,他们使用API网关,该网关通过运行特定于设备的适配器代码为每个设备提供量身定制的API。适配器通常通过平均调用六到七个后端服务来处理每个请求。 Netflix API网关每天处理数十亿个请求。
凡事无绝对,使用API网关既有优点也有缺点。使用API网关的主要好处是它封装了应用程序的内部结构。客户端不必调用特定的服务,而只是与网关进行对话。 API网关为每种客户端提供特定的API。这减少了客户端与应用程序之间的往返次数。它还简化了客户端代码。
API网关也有一些缺点。因为它本身是一个必须进行开发,部署和管理的高可用性组件。 API网关也有成为开发瓶颈的风险。开发人员必须更新API网关才能公开每个微服务的端点。重要的是,更新API网关的过程应尽可能轻巧。否则,开发人员将被迫排队等待更新网关。尽管有这些缺点,但是对于大多数实际应用程序而言,使用API网关还是有意义的。
现在,我们已经了解了使用API网关的动机和取舍,现在让我们看看实现api gateway需要考虑的各种设计问题。
性能和可伸缩性
只有少数几家公司拥有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组成的另一个有趣示例是Netflix Video Grid。
使用传统的异步回调方法编写API组合代码会很快导致您陷入困境。该代码将混乱,难以理解且容易出错。更好的方法是使用反应式方法以声明式编写API网关代码。反应式抽象的示例包括Scala中的Future,Java 8中的CompletableFuture和JavaScript中的Promise。还有Reactive Extensions(也称为Rx或ReactiveX),它最初是由Microsoft为.NET平台开发的。 Netflix为JVM创建了RxJava,专门用于其API网关。还有用于JavaScript的RxJS,它可以在浏览器和Node.js中运行。使用反应式方法将使您能够编写简单而有效的API网关代码。
服务调用
基于微服务的应用程序是一个分布式系统,必须使用进程间通信机制。进程间通信有两种样式。一种选择是使用基于消息的异步机制。一些实现使用消息代理,例如JMS或AMQP。诸如Zeromq之类的其他公司则是没有broker的,服务之间直接通信。进程间通信的另一种形式是同步机制,例如HTTP或Thrift。系统通常会同时使用异步和同步样式。它甚至可以使用每种样式的多个实现。因此,API网关将需要支持多种通信机制。
服务发现
API网关需要知道与之通信的每个微服务的位置(IP地址和端口)。在传统的应用程序中,您可能需要对位置进行硬连线,但是在现代的基于云的微服务应用程序中,这是一个不小的问题。基础结构服务(例如消息代理)通常将具有静态位置,可以通过OS环境变量指定该位置。但是,确定应用程序服务的位置并非易事。应用程序服务具有动态分配的位置。而且,服务的实例集会由于autoscaling 和upgrades而动态更改。因此,API网关与系统中的其他任何服务客户端一样,需要使用系统的服务发现机制:服务器端发现或客户端端发现。如果系统使用客户端发现,则API网关必须能够查询服务注册表,该注册表是所有微服务实例及其位置的数据库。这一部分的具体实现这里不做展开,可以参考博客:使用zookeeper实现服务注册与发现
处理部分故障
实现API网关时必须解决的另一个问题是部分失败的问题。每当一个服务调用另一个响应缓慢或不可用的服务时,在所有分布式系统中都会出现此问题。 API网关绝不应无限期地阻塞等待下游服务。但是,它如何处理故障取决于特定的情况以及哪个服务出现故障。例如,如果推荐服务在产品详细信息场景中无响应,则API网关应将其余产品详细信息返回给客户端,因为它们仍然对用户有用。这些建议可以是空的,也可以由例如前十名的硬列表取代。但是,如果产品信息服务无响应,则API Gateway应该将错误返回给客户端。
如果可用,API网关也可以返回缓存的数据。例如,由于产品价格很少变化,因此如果定价服务不可用,API网关可以返回缓存的定价数据。数据可以由API网关本身缓存,也可以存储在外部缓存中,例如Redis或Memcached。通过返回默认数据或缓存的数据,API网关可确保系统故障不会影响用户体验。
Netflix Hystrix是一个非常有用的库,用于编写调用远程服务的代码。 Hystrix使超出指定阈值的呼叫超时。它实现了断路器模式,可阻止客户端不必要地等待无响应的服务。如果服务的错误率超过指定的阈值,则Hystrix将使断路器跳闸,并且所有请求将在指定的时间内立即失败。 Hystrix允许您定义请求失败时的回退操作,例如从缓存中读取或返回默认值。如果您使用的是JVM,则绝对应该考虑使用Hystrix。并且,如果您在非JVM环境中运行,则应使用等效的库。
对于大多数基于微服务的应用程序而言,实现一个API网关是有意义的,该网关充当系统的单个入口点。 API网关负责请求路由,组合和协议转换。 它为应用程序的每个客户端提供了自定义API。 API网关还可以通过返回缓存或默认数据来掩盖后端服务中的故障。
p.s 本篇博客参考nginx官方网站