Redis实现多种限流算法

一 常见限流算法

1 固定窗口限流

每一个时间段计数器,当计数器达到阈值后拒绝,每过完这个时间段,计数器重置0,重新计数。

优点:实现简单,性能高;

缺点:明显的临界问题,限流不准确;

--KEYS[1]: 限流 key
--ARGV[1]: 阈值
--ARGV[2]: 时间窗口,计数器的过期时间
local rateLimitKey = KEYS[1];
local rate = tonumber(ARGV[1]);
local rateInterval = tonumber(ARGV[2]);

local allowed = 1;
-- 每次调用,计数器rateLimitKey的值都会加1
local currValue = redis.call('incr', rateLimitKey);

if (currValue == 1) then
--  初次调用时,通过给计数器rateLimitKey设置过期时间rateInterval达到固定时间窗口的目的
    redis.call('expire', rateLimitKey, rateInterval);
    allowed = 1;
else
--  当计数器的值(固定时间窗口内) 大于频度rate时,返回0,不允许访问
    if (currValue > rate) then
        allowed = 0;
    end
end
return allowed

2 滑动日志限流

记录每次请求时间戳,新请求到来后以该时间为时间窗口的结尾统计该时间窗口内请求数是否超过阈值。

优点:没有临界问题,限流较准确;

缺点:记录时间戳内存占用大,每次重新计算请求数,计算性能差;

--KEYS[1]: 限流器的 key
--ARGV[1]: 当前时间窗口的开始时间
--ARGV[2]: 请求的时间戳(也作为score)
--ARGV[3]: rate阈值
--ARGV[4]: 时间间隔
-- 1. 移除时间窗口之前的数据
redis.call('zremrangeByScore', KEYS[1], ARGV[1]-ARGV[4], ARGV[1])
-- 2. 统计当前元素数量
local res = redis.call('zcard', KEYS[1])
-- 3. 是否超过阈值
if (res == nil) or (res < tonumber(ARGV[3])) then
    -- 4、保存每个请求的时间搓
    redis.call('zadd', KEYS[1], ARGV[2], ARGV[2])
    return 1
else
    return 0
end

3 滑动窗口

3.1普通模式

假设n秒内最多处理b个请求。我们可以将n秒切分成每个大小为m毫秒得时间片。只有最新的时间片内缓存请求和时间戳(如图中1000ms-1200ms当前窗口),之前的时间片内只保留一个请求量的数字(如40,10,20,50,10)。这样可以大大优化存储。例如,如果我们有一个小时费率限制,我们可以为每分钟保留一个计数,并在收到计算限制的新请求时计算过去一小时内所有计数器的总和。每m毫秒滑动一次窗口,滑动一次统计前n秒各子块请求数之和进而判断当前子块窗口阈值。如1秒内允许通过的请求是200个,但是在这里我们需要把1秒的时间分成多格,假设分成5格(格数越多,流量过渡越平滑),每格窗口的时间大小是200毫秒,每个小窗口中的数字表示在这个窗口中请求数,所以通过观察上图,可知在当前窗口(1000ms-1200ms)只要超过110(200-(10+20+50+10))就会被限流。

优点:实现简单,相比较固定窗口,稍微可以降低临界问题;

缺点:临界问题依然存在;

4 漏桶限流

每一个请求到来就会向桶中添加一定的水量,桶底有一个孔,以恒定速度不断的漏出水;当一个请求过来需要向加水时,如果漏桶剩余容积不足以容纳添加的水量,就会触发拒绝策略。漏桶为空,为并发最大情况。

优点:避免激增流量;

缺点:对激增流量反应迟钝,不能高效地利用可用的资源。因为它只在固定的时间间隔放行请求,所以在很多情况下,流量非常低,即使不存在资源争用,也无法有效地消耗资源;实现复杂,内存占用大,性能差;

--参数说明:
--KEYS[1]: 限流器的 key
--ARGV[1]: 容量,决定最大的并发量
--ARGV[2]: 漏水速率,决定平均的并发量
--ARGV[3]: 一次请求的加水量
--ARGV[4]: 时间戳
local limitInfo = redis.call('hmget', KEYS[1], 'capacity', 'passRate', 'addWater','water', 'lastTs')
local capacity = limitInfo[1]
local passRate = limitInfo[2]
local addWater= limitInfo[3]
local water = limitInfo[4]
local lastTs = limitInfo[5]

--初始化漏斗
if capacity == false then
    capacity = tonumber(ARGV[1])
    passRate = tonumber(ARGV[2])
    --请求一次所要加的水量,一定不能大于容量值的
    addWater=tonumber(ARGV[3])
    --当前储水量,初始水位一般为0
    water = addWater
    lastTs = tonumber(ARGV[4])
    redis.call('hmset', KEYS[1], 'capacity', capacity, 'passRate', passRate,'addWater',addWater,'water', water, 'lastTs', lastTs)
    return 1
else
    local nowTs = tonumber(ARGV[4])
    --计算距离上一次请求到现在的漏水量 =  流水速度 *  (nowTs - lastTs)
    local waterPass = tonumber(ARGV[2] *  (nowTs - lastTs))
    --计算当前剩余水量   =  上次水量  - 时间间隔中流失的水量
    water = math.max(tonumber(0), tonumber(water - waterPass))
    --设置本次请求的时间
    lastTs = nowTs
	--判断是否可以加水   (容量 - 当前水量 >= 增加水量,判断剩余容量是否能够容纳增加的水量)
   if capacity - water >= addWater then
        -- 加水
        water = water + addWater
        -- 更新增加后的当前水量和时间戳
        redis.call('hmset', KEYS[1], 'water', water, 'lastTs', lastTs)
        return 1
    end
    -- 请求失败
    return 0
end

5 令牌桶限流

我们以恒定速率往令牌桶里加入令牌,令牌桶被装满时,多余的令牌会被丢弃。当请求到来时,会先尝试从令牌桶获取令牌(相当于从令牌桶移除一个令牌),获取成功则请求被放行,获取失败则阻塞或拒绝请求。那么当突发流量来临时,只要令牌桶有足够的令牌,就不会被限流。

优点:可以适应激增流量,通过业务高峰和负载情况调整令牌桶速率,较大利用率下消耗请求;

缺点:实现复杂,占用内存大,性能差。

-- 令牌桶限流算法实现
-- key:限流的key
-- reqTokens:一次请求所需要的令牌数阈值
-- passRate:生成令牌的速率
-- capacity:桶的大小
-- now:当前时间
-- return:0表示限流,1表示放行
local function token_bucket(key, reqTokens, passRate, capacity, now)
    local current_tokens = tonumber(redis.call('get', key) or '0')
    local last_refreshed = tonumber(redis.call('get', key .. ':last_refreshed') or '0')
    local time_passed = math.max(now - last_refreshed, 0)
    local new_tokens = math.floor(time_passed * passRate)

    if new_tokens > 0 then
        local tokens = math.min(current_tokens + new_tokens, capacity)
        redis.call('set', key, tokens)
        redis.call('set', key .. ':last_refreshed', now)
    end

    if current_tokens < reqTokens then
        redis.call('decr', key)
        return 1
    else
        return 0
    end
end
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis可以通过令牌桶算法、漏桶算法多种算法实现限流功能,以下是其中一种实现方式: 1. 令牌桶算法实现限流 令牌桶算法是一种基于令牌的限流算法,其原理是在令牌桶中放入一定数量的令牌,每次请求需要从令牌桶中获取令牌才能被处理,当令牌桶中的令牌数量耗尽时,新的请求就会被限流实现步骤: 1)设置一个定时器,定时向令牌桶中添加令牌。 2)每次请求需要从令牌桶中获取令牌,如果令牌桶中没有足够的令牌,则限流。 3)实现代码: ```python import redis import time class RedisLimit: def __init__(self, host, port, password, limit, interval): self.limit = limit self.interval = interval self.redis_conn = redis.StrictRedis(host=host, port=port, password=password, db=0) def is_limit_exceeded(self, key): current_time = time.time() pipeline = self.redis_conn.pipeline() pipeline.multi() pipeline.zadd(key, current_time, current_time) pipeline.zremrangebyscore(key, 0, current_time - self.interval) pipeline.zcard(key) count, *_ = pipeline.execute() return count > self.limit if __name__ == '__main__': redis_host = 'localhost' redis_port = 6379 redis_password = '' key = 'redis_limit' limit = 100 interval = 60 # 1分钟内最多访问100次 redis_limit = RedisLimit(redis_host, redis_port, redis_password, limit, interval) for i in range(200): if redis_limit.is_limit_exceeded(key): print('Limit exceeded') else: print('Access granted') ``` 该代码中使用了Redis的有序集合(sorted set)数据结构来实现令牌桶算法,其中: - `zadd(key, current_time, current_time)`:向有序集合`key`中添加一个`current_time`的元素,并将其分值(score)设置为`current_time`。 - `zremrangebyscore(key, 0, current_time - self.interval)`:删除有序集合`key`中分值小于等于`current_time - self.interval`的元素。 - `zcard(key)`:返回有序集合`key`的元素个数。 2. 漏桶算法实现限流 漏桶算法是一种基于漏桶的限流算法,其原理是在一个固定容量的漏桶中不断地积累请求,每次请求会从漏桶中流出一定的容量,当漏桶中的容量耗尽时,新的请求就会被限流实现步骤: 1)设置一个定时器,定时从漏桶中流出一定的容量。 2)每次请求需要向漏桶中添加请求,如果漏桶已满,则限流。 3)实现代码: ```python import redis import time class RedisLimit: def __init__(self, host, port, password, limit, interval): self.limit = limit self.interval = interval self.redis_conn = redis.StrictRedis(host=host, port=port, password=password, db=0) def is_limit_exceeded(self, key): current_time = time.time() pipeline = self.redis_conn.pipeline() pipeline.multi() pipeline.zadd(key, current_time, current_time) pipeline.zremrangebyscore(key, 0, current_time - self.interval) pipeline.zcard(key) count, *_ = pipeline.execute() return count > self.limit if __name__ == '__main__': redis_host = 'localhost' redis_port = 6379 redis_password = '' key = 'redis_limit' limit = 100 interval = 60 # 1分钟内最多访问100次 redis_limit = RedisLimit(redis_host, redis_port, redis_password, limit, interval) for i in range(200): if redis_limit.is_limit_exceeded(key): print('Limit exceeded') else: print('Access granted') ``` 该代码中使用了Redis的有序集合(sorted set)数据结构来实现漏桶算法,其中: - `zadd(key, current_time, current_time)`:向有序集合`key`中添加一个`current_time`的元素,并将其分值(score)设置为`current_time`。 - `zremrangebyscore(key, 0, current_time - self.interval)`:删除有序集合`key`中分值小于等于`current_time - self.interval`的元素。 - `zcard(key)`:返回有序集合`key`的元素个数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值