聊聊 API Gateway 和 Netflix Zuul

标签: api gateway netflix | 发表时间:2017-05-30 13:03 | 作者:
出处:http://www.scienjus.com/

最近参与了公司 API Gateway 的搭建工作,技术选型是 Netflix Zuul,主要聊一聊其中的一些心得和体会。

本文主要是介绍使用 Zuul 且在不强制使用其他 Neflix OSS 组件时,如何搭建生产环境的 Gateway,以及能使用 Gateway 做哪些事。不打算介绍任何关于如何快速搭建 Zuul,或是一些轻易集成 Eureka 之类的的方法,这些在官方文档上已经介绍的很明确了。

API Gateway

API Gateway 是随着微服务(Microservice)这个概念一起兴起的一种架构模式,它用于解决微服务过于分散,没有一个统一的出入口进行流量管理的问题。

用 Kong 官网的两张图来解释再合适不过。

分散的 API 管理

当使用微服务构建整个 API 服务时,一般会有许许多多职责不同的应用在运行着,这些应用会需要一些通用的功能,例如鉴权、流控、监控、日志统计。

在传统的单体应用中,这些功能一般都是内嵌在应用中,作为一个组件运行。但是在微服务模式下,不同种类且独立运行的应用可能会有数十甚至数百种,继续使用这种方式会造成非常高的管理和发布成本。所以就需要在这些应用上抽象出一个统一的流量入口,完成这些功能的实现。

统一的 API 管理

在我看来,API Gateway 的职责主要分为两部分:

  1. 对服务应用有感知且重要的功能,例如鉴权。
  2. 对服务应用无感知的边缘服务,例如流控、监控、页面级缓存等。

Netflix Zuul

对于 API Gateway,常见的选型有基于 Openresty 的 Kong、基于 Go 的 Tyk 和基于 Java 的 Zuul。

这三个选型本身没有什么明显的区别,主要还是看技术栈是否能满足快速应用和二次开发,例如我司原有的技术栈就是使用 Go/Openresty 的平台组和使用 Java 的后端组,讨论后觉得 API Gateway 未来还是处理业务功能的场景更多些,而且后端这边有很多功能可以直接移植过来,最终就选择了 Zuul。

关于 Zuul,大部分使用 Java 做微服务的人可能都会或多或少了解 Spring Cloud 和 Netflix 全家桶。而对于完全不了解的人,可以暂时将它想象为一个类似于 Servlet 中过滤器(Filter)的概念。

Zuul

就像上图中所描述的一样,Zuul 提供了四种过滤器的 API,分别为前置(Pre)、后置(Post)、路由(Route)和错误(Error)四种处理方式。

一个请求会先按顺序通过所有的前置过滤器,之后在路由过滤器中转发给后端应用,得到响应后又会通过所有的后置过滤器,最后响应给客户端。在整个流程中如果发生了异常则会跳转到错误过滤器中。

一般来说,如果需要在请求到达后端应用前就进行处理的话,会选择前置过滤器,例如鉴权、请求转发、增加请求参数等行为。在请求完成后需要处理的操作放在后置过滤器中完成,例如统计返回值和调用时间、记录日志、增加跨域头等行为。路由过滤器一般只需要选择 Zuul 中内置的即可,错误过滤器一般只需要一个,这样可以在 Gateway 遇到错误逻辑时直接抛出异常中断流程,并直接统一处理返回结果。

应用场景

以下介绍一些 Zuul 中不同过滤器的应用场景。

前置过滤器

鉴权

一般来说整个服务的鉴权逻辑可以很复杂。

  • 客户端:App、Web、Backend
  • 权限组:用户、后台人员、其他开发者
  • 实现:OAuth、JWT
  • 使用方式:Token、Cookie、SSO

而对于后端应用来说,它们其实只需要知道请求属于谁,而不需要知道为什么,所以 Gateway 可以友善的帮助后端应用完成鉴权这个行为,并将用户的唯一标示透传到后端,而不需要、甚至不应该将身份信息也传递给后端,防止某些应用利用这些敏感信息做错误的事情。

Zuul 默认情况下在处理后会删除请求的 Authorization 头和 Set-Cookie 头,也算是贯彻了这个原则。

流量转发

流量转发的含义就是将指向 /a/xxx.json 的请求转发到指向 /b/xxx.json 的请求。这个功能可能在一些项目迁移、或是灰度发布上会有一些用处。

在 Zuul 中并没有一个很好的办法去修改 Request URI。在某些 Issue 中开发者会建议设置 requestURI 这个属性,但是实际在 Zuul 自身的 PreDecorationFilter 流程中又会被覆盖一遍。

不过对于一个基于 Servlet 的应用,使用 HttpServletRequestWrapper 基本可以解决一切问题,在这个场景中只需要重写其 getRequestURI 方法即可。

     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
     
class RewriteURIRequestWrapper extends HttpServletRequestWrapper {
private String rewriteURI;
public RewriteURIRequestWrapper(HttpServletRequest request, String rewriteURI) {
super(request);
this.rewriteURI = rewriteURI;
}
@Override
public String getRequestURI() {
return rewriteURI;
}
}
```
## 后置过滤器
### 跨域
使用 Gateway 做跨域相比应用本身或是 Nginx 的好处是规则可以配置的更加灵活。例如一个常见的规则。
1. 对于任意的 AJAX 请求,返回 `Access-Control-Allow-Origin` 为 `*`,且 `Access-Control-Allow-Credentials` 为 `true`,这是一个常用的允许任意源跨域的配置,但是不允许请求携带任何 Cookie
2. 如果一个被信任的请求者需要携带 Cookie,那么将它的 `Origin` 增加到白名单中。对于白名单中的请求,返回 `Access-Control-Allow-Origin` 为该域名,且 `Access-Control-Allow-Credentials` 为 `true`,这样请求者可以正常的请求接口,同时可以在请求接口时携带 Cookie
3. 对于 302 的请求,即使在白名单内也必须要设置 `Access-Control-Allow-Origin` 为 `*`,否则重定向后的请求携带的 `Origin` 会为 `null`,有可能会导致 iOS 低版本的某些兼容问题
### 统计
Gateway 可以统一收集所有应用请求的记录,并写入日志文件或是发到监控系统,相比 Nginx 的 access log,好处主要也是二次开发比较方便,比如可以关注一些业务相关的 HTTP 头,或是将请求参数和返回值都保存为日志打入消息队列中,便于线上故障调试。也可以收集一些性能指标发送到类似 Statsd 这样的监控平台。
## 错误过滤器
错误过滤器的主要用法就像是 Jersey 中的 `ExceptionMapper` 或是 Spring MVC 中的 `@ExceptionHandler` 一样,在处理流程中认为有问题时,直接抛出统一的异常,错误过滤器捕获到这个异常后,就可以统一的进行返回值的封装,并直接结束该请求。
# 配置管理
虽然将这些逻辑都切换到了 Gateway,省去了很多维护和迭代的成本,但是也面临着一个很大的问题,就是 Gateway 只有逻辑却没有配置,它并不知道一个请求要走哪些流程。
例如同样是后端服务 API,有的可能是给网页版用的、有的是给客户端用的,亦或是有的给用户用、有的给管理人员用,那么 Gateway 如何知道到底这些 API 是否需要登录、流控以及缓存呢?
理论上我们可以为 Gateway 编写一个管理后台,里面有当前服务的所有 API,每一个开发者都可以在里面创建新的 API,以及为它增加鉴权、缓存、跨域等功能。为了简化使用,也许我们会额外的增加一个权限组,例如 `/admin/*` 下的所有 API 都应该为后台接口,它只允许内部来源的鉴权访问。
但是这样做依旧太复杂了,而且非常硬编码,当开发者开发了一个新的 API 之后,即使这个应用已经能正常接收特定 URI 的请求并处理之后,却还要通过人工的方式去一个管理后台进行额外的配置,而且可能会因为不谨慎打错了路径中的某个单词而造成不必要的事故,这都是不合理的。
我个人推荐的做法是,在后端应用中依旧保持配置的能力,即使应用里已经没有真实处理的逻辑了。例如在 Java 中通过注解声明式的编写 API,且在应用启动时自动注册 Gateway 就是一种比较好的选择。

/**

  • 这个接口需要鉴权,鉴权方式是 OAuth
    */
    @Authorization(OAuth)
    @RequestMapping(value = “/users/{id}”, method = RequestMethod.DELETE)
    public void del(@PathVariable int id) {
    //…
    }

/**

  • 这个接口可以缓存,并且每个 IP/User 每秒最多请求 10 次
    */
    @Cacheable
    @RateLimiting(limit = “10/1s”, scope = {IP, USER})
    @RequestMapping(value = “/users/{id}”, method = RequestMethod.GET)
    public void info(@PathVariable int id) {
    //…
    }
    ```

这样 API 的编写者就会根据业务场景考虑该 API 需要哪些功能,也减少了管理的复杂度。

除此之外还会有一些后端应用无关的配置,有些是自动化的,例如恶意请求拦截,Gateway 会将所有请求的信息通过消息队列发送给一些实时数据分析的应用,这些应用会对请求分析,发现恶意请求的特征,并通过 Gateway 提供的接口将这些特征上报给 Gateway,Gateway 就可以实时的对这些恶意请求进行拦截。

稳定性

在 Nginx 和后端应用之间又建立了一个 Java 应用作为流量入口,很多人会去担心它的稳定性,亦或是担心它能否像 Nginx 一样和后端的多个 upstream 进行交互,以下主要介绍一下 Zuul 的隔离机制以及重试机制。

隔离机制

在微服务的模式下,应用之间的联系变得没那么强烈,理想中任何一个应用超过负载或是挂掉了,都不应该去影响到其他应用。但是在 Gateway 这个层面,有没有可能出现一个应用负载过重,导致将整个 Gateway 都压垮了,已致所有应用的流量入口都被切断?

这当然是有可能的,想象一个每秒会接受很多请求的应用,在正常情况下这些请求可能在 10 毫秒之内就能正常响应,但是如果有一天它出了问题,所有请求都会 Block 到 30 秒超时才会断开(例如频繁 Full GC 无法有效释放内存)。那么在这个时候,Gateway 中也会有大量的线程在等待请求的响应,最终会吃光所有线程,导致其他正常应用的请求也受到影响。

在 Zuul 中,每一个后端应用都称为一个 Route,为了避免一个 Route 抢占了太多资源影响到其他 Route 的情况出现,Zuul 使用 Hystrix 对每一个 Route 都做了隔离和限流。

Hystrix 的隔离策略有两种,基于线程或是基于信号量。Zuul 默认的是基于线程的隔离机制,这意味着每一个 Route 的请求都会在一个固定大小且独立的线程池中执行,这样即使其中一个 Route 出现了问题,也只会是某一个线程池发生了阻塞,其他 Route 不会受到影响。

一般使用 Hystrix 时,只有调用量巨大会受到线程开销影响时才会使用信号量进行隔离策略,对于 Zuul 这种网络请求的用途使用线程隔离更加稳妥。

重试机制

一般来说,后端应用的健康状态是不稳定的,应用列表随时会有修改,所以 Gateway 必须有足够好的容错机制,能够减少后端应用变更时造成的影响。

Zuul 的路由主要有 Eureka 和 Ribbon 两种方式,由于我一直使用的都是 Ribbon,所以简单介绍下 Ribbon 支持哪些容错配置。

重试的场景分为三种:

  • okToRetryOnConnectErrors:只重试网络错误
  • okToRetryOnAllErrors:重试所有错误
  • OkToRetryOnAllOperations:重试所有操作(这里不太理解,猜测是 GET/POST 等请求都会重试)

重试的次数有两种:

  • MaxAutoRetries:每个节点的最大重试次数
  • MaxAutoRetriesNextServer:更换节点重试的最大次数

一般来说我们希望只在网络连接失败时进行重试、或是对 5XX 的 GET 请求进行重试(不推荐对 POST 请求进行重试,无法保证幂等性会造成数据不一致)。单台的重试次数可以尽量小一些,重试的节点数尽量多一些,整体效果会更好。

如果有更加复杂的重试场景,例如需要对特定的某些 API、特定的返回值进行重试,那么也可以通过实现 RequestSpecificRetryHandler 定制逻辑(不建议直接使用 RetryHandler,因为这个子类可以使用很多已有的功能)。

相关 [api gateway netflix] 推荐:

聊聊 API Gateway 和 Netflix Zuul

- - ScienJus's Blog
最近参与了公司 API Gateway 的搭建工作,技术选型是 Netflix Zuul,主要聊一聊其中的一些心得和体会. 本文主要是介绍使用 Zuul 且在不强制使用其他 Neflix OSS 组件时,如何搭建生产环境的 Gateway,以及能使用 Gateway 做哪些事. 不打算介绍任何关于如何快速搭建 Zuul,或是一些轻易集成 Eureka 之类的的方法,这些在官方文档上已经介绍的很明确了.

apache/apisix: The Cloud-Native API Gateway

- -
Health Checks: Enable health check on the upstream node and will automatically filter unhealthy nodes during load balancing to ensure system stability..

Blog: Kubernetes Gateway API 进入 Beta 阶段

- - Kubernetes – 生产级别的容器编排系统
作者: Shane Utt (Kong)、Rob Scott (Google)、Nick Young (VMware)、Jeff Apple (HashiCorp). 我们很高兴地宣布 Gateway API 的 v0.5.0 版本发布. 我们最重要的几个 Gateway API 资源首次进入 Beta 阶段.

如何理解 Istio Ingress, 它与 API Gateway 有什么区别?

- - Jimmy Song - 云原生|开源|社区 – 博客
API 网关作为客户端访问后端的入口,已经存在很长时间了,它主要是用来管理”南北向“的流量;近几年服务网格开始流行,它主要是管理系统内部,即“东西向”流量,而像 Istio 这样的服务网格还内置了网关,从而将系统内外部的流量纳入了统一管控. 这经常给初次接触 Istio 的人带来困惑——服务网格与 API 网关之间是什么关系.

为什么说 Gateway API 是 Kubernetes 和服务网格入口中网关的未来?

- - Jimmy Song - 专注于探索后 Kubernetes 时代的云原生新范式 – 博客
本文将以 Kubernetes Ingress、Istio 和 Envoy Gateway 为例,向你介绍 Kubernetes 中的入口网关和 Gateway API,同时介绍 Gateway API 使得 Kubernetes 和服务网格入口网关融合的新趋势. Ingress 作为 Kubernetes 的初代入口网关,它的资源模型过于简单以致于无法适应当今的可编程网络;.

技术 in Netflix

- - 后端技术杂谈 | 飒然Hang
综合市面上的公开资料总结了Netflix在技术上面的一些实践和创新,从中能够得到不少启发和提示.

Spring Cloud Gateway(限流) | Wind Mt

- -
限流可以保障我们的 API 服务对所有用户的可用性,也可以防止网络攻击. 一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如 nginx 的 limit_conn 模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制 MQ 的消费速率.

快速突击 Spring Cloud Gateway

- - 掘金后端
认识 Spring Cloud Gateway. Spring Cloud Gateway 是一款基于 Spring 5,Project Reactor 以及 Spring Boot 2 构建的 API 网关,是 Spring Cloud 微服务生态的主要组件之一. Spring Cloud Gateway 主要负责接口请求的路由分发,并且支持对请求的安全验证,流量监控和流量控制等扩展操作.

Api Blocking

- - xiaobaoqiu Blog
4.RateLimiter实现限流. 接口限流是保证系统稳定性的三大法宝之一(缓存, 限流, 降级).. 本文使用三种方式实现Api限流, 并提供了一个用Spring实现的Api限流的简单Demo, Demo的git地址: https://github.com/xiaobaoqiu/api-blocking.

SpringCloud Gateway与k8s_zhangjunli的博客-CSDN博客

- -
接下来的内容由以下几部分组成:. 什么是SpringCloud Gateway. SpringCloud Gateway实战参考. kubernetes上的SpringCloud Gateway. 开发k8sgatewaydemo. 什么是SpringCloud Gateway. SpringCloud Gateway是SpringCloud技术栈下的网关服务框架,在基于SpringCloud的微服务环境中,外部请求会到达SpringCloud Gateway应用,该应用对请求做转发、过滤、鉴权、熔断等前置操作,一个典型的请求响应流程如下所示:.