SpringCloudGateway基于redis令牌桶限流

令牌桶算法:

令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用
的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令
牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令
牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的
进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这
时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就
已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时
的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行

 

 


在SpringCloudGateway中提供了基于令牌桶的限流支持,基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory 实现。

在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。

 
 

(1)环境搭建

 <!-- 网关依赖 -->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- eureka网关-->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- Lombok -->
<dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.4</version>
       <scope>provided</scope>
</dependency>

<!--redis gateway令牌桶依赖 监控依赖-->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

 <!--redis gateway令牌桶依赖 -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

上面依赖中:

1.Gateway网关依赖则是我们要搭建一个网关的必需品

2.Eureka依赖则是因为我们的微服务对应的ip与端口众多,我们不可能一 一在配置文件中填写路径端口。
  而是直接采用Eureka客户端直接对微服务进行获取。这样在配置文件中就可以简化填写内容。也方便用户维护。
3.lombok依赖为我们的网关过滤器提供log的支持
4.redis监控依赖,方便我们对资源进行限流
5.redis令牌桶依赖。这是我们后续对于Gateway提供的令牌桶依赖进行支持。
 
 

(2)修改application.yml配置文件

server:
  port: 8001

spring:
  application:
    name: server-gateway #服务名称
  cloud:
    gateway:
      routes:
      - id: category-service
        uri: lb://service-category
        predicates:
        - Path=/category/**,/admin/category/**
        filters: #redis令牌桶限流功能
        - RewritePath=/admin/category/(?<segment>.*), /admin/category/$\{segment}
        - name: RequestRateLimiter
          args:
            # 使用SpEL从容器中获取对象
            key-resolver: '#{@pathKeyResolver}' #当需要用到这里的对象时需要去filter包下的KeyResolverConfiguration类中开启对应的限流bean
            # 令牌桶每秒填充平均速率
            redis-rate-limiter.replenishRate: 1
            # 令牌桶的总容量
            redis-rate-limiter.burstCapacity: 3
      - id: headline-service
        uri:  lb://service-headline
        predicates:
        - Path=/headline/**,/admin/headline/**
  redis:  #当需要用到redis令牌桶限流的时候开启
    port: 6379
    host: localhost

eureka: client: service-url: defaultZone: http://127.0.0.1:8000/eureka registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s instance: prefer-ip-address: true #使用ip注册 ip-address: 127.0.0.1

 在 application.yml 中添加了redis的信息,并配置了RequestRateLimiter的限流过滤器:

   burstCapacity:  令牌桶总容量。
   replenishRate:  令牌桶每秒填充平均速率。
   key-resolver:     用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

 

(3)配置KeyResolver

为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。
这里的KeyResolverConfiguration类对应的application.yml配置文件中的对应分解器 key-resolver
package com.kerry.gateway.filter;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

//用作gateway自带的redis令牌桶限流功能  可用sentinel替代

@Configuration
public class KeyResolverConfiguration {
    /**
     * /abc
     * /category
     * 基于请求路径的限流
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getPath().toString());
            }
        };
    }
    /**
     * 基于请求参数的限流
     *
     * http://localhost:8001/admin/category/demo.do/1
     * 当请求的链接未带参数时候会报错500
     */

//    @Bean
//    public KeyResolver paramResolver() {
//        return new KeyResolver() {
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
//                return Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
//            }
//        };
//    }
    /**
     * 基于请求ip地址的限流
     * 暂时未能使用
     */
//
//    @Bean
//   public KeyResolver ipKeyResolver() {
//        return new KeyResolver() {
//            @Override
//            public Mono<String> resolve(ServerWebExchange exchange) {
//                return Mono.just(exchange.getRequest().getHeaders().getFirst("X-Forwarded-For"));
//            }
//        };
//    }
    /**
     * 基于用户的限流
     */
   /* @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getQueryParams().getFirst("user")
        );
    }*/

}

 


 

此时我们的网关基于Redis令牌桶的限流功能就已经完成了。当我们访问/admin/category链接下的访问时,当刷新访问的数度足够快时便会促发我们的限流方式。

但是在使用网关提供的限流方式时候,我们会发现这种限流还是很麻烦的。首先在对限流进行配置的时候,我们需要去配置限流的规则分解器,其次还需要在application.yml

配置文件上对对应的资源进行配置上各种信息。如果说我们需要配置多个资源与对应的限流模式,那在配置文件上将会产生许许多多的信息,也不便于后期的代码阅读

其次它没有友好的限流提示而是直接报错429

 

 

对此我们可以采用下一篇章的内容进行替代:

SpringCloudGateway基于Sentinel的限流

 

posted @ 2022-04-20 08:23  _kerry  阅读(610)  评论(0编辑  收藏  举报