如何在Python中实现Redis的原子获取或设置&获取键?

3 投票
1 回答
5046 浏览
提问于 2025-04-18 13:31

我有一个redis服务器,想实现一个原子(或者说伪原子)的方法,具体步骤如下(注意:我的系统有多个会话同时连接到redis服务器):

  1. 如果某个键K存在,就获取它的值。
  2. 如果不存在,就调用SETNX函数,并用某个函数F生成一个随机值(这个函数会生成盐值)。
  3. 询问redis当前会话(或者是另一个会话“同时”生成的)刚刚生成的键K的值。

我不想在检查值是否存在之前就用函数F预先生成一个值,原因有:

  1. 我不想在没有理由的情况下调用F(这可能会导致CPU负担过重)。
  2. 我想避免以下这种问题情况: T1:会话1生成了一个随机值VAL1。 T2:会话1检查键K是否存在,结果是“不存在”。 T3:会话2生成了一个随机值VAL2。 T4:会话2检查键K是否存在,结果是“不存在”。 T5:会话2调用SETNX,使用值VAL2。 T6:会话1也调用SETNX,使用值VAL1,而此时键K的实际值是VAL2

我写了一个python伪代码,如下:

    import redis
    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    ''' gets the value of key K if exists (r.get(K) if r.exists(K)), 
    otherwise gets the value of key K if calling SETNX function returned TRUE 
    (else r.get(K) if r.setnx(K,F())), meaning this the sent value is really the value,
    otherwise, get the value of key K, that was generated by another session a         
    short moment ago (else r.get(K))
    The last one is redundant and I can write "**r.setnx(K,F()) or True**" to get the 
    latest value instead, but the syntax requires an "else" clause at the end '''
    r.get(K) if r.exists(K) else r.get(K) if r.setnx(K,F()) else r.get(K)

还有其他解决方案吗?

1 个回答

7

是的,你可以使用 WATCH 来实现这个功能。下面是一个使用 redis-py 的修改过的例子:

def atomic_get_set(some_key):
    with r.pipeline() as pipe:
        try:
            # put a WATCH on the key that holds our sequence value
            pipe.watch(some_key)
            # after WATCHing, the pipeline is put into immediate execution
            # mode until we tell it to start buffering commands again.
            # this allows us to get the current value of our sequence
            if pipe.exists(some_key):
                return pipe.get(some_key)
            # now we can put the pipeline back into buffered mode with MULTI
            pipe.multi()
            pipe.set(some_key, F())
            pipe.get(some_key)
            # and finally, execute the pipeline (the set and get commands)
            return pipe.execute()[-1]
            # if a WatchError wasn't raised during execution, everything
            # we just did happened atomically.
        except WatchError:
            # another client must have changed some_key between
            # the time we started WATCHing it and the pipeline's execution.
            # Let's just get the value they changed it to.
            return pipe.get(some_key)

撰写回答