当前位置: 首页 > 后端技术 > PHP

php和Redis实现分布式锁

时间:2023-03-29 18:44:14 PHP

1.分布式锁的作用:Redis在写的时候没有加锁的功能。为了防止多个进程同时执行一个操作,出现意想不到的结果,所以...缓存自定义插入和更新操作时的锁定功能。2、Redis的NX后缀命令Redis有一系列的命令,其特点是以NX结尾。NX的含义可以理解为NOTEXISTS(不存在),SETNX命令(SETIFNOTEXISTS)可以理解为不存在则插入。Redis分布式锁的实现主要使用SETNX命令。3.实现原理在进程请求执行操作之前判断锁是否成功。如果加锁成功,则允许进行下一步操作;如果不成功,则判断锁的值(时间戳)是否大于当前时间。如果获取锁失败,则不允许进行下一步操作;如果锁值(时间戳)小于当前时间,且GETSET命令获取的锁旧值仍小于当前时间,则锁获取成功,允许进行下一步操作;如果锁值(时间戳)小于当前时间,而GETSET命令获取的锁的旧值大于当前时间,则获取锁失败,不允许进行下一步操作;4.$redis->setnx()设置锁$expire=10;//有效期为10秒$key='lock';//key$value=time()+$expire;//的值thelock=Unix时间戳+锁的有效期$lock=$redis->setnx($key,$value);//判断是否加锁成功,如果成功则执行下一步if(!empty($lock)){//Nextstep...}如果返回1,说明当前进程已经获取到锁,并且获得了对当前insert/updatecache的操作权限。如果返回0,表示锁已经被其他进程获取。这意味着进程可以返回结果或等待当前锁过期后再请求。5.解决死锁如果只使用SETNX命令设置锁,如果持有锁的进程崩溃或者删除锁失败,其他进程将无法获取到锁,问题会很严重。解决方法是获取锁失败时获取锁的值,与当前时间进行比较。如果该值小于当前时间,则表示锁已过期。进程可以使用Redis的DEL命令来删除锁。$expire=10;//有效期10秒$key='lock';//key$value=time()+$expire;//锁值=Unix时间戳+锁的有效期$status=true;while($status){$lock=$redis->setnx($key,$value);如果(空($lock)){$value=$redis->get($key);如果($valuedel($key);}}else{$status=false;//下一步....}}但是简单粗暴的用DEL命令删除锁然后用SETNX命令加锁也会出现问题。比如进程1获取锁后崩溃或者删除锁失败。此时进程2检测到锁已经过期,用DEL命令删除锁,用SETNX命令设置锁。进程3也检测到锁过期,用DEL命令删除锁。也可以使用SETNX命令设置锁定。此时进程2和进程3同时获得了锁。大问题!为了解决这个问题,这里使用了Redis的GETSET命令。GETSET命令在为锁设置新值的同时返回锁的旧值。这里使用GETSET命令同时获取和赋值。在此期间,其他进程不能修改锁。价值。例如:进程1获取锁后,操作超时/崩溃/删除锁失败。进程2检测到锁已经存在,但是将获取到的锁的值与当前时间进行比较,发现锁已经过期。进程2通过GETSET命令为锁重新分配一个新值。并得到锁的旧值,再次将锁的旧值与当前时间进行比较,如果锁的旧值仍然小于当前时间,则进程2可以忽略进程1留下的剩余废锁并进行下一步。进程2应该在完成下一步后返回之前删除锁。但是在删除锁的时候,可以先检查锁是否没有过期,然后再执行删除操作。如果锁已经过期,则不需要删除锁,因为很有可能其他进程在检测到锁已过期时已经获取了锁。这里要说明的是,如果其他进程先于进程2获取锁,那么进程2会获取锁失败,但是进程2在使用GETSET获取锁旧值的时候也给了锁一个新值,重写其他进程分配给锁的超时值。看到这里,大家可能会有疑惑。进程2如何在没有获取到锁的情况下改变锁的值呢?是的,进程2改变了锁原来的值,但是这个小的时间错误的影响可以忽略。以下是Redis实现分布式锁的完整PHP代码:get($key);//判断缓存中是否有数据if(empty($result)){$状态=真;while($status){//设置锁值为当前时间戳+有效期$lockValue=time()+$lockExpire;/***创建锁*尝试使用$lockKey作为key创建缓存,值为当前时间戳*由于setnx()函数只有在当前key没有缓存的情况下才会创建成功*所以,这个函数可以用来判断当前执行的操作是否已经被其他进程执行过*@var[type]*/$lock=$redis->setnx($lockKey,$lockValue);/***满足两个条件之一即可操作*1.上面这一步创建锁成功;*2,1)判断锁的值(时间戳)是否小于当前时间$redis->get()*2)同时为锁设置新值成功$redis->getset()*/if(!empty($lock)||($redis->get($lockKey)getSet($lockKey,$lockValue)exp的生命周期愤怒($lockKey,$lockExpire);//***********************************//在这里插入和更新缓存Operation...//******************************//以上程序完成后删除锁//检查锁是否过期,没有需要删除过期锁if($redis->ttl($lockKey))$redis->del($lockKey);$状态=假;}else{/***如果存在有效锁,则在此处做相应处理*Wait当前操作完成后执行本次请求*直接return*/sleep(2);//等待2秒再尝试执行操作}}}用于实现分布式锁的Redis命令介绍:setnx(key,value)设置key的值设置为value,当且仅当key不存在。如果给定的键已经存在,SETNX不采取任何行动。SETNX是“SETifNoteXists”(如果不存在则设置)的简写。返回值:设置成功,返回1。设置失败,返回0。get(key)返回key关联的字符串值。如果键不存在,则返回特殊值nil。如果key存储的值不是字符串类型,则返回错误,因为GET只能用于处理字符串值。返回值:key的值。如果键不存在则返回nil。getset(key,value)将给定键的值设置为值并返回键的旧值。当键存在但不是字符串时返回错误。返回值:返回给定键的旧值(oldvalue)。当键没有旧值时返回nil。expire(key,seconds)设置给定键的生存时间。当密钥过期时,它会被自动删除。在Redis中,具有生存时间的键被称为“易失性”。在低于2.1.3的Redis版本中,无法覆盖现有的生存时间。从版本2.1.3开始,可以通过PERSIST命令更新或删除密钥的生命周期。(详见[http://redis.io/topics/expire]返回值:设置成功则返回1。当key不存在或无法为key设置生存期时(例如,如果你尝试更新低于2.1.3timetolive的key),返回0。ttl(key)返回给定key的剩余生存时间(以秒为单位)。返回值:key的剩余生存时间(以秒为单位)seconds).当key没有或没有设置lifetime时,返回-1.del(key)删除给定的一个或多个key.返回值:删除的key个数。