【问题标题】:How can I implement an atomic get or set&get key in redis using python?如何使用 python 在 redis 中实现原子 get 或 set&get 键?
【发布时间】:2016-08-04 04:32:45
【问题描述】:

我有一个 redis 服务器,我想实现一个原子(或伪原子)方法,它将执行以下操作(注意:我有一个与 redis 服务器有多个会话的系统):

  1. 如果某个键 K 存在,则获取它的值
  2. 否则,使用某个函数F(生成salts)生成的随机值调用SETNX function
  3. 向 redis 询问当前会话刚刚生成的键 K 的值(或由另一个会话“同时”生成 - 在当前会话生成之前不久)

我不想使用 F 函数预先生成(在检查值是否存在之前)一个值,并在键不存在时使用它的原因是:

  1. 我不想毫无理由地调用 F(这可能会导致 CPU 密集型行为(
  2. 我想避免下一个有问题的情况: T1 : 会话 1 生成一个随机值 VAL1 T2:会话 1 询问密钥 K 是否存在并得到“假” T3:会话 2 生成一个随机值 VAL2 T4:会话 2 询问密钥 K 是否存在并得到“假” T5:会话 2 调用 SETNX 并使用值 VAL2 并从现在开始使用 VAL2 T6:会话 1 调用 SETNX 并使用值 VAL1 并从现在开始使用 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)

还有其他解决方案吗?

【问题讨论】:

    标签: python python-2.7 redis redis-py


    【解决方案1】:

    是的,您可以为此使用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)
    

    【讨论】:

    • 会不会在我们每次调用atomic_get_set方法时创建一个事务?这不是开销吗?我的系统特点是我不会有太多的键/条目,一旦一个键有一个值,它就不会改变,所以也许一个 if r.exists(some_key): return r.get(some_key) 在函数的开头将避免不断创建新事务
    • 我们在立即执行模式下运行if pipe.exists(some_key): return pipe.get(some_key),所以那里没有开销。真的,在 Redis 方面,一切都进展得如此之快,瓶颈几乎总是在网络方面,而这段代码最多可以在 2 个网络来回中完成你想要的一切。如果您需要更快,您可以在 lua 中编写 F() 并将整个事情作为一个 Lua 调用运行,但除非您每秒多次运行此函数,否则节省的成本可能不值得.除非您意识到必须这样做,否则不要过早地进行优化。
    • return pipe.get(some_key) 子句有问题,它返回的不是实际的字符串值,而是一个 StrictPipeline 对象..
    • 显然,StrictPipeline 的执行方法返回一个在管道中执行的结果数组,所以我认为不是行“return pipe.get(some_key)”+“pipe.execute()”它应该“返回 pipe.get(some_key).execute()[-1]”以获取最后一个命令的结果
    • 开发者回应:一个管道实例可以在两种模式下运行。默认模式是缓冲所有命令,直到调用 pipeline.execute()。 execute 方法返回缓冲命令的结果列表。或者,您可以使用 pipeline.watch(key1, key2, ...) 方法将管道置于立即执行模式。监视键时,管道命令会立即返回结果,直到调用 pipeline.multi()。multi 方法会将管道返回到默认的缓冲执行模式。这是 Eli 建议的答案。您似乎忘记了 watch 方法
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-20
    • 2013-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多