如何在使用 Redis 实现流控制时防止争用情况



我们有一个服务器,如果它有太多用户同时登录(意味着间隔不到 7 秒),它会变得古怪。 一旦用户登录,就没有问题(同时登录一两个也不是问题,但是当10-20次尝试时,整个服务器就会陷入死亡螺旋式的叹息)。

我正在尝试编写一个页面,该页面将留住用户(显示动画倒计时等),并让他们间隔 7 秒。 算法很简单

  1. 获取上次登录时的时间戳 (t)
  2. 如果t+7是过去的,请开始登录并将now()存储为新的时间戳
  3. 如果将来t+7,请将其存储为新的时间戳,等到t+7,然后开始登录。

一个直接的python/redis实现是:

import time, redis
SLOT_LENGTH = 7  # seconds
now = time.time()
r = redis.StrictRedis()
# lines below contain race condition..
last_start = float(r.get('FLOWCONTROL') or '0.0')  # 0.0 == time-before-time
my_start = last_start + SLOT_LENGTH
r.set('FLOWCONTROL', max(my_start, now))  
wait_period = max(0, my_start - now)
time.sleep(wait_period)
# .. login

这里的竞争条件很明显,许多进程可以同时在my_start =线上。 如何使用 redis 解决此问题?

我已经尝试了 redis-py pipeline功能,但当然直到r.get()调用才获得实际值......

我会记录答案,以防其他人发现这个......

r = redis.StrictRedis()
with r.pipeline() as p:
    while 1:
        try:
            p.watch('FLOWCONTROL')  # --> immediate mode
            last_slot = float(p.get('FLOWCONTROL') or '0.0')
            p.multi()  # --> back to buffered mode
            my_slot = last_slot + SLOT_LENGTH
            p.set('FLOWCONTROL', max(my_slot, now))
            p.execute()  # raises WatchError if anyone changed TCTR-FLOWCONTROL
            break  # break out of while loop
        except WatchError:
            pass  # someone else got there before us, retry.

比原来的三行复杂一点...

最新更新