如何使用redis实现分布式锁功能?
由于Redis是单线程且执行速度非常快,因此更适合全局分布式锁。基本流程是在执行全局冲突操作时,通过一个全局唯一的key来判断是否有其他线程占用该资源;如果其他线程占用了该资源,则报错,资源耗尽或者系统循环等待。
如果没有其他线程占用,可以通过添加分布式锁来占用这个资源,然后任务执行完成后,释放分布式锁,其他线程就可以继续使用这个资源了。
那么通过Redis加锁的动作是什么呢? 简单加锁命令:命令为:setNX内部实现机制是判断key状态是否有数据。
如果没有数据,则设置为value并返回特殊值。
但这里有一个问题。
如果占用资源的线程意外退出,来不及释放分布式锁,锁就会永远被占用: 命令为: 1.setnx2.expire 给分布式锁添加同样的添加时间,锁过期时间。
这样,当锁定线程耗尽时,锁将有机会在最短时间后释放。
这里的一个小问题是,这两个命令都是单独执行的,不是原子操作。
理论上,有可能在执行第一个命令后,发生错误,来不及执行过期的命令。
一种方法是自己使用Lua编写可以实现多个命令的原子执行的脚本。
一种方法是引用一些开源库。
2.8版本之后,为了解决这个问题,Redis官方提供了一个解决方案,就是命令:setkeyvaluenxexpireTimeNumex,它将以上两个命令合并为一个命令。
有了过期时间,部分问题就解决了,但是也有可能锁已经过期了,但是其间执行的任务还没有完成,第一个线程还在执行,而另一个线程已经获取了锁并且开始执行。
这时,如果第一个线程执行完成,第二个线程的锁就会被释放。
当另一个线程释放锁时,要么出现错误,要么另一个线程释放锁,与预期不一致。
如果只是想解决这个问题,可以在设置值时使用随机数,先判断随机数是否一致,然后释放锁,否则退出。
但判断value和删除key并不是原子操作,这时候就需要使用Lua脚本了。
上述方案仍然无法解决超时释放的问题,仍然违背了分布式锁的初衷。
发生了什么? 解决问题的思路是启动另一个线程。
它的作用是周期性地判断当前线程的工作是否即将结束,如果没有完成则更新当前线程的锁。
经常。
有一个开源库可以解决这个问题,它可能是它会比你更好。
这是雷迪森图书馆,很好记,是雷迪森的儿子,加起来可能没有什么关系,这就够了。
这个库有一个叫做watchdog的组件,字面意思就是看门狗。
它的功能是定期做出决定。
如果继续思考的话,还有一个更严重的问题:如果Redis是单节点的话,就宕机了;如果是单节点的话,那就宕机了。
或者是主备节点,但备份节点没有时间同步主节点的数据。
主节点获取锁后,如果在同步数据前一刻崩溃,也可能会出现锁丢失的问题。
如果你认为这是一个问题并且你想解决它,你会如何解决它? 想法是在加锁的时候多加锁一些Redis服务器。
一般部署Redis时,有2n+1台服务器。
那么加锁的时候,必须保证超过一半的服务器被成功加锁,也就是n+1台服务器。
除非此时整个集群不可用,否则安全性将会大大提高。
这个问题也被一个开源库解决了,它就是Redis Red Lock。
接下来的问题是分布式锁是否可以重入? 如果要实现可重入分布式锁,需要在设置该值时添加线程信息和锁数量信息。
但这是一个简单的想法,如果加上过期时间等问题,重入锁可能会变得更加复杂。
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#生成 a随机字符串,作为密钥的唯一标识 lock_id=str(uuid.uuid4())#设置5个密钥的过期时间 分钟,如果过期未释放,则自动释放 ifnotr.set(lock_key,lock_id,ex=300,nx=True): #如果设置失败,说明该锁已被别人处理,返回FalsereturnFalse #获取的Key成功,返回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#Create a lock node, if any已经存在,则等待该节点前面的子节点完成操作并获取锁 client.put(lock_path ,'data',dir=True)try:#获取密钥 client.put(lock_path+'/data','data',dir=True) exceptEtcdKeyAlreadyExistase:#密钥已被其他进程获取,返回FalsereturnFalse#The获取锁成功,返回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"#Lock SQLtry:cursor.execute(sql)conn.commit()external exceptpymysql.err.OperationalError:#锁被其他进程获取,返回FalsereturnFalse#锁成功获取,返回TruereturnTrue。
上面的代码示例展示了结合Redis、ZooKeeper、etcd和MySQL进行部署的Python基本方法声明分布式密钥。
根据实际情况选择合适的解决方案。
要求。
请注意,这些示例代码需要适应实际环境和特定需求。
分布式锁的三种实现方式
实现分布式锁有以下三种方式: 基于数据库实现分布式锁 该方法主要使用SQL SELECT WHERE FORUPDATE语句来实现排它锁。请注意,如果使用此选项,则必须对 name=lock 字段建立索引。
不创建索引可能会导致表锁定问题。
乐观锁基于CAS(CompareAndSwap)的思想,通过增加版本号字段来实现非互斥操作。
适用于抢购、闪购等场景,减少资源消耗。
基于缓存(如Redis)实现分布式锁 使用Redis实现分布式锁需要三个主要命令:SETNX、EXPIRE、DEL。
首先使用SETNX设置锁,并使用EXPIRE给锁添加超时机制,防止死锁。
随机生成的UUID用作锁值来确定何时释放锁。
实现过程中要考虑锁获取超时、有序性、锁自动释放等问题。
基于Zookeeper实现分布式锁 使用Zookeeper实现分布式锁的步骤包括创建目录、获取线程的临时序列节点、获取子节点并确定锁、配置锁监听和释放。
我们建议使用 ApacheCurator 库,它提供 InterProcessMutex 来实现分布式锁定。
这种方式具有高可用、可重入、阻塞锁的特点,但性能比Redis实现稍低。
数据库分布式锁实现对比分析包括性能下降、表锁风险、非阻塞操作占用资源、资源占用等问题。
Redis实现的分布式锁存在解锁失败、非阻塞操作、占用资源等问题。
Zookeeper的性能不如Redis,主要是频繁的写操作。
虽然数据库和缓存实现在难度、实现复杂性、性能和可靠性方面都相对简单,但 Zookeeper 则更为复杂。
从性能上来说,缓存比数据库和Zookeeper要好。
在可靠性方面,Zookeeper 提供了最好的性能。
如何用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+注解? 伟大的。
使用起来很舒服。
不要重复代码。