dot Redis 8 已发布——而且它是开源的

了解更多

Redis 竞态条件

返回词汇表

理解竞态条件

竞态条件出现在系统行为不可预测地依赖于事件序列时,例如线程执行的顺序。在 Redis 中,当多个客户端并发地访问或修改相同数据时,就会发生这种情况,从而导致不可预测的结果。

Redis 的单线程特性

Redis 以单线程方式运行,顺序处理命令。虽然这确保了单个命令的原子性,但当多个客户端同时与 Redis 交互时,竞态条件仍然可能出现。

信号量操作中的竞态条件

信号量是用于控制对资源的访问的机制。在 Redis 中,当多个进程(例如 A 和 B)争夺同一个信号量时,可能会出现竞态条件。例如,如果 A 在 B 之前更新计数器,但 B 首先在 ZSET 中检查其排名,B 可能会错误地获取信号量。稍后,当 A 检查其排名时,它可能会无意中从 B 手中“夺走”信号量,导致 B 尝试续约或释放信号量时出现混乱。

此前,使用系统时钟管理锁会增加这些竞态条件发生的可能性,尤其是在系统时间差异显著的情况下。通过引入计数器与所有者 ZSET 结合使用,降低了这种风险。然而,由于涉及多个步骤,竞态条件仍然存在可能性。

使用锁解决竞态条件

为了全面解决这些竞态条件,可以使用带超时的分布式锁。这首先需要尝试获取信号量的锁。如果成功,则执行标准的信号量操作。如果失败,则认为信号量获取不成功。下面是该函数的一个简化版本:

def acquire_semaphore_with_lock(conn, semname, limit, timeout=10)

   identifier = acquire_lock(conn, semname, acquire_timeout=.01)

   if identifier

      try

         return acquire_fair_semaphore(conn, semname, limit, timeout)

      finally

         release_lock(conn, semname, identifier)

经过所有这些改进后,诉诸锁可能看起来违反直觉,但 Redis 为类似问题提供了多种解决方案,每种方案都有其优缺点。取决于所需的严格程度:

  1. 对于那些满意于使用系统时钟、不需要刷新信号量并能容忍偶尔超限的用户,最初的信号量方法就足够了。
  2. 如果系统时钟仅在 1-2 秒范围内可信,但偶尔超限可接受,则第二种方法适用。
  3. 为了保证每次的正确性,使用锁是最佳方法。

虽然最后一种方法最准确,但使用更简单信号量节省的时间可能会被资源过度使用所抵消。

信号量的应用

Redis 中的信号量可用于调节同时 API 调用的数量,确保数据库不会因并发请求而过载。它们还可用于遵守网站 robots.txt 等施加的限制,确保服务器不会过载。