首先是鎖的抽象類,定義了繼承的類必須實作加鎖、釋放鎖、回傳鎖擁有者的方法,
namespace Illuminate\Cache;
abstract class Lock implements LockContract
{
use InteractsWithTime;
// 鎖的名稱
protected $name;
// 鎖的時長
protected $seconds;
// 當前操作鎖的擁有者
protected $owner;
// 獲取鎖失敗時,重新獲取鎖需要等待的毫秒數
protected $sleepMilliseconds = 250;
// 建構式
public function __construct($name, $seconds, $owner = null)
{
if (is_null($owner)) {
$owner = Str::random();
}
$this->name = $name;
$this->owner = $owner;
$this->seconds = $seconds;
}
// 加鎖
abstract public function acquire();
// 釋放鎖
abstract public function release();
// 獲取鎖中保存的擁有者資訊
abstract protected function getCurrentOwner();
// 1. 嘗試獲取鎖,并回傳獲取結果
// 2. 嘗試獲取鎖,獲取成功后執行一個回呼函式,執行完成后自動釋放鎖
public function get($callback = null)
{
$result = $this->acquire();
if ($result && is_callable($callback)) {
try {
return $callback();
} finally {
$this->release();
}
}
return $result;
}
// 嘗試在指定的時間內獲取鎖,超時則失敗拋出例外
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;
}
// 回傳當前操作鎖的擁有者
public function owner()
{
return $this->owner;
}
// 判斷當前操作的擁有者是否為鎖中保存的擁有者
protected function isOwnedByCurrentProcess()
{
return $this->getCurrentOwner() === $this->owner;
}
// 設定重試獲取鎖需要等待的毫秒數
public function betweenBlockedAttemptsSleepFor($milliseconds)
{
$this->sleepMilliseconds = $milliseconds;
return $this;
}
}
Redis 鎖實作類,增加了強制洗掉鎖的方法,
class RedisLock extends Lock
{
// Redis物件
protected $redis;
// 建構式
public function __construct($redis, $name, $seconds, $owner = null)
{
parent::__construct($name, $seconds, $owner);
$this->redis = $redis;
}
// 加鎖邏輯代碼
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;
}
}
// 使用 Lua 腳本釋放鎖邏輯代碼
public function release()
{
return (bool) $this->redis->eval(LuaScripts::releaseLock(), 1, $this->name, $this->owner);
}
// 無視鎖的擁有者強制洗掉鎖
public function forceRelease()
{
$this->redis->del($this->name);
}
// 回傳鎖中保存的擁有者資訊
protected function getCurrentOwner()
{
return $this->redis->get($this->name);
}
}
原子性釋放鎖的 Lua 腳本,
class LuaScripts
{
/**
* 使用 Lua 腳本原子性地釋放鎖.
*
* KEYS[1] - 鎖的名稱
* ARGV[1] - 鎖的擁有者,只有是該鎖的擁有者才允許釋放
*
* @return string
*/
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;
}
}
總結:
- 可以通過get()方法直接獲取鎖并傳入回呼函式在成功時執行,
- 可以通過block()方法在指定時間內不斷獲取鎖,知道成功或超時為止,成功時會執行傳入的回呼函式,
- Redis 通過 set() 命令設定一個值為“擁有者”的字串來作為鎖,
- set() 通過 NX 引數來實作排他鎖(只在鍵不存在時,才對鍵進行設定),
- set() 通過 EX 引數來控制鎖的生存時間(防止程式意外終止發生死鎖),
- 不能使用 set()+expire() 來代替set(),防止網路延遲或其他故障導致死鎖,
- Redis 通過 Lua 腳本來達到原子性洗掉鎖,
- Lua 腳本中會判斷字串的內容是否與引數中的擁有者一致,一致才執行洗掉操作,防止當前鎖被其他行程誤洗掉,或者誤洗掉了其他行程的鎖,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/13958.html
標籤:PHP
下一篇:點擊提交form
