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

2018-03-13 20:37:35 +08:00
 soulomoon

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

4028 次点击
所在节点    Python
8 条回复
holyghost
2018-03-13 21:29:09 +08:00
手机上看的,看测试大概了解了思路,不成熟的想法

如果你真要这么做,用 incr 保证线程安全
另外,你没有考虑到滑动窗口的问题
最后 token bucket 了解下?
soulomoon
2018-03-13 22:02:43 +08:00
@holyghost
不好意思 我图放错了 0 0。
这里我有两种 implementaion, 一个用 ordered set, 一个用 incr 写在 luascript,redis 官方是这么推荐的 0。
都用 expire。
token bucket 要单开 put token 的进程吧?
soulomoon
2018-03-13 22:06:44 +08:00
```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)
holyghost
2018-03-13 22:18:54 +08:00
@soulomoon

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

另外,粗略算的话,token bucket 不需要另外开进程,可以在消耗时顺便添加 token (当然了,这种实现需要带时间戳)
soulomoon
2018-03-13 22:44:48 +08:00
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])
soulomoon
2018-03-13 22:45:45 +08:00
*多个 instance 接受了同一个 script 的执行结果。。
soulomoon
2018-03-14 00:35:01 +08:00
发现即使是简单如
def add_key(self, key, expired):
return self.redis.incr(key)
也会有同样的问题

who's to blame....
soulomoon
2018-03-14 01:28:21 +08:00
发现问题了, 是我写 unittest 的问题,interleaving 多个不同时限的 limiter 到相同的 key 中,当然会出现不同的结果 ozn
好开心解决了,原来不是我 implementaion 的问题是我的测试的问题,看来要好好学习测试, 最后谢谢 @holyghost 的回答。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/437746

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX