You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python使用Redis分布式锁时如何设置获取优先级保证FIFO顺序

Redis FIFO分布式锁实现方案

问题根源

原生redis-py库提供的默认锁实现不保障FIFO获取顺序:所有阻塞等待锁的客户端在锁释放时会同时发起抢占请求,率先到达Redis服务端的请求会拿到锁,和客户端提交申请的先后顺序无关,因此会出现先等待的用户后获取锁的乱序问题。

实现方案

基于Redis有序集合(ZSET)实现公平FIFO锁,核心逻辑如下:

  • 每个锁申请请求生成全局唯一的标识ID,以申请时间戳+随机后缀作为score写入对应锁的ZSET
  • 申请者检查自身score是否为ZSET中最小值:如果是则成功获取锁;如果不是则阻塞等待,监听锁释放的广播通知
  • 锁释放时,持有者删除自身在ZSET中的记录,同时发布锁释放通知,等待队列中的申请者收到通知后再次检查自身排名,排名第一的申请者获得锁
  • 额外增加锁超时自动释放逻辑,避免客户端异常崩溃导致锁永久占用

代码实现示例

import redis
import time
import uuid

REDIS_CLIENT = redis.Redis(decode_responses=True)

class FIFORedisLock:
    def __init__(self, lock_key, timeout=60, blocking_timeout=60):
        self.lock_key = lock_key
        self.wait_zset_key = f"{lock_key}:wait_queue"
        self.pubsub_channel = f"{lock_key}:release_notify"
        self.timeout = timeout
        self.blocking_timeout = blocking_timeout
        self.request_id = f"{time.time()}:{uuid.uuid4().hex}"
        self.score = float(self.request_id.split(":")[0])

    def acquire(self):
        start_time = time.time()
        # 写入等待队列
        REDIS_CLIENT.zadd(self.wait_zset_key, {self.request_id: self.score})
        pubsub = REDIS_CLIENT.pubsub()
        pubsub.subscribe(self.pubsub_channel)

        try:
            while time.time() - start_time < self.blocking_timeout:
                # 获取队列中排名第一的请求
                first_request = REDIS_CLIENT.zrange(self.wait_zset_key, 0, 0, withscores=True)
                if first_request and first_request[0][0] == self.request_id:
                    # 成功获取锁,设置过期时间
                    REDIS_CLIENT.expire(self.wait_zset_key, self.timeout)
                    return True
                # 等待释放通知,最多等1秒避免死等
                message = pubsub.get_message(timeout=1)
            # 阻塞超时,清理自身请求记录
            REDIS_CLIENT.zrem(self.wait_zset_key, self.request_id)
            return False
        finally:
            pubsub.close()

    def release(self):
        # 只有锁的持有者可以释放
        first_request = REDIS_CLIENT.zrange(self.wait_zset_key, 0, 0)
        if first_request and first_request[0] == self.request_id:
            REDIS_CLIENT.zrem(self.wait_zset_key, self.request_id)
            # 通知其他等待者
            REDIS_CLIENT.publish(self.pubsub_channel, "release")
            return True
        return False

# 用法和原有锁基本一致
lock = FIFORedisLock("task_key", timeout=60)
have_lock = lock.acquire()
if have_lock:
    try:
        # 运行你的任务逻辑
        pass
    finally:
        lock.release()

Celery场景适配

该实现可以直接替换你现有Celery唯一任务逻辑中的原生Redis锁,原有任务调度逻辑不需要做额外修改,即可保障任务执行顺序和用户申请顺序一致。

内容的提问来源于stack exchange,提问作者Rom1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 20:18:04