设计思路
- 锁的竞争处理:利用Redis的原子操作SETNX(SET if Not eXists)命令来尝试获取锁。只有当锁不存在时,SETNX才会成功设置锁的值,从而保证同一时间只有一个客户端能获取到锁。
- 锁的过期时间设置与续期:在获取锁时,同时设置一个过期时间,以防止因客户端崩溃或其他原因导致锁永远不释放。对于续期,可以使用一个后台线程定期检查锁的持有情况,如果还持有锁则延长过期时间,或者采用自动续期机制(如Redisson的看门狗机制)。
- 防止死锁:通过给每个锁设置唯一的标识(如UUID),客户端在释放锁时,首先检查当前锁的标识是否与自己持有的标识一致,只有一致时才释放锁,这样可以避免误释放其他客户端的锁。
- 应对网络分区:可以采用多副本的方式,将锁的数据复制到多个节点上。当出现网络分区时,只要大部分节点正常,锁服务依然可用。同时,客户端在获取锁时,需要尝试从多个节点获取,提高获取锁的成功率。
Lua脚本示例
-- KEYS[1] 是锁的键
-- ARGV[1] 是锁的值(例如UUID)
-- ARGV[2] 是锁的过期时间(秒)
if (redis.call('SETNX', KEYS[1], ARGV[1]) == 1) then
redis.call('EXPIRE', KEYS[1], ARGV[2])
return 1
else
return 0
end
客户端代码示例(以Python为例)
import redis
import uuid
class DistributedLock:
def __init__(self, host='localhost', port=6379, db=0):
self.redis_client = redis.StrictRedis(host=host, port=port, db=db)
self.lock_key = 'distributed_lock'
self.lock_value = str(uuid.uuid4())
self.expire_time = 10 # 锁过期时间,秒
def acquire_lock(self):
lua_script = """
if (redis.call('SETNX', KEYS[1], ARGV[1]) == 1) then
redis.call('EXPIRE', KEYS[1], ARGV[2])
return 1
else
return 0
end
"""
result = self.redis_client.eval(lua_script, 1, self.lock_key, self.lock_value, self.expire_time)
return result == 1
def release_lock(self):
lua_script = """
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
return redis.call('DEL', KEYS[1])
else
return 0
end
"""
result = self.redis_client.eval(lua_script, 1, self.lock_key, self.lock_value)
return result == 1
# 使用示例
lock = DistributedLock()
if lock.acquire_lock():
try:
print('获取到锁,执行任务...')
# 执行具体任务
finally:
lock.release_lock()
print('释放锁')
else:
print('未获取到锁')