compareAndSet如何在Redis内部工作

时间:2019-01-17 10:46:23

标签: java spring redis

spring-data-redis模块包含RedisAtomicLong类。

在本课程中,您可以看到

public boolean compareAndSet(long expect, long update) {

    return generalOps.execute(new SessionCallback<Boolean>() {

        @Override
        @SuppressWarnings("unchecked")
        public Boolean execute(RedisOperations operations) {
            for (;;) {
                operations.watch(Collections.singleton(key));
                if (expect == get()) {
                    generalOps.multi();
                    set(update);
                    if (operations.exec() != null) {
                        return true;
                    }
                }
                {
                    return false;
                }
            }
        }
    });
}

我的问题是为什么它起作用?

generalOps.multi()在调用get()之后开始事务。这意味着有可能两个不同的线程(甚至客户端)可以更改值,并且两个都将成功。

operations.watch是否以某种方式阻止了它? JavaDoc没有解释此方法的目的。

PS:次要问题:为什么for (;;)?总是有一个迭代。

3 个答案:

答案 0 :(得分:1)

  

operations.watch是否以某种方式阻止了它?

是。查看密钥后,如果在事务完成之前已对密钥进行了修改,则EXEC将失败。因此,如果EXEC成功,则其他人可以保证该值保持不变。

  

为什么要使用(;;)?总是有一个迭代。

在您的情况下,似乎无限循环是多余的。

但是,如果要执行检查并设置操作以用旧值修改值,则需要无限循环。从redis doc中查看以下示例:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

由于EXEC可能会失败,因此您需要循环重试整个过程,直到成功为止。

答案 1 :(得分:1)

问:Operations.watch是否以某种方式阻止了它?

是。

引用Redis documentation about transaction

  

WATCH用于为Redis事务提供检查并设置(CAS)行为。

     

监视已监视的键,以便检测对它们的更改。如果在EXEC命令之前至少修改了一个监视键,则整个事务将中止,并且EXEC返回Null答复以通知该事务失败。

您可以从该文档中了解有关Redis交易的更多信息。

问:为什么要使用(;;)?总是有一个迭代。

您发布的代码似乎很旧。从Google的this url缓存中,我看到了您提供的代码可以追溯到Oct 15th, 2012

最新代码看起来有很大不同:

答案 2 :(得分:0)

RedisAtomicLong.compareAndSet的实现并非最佳,因为它需要 5个Redis请求

Redisson-Redis Java客户端提供了更有效的实现。

org.redisson.RedissonAtomicLong#compareAndSetAsync方法是使用原子EVAL脚本实​​现的:

  "local currValue = redis.call('get', KEYS[1]); "
+ "if currValue == ARGV[1] "
       + "or (tonumber(ARGV[1]) == 0 and currValue == false) then "
      + "redis.call('set', KEYS[1], ARGV[2]); "
      + "return 1 "
+ "else "
      + "return 0 "
+ "end",

此脚本仅需要对Redis的单个请求

用法示例:

RAtomicLong atomicLong = redisson.getAtomicLong("myAtomicLong");
atomicLong.compareAndSet(1L, 2L);