根据你的需求,我将优化代码实现以下两个目标:

  1. 确保任务一定会执行(无论是否获取到锁)
  2. 实现Redis连接的单例模式,避免过多连接

以下是优化后的完整实现:

import time
import uuid
import redis
from functools import wraps


class RedisSingleton:
    _instance = None
    
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
            cls._instance._redis_client = redis.Redis(*args, **kwargs)
        return cls._instance
    
    @property
    def client(self):
        return self._redis_client


class RedisDistributedLock:
    def __init__(self, redis_client, lock_name, expire_time=600):
        """
        初始化分布式锁
        
        :param redis_client: Redis客户端实例
        :param lock_name: 锁的名称
        :param expire_time: 锁的过期时间(秒),默认10分钟(600秒)
        """
        self.redis_client = redis_client
        self.lock_name = lock_name
        self.expire_time = expire_time
        self.identifier = str(uuid.uuid4())  # 唯一标识,用于释放锁时验证
    
    def acquire(self, blocking=True, timeout=None):
        """
        获取锁
        
        :param blocking: 是否阻塞等待
        :param timeout: 阻塞等待的超时时间(秒)
        :return: 是否成功获取锁
        """
        if blocking:
            if timeout is None:
                # 无限等待直到获取锁
                while True:
                    if self._try_acquire():
                        return True
                    time.sleep(0.1)  # 适当增加等待间隔减少CPU压力
            else:
                # 有限等待
                end_time = time.time() + timeout
                while time.time() < end_time:
                    if self._try_acquire():
                        return True
                    time.sleep(0.1)
                return False
        else:
            # 非阻塞模式,尝试一次
            return self._try_acquire()
    
    def _try_acquire(self):
        """
        尝试获取锁的内部方法
        """
        # 使用SET命令的NX和EX参数实现原子操作
        result = self.redis_client.set(
            self.lock_name, 
            self.identifier, 
            ex=self.expire_time, 
            nx=True
        )
        return result is True
    
    def release(self):
        """
        释放锁
        
        使用Lua脚本确保原子性操作:
        1. 检查锁的值是否匹配
        2. 如果匹配则删除键
        """
        lua_script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        return self.redis_client.eval(lua_script, 1, self.lock_name, self.identifier)
    
    def __enter__(self):
        # 默认阻塞直到获取锁
        self.acquire(blocking=True)
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.release()


def ensure_execution(lock_name, expire_time=600):
    """
    确保任务执行的装饰器
    
    :param lock_name: 锁的名称
    :param expire_time: 锁的过期时间(秒)
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 获取单例Redis连接
            redis_client = RedisSingleton(host='localhost', port=6379, db=0).client
            lock = RedisDistributedLock(redis_client, lock_name, expire_time)
            
            # 阻塞直到获取锁
            lock.acquire(blocking=True)
            try:
                return func(*args, **kwargs)
            finally:
                lock.release()
        return wrapper
    return decorator


# 使用示例
if __name__ == "__main__":
    # 获取单例Redis连接
    redis_client = RedisSingleton(host='localhost', port=6379, db=0).client
    
    # 方式1:使用上下文管理器(会阻塞直到获取锁)
    lock_name = "order:update:123"
    with RedisDistributedLock(redis_client, lock_name):
        print("成功获取锁,执行业务逻辑...")
        time.sleep(2)
        print("操作完成")
    
    # 方式2:使用装饰器(会阻塞直到获取锁)
    @ensure_execution("user:update:456")
    def update_user_info(user_id, new_info):
        print(f"更新用户 {user_id} 的信息: {new_info}")
        time.sleep(3)
        print("更新完成")
    
    update_user_info(456, {"name": "张三", "age": 30})
    
    # 方式3:手动获取(会阻塞直到获取锁)
    lock = RedisDistributedLock(redis_client, "product:inventory:789")
    lock.acquire(blocking=True)  # 这里会一直等待直到获取锁
    try:
        print("成功获取锁,执行业务逻辑...")
        time.sleep(2)
    finally:
        lock.release()

主要优化点说明:

  1. 确保任务执行

    • 所有获取锁的操作默认都是阻塞模式(blocking=True
    • 如果没有获取到锁,会持续等待直到获取锁,确保任务一定会执行
    • 移除了获取锁失败的else分支,因为现在不会出现获取失败的情况
  2. Redis连接单例

    • 实现了RedisSingleton类,确保整个应用只有一个Redis连接实例
    • 通过__new__方法实现单例模式
    • 提供了client属性获取Redis连接实例
  3. 使用便利性

    • 保留了三种使用方式(上下文管理器、装饰器、手动获取)
    • 新增了ensure_execution装饰器,更直观地表达"确保执行"的语义
    • 所有获取锁的操作都会自动等待,不需要手动处理获取失败的情况
  4. 性能优化

    • 增加了获取锁时的等待间隔(从0.001秒调整为0.1秒),减少CPU压力
    • 保持了原有的锁自动释放机制(默认10分钟)

这样修改后,无论是否能够立即获取锁,代码都会等待直到获取锁后再执行任务,且整个应用只有一个Redis连接实例。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐