深入理解 Spring Cloud Gateway 的原理

本文涉及的产品
对象存储 OSS,标准 - 本地冗余存储 20GB 3个月
云原生 API 网关,700元额度,多规格可选
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 你好,我是悟空。本篇给大家带来的是微服务框架中非常重要的一个组件 API 网关。

你好,我是悟空。

本篇给大家带来的是微服务框架中非常重要的一个组件 API 网关。

本文已收录至《深入剖析 Spring Cloud 底层架构原理》

网络异常,图片无法展示
|

前言

在 PassJava 项目中,我用到了 Spring Cloud Gateway 作为 API 网关,客户端的所有的请求都是先经过网关,然后再转发到会员微服务、题目微服务等。

比如 API 网关和会员微服务对应的访问地址如下:

API 网关地址:http://localhost:8060

会员微服务地址:http://localhost:14000

客户端请求都是访问的 API 网关,然后网关转发到会员微服务,客户端无需知道会员微服务的地址。

本篇将会以 PassJava 作为案例进行讲解。

PassJava 开源地址:https://github.com/Jackson0714/PassJava-Platform

为什么需要 API 网关

在 SpringBoot 单体架构中,一般只有一个后端服务,如下图所示:

网络异常,图片无法展示
|

但是在 SpringCloud 微服务架构中,往往有多个微服务,这些微服务可能部署在不同的机器上,而且一个微服务可能会扩容成多个相同的微服务,组成微服务集群。

网络异常,图片无法展示
|

这种情况下,会存在如下问题:

  • 如果需要添加鉴权功能,则需要对每个微服务进行改造。
  • 如果需要对流量进行控制,则需要对每个微服务进行改造。
  • 跨域问题,需要对每个微服务进行改造。
  • 存在安全问题,每个微服务需要暴露自己的 Endpoint 给客户端。Endpoint 就是服务的访问地址 + 端口。比如下面的地址:
http://order.passjava.cn:8000
  • 灰度发布、动态路由需要对每个微服务进行改造。

这个问题的痛点是各个微服务都是一个入口,有没有办法统一入口呢?

解决这个问题的方式就是在客户端和服务器之间加个中间商就好了呀,只有中间商一个入口,这个中间商就是网关。

还有一个细节问题:多个微服务之间是如何通信的?这就要用到远程调用组件了,比如 OpenFeign。但是服务之间的调用是需要知道对方的 Endpoint 的,如果一个服务有多个微服务,就需要通过负载均衡组件进行流量分发。那微服务之间不就暴露 Endpoint 了吗?这个没有问题,毕竟只是后端服务知道,外界是不知道的。

网络异常,图片无法展示
|

为了帮助大家更容易理解网关的作用,这里有个网关、客户端、微服务的三方通话。

网关对话

网关:客户端你好,你现在可以只跟我通信了,我可以将你本来想发给微服务的流量进行转发,微服务处理完之后,将结果返回给我,我再给你。

客户端:你没有赚差价吧?

API 网关:我可能会加些请求头、做下认证、鉴权、限流等。

客户端:微服务不是自己可以做吗?

API 网关:但是每个微服务都得自己加,这就很麻烦了,都交给我就好了。

微服务:网关你好,你会为我保密我的地址对吗?

API 网关:当然,我给客户端看的是我自己的地址,客户端不需要知道你的地址,只需要知道你的 API 是哪个就行,剩下的交给我来转发给你。

API 网关选型对比

业界比较出名的网关:Spring Cloud Gateway、Netflix Zuul、Nginx、Kong、Alibaba Tengine。

作为 Spring Cloud 全家桶中的一款组件,当然选择 Spring Cloud Gateway 了。

最开始 Spring Cloud 推荐的网关是 Netflix Zuul 1.x,但是停止维护了,后来又有 Zuul 2.0,但是因为开发延期比较严重,Spring Cloud 官方自己开发了 Spring Cloud Gateway 网关组件,用于代替 Zuul 网关。

所以本篇我们只会讲解 Spring Cloud Gateway 网关组件。

Spring Cloud Gateway 的工作流程

Gateway 的工作流程如下图所示:

网络异常,图片无法展示
|

路由判断;客户端的请求到达网关后,先经过 Gateway Handler Mapping 处理,这里面会做断言(Predicate)判断,看下符合哪个路由规则,这个路由映射后端的某个服务。

请求过滤:然后请求到达 Gateway Web Handler,这里面有很多过滤器,组成过滤器链(Filter Chain),这些过滤器可以对请求进行拦截和修改,比如添加请求头、参数校验等等,有点像净化污水。然后将请求转发到实际的后端服务。这些过滤器逻辑上可以称作 Pre-Filters,Pre 可以理解为“在...之前”。

服务处理:后端服务会对请求进行处理。

响应过滤: 后端处理完结果后,返回给 Gateway 的过滤器再次做处理,逻辑上可以称作 Post-Filters,Post 可以理解为“在...之后”。

响应返回:响应经过过滤处理后,返回给客户端。

小结:客户端的请求先通过匹配规则找到合适的路由,就能映射到具体的服务。然后请求经过过滤器处理后转发给具体的服务,服务处理后,再次经过过滤器处理,最后返回给客户端。

Spring Cloud Gateway 的断言

断言(Predicate)这个词听起来极其深奥,它是一种编程术语,我们生活中根本就不会用它。说白了它就是对一个表达式进行 if 判断,结果为真或假,如果为真则做这件事,否则做那件事。

在 Gateway 中,如果客户端发送的请求满足了断言的条件,则映射到指定的路由器,就能转发到指定的服务上进行处理。

断言配置的示例如下,配置了两个路由规则,有一个 predicates 断言配置,当请求 url 中包含 api/thirdparty,就匹配到了第一个路由 route_thirdparty。(代码示例来自我的开源项目 PassJava)

网络异常,图片无法展示
|

接下来我们看下 Route 路由和 Predicate 断言的对应关系:

网络异常,图片无法展示
|

  • 一对多:一个路由规则可以包含多个断言。如上图中路由 Route1 配置了三个断言 Predicate。
  • 同时满足:如果一个路由规则中有多个断言,则需要同时满足才能匹配。如上图中路由 Route2 配置了两个断言,客户端发送的请求必须同时满足这两个断言,才能匹配路由 Route2。
  • 第一个匹配成功:如果一个请求可以匹配多个路由,则映射第一个匹配成功的路由。如上图所示,客户端发送的请求满足 Route3 和 Route4 的断言,但是 Route3 的配置在配置文件中靠前,所以只会匹配 Route3。

常见的 Predicate 断言配置如下所示,假设匹配路由成功后,转发到 http://localhost:9001

网络异常,图片无法展示
|

代码演示

下面演示 Gateway 中通过断言来匹配路由的例子。

  • 新建一个 Maven 工程,引入 Gateway 依赖。
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 新建 application.yml 文件,添加 Gateway 的路由规则。
spring:
  cloud:
    gateway:
      routes:
        - id: route_qq
          uri: http://www.qq.com
          predicates:
            - Query=url,qq
        - id: route_baidu
          uri: http://www.baidu.com
          predicates:
            - Query=url,baidu
server:
  port: 8060

第一条路由规则:断言为 Query=url,qq,表示当请求路径中包含 url=qq,则跳转到http://www.qq.com

第二条路由规则:当请求路径中包含 url=baidu,则跳转到http://www.baidu.com

Spring Cloud Gateway 动态路由

在微服务架构中,我们不会直接通过 IP + 端口的方式访问微服务,而是通过服务名的方式来访问。如下图所示,微服务中加入了注册中心,多个微服务将自己注册到了注册中心,这样注册中心就保存了服务名和 IP+端口的映射关系。

网络异常,图片无法展示
|

接下来我们来看下加入 Gateway 后,请求是如何进行转发的。

客户端先将请求发送给 Nginx,然后转发到网关,网关经过断言匹配到一个路由后,将请求转发给指定 uri,这个 uri 可以配置成 微服务的名字,比如 passjava-member。

那么这个服务名具体要转发到哪个 IP 地址和端口上呢?这个就依赖注册中心的注册表了,Gateway 从注册中心拉取注册表,就能知道服务名对应具体的 IP + 端口,如果一个服务部署了多台机器,则还可以通过负载均衡进行请求的转发。原理如下图所示:

网络异常,图片无法展示
|

对应的配置为 uri 字段如下所示

网络异常,图片无法展示
|

uri: lb://passjava-question,表示将请求转发给 passjava-question 微服务,且支持负载均衡。lb 是 loadbalance(负载均衡) 单词的缩写。

那什么叫动态路由呢?

当 passjava-question 服务添加一个微服务,或者 IP 地址更换了,Gateway 都是可以感知到的,但是配置是不需要更新的。这里的动态指的是微服务的集群个数、IP 和端口是动态可变的。

代码示例

案例:调用 OSS 第三方服务,上传文件到 OSS。(基于 PassJava 项目)

前提:前端页面配置的统一访问路径是网关的地址:http://localhost:8060/api/,OSS 服务对应的地址是http://localhost:14000。

期望结果:将前端请求

http://localhost:8060/api/thirdparty/v1/admin/oss/getPolicy

转发到 OSS 服务。

http://localhost:14000/thirdparty/v1/admin/oss/getPolicy

配置网关:

spring:
  cloud:
    gateway:
      routes:
        - id: route_thirdparty # 第三方微服务路由规则
          uri: lb://passjava-thirdparty # 负载均衡,将请求转发到注册中心注册的 passjava-thirdparty 服务
          predicates: # 断言
            - Path=/api/thirdparty/** # 如果前端请求路径包含 api/thirdparty,则应用这条路由规则
          filters: #过滤器
            - RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空

测试上传文件成功。

网络异常,图片无法展示
|

接下来我们看下 Gateway 非常重要且核心的功能:过滤器。

Spring Cloud Gateway 的过滤器

网关,顾名思义,就是网络中的一道关卡,可以统一对请求和响应进行一些操作。

过滤器 Filter 的分类

过滤器 Filter 按照请求和响应可以分为两种:Pre 类型和 Post 类型。

Pre 类型:在请求被转发到微服务之前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。

Post 类型:微服务处理完请求后,返回响应给网关,网关可以再次进行处理,例如修改响应内容或响应头、日志输出、流量监控等。

另外一种分类是按照过滤器 Filter 作用的范围进行划分:

GlobalFilter:全局过滤器,应用在所有路由上的过滤器。

局部过滤器

GatewayFilter:局部过滤器,应用在单个路由或一组路由上的过滤器。标红色表示比较常用的过滤器。

整理了一份 27 种自带的 GatwayFilter 过滤器。

网络异常,图片无法展示
|

具体怎么用呢,这里有个示例,如果 URL 匹配成功,则去掉 URL 中的 “api”。

filters: #过滤器
   - RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的 “api” 替换成空

当然我们也可以自定义过滤器,本篇不做展开。

全局过滤器

整理了一份全局过滤器的表格,具体用法可以参照官方文档。

官方文档:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_global_filters

网络异常,图片无法展示
|

全局过滤器最常见的用法是进行负载均衡。配置如下所示:

spring:
  cloud:
    gateway:
      routes:
        - id: route_member # 第三方微服务路由规则
          uri: lb://passjava-member # 负载均衡,将请求转发到注册中心注册的 passjava-member 服务
          predicates: # 断言
            - Path=/api/member/** # 如果前端请求路径包含 api/member,则应用这条路由规则
          filters: #过滤器
            - RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空

这里有个关键字 lb,用到了全局过滤器 LoadBalancerClientFilter,当匹配到这个路由后,会将请求转发到 passjava-member 服务,且支持负载均衡转发,也就是先将 passjava-member 解析成实际的微服务的 host 和 port,然后再转发给实际的微服务。

实现简单的 token 认证

在用 Gateway 做登录认证的时候,通常需要我们自定义一个过滤器做登录认证。

比如客户端登录时,将用户名和密码发送给网关,网关转发给认证服务器后,如果账号密码正确,则拿到一个 JWT token,然后客户端再访问应用服务时,先将请求发送给网关,网关统一做 JWT 认证,如果 JWT 符合条件,再将请求转发给应用服务。

原理如下图所示,红色框框的部分就是待会我要演示的部分。

网络异常,图片无法展示
|

案例演示

下面做一个简单的认证实例。客户端携带 token 访问 member 服务,网关会先校验 token 的合法性,验证规则如下:

当请求的 header 中包含 token,且 token = admin,则认证通过。

当验证通过后,就会将请求转发给 member 服务。

代码示例

  • 先定义一个全局过滤器,验证 token 的合法性。
@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request= exchange.getRequest();
        String token = request.getHeaders().getFirst("token");
        if(!StringUtils.isEmpty(token)){
            if("admin".equals(token)){
                return chain.filter(exchange);
            }
        }
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 测试 token 不正确的场景。

先测试在 header 中添加 token=123,响应结果为 401  Unauthorized,没有权限。

网络异常,图片无法展示
|

  • 测试 token 正确的场景。

然后测试在 header 中添加 token=admin,正常返回响应数据。

网络异常,图片无法展示
|

下一篇:如何用 Gateway 做登录鉴权:SpringCloud Gateway + JWT Token

相关文章
|
3月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
3月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
|
3月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
2月前
|
缓存 JSON NoSQL
别再手写过滤器!SpringCloud Gateway 内置30 个,少写 80% 重复代码
小富分享Spring Cloud Gateway内置30+过滤器,涵盖请求、响应、路径、安全等场景,无需重复造轮子。通过配置实现Header处理、限流、重试、熔断等功能,提升网关开发效率,避免代码冗余。
319 1
|
2月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
388 2
|
4月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
8月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
3590 113
|
5月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
340 0
|
7月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
214 32

热门文章

最新文章