天天看點

Spring Cloud Gateway 網關限流一、背景二、實作功能三、網關層限流四、完成代碼五、參考文檔

Spring Cloud Gateway 限流

  • 一、背景
  • 二、實作功能
  • 三、網關層限流
    • 1、使用預設的redis來限流
      • 1、引入jar包
      • 2、編寫配置檔案
      • 3、網關正常響應
      • 4、網關限流響應
    • 2、自定義限流算法和限流key
      • 1、自定義限流key
      • 2、自定義限流算法
      • 3、配置檔案中的寫法
  • 四、完成代碼
  • 五、參考文檔

一、背景

在我們平時開發過程中,一般一個請求都是需要經過多個微服務的,**比如:**請求從A服務流過B服務,如果A服務請求過快,導緻B服務響應慢,那麼必然會導緻系統出現問題。因為,我們就需要有限流操作。

二、實作功能

  1. 提供自定義的限流key生成,需要實作

    KeyResolver

    接口。
  2. 提供預設的限流算法,實作實作

    RateLimiter

    接口。
  3. 當限流的key為空時,直接不限流,放行,由參數

    spring.cloud.gateway.routes[x].filters[x].args[x].deny-empty-key

    來控制
  4. 限流時傳回用戶端的相應碼有

    spring.cloud.gateway.routes[x].filters[x].args[x].status-code

    來控制,需要寫這個

    org.springframework.http.HttpStatus

    類的枚舉值。
  5. RequestRateLimiter

    隻能使用

    name || args

    這種方式來配置,不能使用簡寫的方式來配置。
    Spring Cloud Gateway 網關限流一、背景二、實作功能三、網關層限流四、完成代碼五、參考文檔
  6. RequestRateLimiter

    過濾器的

    redis-rate-limiter

    參數是在

    RedisRateLimiter

    CONFIGURATION_PROPERTY_NAME

    屬性配置的。構造方法中用到了。

三、網關層限流

限流的key 生成規則,預設是

PrincipalNameKeyResolver

來實作

限流算法,預設是

RedisRateLimiter

來實作,是令牌桶算法。

1、使用預設的redis來限流

Spring Cloud Gateway

中預設提供了

RequestRateLimiter

過濾器來實作限流操作。

1、引入jar包

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
           

2、編寫配置檔案

spring:
  application:
    name: gateway-9205
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8847
    gateway:
      routes:
        - id: user-provider-9206
          uri: lb://user-provider-9206
          predicates:
            - Path=/user/**
          filters:
            - RewritePath=/user(?<segment>/?.*), $\{segment}
            - name: RequestRateLimiter
              args:
                # 如果傳回的key是空的話,則不進行限流
                deny-empty-key: false
                # 每秒産生多少個令牌
                redis-rate-limiter.replenishRate: 1
                # 1秒内最大的令牌,即在1s内可以允許的突發流程,設定為0,表示阻止所有的請求
                redis-rate-limiter.burstCapacity: 1
                # 每次請求申請幾個令牌
                redis-rate-limiter.requestedTokens: 1
  redis:
    host: 192.168.7.1
    database: 12
    port: 6379
    password: 123456

server:
  port: 9205
debug: true
           
Spring Cloud Gateway 網關限流一、背景二、實作功能三、網關層限流四、完成代碼五、參考文檔

3、網關正常響應

Spring Cloud Gateway 網關限流一、背景二、實作功能三、網關層限流四、完成代碼五、參考文檔

4、網關限流響應

Spring Cloud Gateway 網關限流一、背景二、實作功能三、網關層限流四、完成代碼五、參考文檔

2、自定義限流算法和限流key

1、自定義限流key

編寫一個類實作 KeyResolver 接口即可。
package com.huan.study.gateway;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Optional;

/**
 * 限流的key擷取
 *
 * @author huan.fu 2021/9/7 - 上午10:25
 */
@Slf4j
@Component
public class DefaultGatewayKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        // 擷取目前路由
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getPath();
        log.info("目前傳回的uri:[{}]", uri);

        return Mono.just(Optional.ofNullable(route).map(Route::getId).orElse("") + "/" + uri);
    }
}

           

配置檔案中的寫法(部分)

spring:
  cloud:
    gateway:
      routes:
        - id: user-provider-9206
          filters:
            - name: RequestRateLimiter
              args:
                # 傳回限流的key
                key-resolver: "#{@defaultGatewayKeyResolver}"
              
           

2、自定義限流算法

編寫一個類實作 RateLimiter ,此處使用記憶體限流
package com.huan.study.gateway;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.ratelimit.AbstractRateLimiter;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
 * @author huan.fu 2021/9/7 - 上午10:36
 */
@Component
@Slf4j
@Primary
public class DefaultGatewayRateLimiter extends AbstractRateLimiter<DefaultGatewayRateLimiter.Config> {

    /**
     * 和配置檔案中的配置屬性相對應
     */
    private static final String CONFIGURATION_PROPERTY_NAME = "default-gateway-rate-limiter";

    private RateLimiter rateLimiter = RateLimiter.create(1);

    protected DefaultGatewayRateLimiter(ConfigurationService configurationService) {
        super(DefaultGatewayRateLimiter.Config.class, CONFIGURATION_PROPERTY_NAME, configurationService);
    }

    @Override
    public Mono<Response> isAllowed(String routeId, String id) {
        log.info("網關預設的限流 routeId:[{}],id:[{}]", routeId, id);

        Config config = getConfig().get(routeId);

        return Mono.fromSupplier(() -> {
            boolean acquire = rateLimiter.tryAcquire(config.requestedTokens);
            if (acquire) {
                return new Response(true, Maps.newHashMap());
            } else {
                return new Response(false, Maps.newHashMap());
            }
        });
    }

    @Getter
    @Setter
    @ToString
    public static class Config {
        /**
         * 每次請求多少個 token
         */
        private Integer requestedTokens;
    }
}

           

配置檔案中的寫法(部分)

spring:
  cloud:
    gateway:
      routes:
        - id: user-provider-9206
          filters:
            - name: RequestRateLimiter
              args:
                # 自定義限流規則
                rate-limiter: "#{@defaultGatewayRateLimiter}"
           

注意⚠️:

這個類需要加上

@Primary

注解。

3、配置檔案中的寫法

spring:
  application:
    name: gateway-9205
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8847
    gateway:
      routes:
        - id: user-provider-9206
          uri: lb://user-provider-9206
          predicates:
            - Path=/user/**
          filters:
            - RewritePath=/user(?<segment>/?.*), $\{segment}
            - name: RequestRateLimiter
              args:
                # 自定義限流規則
                rate-limiter: "#{@defaultGatewayRateLimiter}"
                # 傳回限流的key
                key-resolver: "#{@defaultGatewayKeyResolver}"
                # 如果傳回的key是空的話,則不進行限流
                deny-empty-key: false
                # 限流後向用戶端傳回的響應碼429,請求太多
                status-code: TOO_MANY_REQUESTS
                # 每次請求申請幾個令牌  default-gateway-rate-limiter 的值是在 defaultGatewayRateLimiter 中定義的。
                default-gateway-rate-limiter.requestedTokens: 1
server:
  port: 9205
debug: true
           

四、完成代碼

https://gitee.com/huan1993/spring-cloud-alibaba-parent/tree/master/gateway-redis-limiter

五、參考文檔

1、https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-redis-ratelimiter

繼續閱讀