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
.
我在 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
.