乐趣区

关于java:Spring-Cloud-Gateway-网关限流

一、背景

在咱们平时开发过程中,个别一个申请都是须要通过多个微服务的,比方:申请从 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 这种形式来配置,不能应用简写的形式来配置。
  6. RequestRateLimiter过滤器的 redis-rate-limiter 参数是在 RedisRateLimiterCONFIGURATION_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

3、网关失常响应

4、网关限流响应

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

退出移动版