首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
拉钩
V2EX  ›  Python

最近写了一个 Python 的 redis rate limiter,在多线程和多进程踩到了大坑,向万能的 v2er 求助 onz

  •  
  •   soulomoon · 277 天前 · 1880 次点击
    这是一个创建于 277 天前的主题,其中的信息可能已经有所发展或是发生改变。

    soulomoon/python-throttle 在假如多线程和多进程测试前一切都是好好的, 感觉好开心(^・ω・^ )
    但是加入多线程,多进程测试后发现出现了 race condition😢
    但是写的时候我已经好好地去尝试规避这个问题了,找不到原因😫
    race condition 的问题出现在 test_limiter 中(;´༎ຶД༎ຶ`)

    IMAGE ALT TEXT HERE

    第 1 条附言  ·  277 天前
        def add_key(self, key, expired):
            """use lua script to avoid race condition"""
            multiply = self.redis.register_script(self.lua_incr)
            return multiply([key, expired])
    

    出现问题的地方在这儿0 - 0

    第 2 条附言  ·  277 天前

    test 不好意思 这个才是fixed windows的测试, 实际上,两种implementation都fail了

    8 回复  |  直到 2018-03-14 01:28:21 +08:00
        1
    holyghost   277 天前 via iPhone   ♥ 1
    手机上看的,看测试大概了解了思路,不成熟的想法

    如果你真要这么做,用 incr 保证线程安全
    另外,你没有考虑到滑动窗口的问题
    最后 token bucket 了解下?
        2
    soulomoon   277 天前
    @holyghost
    不好意思 我图放错了 0 0。
    这里我有两种 implementaion, 一个用 ordered set, 一个用 incr 写在 luascript,redis 官方是这么推荐的 0。
    都用 expire。
    token bucket 要单开 put token 的进程吧?
        3
    soulomoon   277 天前
    ```python
    lua_incr = """
    local current
    current = redis.call("incr",KEYS[1])
    if tonumber(current) == 1 then
    redis.call("expire",KEYS[1],KEYS[2])
    end
    return current-1
    """
    ```
    这个是我改过的 lua script,原版的在[这里]( https://redis.io/commands/incr)
        4
    holyghost   277 天前
    @soulomoon

    我的理解,incr 不需要放在 lua script 里面来保证单线程吧? incr 本身是有返回值的, 比较下返回值和 threshold 应该就可以了

    另外,粗略算的话,token bucket 不需要另外开进程,可以在消耗时顺便添加 token (当然了,这种实现需要带时间戳)
        5
    soulomoon   277 天前
    incr 放进去是为了和 expire 一起,保证 key 不会因为没有 set expire 而 leak @holyghost,现在我怀疑 redis-py run script 的特点,因为是通过 register 到远端,然后再通过 sha1 作为 key 执行,可能是多个 instance 同时执行了同一 script,获取到了相同的返回值。。。
    还有一个 sliding log 的 implementaion,pipeline 看 redis-py 的简介 0 0 理论上是一个 multi exce 的行为,0 0, 也是 fail。这样的:
    def add_key(self, key, expired):
    """use ordered set for counting keys get_set manner
    """
    now = time.time()
    with self.redis.pipeline() as session:
    session.zremrangebyscore(key, 0, now - expired)
    session.zrange(key, 0, -1)
    session.zadd(key, now, uuid.uuid4().hex)
    session.expire(key, expired)
    result = session.execute()
    return len(result[1])
        6
    soulomoon   277 天前
    *多个 instance 接受了同一个 script 的执行结果。。
        7
    soulomoon   277 天前
    发现即使是简单如
    def add_key(self, key, expired):
    return self.redis.incr(key)
    也会有同样的问题

    who's to blame....
        8
    soulomoon   277 天前
    发现问题了, 是我写 unittest 的问题,interleaving 多个不同时限的 limiter 到相同的 key 中,当然会出现不同的结果 ozn
    好开心解决了,原来不是我 implementaion 的问题是我的测试的问题,看来要好好学习测试, 最后谢谢 @holyghost 的回答。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2228 人在线   最高记录 4019   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.1 · 19ms · UTC 10:02 · PVG 18:02 · LAX 02:02 · JFK 05:02
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1