① pom 依赖
这里一定要注意,是网关引入的redis-reactive,背压模式的redis。
<!--基于 reactive stream 的redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>
② 配置按照请求IP 的限流
spring:cloud:gateway:routes:- id: requestratelimiter_routeuri: lb://pig-upmsorder: 10000predicates:- Path=/admin/**filters:- name: RequestRateLimiterargs:redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率redis-rate-limiter.burstCapacity: 3 # 令牌桶总容量key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean- StripPrefix=1
- 配置bean,多维度限流量的入口 对应上边key-resolver
/*** 自定义限流标志的key,多个维度可以从这里入手* exchange对象中获取服务ID、请求信息,用户信息等*/@BeanKeyResolver remoteAddrKeyResolver() {return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());}
OK 完成。
③ 压力测试
并发5个线程。

Redis 数据变化
我们使用redis的monitor 命令,实时查看redis 的操作情况。
会发现在redis中会操作两个key
- request_rate_limiter.{xxx}.timestamp
- request_rate_limiter.{xxx}.tokens

④ 实现原理

Spring Cloud Gateway 默认实现 Redis限流,如果扩展只需要实现ratelimter接口即可。
核心代码
- 判断是否取到令牌的实现,通过调用 redis的LUA 脚本。
public Mono<Response> isAllowed(String routeId, String id) {Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);int replenishRate = routeConfig.getReplenishRate();int burstCapacity = routeConfig.getBurstCapacity();try {List<String> keys = getKeys(id);returns unixtime in seconds.List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",Instant.now().getEpochSecond() + "", "1");// 这里是核心,执行redis 的LUA 脚本。Flux<List<Long>> flux =this.redisTemplate.execute(this.script, keys, scriptArgs);return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L))).reduce(new ArrayList<Long>(), (longs, l) -> {longs.addAll(l);return longs;}) .map(results -> {boolean allowed = results.get(0) == 1L;Long tokensLeft = results.get(1);Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));if (log.isDebugEnabled()) {log.debug("response: " + response);}return response;});}catch (Exception e) {log.error("Error determining if user allowed from redis", e);}return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));}
LUA 脚本

