千家信息网

如何理解spring-cloud-gateway自带redis限流脚本

发表于:2025-01-16 作者:千家信息网编辑
千家信息网最后更新 2025年01月16日,这篇文章主要讲解了"如何理解spring-cloud-gateway自带redis限流脚本",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何理解spr
千家信息网最后更新 2025年01月16日如何理解spring-cloud-gateway自带redis限流脚本

这篇文章主要讲解了"如何理解spring-cloud-gateway自带redis限流脚本",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何理解spring-cloud-gateway自带redis限流脚本"吧!

  • filter入口: org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory#apply

  • 限流判断入口: org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter#isAllowed

@Override        @SuppressWarnings("unchecked")        public Mono isAllowed(String routeId, String id) {                if (!this.initialized.get()) {                        throw new IllegalStateException("RedisRateLimiter is not initialized");                }                Config routeConfig = loadConfiguration(routeId);                // How many requests per second do you want a user to be allowed to do?                int replenishRate = routeConfig.getReplenishRate();                // How much bursting do you want to allow?                int burstCapacity = routeConfig.getBurstCapacity();                try {                        List keys = getKeys(id);                        // The arguments to the LUA script. time() returns unixtime in seconds.                        List scriptArgs = Arrays.asList(replenishRate + "",                                        burstCapacity + "", Instant.now().getEpochSecond() + "", "1");                        // allowed, tokens_left = redis.eval(SCRIPT, keys, args)                        Flux> flux = this.redisTemplate.execute(this.script, keys,                                        scriptArgs);                        // .log("redisratelimiter", Level.FINER);                        return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))                                        .reduce(new ArrayList(), (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) {                        /*                         * We don't want a hard dependency on Redis to allow traffic. Make sure to set                         * an alert so you know if this is happening too much. Stripe's observed                         * failure rate is 0.01%.                         */                        log.error("Error determining if user allowed from redis", e);                }                return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));        }
  • lua脚本加载入口: org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration#redisRequestRateLimiterScript

@Bean        @SuppressWarnings("unchecked")        public RedisScript redisRequestRateLimiterScript() {                DefaultRedisScript redisScript = new DefaultRedisScript<>();                redisScript.setScriptSource(new ResourceScriptSource(                                new ClassPathResource("META-INF/scripts/request_rate_limiter.lua")));                redisScript.setResultType(List.class);                return redisScript;        }
  • request_rate_limiter.lua脚本

local tokens_key = KEYS[1]local timestamp_key = KEYS[2]--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)local rate = tonumber(ARGV[1])local capacity = tonumber(ARGV[2])local now = tonumber(ARGV[3])local requested = tonumber(ARGV[4])local fill_time = capacity/ratelocal ttl = math.floor(fill_time*2)--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)--redis.log(redis.LOG_WARNING, "ttl " .. ttl)local last_tokens = tonumber(redis.call("get", tokens_key))if last_tokens == nil then  last_tokens = capacityend--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)local last_refreshed = tonumber(redis.call("get", timestamp_key))if last_refreshed == nil then  last_refreshed = 0end--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)local delta = math.max(0, now-last_refreshed)--重点是这里,rate是相对于capacity而言,如果大于等于capacity,那么每秒的并发量就是capacity,--如果小于capacity,那么才会每秒固定添加rate个令牌到桶中。--正常限流建议设置小于capacity,否则当capacity瞬间用完,这个时候说明已经达到了系统最大并发阀值,--下一秒瞬间又恢复最大令牌桶阀值,速率过大反而起不到限流作用。local filled_tokens = math.min(capacity, last_tokens+(delta*rate))local allowed = filled_tokens >= requestedlocal new_tokens = filled_tokenslocal allowed_num = 0if allowed then  new_tokens = filled_tokens - requested  allowed_num = 1end--redis.log(redis.LOG_WARNING, "delta " .. delta)--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)redis.call("setex", tokens_key, ttl, new_tokens)redis.call("setex", timestamp_key, ttl, now)return { allowed_num, new_tokens }

感谢各位的阅读,以上就是"如何理解spring-cloud-gateway自带redis限流脚本"的内容了,经过本文的学习后,相信大家对如何理解spring-cloud-gateway自带redis限流脚本这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0