关于pig:
基于Spring Cloud、oAuth2.0开发基于Vue前后分离的开发平台,支持账号、短信、SSO等多种登录,提供配套视频开发教程。
码云地址:https://gitee.com/log4j/pig
关于 Spring Cloud Gateway
SpringCloudGateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring云网关旨在提供一种简单而有效的路由API的方法。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
zuul如何实现多维度限流请参考我的博客
开始Gateway 限流
POM 依赖
1<!--spring cloud gateway依赖--> 2<dependency> 3 <groupId>org.springframework.cloud</groupId> 4 <artifactId>spring-cloud-starter-gateway</artifactId> 5</dependency> 6<!--基于 reactive stream 的redis --> 7<dependency> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-data-redis-reactive</artifactId> 10</dependency>
配置按照请求IP 的限流
1spring: 2 cloud: 3 gateway: 4 routes: 5 - id: requestratelimiter_route 6 uri: lb://pigx-upms 7 order: 10000 8 predicates: 9 - Path=/admin/** 10 filters: 11 - name: RequestRateLimiter 12 args: 13 redis-rate-limiter.replenishRate: 1 # 令牌桶的容积 14 redis-rate-limiter.burstCapacity: 3 # 流速 每秒 15 key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean 16 - StripPrefix=1
配置bean,多维度限流量的入口
1/** 2* 自定义限流标志的key,多个维度可以从这里入手 3* exchange对象中获取服务ID、请求信息,用户信息等 4*/ 5@Bean 6KeyResolver remoteAddrKeyResolver() { 7 return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); 8}
OK 完成。
压力测试
并发5个线程。

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

实现原理

Spring Cloud Gateway 默认实现 Redis限流,如果扩展只需要实现ratelimter接口即可。
RedisRateLimter 的核心代码,判断是否取到令牌的实现,通过调用 redis的LUA 脚本。
1public Mono<Response> isAllowed(String routeId, String id) { 2 Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig); 3 int replenishRate = routeConfig.getReplenishRate(); 4 int burstCapacity = routeConfig.getBurstCapacity(); 5 6 try { 7 List<String> keys = getKeys(id); 8 returns unixtime in seconds. 9 List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", 10 Instant.now().getEpochSecond() + "", "1"); 11 // 这里是核心,执行redis 的LUA 脚本。 12 Flux<List<Long>> flux = 13 this.redisTemplate.execute(this.script, keys, scriptArgs); 14 return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L))) 15 .reduce(new ArrayList<Long>(), (longs, l) -> { 16 longs.addAll(l); 17 return longs; 18 }) .map(results -> { 19 boolean allowed = results.get(0) == 1L; 20 Long tokensLeft = results.get(1); 21 22 Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft)); 23 24 if (log.isDebugEnabled()) { 25 log.debug("response: " + response); 26 } 27 return response; 28 }); 29 } 30 catch (Exception e) { 31 log.error("Error determining if user allowed from redis", e); 32 } 33 return Mono.just(new Response(true, getHeaders(routeConfig, -1L))); 34}
LUA 脚本
