结合Redis的WATCH命令优化分布式锁
- 原理:
- Redis的
WATCH
命令可以监控一个或多个键。在执行MULTI
和EXEC
之间,如果被监控的键发生了变化,那么EXEC
命令将返回nil
,表示事务执行失败。通过结合WATCH
命令,在获取锁时监控锁对应的键,在尝试释放锁时,只有在锁的状态没有被其他客户端修改的情况下,才能成功释放锁。
- 例如,获取锁时使用
SETNX
(SET if Not eXists
)命令设置锁键的值,如果设置成功则获取到锁。同时使用WATCH
监控该锁键。当释放锁时,先开启事务,检查锁键的值是否还是获取锁时设置的值(确保锁没有被其他客户端释放或重新获取),如果是则删除锁键以释放锁,然后执行事务。
- 示例代码(以Python和Redis - Py为例):
import redis
r = redis.StrictRedis(host='localhost', port=6379, db = 0)
def acquire_lock(lock_key, lock_value, timeout=10):
result = r.setnx(lock_key, lock_value)
if result:
r.expire(lock_key, timeout)
return result
def release_lock(lock_key, lock_value):
pipe = r.pipeline()
while True:
try:
pipe.watch(lock_key)
current_value = pipe.get(lock_key)
if current_value is None or current_value.decode('utf - 8') != lock_value:
pipe.unwatch()
return False
pipe.multi()
pipe.delete(lock_key)
pipe.execute()
return True
except redis.WatchError:
continue
局限性及解决方案
- 局限性:
- 网络延迟问题:在
WATCH
监控期间和执行事务之间,如果网络发生延迟,可能导致其他客户端在这段时间内修改了被监控的键,从而使得本客户端事务执行失败。
- 性能影响:
WATCH
命令增加了额外的网络交互和逻辑复杂度,在高并发场景下,由于事务频繁执行失败(因为WATCH
监控的键被修改),可能导致性能下降。
- 解决方案:
- 针对网络延迟问题:
- 可以适当增加重试次数,如上述代码中在
WatchError
异常时进行重试,确保在网络恢复正常后能够成功释放锁。
- 可以考虑使用乐观锁机制,结合版本号。在获取锁时,将版本号作为锁值的一部分,每次操作锁时,版本号递增。释放锁时,检查版本号是否匹配,这样即使网络有延迟,只要版本号匹配,就可以认为锁状态未被破坏。
- 针对性能影响:
- 优化网络架构,减少网络延迟,例如使用更高速的网络设备和更优化的网络拓扑。
- 可以考虑使用更细粒度的锁,将大的业务操作分解为多个小的操作,每个操作使用不同的锁,从而减少锁竞争,降低
WATCH
命令导致事务失败的概率。