1.实现Gateway网关限流

SpringCloudGateway自带了 RequestRateLimiterGatewayFilterFactory 限流方案,依赖redis与内置的RedisRateLimiter过滤器进行限流操作,默认限流算法为令牌桶算法。

第一步:引入redis依赖

引入如下redis依赖或其他依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

引入依赖后配置redis:

  redis:
    host: 127.0.0.1
    port: 6379

确保可以访问即可!

第二步:注入KeyResolver

@Configuration
public class GatewayResolver {
  @Bean("keyResolver")
  public KeyResolver hostAddrKeyResolver() {
    return exchange -> {
      String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
      return Mono.just(ip);
    };
  }
}

以上实例为根据IP进行限流,也可根据其他限流,保证Mono.just()中的参数为限流依据即可。

比如:根据url限流

@Configuration
public class GatewayResolver {
  @Bean("keyResolver")
  public KeyResolver hostAddrKeyResolver() {
    return exchange -> {
      String url = exchange.getRequest().getPath().toString();
      return Mono.just(url);
    };
  }
}

第三步:编写配置文件

    - id: test
      uri: lb://test-service
      predicates:
        - Path=/test/**
      filters:
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@keyResolver}'
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 3

其中:

- redis-rate-limiter.replenishRate字段为令牌桶恢复速度,即每秒访问个数
- redis-rate-limiter.burstCapacity字段为令牌桶大小,即峰值流量来临时最大可访问数

注意:

  • 那个服务需要限流就在那个服务下加入filter的配置即可,如果不需要限流则不需要添加

  • name字段必须为RequestRateLimiter

  • key-resolver参数对应注入到Spring中Bean的名称。

启动测试:

在这里插入图片描述

同一时间多次访问后出现报错提示!

2.返回自定义异常

以下内容参考:https://youbl.blog.csdn.net/article/details/115178343

目前限流为正常,但无法返回自定义数据,而是报错429异常,而且并没有相应配置改变错误处理方式,只能修改默认代码,发现限流过滤器编写在RequestRateLimiterGatewayFilterFactory中,Gateway中配置的RequestRateLimiter正是此过滤器去掉后缀后的结果,所以只需重写此过滤器即可。

重写默认限流过滤器:

先重写过滤器

import com.alibaba.fastjson.JSONObject;
import com.mgmiot.dlp.component.commonbase.model.ResponseCode;
import com.mgmiot.dlp.component.commonbase.model.ResponseVO;
import com.mgmiot.dlp.component.commonbase.utils.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
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.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.Map;

@Slf4j
@Component
public class GatewayRequestRateLimiterGatewayFilterFactory extends RequestRateLimiterGatewayFilterFactory {

  private final RateLimiter defaultRateLimiter;

  private final KeyResolver defaultKeyResolver;

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

  @Override
  public GatewayFilter apply(Config config) {
    KeyResolver resolver = getOrDefault(config.getKeyResolver(), defaultKeyResolver);
    RateLimiter<Object> limiter = getOrDefault(config.getRateLimiter(), defaultRateLimiter);
    return (exchange, chain) -> resolver.resolve(exchange).flatMap(key -> {
      String routeId = config.getRouteId();
      if (routeId == null) {
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        routeId = route.getId();
      }
      String finalRouteId = routeId;
      return limiter.isAllowed(routeId, key).flatMap(response -> {
        for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
          exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
        }
        if (response.isAllowed()) {
          return chain.filter(exchange);
        }
        log.warn("已限流: {}", finalRouteId);
        ServerHttpResponse httpResponse = exchange.getResponse();
        //修改code为500
        httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        if (!httpResponse.getHeaders().containsKey("Content-Type")) {
          httpResponse.getHeaders().add("Content-Type", "application/json");
        }
        //此处无法触发全局异常处理,手动返回
        DataBuffer buffer = httpResponse.bufferFactory().wrap(("{\n"
            + "  \"code\": \"1414\","
            + "  \"message\": \"服务器限流\","
            + "  \"data\": \"Server throttling\","
            + "  \"success\": false"
            + "}").getBytes(StandardCharsets.UTF_8));
        return httpResponse.writeWith(Mono.just(buffer));
      });
    });
  }

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

然后将配置文件配置的filtername配置为重写后的过滤器:

    - id: test
      uri: lb://test-service
      predicates:
        - Path=/test/**
      filters:
        - name: GatewayRequestRateLimiter #此处修改
          args:
            key-resolver: '#{@keyResolver}'
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 3

测试代码:

在这里插入图片描述

成功返回自定义内容!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐