如何用redis实现分布式锁
Redis 有一系列以 NX 结尾的命令。NX 是 NoteXists 的缩写。
例如,SETNX命令应理解为SETifNoteXists。
这组命令非常有用。
本文介绍如何使用 SETNX 实现分布式锁定。
使用 SETNX 实现分布式锁定 使用 SETNX 实现分布式锁定非常简单。
示例:客户端想要获取名为 foo 的锁。
客户端使用以下命令获取: 如果 SETNXlock.foo
如果将 .foo 键设置为指示其被锁定的时间值,则客户端最终可以通过 DELlock.foo 将其解锁。
返回 0 意味着另一个客户端已经获取了锁。
此时,要么先返回,重试,等待对方说完,要么等待锁超时。
上述解决死锁的加锁逻辑存在问题。
如果持有锁的客户端失败或崩溃而无法释放锁,该如何解决? 您可以通过查看锁定密钥对应的时间戳来确定是否发生了这种情况。
如果当前时间大于lock.foo的值,则说明锁已过期,可以重新使用。
如果发生这种情况,您不能简单地通过 DEL 清除锁定,然后再次运行 SETNX。
如果多个客户端检测到锁已超时,则此处可能会发生竞争条件。
操作 C0 超时,但仍持有锁的 C1 和 C2 读取 lock.foo,检查时间戳,发现超时。
C1 发送 DELlock.foo。
C1 发送 SETNXlock.foo 并成功。
C2 发送 DELlock.foo。
C2 发送 SETNXlock.foo 并成功。
这样C1和C2都获得了锁! 大问题! 幸运的是,这个问题是可以避免的。
客户端C3发送SETNXlock.foo来获取锁。
由于C0仍然持有锁,Redis返回0给C3,C3发送foo进行确认。
锁是否超时,如果没有,则等待或重试。
相反,如果发生超时,C3会尝试通过以下动作获取锁。
GETSETlock.foo
如预期锁定。
如果在C3之前调用C4的客户端比C3快一步执行上述操作,则C3获得的时间戳就是没有按预期获取锁的值,因此它必须等待。
请再试一次。
虽然C3未能获取锁并重写了C4设置的锁超时值,但是这个非常小的错误的影响可以忽略不计。
注意:为了使分布式锁算法更加可靠,持有锁的客户端在释放锁之前必须仔细检查其锁是否超时,然后执行 DEL 操作。
这是因为客户端可能会由于耗时的过程而被阻止。
操作已暂停。
一旦操作完成,现在就不需要释放锁了,因为由于超时其他人已经获取了它。
示例伪代码 基于上面的代码,我编写了一个小的伪代码片段来说明使用分布式锁定的整个过程。
#getlocklock=0whilelock!=1:timestamp=currentUnixtime+locktimeout+1lock=SETNXlock.footimestampiflock==1or(now ( )>(GETlock.foo)and no w()>(GETSETlock.footimestamp)):break;else:sleep(10ms)#doyourjobdo_job()#releaseifnow()
立刻就想到了,但是当你使用Java时,是否也会想到那个人? AOP+注解? 伟大的。
使用起来很舒服。
不要重复代码。
如何使用redis实现分布式锁功能?
Redis是单线程的,运行速度非常快,因此更适合全局分布式锁。基本过程是,在处理全局竞争资源时,使用全局唯一键来确定其他线程是否正在占用该资源。
如果其他线程占用了该资源,则会报错并终止该资源或者系统循环等待。
如果没有其他线程占用,则添加分布式锁来占用此资源,然后在任务执行完成后释放分布式锁,以便其他线程可以继续使用此资源。
那么通过Redis进行加锁的动作是什么呢?一个简单的加锁命令:命令如下: setnx的内部实现机制是判断key位置是否有数据,没有数据则设置为value,有数据则返回。
特殊价值。
然而,问题就在这里。
如果占用资源的线程意外终止并且没有来得及释放分布式锁,则该锁将永远被占用。
添加的命令是1.setnx2.expire。
分布式锁同时我们添加了锁过期时间。
这样,当加锁线程终止时,至少可以在一定时间后释放锁。
这里的小问题是这两个命令是分开执行的,不是原子操作。
理论上,错误可能发生在执行第一个命令之后,此时再执行过期的命令就来不及了。
一种方法是编写自己的 Lua 脚本,该脚本可以自动执行多个命令。
一种方法是参考一些开源库。
从2.8版本开始,redis官方提供了解决方案来解决这个问题。
这是命令 setkeyvaluenxexpireTimeNumex ,它将上述两个命令合并为一个命令。
设置过期时间解决了部分问题,但是当锁过期时,部分执行的任务仍然没有完成,第一个线程仍在运行,而第二个线程也有可能已经获取了锁。
这时,当第一个线程执行完成后,第二个线程的锁就被释放。
如果第二个线程释放了锁,要么发生错误,要么释放了另一个线程的锁,这与预期不一致。
如果只是想解决这个问题,可以在设置值时使用随机数。
释放锁时,首先判断随机数是否一致,如果一致则解除锁,否则退出。
然而,确定值和删除键目前需要使用Lua脚本。
上述方案仍然无法解决超时释放问题,仍然违背了分布式锁的初衷。
发生了什么?解决问题的思路是启动另一个线程。
它的任务是当发现当前线程的任务已经过期并且尚未完成时,更新当前线程的锁。
经常。
有一些开源库可以解决这个问题,而且它们可能比你更好。
这个库是redisson,非常好学。
redis的儿子,这些可能没有关系,但是这样就足够了。
这个库有一个叫做watchdog的组件,字面意思就是看门狗。
它的作用是每隔一段时间进行判断。
如果再想下去,就会出现更极端的问题。
如果redis是单节点,要么宕机,要么主节点和备份节点宕机,备份节点没有时间同步主节点的数据。
如果主节点在获取锁之后但在同步数据之前崩溃,也可能会出现无法锁定的问题。
如果你认为这是一个问题并且想要解决它,你如何解决它呢?通常当你部署redis时你有2n+1台服务器,并且你需要确保在锁定时至少有一半的服务器被成功锁定。
。
这意味着n+。
1台服务器。
除非整个集群当前不可用,否则安全性将得到显着提高。
这个问题也被开源库redis红锁解决了。
下一个问题是,分布式锁是可重入的吗? 如果实现可重入分布式锁,则在设置该值时必须添加线程信息和锁数量信息。
但如果您想添加到期日期或类似的,这是一个简单的想法。
发生此问题时,可重入锁定可能会变得更加复杂。
分布式锁的三种实现方式
分布式锁有三种实现方式: 基于数据库的分布式锁实现 该方法主要利用SQL的SELECT WHERE FORUPDATE语句来实现排它锁。使用此功能时,请记住您必须对 name=lock 字段建立索引,否则可能会遇到表锁定问题。
乐观锁基于CompareAndSwap(CAS)思想,通过增加版本号字段来实现非互斥操作。
非常适合紧急采购、闪购等场景,减少资源消耗。
基于缓存的分布式锁实现(例如Redis) 使用Redis实现分布式锁需要三个主要指令:SETNX、EXPIRE和DEL。
首先,我们使用 SETNX 设置锁定并设置超时。
EXPIRE给锁增加了超时机制,防止死锁。
使用随机生成的UUID作为锁定值来决定是否解锁。
实现时必须考虑锁获取超时、有序性、锁自动释放等问题。
基于Zookeeper实现分布式锁 Zookeeper中实现分布式锁的步骤包括创建目录、获取线程临时序列节点、获取子节点并确定锁、建立监听、释放锁。
要实现分布式锁定,我们建议使用 ApacheCurator 库,它提供了 InterProcessMutex。
这种方式具有高可用、可重入、阻塞锁等特点,但性能相比Redis实现稍低。
数据库分布式锁实现的对比分析包括性能下降、锁表风险、非阻塞操作的资源占用以及资源占用问题。
Redis实现的分布式锁存在清锁失败、非阻塞操作、资源抢占等问题。
Zookeeper的性能不如Redis,主要是频繁的写操作。
从难度、实现复杂度、性能和可靠性来看,数据库和缓存实现相对简单,而Zookeeper则更复杂。
从性能上来说,缓存比数据库或者Zookeeper要好。
在可靠性方面,Zookeeper 表现最好。
python使用四种方案(Redis,Zookeeper,etcd,mysql)实现分布式锁代码实例
分布式锁在分布式系统中发挥着重要作用,用于解决访问多个节点之间共享资源的问题。下面我们介绍四种实现方法及其代码示例,分别使用Redis、ZooKeeper、etcd和MySQL。
1、Python结合Redis实现分布式区块。
Redis支持原子操作,非常适合实现分布式块。
下面是使用Redis实现分布式锁的示例代码: pythonimportredisdefdistributed_lock(key):r=redis.Redis(host='localhost',port=6379,db=0)lock_key='lock:' +key#生成随机字符串, 作为唯一区块标识符 lock_id=str(uuid.uuid4())#设置过期时间 5分钟锁,如果过期没有释放,会自动释放 ifnotr.set(lock_key,lock_id,ex=300,nx=True): #如果设置失败,说明锁被其他进程获取了, returns FalsereturnFalse #获取锁成功,返回TruereturnTrue 2.Python结合ZooKeeper实现分布式锁 ZooKeeper提供了高度一致的分布式协调服务,其特点可以是 用于实现分布式块。
下面是使用 ZooKeeper 实现分布式锁的代码示例: pythonfromkazoo.clientimportKazooClientdefdistributed_lock(key):zk=KazooClient(hosts='localhost:2181')zk.start()lock_path='/lock/'+key# 创建锁节点,如果已经存在,则等待该节点前面的子节点完成操作并获取锁zk.e nsure_path(lock_path)try:#获取锁 zk.create(lock_path,b'data',ephemeral=True) exceptKazooExceptionase:#锁被其他进程获取,返回FalsereturnFalse#获取锁成功,返回TruereturnTrue 3. Python结合etcd实现分布式 Etcd是一个分布式键值存储系统,适合实现分布式块。
下面是使用etcd实现分布式锁的示例代码: pythonfrometcd3importEtcd3Clientdefdistributed_lock(key):client=Etcd3Client(host='localhost',port=2379)lock_path='/lock/'+key#创建锁节点,如果是已经存在,则等待该节点前面的子节点完成操作并获取锁 client.put(lock_path ,'data',dir=True)try:#获取锁 client.put(lock_path+'/data','data',dir=True) exceptEtcdKeyAlreadyExistase:#锁被其他进程获取,返回FalsereturnFalse#锁为获取成功,返回TruereturnTrue 4.使用MySQL实现分布式锁 低并发场景下,可以使用MySQL数据库作为替代实现 分布式块。
下面是使用 MySQL 实现分布式锁的示例代码: pythonimportpymysqldefdistributed_lock(key):conn=pymysql.connect(host='localhost',port=3306,user ='root',password='password',db='db_name',charset='utf8mb4')cursor=conn.cursor()lock_key='lock:'+keysql=f"SELECT*FROMlock_tableWHEREkey='{lock_key} 'FORUPDATE'#锁定 SQLtry:cursor.execute(sql)conn.commit() exceptpymysql.err.OperationalError:#Lock 被其他进程获取,返回 FalsereturnFalse#Lock 获取成功,返回 TruereturnTrue 上面的代码示例展示了 Python 核心结合 Redis 的方法, ZooKeeper、etcd和MySQL来实现分布式锁定。
根据您的实际数据需求选择合适的解决方案。
请注意,这些示例代码需要根据您的实际环境和特定需求进行调整。