Spring Cloud Gateway 使用 Redis 限流使用教程

从本文开始,笔者将总结 spring cloud 相关内容的教程

版本选择

为了适应 java8,笔者选择了下面的版本,后续会出 java17的以SpringBoot3.0.X为主的教程

SpringBoot 版本 2.6.5

SpringCloud 版本 2021.0.1

SpringCloudAlibaba 版本 2021.0.1.0

SpringCloudAlibaba github 版本说明截图

Spring Cloud Gateway 使用 Redis 限流使用教程_第1张图片

SpringCloud 官网:https://spring.io/projects/spring-cloud

Spring Cloud Alibaba 官网:https://sca.aliyun.com/zh-cn/

目录

1、环境准备

2、项目创建

3、测试限流

4、改进限流返回

5、项目代码


1、环境准备

本文讲解Spring Cloud Gateway 使用 Redis 限流,注册中心使用 Nacos

Macos 官网:https://nacos.io/zh-cn/index.html

Nacos 安装这里不做过多介绍,不了解的朋友可以参考

Nacos 单机安装:https://blog.csdn.net/wsjzzcbq/article/details/123916233

Nacos 集群安装:https://blog.csdn.net/wsjzzcbq/article/details/123956116

笔者使用 docker 开启 nacos 和 redis

Spring Cloud Alibaba  2021.0.1.0 版本对应的nacos版本是 1.4.2

笔者使用的 Naocs 版本是 2.0.0-bugfix,redis 版本是 7.0.6

Spring Cloud Gateway 使用 Redis 限流使用教程_第2张图片

2、项目创建

新建 maven 聚合项目 cloud-learn

最外层父工程 cloud-learn 的 pom.xml



    4.0.0

    com.wsjzzcbq
    cloud-learn
    1.0-SNAPSHOT
    
        gateway-learn
        consumer-learn
    
    pom

    
        
            naxus-aliyun
            naxus-aliyun
            https://maven.aliyun.com/repository/public
            
                true
            
            
                false
            
        
    

    
        org.springframework.boot
        spring-boot-starter-parent
        2.6.5
        
    

    
        2021.0.1
        2021.0.1.0
        2021.1
        2021.1
        3.1.1
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            

            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                ${spring-cloud-alibaba.version}
                pom
                import
            

            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-discovery
                ${alibaba-nacos-discovery.veriosn}
            

            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-config
                ${alibaba-nacos-config.version}
            

            
            
                org.springframework.cloud
                spring-cloud-starter-bootstrap
                ${spring-cloud-starter-bootstrap.version}
            

            
                com.alibaba.fastjson2
                fastjson2
                2.0.40
            
        
    

    
        
            org.projectlombok
            lombok
        
    


然后创建2个子工程 consumer-learn 和 gateway-learn

gateway-learn 中配置路由转发到 consumer-learn

consumer-learn 工程 pom.xml



    
        cloud-learn
        com.wsjzzcbq
        1.0-SNAPSHOT
    
    4.0.0

    consumer-learn

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        

        
        
            org.springframework.cloud
            spring-cloud-starter-loadbalancer
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

consumer-learn 工程 启动类

package com.wsjzzcbq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * ConsumerApplication
 *
 * @author wsjz
 * @date 2023/09/17
 */
@EnableFeignClients
@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

consumer-learn 工程 配置文件

spring.application.name=consumer-learn
server.port=8081
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.server-addr=192.168.31.152:8848
spring.cloud.nacos.discovery.namespace=public
logging.level.com.alibaba.cloudlearnconsumer.feign.ProducerService=DEBUG

consumer-learn 工程 controller

package com.wsjzzcbq.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * ConsumerController
 *
 * @author wsjz
 * @date 2023/09/17
 */
@RestController
public class ConsumerController {

    @RequestMapping("/name")
    public String user() {
        return "宝剑锋从磨砺出,梅花香自苦寒来";
    }
}

gateway-learn

gateway-learn 工程 pom.xml



    
        cloud-learn
        com.wsjzzcbq
        1.0-SNAPSHOT
    
    4.0.0

    gateway-learn

    
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

        
        
            org.springframework.cloud
            spring-cloud-starter-loadbalancer
        

        
        
        
            org.springframework.boot
            spring-boot-starter-data-redis-reactive
        

        
            com.alibaba.fastjson2
            fastjson2
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

gateway-learn 工程 启动类

package com.wsjzzcbq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * GatewayApplication
 *
 * @author wsjz
 * @date 2023/09/17
 */
@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

限流需要实现 KeyResolver 接口的 resolve 方法

在 resolve 方法中返回限流的维度,如请求路径、ip地址、请求参数等

笔者这里限流维度是请求路径

package com.wsjzzcbq.limit;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * ApiKeyResolver
 *
 * @author wsjz
 * @date 2023/09/17
 */
@Component
public class ApiKeyResolver implements KeyResolver {
    @Override
    public Mono resolve(ServerWebExchange exchange) {
        System.out.println("API限流: " + exchange.getRequest().getPath().value());
        return Mono.just(exchange.getRequest().getPath().value());
    }
}

gateway-learn 工程 配置文件

server:
  port: 9000
spring:
  application:
    name: gateway-learn

  redis:
    host: 192.168.31.152
    password: 123456
    timeout: 5000
    database: 0
  cloud:
    nacos:
      discovery:
        server-addr: http://192.168.31.152:8848
    gateway:
      routes:
        - id: consumer-learn
          uri:  lb://consumer-learn
          predicates:
            - Path=/cloudlearn/consumer/**
          filters:
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@apiKeyResolver}"
                redis-rate-limiter.replenishRate: 1 #生成令牌速率:个/秒
                redis-rate-limiter.burstCapacity: 2 #令牌桶容量
                redis-rate-limiter.requestedTokens: 1 #每次消费的Token数量,默认是 1
            - StripPrefix=2



配置说明:

RequestRateLimiter 是 gateway 提供的限流过滤器

#{@apiKeyResolver} 是笔者实现的 ApiKeyResolver

redis-rate-limiter.replenishRate 生成令牌的速率每秒几个

redis-rate-limiter.burstCapacity 令牌桶容量

redis-rate-limiter.requestedTokens 每次消费的令牌数量

当请求到网关以 /cloudlearn/consumer/  为开头前缀时,会路由到 consumer-learn 服务上

创建完成的项目结构

Spring Cloud Gateway 使用 Redis 限流使用教程_第3张图片

3、测试限流

分别启动 consumer-learn 和 gateway-learn

登录 Nacos 控制台查看

启动成功后,可在Naocs 控制台查看注册服务信息

浏览器访问测试限流:http://localhost:9000/cloudlearn/consumer/name

运行效果

Spring Cloud Gateway 使用 Redis 限流使用教程_第4张图片

可以看到当1秒钟内请求超过2次时会被限流

4、改进限流返回

上面代码实现了限流,但限流触发后返回的是429,不利于前端处理,这里我们可以在默认的限流过滤器基础上进行改进,自定义限流时的返回

新建 NewRequestRateLimiterGatewayFilterFactory 类

继承默认的 RequestRateLimiterGatewayFilterFactory

package com.wsjzzcbq.filter;

import com.alibaba.fastjson2.JSONObject;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map;

/**
 * NewRequestRateLimiterGatewayFilterFactory
 *
 * @author wsjz
 * @date 2023/09/17
 */
@Component
public class NewRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {

    private final RateLimiter defaultRateLimiter;

    private final KeyResolver defaultKeyResolver;

    private boolean denyEmptyKey = true;

    private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();

    public NewRequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
        super(defaultRateLimiter, defaultKeyResolver);
        this.defaultRateLimiter = defaultRateLimiter;
        this.defaultKeyResolver = defaultKeyResolver;
    }

    @Override
    public GatewayFilter apply(Config config) {
        System.out.println("过滤限流");
        KeyResolver resolver = (KeyResolver)this.getOrDefault(config.getKeyResolver(), this.defaultKeyResolver);
        RateLimiter limiter = (RateLimiter)this.getOrDefault(config.getRateLimiter(), this.defaultRateLimiter);
        boolean denyEmpty = (Boolean)this.getOrDefault(config.getDenyEmptyKey(), this.denyEmptyKey);
        HttpStatusHolder emptyKeyStatus = HttpStatusHolder.parse((String)this.getOrDefault(config.getEmptyKeyStatus(), this.emptyKeyStatusCode));
        return (exchange, chain) -> {
            return resolver.resolve(exchange).defaultIfEmpty("____EMPTY_KEY__").flatMap((key) -> {
                if ("____EMPTY_KEY__".equals(key)) {
                    if (denyEmpty) {
                        ServerWebExchangeUtils.setResponseStatus(exchange, emptyKeyStatus);
                        return exchange.getResponse().setComplete();
                    } else {
                        return chain.filter(exchange);
                    }
                } else {
                    String routeId = config.getRouteId();
                    if (routeId == null) {
                        Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                        routeId = route.getId();
                    }

                    return limiter.isAllowed(routeId, key).flatMap((response) -> {
                        Iterator var4 = response.getHeaders().entrySet().iterator();

                        while(var4.hasNext()) {
                            Map.Entry header = (Map.Entry)var4.next();
                            exchange.getResponse().getHeaders().add((String)header.getKey(), (String)header.getValue());
                        }

                        if (response.isAllowed()) {
                            return chain.filter(exchange);
                        } else {
                            ServerHttpResponse httpResponse = exchange.getResponse();
                            httpResponse.getHeaders().set("Content-Type", "application/json");
                            JSONObject json = new JSONObject();
                            json.put("code", 0);
                            json.put("msg", "当前请求人数较多,请稍后再访问");
                            DataBuffer dataBuffer = httpResponse.bufferFactory().wrap(json.toJSONString().getBytes(StandardCharsets.UTF_8));
                            return httpResponse.writeWith(Mono.just(dataBuffer));
                        }
                    });
                }
            });
        };
    }

    private  T getOrDefault(T configValue, T defaultValue) {
        return configValue != null ? configValue : defaultValue;
    }
}

修改配置文件

配置我们自定义的限流过滤器

server:
  port: 9000
spring:
  application:
    name: gateway-learn

  redis:
    host: 192.168.31.152
    password: 123456
    timeout: 5000
    database: 0
  cloud:
    nacos:
      discovery:
        server-addr: http://192.168.31.152:8848
    gateway:
      routes:
        - id: consumer-learn
          uri:  lb://consumer-learn
          predicates:
            - Path=/cloudlearn/consumer/**
          filters:
            - name: NewRequestRateLimiter
              args:
                key-resolver: "#{@apiKeyResolver}"
                redis-rate-limiter.replenishRate: 1 #生成令牌速率:个/秒
                redis-rate-limiter.burstCapacity: 2 #令牌桶容量
                redis-rate-limiter.requestedTokens: 1 #每次消费的Token数量
            - StripPrefix=2



重新启动 gateway-learn

请求测试

Spring Cloud Gateway 使用 Redis 限流使用教程_第5张图片

5、项目代码

码云地址:https://gitee.com/wsjzzcbq/csdn-blog/tree/master/cloud-learn

至此完

你可能感兴趣的:(spring,cloud,springcloud,gateway,redis,java,微服务,分布式)