laravel redis分布式锁原理
Route::get('/test', function () {
$lock = \Illuminate\Support\Facades\Cache::lock('test', 10, 'owner');
try {
// 未获取锁的消费者将阻塞在这里
$lock->block(10);
sleep(10);
dump(123);
} catch (\Illuminate\Contracts\Cache\LockTimeoutException $e) {
dump('库存不足---------');
} finally {
optional($lock)->release();
}
});
1. Illuminate\Support\Facades\Cache::lock(‘test’, 10, ‘owner’)
1.1 实例化RedisLock
Illuminate\Cache\RedisStore
public function lock($name, $seconds = 0, $owner = null)
{
$lockName = $this->prefix.$name;
$lockConnection = $this->lockConnection();
if ($lockConnection instanceof PhpRedisConnection) {
return new PhpRedisLock($lockConnection, $lockName, $seconds, $owner);
}
//实例化redisLock
return new RedisLock($lockConnection, $lockName, $seconds, $owner);
}
1.2 赋值参数
Illuminate\Cache\RedisLock;
public function __construct($redis, $name, $seconds, $owner = null)
{
parent::__construct($name, $seconds, $owner);
$this->redis = $redis;
}
Illuminate\Cache\Lock;
public function __construct($name, $seconds, $owner = null)
{
//锁拥有者,不传参数则生成
if (is_null($owner)) {
$owner = Str::random();
}
$this->name = $name;
$this->owner = $owner;
$this->seconds = $seconds;
}
2 $lock->block(2)
2.1 阻塞原理
Illuminate\Cache\Lock;
public function block($seconds, $callback = null)
{
//阻塞开始时间
$starting = $this->currentTime();
//如果一个客户端加锁成功并未释放锁,在加锁期间另外一个客户端也访问,则进入循环(循环过程中,如果第一个客户端锁已释放,则加锁...)
while (! $this->acquire()) {
//进程休息多少微秒
usleep($this->sleepMilliseconds * 1000);
//当前时间-阻塞时间>开始时间 则抛出异常
if ($this->currentTime() - $seconds >= $starting) {
throw new LockTimeoutException;
}
}
if (is_callable($callback)) {
try {
return $callback();
} finally {
$this->release();
}
}
return true;
}
如果加锁成功返回true,锁存在则返回false
Illuminate\Cache\RedisLock;
public function acquire()
{
if ($this->seconds > 0) {
return $this->redis->set($this->name, $this->owner, 'EX', $this->seconds, 'NX') == true;
} else {
return $this->redis->setnx($this->name, $this->owner) === 1;
}
}
3 optional($lock)->release();
3.1释放锁
Illuminate\Cache\RedisLock;
public function release()
{
return (bool) $this->redis->eval(LuaScripts::releaseLock(), 1, $this->name, $this->owner);
}
redis脚本,保持操作原子性
Illuminate\Cache\LuaScripts;
public static function releaseLock()
{
return <<<'LUA'
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
LUA;
}