系统的门面API网关应该怎么设计?

2023.10.27


API网关的关键在于性能和可扩展性的实现。为了提高网关的性能,可以采用多路I/O复用模型和线程池并发处理请求。而为了提升网关的可扩展性,可以使用责任链模式来组织和管理过滤器,以便轻松添加、移除或修改过滤器,以满足不同的需求。

随着自己的电商网站知名度越来越高,系统迎来了一些“不速之客”,在凌晨的时候,系统中的搜索商品和用户接口的调用量会急剧上升,持续一段时间之后又回归正常。

这种搜索请求的特点是它们来自一组特定的设备。当我在搜索服务上引入了设备ID的限流策略后,凌晨的高峰搜索请求得到了控制。然而,不久之后,用户服务开始受到大量爬虫请求,请求用户信息,商品服务也开始受到爬虫请求,请求商品信息。因此,我被迫在这两个服务上添加了相同的限流策略。但这会引发一个问题:相同的策略需要在多个服务中重复实现,无法实现代码的重用。如果其他服务也面临类似问题,那么就需要复制代码,这显然不是一个好的做法。

作为一名Java程序员,我的第一反应是将限流功能独立成一个可重用的JAR包供这三个服务引用。但问题是,我们的电商团队不仅使用Java,还使用PHP、Go等多种编程语言开发服务。对于不同编程语言的服务,无法直接使用Java的JAR包。这就是引入API网关的原因。API网关可以提供一种跨语言的解决方案,让不同编程语言的服务都能够轻松地使用相同的限流策略。

API 网关起到的作用

API网关(API Gateway)并非单一的开源组件,而是一种架构模式。它将一些服务共享的功能整合在一起,独立部署为一个独立的层级,用于解决服务治理问题。你可以将其视为系统的前门,负责对系统的流量进行统一管理。从我的角度来看,API网关可以分为两种主要类型:入口网关和出口网关。

  • 入口网关:入口网关位于系统内部,用于处理外部请求进入系统的流量。它可以执行一些重要的任务,如路由请求到正确的服务、身份验证、鉴权、限流、请求转换和响应转换等。入口网关有助于管理系统对外部的可见性,并提供一致的接口。
  • 出口网关:出口网关则位于系统内部,用于处理系统内部服务调用对外部系统的流量。它可以执行任务如负载均衡、缓存、重试、错误处理等,以确保内部服务之间的通信可靠性和性能。出口网关通常用于隔离内部服务和外部系统,同时提供一致的接口。

这两种类型的API网关可以帮助管理系统的边界,提供了流量控制、安全性、性能优化等方面的解决方案,以确保系统的稳定性和可扩展性。

入口网关是我们经常使用的网关种类,它部署在负载均衡服务器和应用服务器之间,主要有几方面的作用。

  • API网关为客户端提供了一个单一的入口地址,它可以根据客户端的请求,动态将请求路由到不同的业务服务,并执行必要的协议转换。在您的系统中,不同微服务可能以不同的协议对外提供服务:一些是HTTP服务,一些已经改造为RPC服务,而一些可能仍然是Web服务。API网关的作用是将这些服务的部署地址和协议的细节对客户端屏蔽,从而为客户端调用带来便利。客户端只需与API网关交互,而无需直接与各种不同协议的服务通信,使整体调用过程更加简便。

  • 另一方面,在 API 网关中,我们可以植入一些服务治理的策略,比如服务的熔断、降级、流量控制和分流等等。
  • 再有,客户端的认证和授权的实现,也可以放在 API 网关中。
  • 另外,API 网关还可以做一些与黑白名单相关的事情,比如针对设备 ID、用户 IP、用户 ID 等维度的黑白名单。
  • 最后,在 API 网关中也可以做一些日志记录的事情,比如记录 HTTP 请求的访问日志,分布式追踪系统时,提到的标记一次请求的 requestId 也可以在网关中来生成。

图片图片

出口网关的功能相对较简,但在系统开发中也发挥着重要的作用。我们的应用经常需要依赖外部的第三方系统,比如第三方账户登录或支付服务。在这种情况下,我们可以在应用服务器和外部系统之间部署出口网关,用于统一处理对外部API的认证、授权、审计和访问控制等任务。这样可以帮助我们更好地管理和保护与外部系统的通信,确保系统的安全性和合规性。

图片图片

API 网关要如何实现

了解了API网关的作用后,下一步是关注在实施API网关时需要注意的关键方面,以及了解一些常见的开源API网关解决方案。这将使您在实际工作中,不论是考虑自主开发API网关还是使用现有的开源实现,都能更自如地做出决策。

在开发API网关时,首要考虑的是其性能。这非常重要,因为API入口网关需要处理所有来自客户端的流量。举个例子来说,假设业务服务的处理时间是10毫秒,而API网关的处理时间是1毫秒,那么相当于每个接口的响应时间都要增加10%,这将对性能产生巨大的影响。性能的关键通常在于I/O模型的选择,这里只是举一个例子来说明I/O模型对性能的影响

在Netflix开源的API网关Zuul的1.0版本中,采用的是同步阻塞I/O模型。整体系统可以看作是一个Servlet,它接收用户的请求,然后在网关中执行配置的认证、协议转换等逻辑,最后调用后端服务获取数据并返回给用户。

在Zuul 2.0中,Netflix团队进行了重大改进。他们将原本的Servlet转变为了一个Netty服务器,采用了I/O多路复用模型来处理传入的I/O请求。同时,他们还对之前的同步阻塞方式调用后端服务进行了改进,采用了非阻塞方式的Netty客户端调用。经过这些改进,Netflix团队测试发现性能提升了约20%。

API网关的功能可以分为两种类型:一种是可以预先定义和配置的,如黑白名单设置、接口动态路由等;另一种需要根据具体业务来定义和实现。因此,在API网关的设计中,关注扩展性是非常重要的。这意味着您可以随时在执行链路上添加自定义逻辑,也可以随时移除不需要的逻辑,实现所谓的热插拔功能。这种灵活性允许根据不同的业务需求自定义API网关的行为,使其更具通用性和适应性。

通常情况下,我们可以将每个操作定义为一个过滤器(filter),然后使用责任链模式将这些过滤器串起来。责任链模式允许我们动态地组织这些过滤器,使它们之间解耦。这意味着我们可以随时添加或移除过滤器,而不会对其他过滤器产生影响。这种模式使得我们可以灵活地定制API网关的行为,根据具体需求来构建过滤器链,无需担心过滤器之间的相互影响。

Zuul采用责任链模式来处理请求。在Zuul 1中,它将过滤器定义为三种类型:预处理过滤器(pre routing filter)、路由过滤器(routing filter)和后处理过滤器(after routing filter)。每个过滤器都定义了执行的顺序,在注册过滤器时,它们会按照顺序插入到过滤器链中。这意味着当Zuul接收到请求时,它会按照顺序依次执行过滤器链中插入的过滤器。这种方式使得可以在请求处理过程中轻松应用各种过滤器,以实现不同类型的操作。

图片图片

此外,还需要特别注意,为了提高网关对请求的并行处理能力,通常会使用线程池来同时处理多个请求。然而,这也带来了一个问题:如果商品服务响应缓慢,导致调用商品服务的线程被阻塞,无法释放,随着时间的推移,线程池中的线程可能会被商品服务所占用,从而影响其他服务。因此,我们需要考虑针对不同的服务采取线程隔离或保护的策略。从我看来,有两种主要思路来应对这个问题:

如果你后端的服务拆分得不多,可以针对不同的服务,采用不同的线程池,这样商品服务的故障就不会影响到支付服务和用户服务了;

在线程池内部可以针对不同的服务甚至不同的接口做线程的保护。比如说,线程池的最大线程数是 1000,那么可以给每个服务设置一个最多可以使用的配额。

通常情况下,服务的执行时间应该非常快,通常在毫秒级别。使用线程后,线程应迅速释放回线程池,以供后续请求使用。同时,系统中运行的执行中线程数量应该保持较低水平,不会大量增加,这样可以确保服务或接口的线程配额不会对正常执行产生影响。

然而,一旦发生故障,某个接口或服务的响应时间变慢,可能会导致线程数大幅增加。但由于有线程配额的限制,这不会对其他接口或服务产生负面影响。这种方式有助于隔离故障,确保故障不会蔓延到整个系统。

你在实际应用中也可以将这两种方式结合,比如说针对不同的服务使用不同的线程池,在线程池内部针对不同的接口设置配额。

如何在你的系统中引入 API 网关

目前,我们的电商系统已经经历了服务化改造,其中在服务层和客户端之间引入了一个薄薄的Web层。这个Web层承担两项主要任务:首先,它负责对服务层接口数据进行聚合。举例来说,商品详情页面的接口可能会聚合多个服务接口的数据,包括商品信息、用户信息、店铺信息以及用户评论等。其次,Web层需要将HTTP请求转换为RPC请求,并对前端的流量进行一些限制,例如为某些请求添加设备ID的黑名单等。

因此,在进行改造时,我们可以首先将API网关从Web层中独立出来,将协议转换、限流、黑白名单等功能移到API网关中进行处理,形成一个独立的入口网关层。而对于服务接口数据聚合的操作,

通常有两种解决思路:一种是独立出一组网关,专门负责流量聚合和超时控制,我们通常称其为流量网关;

另一种是将接口聚合操作提取到一个独立的服务层中。这样,服务层可以大致分为原子服务层和聚合服务层。

在我看来,接口数据聚合属于业务操作,与其将其置于通用的网关层实现,不如将其放在更贴近业务的服务层中实现。因此,我更倾向于第二种方案。这种方式有助于更好地划分职责,使系统更模块化和可维护。

图片图片

同时,我们可以在系统和第三方支付服务,以及登陆服务之间部署出口网关服务。原先,你会在拆分出来的支付服务中完成对于第三方支付接口所需要数据的加密、签名等操作,再调用第三方支付接口完成支付请求。现在,你把对数据的加密、签名的操作放在出口网关中,这样一来,支付服务只需要调用出口网关的统一支付接口就可以了。

在引入了 API 网关之后,我们的系统架构就变成了下面这样:

图片图片

强调的重点:

API网关可以划分为两类:入口网关和出口网关。入口网关具有多种功能,包括隔离客户端和微服务、提供协议转换、实施安全策略、进行认证、限流以及实现熔断等功能。出口网关的主要作用是为调用第三方服务提供统一的出口,它可以执行统一的认证、授权、审计和访问控制等任务,以确保与外部系统的通信安全和合规。

API网关的关键在于性能和可扩展性的实现。为了提高网关的性能,可以采用多路I/O复用模型和线程池并发处理请求。而为了提升网关的可扩展性,可以使用责任链模式来组织和管理过滤器,以便轻松添加、移除或修改过滤器,以满足不同的需求。

API 网关中的线程池可以针对不同的接口或者服务做隔离和保护,这样可以提升网关的可用性;

API 网关可以替代原本系统中的 Web 层,将 Web 层中的协议转换、认证、限流等功能挪入到 API 网关中,将服务聚合的逻辑下沉到服务层。

API网关不仅提供了方便的API调用方式,还能将一些服务治理功能独立出来,以实现更好的复用性。尽管在性能方面可能会有一些牺牲,但通常情况下,使用成熟的开源API网关组件,这些性能损失是可以接受的。因此,当您的微服务系统变得越来越复杂时,可以考虑将API网关作为整个系统的门面,以简化系统架构并提供更好的可维护性。

责任编辑:武晓燕来源: 二进制跳动