flask_limiter 实践与原理解析

摘要:
可以在nginx层中设置流量限制。当然,在API业务层中执行它更为灵活和简单。今天我们将讨论烧瓶中限制器的使用。flask_限制器简介flask_限制器是将限制器库包装到烧瓶中的扩展组件。提供砂箱通道的频率限制。RATELIMIT_策略固定窗口或固定窗口弹性到期或移动窗口。稍后将详细解释这三种模式。

背景:
一个强大的软件产品是由许多不同的组件结合完成的, 其中在每一个产品中离不开的就是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的列表长度维护, 相比前两种会有更大的内存花销。

免责声明:文章转载自《flask_limiter 实践与原理解析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇PHP ftp_chmod() 函数Android(java)学习笔记47:通过反射获得构造方法并且使用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

阮一峰:Flex 布局教程

2009年,W3C提出了一个新的解决方案——Flex布局,它可以轻松、完整、快速地实现各种页面布局。柔性布局将成为未来布局的首选解决方案。本文介绍了它的语法,下一篇文章给出了常见布局的Flex编写方法。Flex是FlexibleBox的缩写,意思是“灵活布局”。它用于为盒形模型提供最大的灵活性。框{display:flex;}行中的元素也可以使用flex布局...

手把手教你安装Navicat——靠谱的Navicat安装教程

Navicat是用于MySQL连接和管理的轻量级工具。它非常容易使用,方便和简单。以下介绍了安装过程。最好找到navicat的破解版本,或者先下载,然后破解。...

Jumpserver-堡垒机

管理用户是资产的根用户,或具有NOPASSWD:ALLsudo权限的用户。Jumpserver使用此用户推送系统用户并获取资产硬件信息。使用Web终端连接到服务器后,我查看当前用户,显示为Jumpserver。下面是一个通过单击Docker直接安装koko的命令。当系统用户创建时,如果选择了自动推送,Jumpserver将使用...

PHP是怎么运行的

严格来说,与PHP相关的过程不需要手动启动。它们与Apache启动一起运行。当然,如果需要重新启动PHP服务,可以手动重新启动PHP。最后,它被移交给PHP内核的ZendEngine进行顺序执行。PHP在开始执行后将经历两个主要阶段:处理请求之前的开始阶段和请求之后的结束阶段。PHP的结束阶段分为两个阶段:禁用模块和关闭模块。...

Android 帧动画使用

本文介绍使用AnimationDrawable类来实现动画效果。oneshot="false",表示让动画一直循环播放下去。.backgroundasAnimationDrawableani.start()当动画正在播放时,调用start()方法是不会影响当前播放的。˃android:oneshot="true",动画播放1次后就会自行停止并保持在最后一帧。...

流控制、FlowControl

作用就是防止网络拥堵时导致的“丢包”问题,大致的工作原理就是当链路两端的设备有一端忙不过来了,他会给另外一端的设备发一个暂停发包的命令,通过这种方式来缓解压力,解决丢包问题。看上去流控制应该是个非常好的防止丢包的方法,但是为什么我们还要在无盘上关闭他呢?...