Redis 排序集上的事务看起来不是原子的

Transaction on a redis sorted set doesn't appear atomic

我在 Redis 的 pipelined/transaction 中遇到了一些奇怪的行为,这让我怀疑代码实际上是在事务中执行的:

class RedisThread:

    KEY = "threading_test"

    def __init__(self, id:int):
        self._redis = Redis(host="localhost", port=6379, db=0)
        self._id    = id
        self._redis.delete(self.KEY)

    def __call__(self, *args, **kwargs):
        results = []
        for i in range(3):
            # Transaction
            pipe = self._redis.pipeline(transaction=True)

            # ZADD current time as score
            pipe.zadd(self.KEY, {f"{self._id}-{i}": time.time()})

            # ZRANK
            pipe.zrank(self.KEY, f"{self._id}-{i}")

            # Commit and get result of ZRANK
            results.append(str(pipe.execute()[1]))

        print(", ".join(results))


    threads = [
        threading.Thread(target=RedisThread(1)),
        threading.Thread(target=RedisThread(2)),
        threading.Thread(target=RedisThread(3)),
        threading.Thread(target=RedisThread(4)),
        threading.Thread(target=RedisThread(5)),
    ]

    for t in threads:
        t.start()

    for t in threads:
        t.join()

当我运行这段代码时,结果是这样的:

1, 5, 9
3, 6, 10
0, 3, 13
4, 11, 12
1, 4, 13

注意线程之间存在重复值。由于我正在做 ZRANK,并且我添加(通过 ZADD)到集合中的值是基于时间的(因此值总是增加),我不应该看到任何重复项,但有重复项......

Redis 事务是原子的。问题是您使用 time() 作为分数。

由于程序运行速度很快,因此time() 多次调用可能return 得到相同的结果。换句话说,您在排序集中设置具有相同分数的成员。

如果成员得分相同,则按字典顺序排序。

现在,让我们看看为什么会出现重复值:

thread1: ZADD key same-time 1-0
thread1: ZRANK key 1-0 ----------> 0

thread2: ZADD key same-time 2-0
thread2: ZRANK key 2-0 ----------> 1

thread1: ZADD key same-time 1-1
thread1: ZRANK key 1-1 ----------> 1

thread1的第二个事务在thread2的第一个事务之后执行,并且1-1在字典序上小于2-0。所以当thread1的第二个事务执行时,1-1会被插入到2-0之前,所以他们都得到了rank:1.