引入网关的必要性
在微服务架构下,我们一般会把相对独立的业务或功能划分为不同的服务,不同服务之间相互隔离,独立部署。这些微服务由服务注册中心集中管理。对于来自外部的用户请求来说,在处理请求的核心业务逻辑之前,一般还需要对请求做一些预处理,如权限校验、监控统计、流量限制等。如果外部请求直接到达微服务,那么所有的服务都需要各自负责处理这些功能,会造成这部分功能逻辑在各个服务重复出现,增加了未来对这些基础功能的维护升级的成本。
所以在微服务环境下,我们还需要一个网关组件来作为请求入口。一些基础的请求预处理的逻辑可以统一实现在网关这一层,这样业务服务只需要专注于处理业务逻辑即可。另外,引入网关作为统一请求入口之后,还可以利用网关来实现一些其他的功能,比如服务保护、灰度发布等。
1. Spring Cloud Gateway 简介
Spring Cloud Gateway 是 Spring Cloud 微服务生态下的网关组件。Spring Cloud Gateway 是基于 Spring 5 和 Spring Boot 2 搭建的,本质上是一个 Spring Boot 应用。在详细介绍其基本原理之前,先看一下通常而言,可以由微服务网关提供的功能。
在 Spring Cloud Gateway 发布之前,Spring Cloud 使用的是由 Netflix 开源的 Zuul 1 作为网关组件。Zuul 1 是基于传统的 Servlet API 开发的,使用了阻塞的网络模型,每个请求需要分配专门的线程处理,所以资源开销比较大,在高并发的情况下需要创建大量的线程来处理请求,线程数目会成为系统的瓶颈。作为取代 Spring Cloud Zuul 的组件,Spring Cloud Gateway 网络层使用了基于非阻塞的 Netty,解决了线程数瓶颈从而提升了系统性能。
微服务网关的功能
- 请求路由:根据请求本身的属性把请求转发到不同的微服务是微服务网关的基本功能之一。业务运维人员需要针对不同的服务配置路由,使网关能够根据请求的 header、路径、参数、协议等属性将其转发到对应的服务。
- 服务发现:网关是微服务环境的请求入口。支持服务发现能使网关在转发请求到目标服务时充分利用服务注册中心动态管理服务实例的优势,在配置路由转发的目标地址时也会更加方便。
- 修改请求响应:网关在收到外部请求,将其转发到目标服务之前,可以根据需求对请求进行修改,比如果更改请求 header、参数等。类似地,也可以在获取到业务服务响应之后,返回给用户前对响应进行修改。
- 权限校验:某些业务场景在处理用户请求时需要先对用户进行权限校验,这部分逻辑也可以由网关来负责。请求在到达网关时,由网关根据请求要访问的业务接口先对用户鉴权,只有校验通过的请求才会转发到对应的服务,而校验不通过的请求会被网关直接拒绝。这样做能够把拒绝无效请求这一步提前到网关这一层,减少无效的流量进入到业务服务。
- 限流熔断:网关可以通过添加限流、熔断等机制来对业务服务起保护作用,提升系统整体的可用性。根据业务服务的吞吐量,网关可以限制转发到该服务的请求数量,超出限制的请求直接拒绝或降级,这样可以避免因为过多的请求导致业务服务负载过高的情况。当业务服务异常时,还可以通过熔断的方式到达快速失败的效果。
- 请求重试:对于一些幂等的请求,当网关转发目标服务失败时,可以在网关层做自动重试。对于一些多实例部署服务,重试时还可以考虑把请求转发到不同的实例,以提高请求成功的概率。
- 响应缓存:当用户请求获取的是一些静态的或更新不频繁的数据时,一段时间内多次请求获取到的数据很可能是一样的。对于这种情况可以将响应缓存起来。这样用户请求可以直接在网关层得到响应数据,无需再去访问业务服务,减轻业务服务的负担。
- 响应聚合:某些情况下用户请求要获取的响应内容可能会来自于多个业务服务。网关作为业务服务的调用方,可以把多个服务的响应整合起来,再一并返回给用户。
- 监控统计:因为网关是请求入口,所以在网关这一层可以方便地对外部的访问请求做监控和统计,同时还可以对业务服务的响应做监控,方便发现异常情况。
- 灰度流量:网关可以用来做服务流量的灰度切换。比如某个业务服务上线了新版本,那可以在网关这一层按照灰度策略,把一部分请求流量切换到新版本服务上,以达到验证新版本业务服务的功能和性能的效果。
- 异常响应处理:对于业务服务返回的异常响应,可以在网关层在返回给用户之前做转换处理。这样可以把一些业务侧返回的异常细节隐藏,转换成用户友好的错误提示返回。
2. Spring Cloud Gateway 基本原理
Spring Cloud Gateway 使用了 Spring WebFlux 非阻塞网络框架,网络层默认使用了高性能非阻塞的 Netty Server,解决了 Spring Cloud Zuul 因为阻塞的线程模型带来的性能下降的问题。
Gateway 本身是一个 Spring Boot 应用,它处理请求是逻辑是根据配置的路由对请求进行预处理和转发。Gateway 有几个比较核心的概念:
- Route:一个 Route 由路由 ID,转发 URI,多个 Predicates 以及多个 Filters 构成。Gateway 上可以配置多个 Routes。处理请求时会按优先级排序,找到第一个满足所有 Predicates 的 Route;
- Predicate:表示路由的匹配条件,可以用来匹配请求的各种属性,如请求路径、方法、header 等。一个 Route 可以包含多个子 Predicates,多个子 Predicates 最终会合并成一个;
- Filter:过滤器包括了处理请求和响应的逻辑,可以分为 pre 和 post 两个阶段。多个 Filter 在 pre 阶段会按优先级高到低顺序执行,post 阶段则是反向执行。Gateway 包括两类 Filter。
- 全局 Filter:每种全局 Filter 全局只会有一个实例,会对所有的 Route 都生效。
- 路由 Filter:路由 Filter 是针对 Route 进行配置的,不同的 Route 可以使用不同的参数,因此会创建不同的实例。
Gateway 在启动时会创建 Netty Server,由它接收来自 Client 的请求。收到请求后根据路由的匹配条件找到第一个满足条件的路由,然后请求在被该路由配置的过滤器处理后由 Netty Client 转到目标服务。服务返回响应后会再次被过滤器处理,最后返回给 Client。
Gateway 路由配置
Spring Cloud Gateway 本身提供了很多 Predicate 和 Filter 的实现,一些基本的功能可以通过这些现成的 Predicate 和 Filter 配置实现。这些 Gateway 本身提供的 Predicate 和 Filter 在官方文档上有详细的介绍,这里给一个大致的例子:
spring:
cloud:
gateway:
routes:
- id: test_route
uri: lb://service-A
predicates:
- Path=/hello
filters:
- SetRequestHeader=X-Request-Red, Blue
复制代码
路由是 Gateway 的核心构件,不同的路由根据匹配条件可以处理不同类型的请求,并转发到对应的目标服务。一个路由由以下几个属性组成:
- Id: 路由 ID;
- Uri: 转发请求的目标地址;
- Order: 顺序(优先级);
- Predicate: 匹配条件。多个 Predicates 会合并成一个聚合的条件;
- Filters: 路由过滤器。这些过滤器最终会和全局过滤器一起排序处理匹配成功的请求;
- Metadata: 额外的元数据。
Gateway 请求路由原理
Gateway 使用了 Spring WebFlux 框架,该框架处理请求的入口在类 DispatcherHandler 。它会根据提供的 HandlerMapping 来获取处理请求的 Handler 方法。Gateway 应用对 HandlerMapping 的实现是 RoutePredicateHandlerMapping 。
- 进来的请求由 DispatcherHandler 处理。
- DispatcherHandler 根据 RoutePredicateHandlerMapping 获取 Handler 方法。
- RoutePredicateHandlerMapping 依赖 RouteLocator 获取所有路由配置并根据匹配条件打到请求匹配的路由。
- RoutePredicateHandlerMapping 把请求交给 FilteringWebHandler 处理。
- FilteringWebHandler 从请求匹配的路由获取对应的路由 Filter,并和全局 Filter 合并构造 GatewayFilterChain ,请求最终由 GatewayFilterChain 里的 Filter 按顺序处理。
Spring Cloud Gateway 上下文(ServerWebExchange)
SpringCloud Gateway 的上下文是 ServerWebExchange,请求的信息都存储在 ServerWebExchange 中,在网关上的后续操作都是基于上下文操作的,在 http 请求到达网关之后,网关入口是ReactorHttpHandlerAdapter#apply 方法,去获取请求的 request 和 response,构建当次请求的上下文供后续 filter 使用:
public class ReactorHttpHandlerAdapter implements BiFunction<HttpServerRequest, HttpServerResponse, Mono<Void>> {
@Override
public Mono<Void> apply(HttpServerRequest reactorRequest, HttpServerResponse reactorResponse) {
NettyDataBufferFactory bufferFactory = n