Python Redis分布式锁(阻塞式+单例连接)
·
根据你的需求,我将优化代码实现以下两个目标:
- 确保任务一定会执行(无论是否获取到锁)
- 实现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()
主要优化点说明:
-
确保任务执行:
- 所有获取锁的操作默认都是阻塞模式(
blocking=True) - 如果没有获取到锁,会持续等待直到获取锁,确保任务一定会执行
- 移除了获取锁失败的else分支,因为现在不会出现获取失败的情况
- 所有获取锁的操作默认都是阻塞模式(
-
Redis连接单例:
- 实现了
RedisSingleton类,确保整个应用只有一个Redis连接实例 - 通过
__new__方法实现单例模式 - 提供了
client属性获取Redis连接实例
- 实现了
-
使用便利性:
- 保留了三种使用方式(上下文管理器、装饰器、手动获取)
- 新增了
ensure_execution装饰器,更直观地表达"确保执行"的语义 - 所有获取锁的操作都会自动等待,不需要手动处理获取失败的情况
-
性能优化:
- 增加了获取锁时的等待间隔(从0.001秒调整为0.1秒),减少CPU压力
- 保持了原有的锁自动释放机制(默认10分钟)
这样修改后,无论是否能够立即获取锁,代码都会等待直到获取锁后再执行任务,且整个应用只有一个Redis连接实例。
更多推荐
所有评论(0)