Spring-Cloud-Gateway网关限流实现

6/29/2022 微服务

# SpringCloudGateway配合Sentinel实现限流

# 前述

Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。

官方文档 (opens new window)

gateway10

从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:

  • GatewayFlowRule:网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
  • ApiDefinition:用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /foo/**/baz/** 的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。

# 网关限流实现

# 网关模块中引入依赖

<!--nacos注册中心-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

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

<!--    spring cloud gateway整合sentinel的依赖-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<!--    sentinel的依赖-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 配置文件

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: cloud-gateway
  cloud:
    ## 整合sentinel,配置sentinel控制台的地址
    sentinel:
      # 取消控制台懒加载
      eager: true
      transport:
        dashboard: localhost:8080 # 配置Sentinel dashboard 地址
        port: 8719
      filter:
        enabled: false # 排除将网关限流
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
          lower-case-service-id: true #是否将服务名称转小写
      routes:
        - id: payment-service                  #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-payment-service      #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/**              # 断言,路径相匹配的进行路由
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

# 限流配置

经过上述两个步骤已经整合好了 Sentinel ,访问下接口 localhost:9527/payment/paymentSQL/1

然后通过 sentinel 控制台我们可以看见已经被监控到了,监控的路由是 payment-service

gateway11

我们点击 流控 新增一个限流规则

gateway12

上图中对 payment-service 这个路由做出了限流,QPS阈值为1。

此时快速访问:localhost:9527/payment/paymentSQL/1 可以观察到已经被限流了

gateway13

# 自定义限流异常信息

# 配置文件中配置

1.1、配置文件配置限流信息

spring:
  cloud:
    ## 整合sentinel,配置sentinel控制台的地址
    sentinel:
      #配置限流之后,响应内容
      scg:
        fallback:
          ## 两种模式,一种是response返回文字提示信息,
          ## 一种是redirect,重定向跳转,需要同时配置redirect(跳转的uri)
          mode: response
          ## 响应的状态
          response-status: 200
          ## 响应体
          response-body: '{"code": 200,"message": "请求失败,稍后重试!"}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14

1.2、重定向配置

spring:
  cloud:
    ## 整合sentinel,配置sentinel控制台的地址
    sentinel:
      #配置限流之后,响应内容
      scg:
        fallback:
          ## 两种模式,一种是response返回文字提示信息,一种是redirect,重定向跳转,需要同时配置redirect(跳转的uri)
          mode: redirect
          ## 跳转的URL
          redirect: http://www.baidu.com
1
2
3
4
5
6
7
8
9
10
11

# 代码实现

/**
 * @ClassName: GatewaySentinelConfig
 * @Description: 网关限流配置,使用Sentinel控制台来定义限流规则
 * @author: LiJunYi
 */
@Configuration
public class GatewaySentinelConfig
{
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelFallbackHandler sentinelGatewayExceptionHandler()
    {
        return new SentinelFallbackHandler();
    }

    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter()
    {
        return new SentinelGatewayFilter();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * @ClassName: SentinelFallbackHandler
 * @Description: 自定义限流异常处理
 * @author: LiJunYi
 */
public class SentinelFallbackHandler implements WebExceptionHandler
{
    private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
    {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] datas = "{\"status\":429,\"message\":\"请求超过最大数,请稍后再试\"}".getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
        return serverHttpResponse.writeWith(Mono.just(buffer));
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
    {
        if (exchange.getResponse().isCommitted())
        {
            return Mono.error(ex);
        }
        if (!BlockException.isBlockException(ex))
        {
            return Mono.error(ex);
        }
        return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
    }

    private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable)
    {
        return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
    }
}

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