0


深入探索Spring Cloud Gateway:微服务网关的最佳实践

优质博文:IT-BLOG-CN

Spring Cloud Gateway

作为

Spring Cloud

框架的第二代网关,在功能上要比

Zuul

更加的强大,性能也更好。随着

Spring Cloud

的版本迭代,

Spring Cloud

官方有打算弃用

Zuul

的意思。在笔者调用了

Spring Cloud Gateway

的使用和功能上,

Spring Cloud Gateway

替换掉

Zuul

的成本上是非常低的,几乎可以无缝切换。

Spring Cloud Gateway

几乎包含了

Zuul

的所有功能。

一、网关定义

**

API

网关是一个反向路由,屏蔽内部细节,为调用者提供统一入口,接收所有调用者请求,通过路由机制转发到服务实例。

API

网关是一组“过滤器

Filter

”集合,可以实现一系列与核心业务无关的横切面功能,如安全认证、限流熔断、日志监控**。

网关在系统中所处的位置:

二、快速开始

网关启动步骤(代码演示):

【1】添加依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>

【2】配置文件

spring:cloud:gateway:discovery:# 启用通过服务发现自动创建路由。locator:enabled:trueroutes:-id: example_route   #路由ID,根据业务自行定义uri: lb://example-service  #目标服务的地址,这里使用 lb:// 前缀来表示负载均衡。可以是 HTTP(s) URI 或其他协议的 URI - http://bin.org:80/get。predicates:- Path=/example/**#predicates: 谓词数组,用于匹配请求。常见的谓词包括 Path、Method、Header 等。filters:#过滤器数组,用于在请求被转发到目标服务之前和之后进行处理。常见的过滤器包括 AddRequestHeader、StripPrefix、RewritePath 等。- AddRequestHeader=X-Example, ExampleValue #过滤器会在请求头中添加 X-Example,值为 ExampleValue。- StripPrefix=1 #过滤器会移除路径中的第一个前缀。例如,请求路径 /example/test 会变成 /test。-id: rate_limited_route
          uri: http://ratelimited.org
          predicates:- Path=/ratelimited/**filters:- RequestRateLimiter=redis-rate-limiter # 限流:通过 RequestRateLimiter 过滤器实现-id: retry_route
          uri: http://retry.org
          predicates:- Path=/retry/**filters:- Retry=5 #重试:通过 Retry 过滤器实现。default-filters:#default-filters 是全局过滤器数组,适用于所有路由。这个过滤器会在所有响应中添加 X-Response-Default 头,值为 Default。- AddResponseHeader=X-Response-Default, Default
      globalcors:#globalcors 用于配置全局的 CORS(跨域资源共享)设置。corsConfigurations:#corsConfigurations: 定义 CORS 配置的路径模式。'[/**]':#匹配所有路径。allowedOrigins:"*"#允许的源,* 表示允许所有源。allowedMethods:#允许的 HTTP 方法,包括 GET、POST、DELETE 和 PUT。- GET
              - POST
              - DELETE
              - PUT

三、Spring Cloud GateWay 架构图

客户端向

Spring Cloud Gateway

发出请求。 在

Gateway Handler Mapping

中找到请求相对匹配路由(这个时候就用到

predicate

),则将其发送到

Gateway web handler

处理。

handler

处理请求时会经过一系列的过滤器链。 过滤器链被虚线划分的原因是过滤器链可以在发送代理请求之前或之后执行过滤逻辑。 先执行所有

pre

过滤器逻辑,然后进行代理请求。 在发出代理请求之后,收到代理服务的响应之后执行

post

过滤器逻辑。这跟

Zuul

的处理过程很类似。在执行所有

pre

过滤器逻辑时,往往进行了鉴权、限流、日志输出等功能,以及请求头的更改、协议的转换;转发之后收到响应之后,会执行所有

post

过滤器的逻辑,在这里可以响应数据进行了修改,比如响应头、协议的转换等。在上面的处理过程中,有一个重要的点就是将请求和路由进行匹配,这时候就需要用到

predicate

,它是决定了一个请求走哪一个路由。

四、SpringColoud GateWay 核心组件

集合上面的配置和架构图进行说明

**【1】

Route

路由:**

Gateway

的基本构建模块,它由

ID

、目标

URL

、断言集合和过滤器集合组成。如果聚合断言结果为真,则匹配到该路由。

**

Route

路由-动态路由实现原理:**配置变化

Apollo
  • 服务地址实例变化
    Nacos
    
    Spring Cloud Gateway
    
    通过
    RouteDefinitionLocator
    
    RouteRefreshListener
    
    等组件实现动态路由。

先看下配置信息,方便后面原理的理解:

SpringCloudGateway bootstrap.yml

的配置如下:

spring:application:name: gateway-service
  cloud:nacos:discovery:server-addr: ${NACOS_SERVER_ADDR:localhost:8848}apollo:bootstrap:enabled:truemeta: ${APOLLO_META:localhost:8080}
application.yml

的配置如下:

spring:cloud:gateway:discovery:locator:enabled:truelower-case-service-id:trueapollo:bootstrap:namespaces: application # 1、登录 Apollo 控制台。 2、创建一个新的配置,例如 application.yml。 3、内容就是上看配置的SpringCloud Gateway 配置的路由信息

1、

RouteDefinitionLocator

Spring Cloud Gateway

启动时,会通过

RouteDefinitionLocator

Apollo

加载初始的路由定义。
2、

DiscoveryClientRouteDefinitionLocator

:使用

Nacos

进行服务发现,从

Nacos

获取动态路由定义。
3、

RouteDefinitionRepository

:加载的路由定义会存储在

RouteDefinitionRepository

中,供后续路由匹配使用。
4、

RouteRefreshListener

:监听路由定义的变化事件(如配置更新、服务实例变化等)。当监听到路由定义变化事件时,触发路由刷新操作,更新网关的路由规则,重新加载并应用新的路由配置。

GatewayHandlerMapping

根据预先配置的路由信息和请求的属性(如路径、方法、头部信息等)来确定哪个路由与请求匹配。它使用谓词

Predicates

来进行匹配判断。

**【2】

Predicate

断言:** 这是一个

Java 8 Function Predicate

。输入类型是

Spring Framework ServerWebExchange

。允许开发人员匹配来自

HTTP

请求的任何内容,例如

Header

或参数。

Predicate

接受一个输入参数,返回一个布尔值结果。

Spring Cloud Gateway

内置了许多

Predict

,这些

Predict

的源码在

org.springframework.cloud.gateway.handler.predicate

包中,如果读者有兴趣可以阅读一下。现在列举各种 Predicate如下图:

在上图中,有很多类型的

Predicate

,比如说时间类型的

Predicated

[

AfterRoutePredicateFactory BeforeRoutePredicateFactory BetweenRoutePredicateFactory

],当只有满足特定时间要求的请求会进入到此

Predicate

中,并交由

Router

处理;

Cookie

类型的

CookieRoutePredicateFactory

,指定的

Cookie

满足正则匹配,才会进入此

Router

。以及

host

method

path

querparam

remoteaddr

类型的

Predicate

,每一种

Predicate

都会对当前的客户端请求进行判断,是否满足当前的要求,如果满足则交给当前请求处理。如果有很多个

Predicate

,并且一个请求满足多个

Predicate

,则按照配置的顺序第一个生效。

Predicate 断言配置:

server:port:8080spring:application:name: api-gateway
  cloud:gateway:routes:-id: gateway-service
          uri: https://www.baidu.com
          order:0predicates:- After=2017-01-20T17:42:47.789-07:00[America/Denver]- Host=**.foo.org- Path=/headers
            - Method=GET
            - Header=X-Request-Id, \d+
            - Query=foo, ba.
            - Query=baz
            - Cookie=chocolate, ch.p

在上面的配置文件中,配置了服务的端口为

8080

,配置

spring cloud gateway

相关的配置,**

id

标签配置的是

router

id

**,每个

router

都需要一个唯一的

id

,**

uri

配置的是将请求路由到哪里**,本案例全部路由到

https://www.baidu.com

**

Predicates

:**

After=2017-01-20T17:42:47.789-07:00[America/Denver]

会被解析成

PredicateDefinition

对象

name =After ,args= 2017-01-20T17:42:47.789-07:00[America/Denver]

。需要注意的是

Predicates

After

这个配置,遵循契约大于配置的思想,它实际被

AfterRoutePredicateFactory

这个类所处理,这个

After

就是指定了它的

Gateway web handler

类为

AfterRoutePredicateFactory

,同理,其他类型的

Predicate

也遵循这个规则。当请求的时间在这个配置的时间之后,请求会被路由到指定的

URL

。跟时间相关的

Predicates

还有

Before Route Predicate Factory

Between Route Predicate Factory

,读者可以自行查阅官方文档,再次不再演示。

**

Query=baz

:**

Query

的值以键值对的方式进行配置,这样在请求过来时会对属性值和正则进行匹配,匹配上才会走路由。经过测试发现只要请求汇总带有

baz

参数即会匹配路由[

localhost:8080?baz=x&id=2

],不带

baz

参数则不会匹配。

Query=foo, ba.

:这样只要当请求中包含

foo

属性并且参数值是以

ba

开头的长度为三位的字符串才会进行匹配和路由。使用

curl

测试,命令行输入:

curl localhost:8080?foo=bab

测试可以返回页面代码,将

foo

的属性值改为

babx

再次访问就会报

404

,证明路由需要匹配正则表达式才会进行路由。

Header=X-Request-Id

,

\d+

:使用

curl

测试,命令行输入:

curl http://localhost:8080 -H "X-Request-Id:88"

则返回页面代码证明匹配成功。将参数

-H "X-Request-Id:88"改为-H "X-Request-Id:spring"

再次执行时返回

404

证明没有匹配。

【3】

Filter

过滤器:方案一:写死在代码中

@BeanpublicRouteLocatorcustomRouteLocator(RouteLocatorBuilder builder){return builder.routes()//openapi路由转发.route("openapi_route", p -> p.path("/openapi/**").filters(f->f.removeRequestHeader("Expect")).uri("lb://order-openapi-service")).build();}

方案二:配置文件

yml
# gateway 的配置形式routes:-id: order-service #路由ID,没有规定规则但要求唯一,建议配合服务名。uri: lb://order-service
    predicates:- Path=/order/**filters:- ValidateCodeGatewayFilter

**

Filter

过滤器:

Filter

按处理顺序

Pre Filter / Post Filter

**

**

Filter

按作用范围分为:**

GlobalFilter

全局过滤器。

GatewayFilter

指定路由的过滤器。
**

Filter

过滤器-扩展自定义

Filter

:**

Filter

支持通过

spi

扩展。实现

GatewayFilter

Ordered

接口。
**

Filter

方法:** 过滤器处理逻辑。

getOrder

:定义优先级,值越大优先级越低。

全局过滤器示例: 创建一个全局过滤器类,实现

GlobalFilter

接口:

publicclassTokenFilterimplementsGlobalFilter,Ordered{Logger logger=LoggerFactory.getLogger(TokenFilter.class);@OverridepublicMono<Void>filter(ServerWebExchange exchange,GatewayFilterChain chain){String token = exchange.getRequest().getQueryParams().getFirst("token");if(token ==null|| token.isEmpty()){
            logger.info("token is empty...");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}return chain.filter(exchange);}@OverridepublicintgetOrder(){// // 过滤器的执行顺序,值越小优先级越高return-100;}}

自定义路由过滤器示例: 创建自定义的路由过滤器,可以实现

GatewayFilter

接口:

importorg.springframework.cloud.gateway.filter.GatewayFilter;importorg.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;@ComponentpublicclassMyCustomFilterextendsAbstractGatewayFilterFactory<MyCustomFilter.Config>{publicMyCustomFilter(){super(Config.class);}@OverridepublicGatewayFilterapply(Config config){return(exchange, chain)->{// 前置过滤逻辑System.out.println("Custom Pre Filter executed");return chain.filter(exchange).then(Mono.fromRunnable(()->{// 后置过滤逻辑System.out.println("Custom Post Filter executed");}));};}publicstaticclassConfig{// 配置属性}}

在配置文件中使用自定义过滤器:

spring:cloud:gateway:routes:-id: my_route
        uri: http://httpbin.org:80predicates:- Path=/get
        filters:-name: MyCustomFilter

五、Gateway 限流

Spring Cloud Gateway

中,有

Filter

过滤器,因此可以在

“pre”

类型的

Filter

中自行实现上述三种过滤器。但是限流作为网关最基本的功能,

Spring Cloud Gateway

官方就提供了

RequestRateLimiterGatewayFilterFactory

这个类,适用

Redis

Lua

脚本实现了令牌桶链接的方式。具体实现逻辑在

RequestRateLimiterGatewayFilterFactory

类中,

Lua

脚本在如下图所示的文件夹中:

以案例的形式来讲解如何在

SpringCloud Gateway

中使用内置的限流过滤器工厂来实现限流。首先在工程的

pom

文件中引入

Gateway

的起步依赖和

Redis

Reactive

依赖,代码如下:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifatId>spring-boot-starter-data-redis-reactive</artifactId></dependency>

在配置文件中做以下的配置:

server:port:8081spring:cloud:gateway:routes:-id: limit_route
        uri: lb://PRODUCTCENTOR   # PRODUCTCENTOR是注册到注册中心的服务名,格式为 lb://服务名predicates:- Path=/**filters:- StripPrefix=1
        -name: RequestRateLimiter  #拦截器,会对上述的请求进行拦击args:key-resolver:'#{@hostAddrKeyResolver}'redis-rate-limiter.replenishRate:1redis-rate-limiter.burstCapacity:3application:name: gateway-limiter
  redis:host: localhost
    port:6379database:0

过滤器

StripPrefix

,作用是去掉请求路径的最前面

n

个部分截取掉。

StripPrefix=1

就代表截取路径的个数为

1

,比如前端过来请求

/test/good/1/view

,匹配成功后,路由到后端的请求路径就会变成

http://localhost:8888/good/1/view

在上面的配置文件,指定程序的端口为

8081

,配置了

Redis

的信息,并配置了

RequestRateLimiter

的限流过滤器,该过滤器需要配置三个参数:
【1】

burstCapacity

:令牌桶总容量。
【2】

replenishRate

:令牌桶每秒填充平均速率。
【3】

key-resolver

:用于限流的键的解析器的

Bean

对象的名字。它使用

SpEL

表达式根据

#{@beanName}

Spring

容器中获取

Bean

对象。

KeyResolver

需要实现

resolve

方法,比如根据

Hostname

进行限流,则需要用

hostAddress

去判断。实现完

KeyResolver

之后,需要将这个类的

Bean

注册到

Ioc

容器中。

publicclassHostAddrKeyResolverimplementsKeyResolver{@OverridepublicMono<String>resolve(ServerWebExchange exchange){returnMono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());}}@BeanpublicHostAddrKeyResolverhostAddrKeyResolver(){returnnewHostAddrKeyResolver();}

可以根据

URL

去限流,这时

KeyResolver

代码如下:

publicclassUriKeyResolverimplementsKeyResolver{@OverridepublicMono<String>resolve(ServerWebExchange exchange){returnMono.just(exchange.getRequest().getURI().getPath());}}@BeanpublicUriKeyResolveruriKeyResolver(){returnnewUriKeyResolver();}

也可以以用户的维度去限流:

// 省略部分代码@BeanKeyResolveruserKeyResolver(){return exchange ->Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));}

jmeter

进行压测,配置

10 thread

去循环请求

lcoalhost:8081

,循环间隔

1s

。从压测的结果上看到有部分请求通过,由部分请求失败。通过

redis

客户端去查看

redis

中存在的

key

。如下:

127.0.0.1:6379> keys *
1>"request_rate_limiter.<127.0.0.1>.timestamp"2>"request_rate_limiter.<127.0.0.1>.tokens"

可见,

RequestRateLimiter

是使用

Redis

来进行限流的,并在

redis

中存储了

2

key

。关注这两个

key

含义可以看

lua

源代码。

六、Zuul 与 Spring Cloud Gateway 比较

优点缺点Gateway1、线程开销小2、使用轻量级 Netty 异步IO实现通信3、支持各种长连接,WebSocket4、Spring 官方推荐,重点支持,功能较 Zuul更丰富,支持限流监控等Zuul1、编码模型简单2、开发调试运维简单
**

Zuul

Gateway

压测结果:** 休眠时间模仿后端请求时间,**线程数

2000

**,请求时间

360

秒=

6

分钟。配置情况:

Gateway

默认配置,

Zuul

网关的

Tomcat

最大线程数为

400

hystrix

超时时间为

100000


休眠时间测试样本,单位=个Zuul/Gateway平均响应时间,单位=毫秒Zuul/Gateway99%响应时间,单位=毫秒Zuul/Gateway错误次数,单位=个Zuul/Gateway错误比例Zuul/Gateway休眠100ms294134/10593212026/5466136/1774104/00.04%/0%休眠300ms101194/3999095595/148915056/16901114/01.10%/0%休眠600ms51732/20126211768/297527217/32032476/04.79%/0%休眠1000ms31896/12095619359/491446259/51153598/011.28%/0%

测试结果:

Gateway

在高并发和后端服务响应慢的场景下比

Zuul

的表现要好

七、SpringCloud GateWay 与 Nginx 组合使用

因为和

GateWay

相关所以这里介绍一下

Nginx

Spring Cloud Gateway

可以组合使用,以实现高效的负载均衡和网关功能。

Nginx

通常用于处理静态内容、

SSL

终止、负载均衡等,而

Spring Cloud Gateway

主要用于动态路由、过滤和服务网关功能。下面是一个基本的配置示例,展示了如何将

Nginx

Spring Cloud Gateway

结合使用。这里主要说下

Nginx

中的配置:

配置

Nginx

作为反向代理,将外部请求转发到

Spring Cloud Gateway

**

nginx.conf

配置示例**

http {
    upstream gateway {# 定义一个名为 gateway 的上游服务器组,包含 Spring Cloud Gateway 的地址(localhost:8080)。
        server localhost:8080;
    }

    server {# 配置 Nginx 服务器块。
        listen 80;  # 监听80端口。

        location / {#  匹配所有请求。
            proxy_pass http://gateway; # http://gateway: 将请求转发到上游服务器组 gateway。
            proxy_set_header Host $host; # 设置一些头信息,用于保持客户端信息。
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }}}

工作流程:
【1】客户端请求:客户端发送请求到

Nginx


【2】

Nginx

转发:

Nginx

接收到请求后,将其转发到

Spring Cloud Gateway


【3】

Spring Cloud Gateway

路由:

Spring Cloud Gateway

根据配置文件中的路由规则,将请求转发到对应的微服务。
【4】微服务响应:微服务处理请求并返回响应,通过

Spring Cloud Gateway

Nginx

返回给客户端。

SpringCloud GateWay 与 Nacos 组合使用

bootstrap.yml

用于配置

Nacos

的基本信息:

spring:application:name: gateway-service #设置网关服务的名称。cloud:nacos:discovery:server-addr: ${NACOS_SERVER_ADDR:localhost:8848}#Nacos 服务发现的地址。 ${NACOS_SERVER_ADDR}: 使用环境变量配置 Nacos 服务器地址,方便在不同环境中切换。config:server-addr: ${NACOS_SERVER_ADDR:localhost:8848}#Nacos 配置管理的地址。file-extension: yaml

可以将

Spring Cloud Gateway

的配置放在

Nacos

配置中心,这样可以实现配置的集中管理和动态更新。在

Nacos

中创建配置:
【1】登录

Nacos

控制台。
【2】创建一个新的配置,例如

gateway-service.yaml


【3】在配置文件中添加路由规则,例如:

spring:cloud:gateway:routes:-id: example-service
          uri: lb://example-service
          predicates:- Path=/example/**-id: another-service
          uri: lb://another-service
          predicates:- Path=/another/**

本文转载自: https://blog.csdn.net/zhengzhaoyang122/article/details/142744233
版权归原作者 程序猿进阶 所有, 如有侵权,请联系我们删除。

“深入探索Spring Cloud Gateway:微服务网关的最佳实践”的评论:

还没有评论