flask_limiter 实践与原理解析
背景:
一个强大的软件产品是由许多不同的组件结合完成的, 其中在每一个产品中离不开的就是api系统, api系统在整个产品中居于中枢地位, 包括系统内部组件, 及客户对产品的对接都要与api打交道, 这就需要最大限度的提高api的处理能力, 并且防范无效请求, 还有黑客的恶意攻击。
限流可以在nginx层进行设置, 当然在api业务层做更加灵活、简便, 今天就讲述下flask_limiter在flask应用中的使用。
flask_limiter介绍
flask_limiter 是对limiter库包装成flask的扩展组件。 提供对flask访问路径的频率限制。 对于其后端数据保存有三种方式可供选择:
redis, memory, memcache。
简单应用步骤
1. 初始一个Limiter对象, 相关参数后续会做详细介绍, 图中的key_func参数是你的限制策略关键字, 上图是对访问的源IP做了限制,
在我们的业务中是针对于客户ID做了访问限制, 这个根据业务需要自己决定, default_limits 是具体的限制策略内容也就是频率, 这个配置是全局配置会对flask的所有url生效
2. 在每个url的view中添加decorator, 上图出现了两种limiter.limit 设定限制的频率, 此处的优先级高于default_limits,
limiter.exempt 屏蔽限制策略。
频率限制的几种表现形式
10 per hour
10/hour
10/hour;100/day;2000 per year
100/day, 500/7days
10 per hour 以 'per' 为分隔符; 10/hour 以'/'做分隔符;10/hour;100/day;2000 per year 以‘;’分隔设置多个,
100/day, 500/7days 可以添加复数
装饰器
Limiter.limit()
1. 单个
@app.route("....")
@limiter.limit("100/day;10/hour;1/minute")
def my_route()
2. 多个
@app.route("....")
@limiter.limit("100/day")
@limiter.limit("10/hour")
@limiter.limit("1/minute")
def my_route():
...
3. 对单个url指定策略key值
def my_key_func():
...
@app.route("...")
@limiter.limit("100/day", my_key_func)
def my_route():
...
4. 动态获取频率限制策略(在策略需要从数据库或者远程api获取的情况下使用)
def rate_limit_from_config():
return current_app.config.get("CUSTOM_LIMIT", "10/s")
@app.route("...")
@limiter.limit(rate_limit_from_config)
def my_route():
...
5. 一些特定情况下屏蔽策略(比如是超级用户请求)
@app.route("/expensive")
@limiter.limit("100/day", exempt_when=lambda: current_user.is_admin)
def expensive_route():
...
Limiter.shared_limit() 共享限制策略
1. 定义
mysql_limit = limiter.shared_limit("100/hour", scope="mysql")
@app.route("..")
@mysql_limit
def r1():
...
@app.route("..")
@mysql_limit
def r2():
...
2. 动态共享限制策略(scope不指定的时候默认是endpoint_name-‘%s.%s’ %(obj.__module__, obj.__name__))
def host_scope(endpoint_name):
return request.host
host_limit = limiter.shared_limit("100/hour", scope=host_scope)
@app.route("..")
@host_limit
def r1():
...
@app.route("..")
@host_limit
def r2():
...
3. tip
与单个limiter一样可以叠加多个share_limiter装饰器
可接受key_function参数
也可结束动态的策略配置函数, 与limiter一致
Limiter.exempt() (屏蔽策略)
Limiter.request_filter() (符合条件的屏蔽策略)
@limiter.request_filter
def header_whitelist():
return request.headers.get("X-Internal", "") == "true"
@limiter.request_filter
def ip_whitelist():
return request.remote_addr == "127.0.0.1"
配置详解
RATELIMIT_DEFAULT 默认策略, 逗号分隔('1/minute,100/hour'), 只要存在其他配置会失效
RATELIMIT_APPLICATION 应用级别的策略, 逗号分隔('1/minute,100/hour'), 不会被覆盖, 存储的key的scope为global
RATELIMIT_STORAGE_URL memory:// or redis://host:port or memcached://host:port, 后端存储数据的方式。
RATELIMIT_STORAGE_OPTIONS dict类型,limits.storage.Storage子类, 比如 {"redis": RedisStorage}。
RATELIMIT_STRATEGY fixed-window 或者 fixed-window-elastic-expiry 或者 moving-window, 后续对三种模式详细讲解。
RATELIMIT_ENABLED 是否应用速率限制策略, 默认True
RATELIMIT_HEADERS_ENABLED 是否返回速率限制的相关信息到reponse header中默认False
X-RateLimit-Reset 重置时间戳, = time.time() + get_expiry -策略的执行周期单位‘秒’
X-RateLimit-Remaining 活跃的请求数量
X-RateLimit-Limit 策略内请求个数限制
Retry-After 到重置时间的秒数。
RATELIMIT_HEADER_LIMIT,RATELIMIT_HEADER_RESET,RATELIMIT_HEADER_REMAINING,RATELIMIT_HEADER_RETRY_AFTER对应配置上边的header的键值字符串。
RATELIMIT_SWALLOW_ERRORS 默认False即可
RATELIMIT_IN_MEMORY_FALLBACK 后端存储异常使用的策略配置
RATELIMIT_KEY_PREFIX 存储key的前缀配置
三大策略算法解析
1.fixed-window (固定窗口)
以5/minute为例
由图可以看出, 第一次hit到url时添加key, 类似"LIMITER/key_function/live_api.live_numbers/5/1/minute"的键值,默认值为0, 在周期内请求超过5次, 后续请求全部返回429, 第二个周期开始时key会被清除, 如此往复。
缺点:
大量请求可以集中在一个周期的结束时,及下一个周期的开始, 黑客可以集中在这段时间攻击系统。
2. fixed-window-elastic-expiry(固定窗口的伸展周期)
与fixed-window区别在于, 每次hit到url时key的过期时间都会延续一个周期, 如果在达到速率限制条件时持续请求将永远不会成功, 必须停止请求超过一周期时间, 才可以继续请求。
3. moving-window(移动窗口)
以5/minute为例
移动窗口策略在内部维护一个列表[],当列表的长度小于5时, 就不会满足速率限制条件, 当长度大于5时, 比如[t6, t5, t4, t3, t2, t1], 此时就会拿出t1对象, 当t1的请求时的时间戳大于等于now-expiry时拒绝请求,在t1的存在于列表中的时间大于一个周期就会被清除。
由此可以看出此策略动态维护一个列表, 比如如果策略为2000/minite 那这样可能是长度为2000的列表长度维护, 相比前两种会有更大的内存花销。
posted on 2020-04-09 21:14 ExplorerMan 阅读(679) 评论(0) 编辑 收藏 举报