标签:datetime delete watcher res script reg 分布式锁 val 解锁过程
NX EX
参数本文主要讲解基于 Redis 实现的分布式锁
import math import time from redis import WatchError def acquire_lock_with_timeout(conn, lock_name,acquire_timeout=3, lock_timeout=2): """ 基于 Redis 实现的分布式锁 :param conn: Redis 连接 :param lock_name: 锁的名称 :param acquire_timeout: 获取锁的超时时间,默认 3 秒 :param lock_timeout: 锁的超时时间,默认 2 秒 :return: """ identifier = str(uuid.uuid4()) lockname = f‘lock:{lock_name}‘ lock_timeout = int(math.ceil(lock_timeout)) end = time.time() + acquire_timeout while time.time() < end: # 如果不存在这个锁则加锁并设置过期时间,避免死锁 if conn.setnx(lockname, identifier): conn.expire(lockname, lock_timeout) return identifier # 如果存在锁,且这个锁没有过期时间则为其设置过期时间,避免死锁 elif conn.ttl(lockname) == -1: conn.expire(lockname, lock_timeout) time.sleep(0.001) return False def release_lock(conn, lockname, identifier): """ 释放锁 :param conn: Redis 连接 :param lockname: 锁的名称 :param identifier: 锁的标识 :return: """ # python 中 redis 事务是通过pipeline的封装实现的 with conn.pipeline() as pipe: lockname = ‘lock:‘ + lockname while True: try: # watch 锁, multi 后如果该 key 被其他客户端改变, 事务操作会抛出 WatchError 异常 pipe.watch(lockname) iden = pipe.get(lockname)
if iden and iden.decode(‘utf-8‘) == identifier: # 事务开始 pipe.multi() pipe.delete(lockname) pipe.execute() return True pipe.unwatch() break except WatchError: pass return False
setnx
设置锁,如果该锁之前不存在其他客户端的锁则加锁成功,接着设置锁的过期时间防止发生死锁并返回锁的唯一标示;setnx
和 expire
两个命令执行不是原子性的,可能会出现加锁成功但是设置超时时间失败出现死锁。如果不存在就给锁重新设置过期时间,存在就不断循环直到加锁时间超时加锁失败。watch
监听锁,防止解锁时出现删除其他人的锁;watch
就会删除失败,这样就不会出现删除了其他客户端锁的情况。如果你使用的 Redis 版本大于等于 2.6.12
版本,加锁的过程就可以进行简化。因为这个版本以后的 Redis set
操作支持 EX
和 NX
参数,是一个原子性的操作。
import uuid import math import time from redis import WatchError def acquire_lock_with_timeout(conn, lock_name, acquire_timeout=3, lock_timeout=2): """ 基于 Redis 实现的分布式锁 :param conn: Redis 连接 :param lock_name: 锁的名称 :param acquire_timeout: 获取锁的超时时间,默认 3 秒 :param lock_timeout: 锁的超时时间,默认 2 秒 :return: """ identifier = str(uuid.uuid4()) lockname = f‘lock:{lock_name}‘ lock_timeout = int(math.ceil(lock_timeout)) end = time.time() + acquire_timeout while time.time() < end: # 如果不存在这个锁则加锁并设置过期时间,避免死锁 if conn.set(lockname, identifier, ex=lock_timeout, nx=True): return identifier time.sleep(0.001) return False def release_lock(conn, lockname, identifier): """ 释放锁 :param conn: Redis 连接 :param lockname: 锁的名称 :param identifier: 锁的标识 :return: """ # python中redis事务是通过pipeline的封装实现的 with conn.pipeline() as pipe: lockname = ‘lock:‘ + lockname while True: try: # watch 锁, multi 后如果该 key 被其他客户端改变, 事务操作会抛出 WatchError 异常 pipe.watch(lockname) iden = pipe.get(lockname) if iden and iden.decode(‘utf-8‘) == identifier: # 事务开始 pipe.multi() pipe.delete(lockname) pipe.execute() return True pipe.unwatch() break except WatchError: pass return False
可能你也发现了解锁过程在代码逻辑上稍微有点复杂,别着急,我们可以使用 Lua
脚本实现原子性操作从而简化解锁过程。
# -*- coding: utf-8 -*- # @DateTime : 2020/3/9 15:36 # @Author : woodenrobot import uuid import math import time def acquire_lock_with_timeout(conn, lock_name, acquire_timeout=3, lock_timeout=2): """ 基于 Redis 实现的分布式锁 :param conn: Redis 连接 :param lock_name: 锁的名称 :param acquire_timeout: 获取锁的超时时间,默认 3 秒 :param lock_timeout: 锁的超时时间,默认 2 秒 :return: """ identifier = str(uuid.uuid4()) lockname = f‘lock:{lock_name}‘ lock_timeout = int(math.ceil(lock_timeout)) end = time.time() + acquire_timeout while time.time() < end: # 如果不存在这个锁则加锁并设置过期时间,避免死锁 if conn.set(lockname, identifier, ex=lock_timeout, nx=True): return identifier time.sleep(0.001) return False def release_lock(conn, lock_name, identifier): """ 释放锁 :param conn: Redis 连接 :param lockname: 锁的名称 :param identifier: 锁的标识 :return: """ unlock_script = """ if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end """ lockname = f‘lock:{lock_name}‘ unlock = conn.register_script(unlock_script) result = unlock(keys=[lockname], args=[identifier]) if result: return True else: return False
截至到目前,我们已经有较好的方法获取锁和释放锁。基于Redis单实例,假设这个单实例总是可用,这种方法已经足够安全。但是如果 Redis 主节点挂了就会出现一些问题,比如主节点加锁后没有同步到从节点,从节点升为主节点,就会出现锁的丢失。
标签:datetime delete watcher res script reg 分布式锁 val 解锁过程
原文地址:https://www.cnblogs.com/tracydzf/p/13153909.html