分布式锁介绍
分布式锁,主要考察使用者对原子性的理解,原子性可以保证程序从异常中恢复后,redis中的数据是正确的,程序依然正常运行。分布式锁是实现线程同步手段之一。
分布式锁原理
分布式锁其实就是在进程里占一个“坑”,当别的进程来占坑时,发现那里已经有一根“大萝卜”了,就只好放弃或者稍后再试。占“坑”就是把所有的逻辑变成单进程来执行。
Redis 实现分布式锁
实现分布式锁,三个版本进行演变,最终达到完美的分布式锁功能。
基础版本
require_once("../RedisClient.php"); $client = RedisClient::getInstance(); /** * 基础版,完成锁的请求与释放,但是有严重的问题. * 如果请求锁完成后,宕机了,就成死锁了.再也不能请求到锁了 * @return null */ function base () { global $client; //请求锁 $lock = $client->set('lock','true'); if(!$lock){ return null; } // do something critical ... //释放锁 $client->del('lock'); }
存在的问题
如果在释放锁前,服务器宕机了,那么我们永远都无法重新申请锁,就成死锁了。我们可以利用redis 的 set
命令给锁设置一个过期时间,服务器宕机了,锁过一段时间也会重新释放。
过期时间随机数版
很多同学会有疑问为什么需要添加随机数,不妨静下心仔细想想,如果我们给的时间过期了,锁不存在了,刚好另一个进程站用了这个“坑”,那我们是不是删错了呢?
require_once("../RedisClient.php"); $client = RedisClient::getInstance(); /** * 设置锁和添加过期时间放在一个命令中,要成功一起成功 * 添加随机数,防止过期时间后,删除了其他进程的锁 * @return null */ function perfect () { global $client; $unique = uniqid(); //请求锁并添加过期时间 $lock = $client->set('lock',$unique,'ex',60,'nx'); if(!$lock){ return null; } // do something critical ... //释放锁 if ($client->get('lock')==$unique){ $client->del('lock'); }; }
存在的问题
由于 Redis
没有提供带条件删除的命令,需我们手动去匹配随机数,在进行了锁匹配中,有可能锁刚匹配完,本进程锁的过期时间到了,系统自动删除。而另一个进程已经申请了锁,我们命令删除另一个进程的锁,造成脏数据。这事我们需要用到 Lua
脚本进行原子性执行。
Lua 脚本版
use Predis\Command\ScriptCommand; require_once("../RedisClient.php"); $client = RedisClient::getInstance(); class releaseLockScript extends ScriptCommand { public function getScript() { return <<<LUA if redis.call("get",ARGV[1]) == ARGV[2] then return redis.call("del",ARGV[1]) else return 0 end LUA; } } // 定义锁命令 $client->getProfile()->defineCommand('releaseLock','releaseLockScript'); function lua () { global $client; $unique = uniqid(); //请求锁并添加过期时间 $lock = $client->set('lock',$unique,'ex',60,'nx'); if(!$lock){ return null; } // do something critical ... //释放锁 $client->releaseLock('lock',$unique) }
存在的问题
当然这个也不是一个完美的方案,只是相对安全一点,如果真的超时了。其他逻辑还没有执行完,其他线程也会趁虚而入。
分布式锁的缺点
- 分布式锁一旦加了之后,对同一个商品的下单请求,会导致所有客户端都必须对同一个商品的库存锁key进行加锁。这样会导致对同一个商品的下单请求,就必须串行化,一个接一个的处理。
- 不适合执行较长逻辑的代码的请求。
拓展
分布式锁是一种思想,除了用 Redis
实现分布式锁外,也可以试着用:Mysql、Memcache、Zookeeper 等去实现