• 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的列表长度维护, 相比前两种会有更大的内存花销。

  • 相关阅读:
    爱情五十三课,爱情银行
    [技术][JavaScript] <<JQuery 实战(Jquery in Action)>> Bear Bibeault & Yehuda Katz(美)
    [生活][健康] <<点食成金>> 范志红
    专一不是一辈子只喜欢一个人,是喜欢一个人的时候一心一意。
    【生活智慧】001.追求实在的东西
    【生活智慧】002.以上帝的"特选子民"自居
    【生活智慧】003.不能为了一顿鸡鸭鱼肉而让其他日子挨饿
    爱情五十四课,最需要的一刻
    爱情五十课,牵手的力量
    爱情五十五课,选择可以承担的
  • 原文地址:https://www.cnblogs.com/ExMan/p/12669622.html
Copyright © 2020-2023  润新知