A Redis based rate limiter implementation for Python
Project description
redis-limiter
基于redis的限流器
install
pip install redis-limiter
intro
陆陆续续写过不少基于redis的限流逻辑,看似简单,实际写下来还是有不少要留意的问题
以一个简单的功能为例:
限制每个用户一天内只能修改昵称3次
直观的想法很简单,伪代码如下:
key=user_id + date
if redis.get(key) >= 3:
return False
... operation logic ...
redis.incr(key)
然而仔细想下,事情没那么简单,上面的逻辑在并发请求的情况下,用户操作很容易会超出3个的限制;为解决这个问题,我们换个思路:
key = user_id + date
if redis.incr(key) > 3:
return False
...operation logic....
嗯,并发情况下不会超额了,问题是如果operation logic执行失败了呢,用户是不是就丢失了操作的配额? 这种情况是不是要把INCR的数值给减回来呢?
还有其他可能出现的问题:
- 执行中间出现了异常,已经执行的redis操作是不是要回滚?
- 3个限额,如果6个并发请求操作,前面3个进入operation logic处理中,后面3个返回失败,还是要等待? 难道要加个分布式锁,保证业务串行执行?
- 如果并发请求,后面的请求要block住等前面的处理结果的话,block怎么实现?
- 上面的例子是限额1天内,可以用当天的日志做key,如果限额周期不是1天呢,比如是100分钟,比如是10天,用什么做key?
嗯,基于redis的限流并没有直观的看上去那么简单
redis-limiter致力于解决上面的所有问题,提供一个通用的基于redis的限流器功能
使用redis-limiter实现上面的例子:
import redis-limiter
quota = 3
key_prefix = "operation_limit"
config = redis_limiter.Config(redis)
limiter = redis_limiter.FixedWindowLimiter(config, key_prefix, quota, '1d')
limit_key = user_id
target = limiter.target(limit_key)
target.acquire()
is_suc = False
try:
...operation logic...
is_suc = True
finally:
if is_suc:
target.apply()
else:
target.release()
留意上面的acquire,表示预扣一个限额指标,业务逻辑执行成功,则执行apply使用掉改指标,如果失败则release释放该指标; 如果acquire之后什么都不做,则经过一定的超时时间自动释放
acquire会先检查已扣的数量是否满额,如果是,则raise redis_limiter.AcquireFullException acquire之后会检查已扣的数量 + 预扣的数据,看是否满额,如果是,则raise redis_limiter.AcquirePendingFullException
对于raise redis_limiter.AcquirePendingFullException,还可以加上指定的block time, 即等待其他现成release指标,等待超过指定block time,才raise该异常; 在limiter对象创建时,使用block参数指定超时时间,单位是秒:
limiter = redis_limiter.FixedWindowLimiter(config, key_prefix, quota, '1d', block=10)
需要留意的时,block功能是使用redis的stream数据结构实现的,需要redis server的版本>=5.0; 如果使用的版本低于5.0则无法使用block功能,并在需要指定config参数: support_blocking=False
eg:
config = redis_limiter.Config(redis, support_blocking=False)
limiter支持使用with语句, 上面的demo中acquire之后的代码,可以改为如下写法:
with limiter.target(limit_key) as target:
...opeation logic...
这种写法,执行无异常会自动apply,异常则自动release,也可以在with的代码块中,手动apply/release:
with limiter.target(limit_key) as target:
is_suc = ...opeation logic...
if is_suc:
target.apply()
else:
target.release()
redis-limiter实现了4种类型的限流器
- IntervalLimiter
- FixedWindowLimiter
- SlideWindowLimiter
- Semaphore
时间周期Period
除了Semaphore,其他几种限流器都需要指定一个时间周期,比如10秒、1天。。。 reids_limiter使用了统一的字符串格式指定时间周期:数字+单位后缀,比如"10s"表示10秒,"10min"表示10分钟,所有的后缀如下:
- s: 秒
- min: 分钟
- h: 小时
- d: 天
- mo: 月
- y: 年
- w: 周
IntervalLimiter
init函数:
def __init__(self, config, key_prefix, quota, period, block=0, pending_ex=20)
IntervalLimiter实现指定时间间隔内不超过某限额的功能,时间精确到秒; 比如指定 period="1d", quota=3, 不是表示1个自然日内的限额是3,而是表示任何24*3600秒的时间段内限额为3
FixedWindowLimiter
init函数:
def __init__(self, config, key_prefix, quota, period, block=0, pending_ex=20)
FixedWindowLimiter实现基于固定时间窗口计数的限流功能; period统一按照自然时间,比“1min"表示每自然分钟,"1d"表示每自然日,"1mo"表示每个自然月,所以period会有些限制; 比如日期单位的数量只能是1,即只能是"1d", 不能是"2d", "10d"或其他任意大于1的数量,因为每月或每年的天数是不固定的,没法取key; 而秒单位的则可以取,"5s", "10s"... , 单要求必须是60(1分钟60秒)的约数 period数量完整的约束如下:
- s: 必须是60的约数
- min: 必须是60的约数
- h: 必须是24的约数
- d: 只能是1
- m: 必须是12的约数
- y: 只能是1
- w: 只能是1
SlideWindowLimiter
init函数:
def __init__(self, config, key_prefix, quota, period, block=0, pending_ex=20)
SlideWindowLimiter实现了基于滑动窗口计数的限流功能,计数的窗口数量不需要在参数中自定,而是自动计算: 取<=10并且最接近10的值
类似FixedWindowLimiter,period也会有一些约束, 假定我们把period数量值表示为$v,则规则如下表示:
- s: $v和60存在一个公约数,并且 $v/该公约数<=10
- min: $v和60存在一个公约数,并且 $v/该公约数<=10
- h: $v和24存在一个公约数,并且 $v/该公约数<=10
- d: $v取1-10
- mo: $v必须和12存在一个公约数,并且$v/该公约数<=10
- y: $v取1-10
- w: $v取1-10
Semaphore
init函数:
def __init__(self, config, key_prefix, quota, block=0, pending_ex=20)
Semaphore用于限定同一时间的并发处理数量,相比其他限流器有些不同:
- 不需要指定时间周期
- 没有apply操作,acquire之后只能release
- 不会 raise redis_limiter.AcquireFullException, 只会 raise redis_limiter.AcquirePendingFullException
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file redis-limiter-0.0.2.tar.gz.
File metadata
- Download URL: redis-limiter-0.0.2.tar.gz
- Upload date:
- Size: 12.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.0 CPython/3.9.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b1f9d1f6bc4b2163f32c42cfd736f89402b0cda99c615d744d943cb3b50598a
|
|
| MD5 |
aef30a323751d59c751899b7cf7921db
|
|
| BLAKE2b-256 |
f27116922a0519e9858b89ee99030bedeb2f0e819f9513b31951bfc80e023e3b
|
File details
Details for the file redis_limiter-0.0.2-py3-none-any.whl.
File metadata
- Download URL: redis_limiter-0.0.2-py3-none-any.whl
- Upload date:
- Size: 9.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.0 CPython/3.9.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed597d0b220282fd4fc3f138ca58ebeaf0fb7ca5c5d7aece9313e28fc64914aa
|
|
| MD5 |
8d6aff72c085982d6825a0f3c507b23f
|
|
| BLAKE2b-256 |
824aae1969ff099036233538ee7f83965515c10d7d381fc6b5c22604fe5edcf8
|