1. 程式人生 > 實用技巧 >Gateway Redis令牌桶請求限流過濾器

Gateway Redis令牌桶請求限流過濾器

spring cloud gateway預設基於redis令牌桶演算法進行微服務的限流保護,採用RateLimter限流演算法來實現。

1.引入依賴包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</
groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> </dependency> <dependency
> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</
artifactId> </dependency>

2、yml中配置redis

spring:
  application:
    name: mima-cloud-gateway
  redis:
    database: 1
    host: localhost
    port: 6379
    password:


3、配置KeyResolver——RateLimiteConfig.java(介面限流/ip限流/使用者限流)

package com.mkevin.gateway.config;

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

/**
 * 限流配置KeyResolver——有三種寫法(介面限流/ip限流/使用者限流)
 */
@Configuration
public class RateLimiteConfig {

    /**
     * 介面限流:根據請求路徑限流
     * @return
     */
    /*
         如果不使用@Primary註解,會報如下錯誤,需要注意
    Description:
    Parameter 1 of method requestRateLimiterGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a single bean, but 3 were found:
            - pathKeyResolver: defined by method 'pathKeyResolver' in class path resource [com/mkevin/gateway/config/RateLimiteConfig.class]
            - ipKeyResolver: defined by method 'ipKeyResolver' in class path resource [com/mkevin/gateway/config/RateLimiteConfig.class]
            - userKeyResolver: defined by method 'userKeyResolver' in class path resource [com/mkevin/gateway/config/RateLimiteConfig.class]
    Action:
    Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier
    to identify the bean that should be consumed
    */
    @Bean
    @Primary
    public KeyResolver pathKeyResolver() {
        //寫法1
       return exchange -> Mono.just(
                exchange.getRequest()
                        .getPath()
                        .toString()
        );

        /*
        //寫法2
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest()
                        .getPath()
                        .toString());
            }
        };
        */
    }

    /**
     * 根據請求IP限流
     * @return
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest()
                        .getRemoteAddress()
                        .getHostName()
        );
    }

    /**
     * 根據請求引數中的userId進行限流
     * 
     * 請求地址寫法:http://localhost:8801/rate/123?userId=lisi
     * 
     * @return
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest()
                        .getQueryParams()
                        .getFirst("userId")
        );
    }
}

4、yml中配置spring.cloud.gateway.routes.filters

spring:
  cloud:
    gateway:
      routes:
        - id: rate-limit-demo
          uri: lb://mima-cloud-producer
          predicates:
            #訪問路徑:http://localhost:8801/rate/123
            - Path=/rate/**
          filters:
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充平均速率, 允許使用者每秒處理多少個請求。
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的容量,允許在1s內完成的最大請求數。
                redis-rate-limiter.burstCapacity: 2
                # 使用SpEL表示式從Spring容器中獲取Bean物件, 檢視RateLimiteConfig實現類中的方法名
                key-resolver: "#{@pathKeyResolver}"
                #key-resolver: "#{@ipKeyResolver}"
                #key-resolver: "#{@userKeyResolver}"

5、訪問地址測試
http://localhost:8801/rate/123

當F5頻繁重新整理請求介面時,控制檯會報429錯誤狀態碼,提示我們請求過多,如下:

[開始]請求路徑:/rate/123
[應答]請求路徑:/rate/123耗時:2ms
2020-09-08 16:23:27.253 DEBUG 18512 --- [ioEventLoop-4-1] o.s.w.s.adapter.HttpWebHandlerAdapter    : [62eb90e0] Completed 429 TOO_MANY_REQUESTS
2020-09-08 16:23:27.394 DEBUG 18512 --- [ctor-http-nio-2] o.s.w.s.adapter.HttpWebHandlerAdapter    : [62eb90e0] HTTP GET "/rate/123"
corsFilter... run
[開始]請求路徑:/rate/123
[應答]請求路徑:/rate/123耗時:2ms
2020-09-08 16:23:27.397 DEBUG 18512 --- [ioEventLoop-4-1] o.s.w.s.adapter.HttpWebHandlerAdapter    : [62eb90e0] Completed 429 TOO_MANY_REQUESTS
2020-09-08 16:23:27.536 DEBUG 18512 --- [ctor-http-nio-2] o.s.w.s.adapter.HttpWebHandlerAdapter    : [62eb90e0] HTTP GET "/rate/123"
corsFilter... run

6、當發生限流時,會向redis中儲存兩個資料

127.0.0.1:1>keys *
 1)  "request_rate_limiter.{/rate/123}.timestamp"
 2)  "request_rate_limiter.{/rate/123}.tokens"
 
127.0.0.1:1>keys *
 1)  "request_rate_limiter.{0:0:0:0:0:0:0:1}.timestamp"
 2)  "request_rate_limiter.{0:0:0:0:0:0:0:1}.tokens"
 
127.0.0.1:1>keys *
 1)  "request_rate_limiter.{lisi}.timestamp"
 2)  "request_rate_limiter.{lisi}.tokens"

引數說明:
request_rate_limiter.{key}.timestamp:
儲存的是當前時間的秒數,也就是System.currentTimeMillis()/1000或者Instant.now().getEpochSecond()。

request_rate_limiter.{key}.tokens:
儲存的是當前這秒鐘對應的可用令牌數量

7、完整yml配置檔案

# 開啟resilience4j斷路器
# spring.cloud.circuitbreaker.resilience4j.enabled: true
# 設定hystrix斷路器超時時間
# hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 2000
spring:
  application:
    name: mima-cloud-gateway
  redis:
    database: 1
    host: localhost
    port: 6379
    password:
  cloud:
    gateway:
      routes:
        - id: rate-limit-demo
          uri: lb://mima-cloud-producer
          predicates:
            #訪問路徑:http://localhost:8801/rate/123
            - Path=/rate/**
          filters:
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充平均速率, 允許使用者每秒處理多少個請求。
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的容量,允許在1s內完成的最大請求數。
                redis-rate-limiter.burstCapacity: 2
                # 使用SpEL表示式從Spring容器中獲取Bean物件, 檢視RateLimiteConfig實現類中的同名方法
                #key-resolver: "#{@pathKeyResolver}"
                #key-resolver: "#{@ipKeyResolver}"
                #請求地址寫法:http://localhost:8801/rate/123?userId=lisi
                key-resolver: "#{@userKeyResolver}"

#  The RequestRateLimiter GatewayFilter Factory
#  The Redis RateLimiter
#  Modify a Request Body GatewayFilter Factory 測試版本,未來可能改動
#  Modify a Response Body GatewayFilter Factory 測試版本,未來可能改動

server:
  port: 8801
eureka:
  client:
    serviceUrl:
      #defaultZone: http://kevin:123456@localhost:8761/eureka/
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
    #instance-id: ${spring.application.name}:${spring.cloud.client.ip-address:}:${server.port}
    instance-id: ${spring.application.name}:${server.port}
debug: true
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
    shutdown: true