Laravel Redis分佈式鎖實現源碼分析

首先是鎖的抽象類,定義了繼承的類必須實現加鎖、釋放鎖、返回鎖擁有者的方法。redis

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();

    // 嘗試獲取鎖,並執行一個回調函數
    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 鎖實現類,增長了強制刪除鎖的方法。dom

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;
    }
}

總結:ui

  1. 能夠經過get()方法直接獲取鎖並傳入回調函數在成功時執行。
  2. 能夠經過block()方法在指定時間內不斷獲取鎖,知道成功或超時爲止,成功時會執行傳入的回調函數。
  3. Redis 經過 set() 命令設置一個值爲「擁有者」的字符串來做爲鎖。
  4. set() 經過 NX 參數來實現排他鎖(只在鍵不存在時,纔對鍵進行設置)。
  5. set() 經過 EX 參數來控制鎖的生存時間(防止程序意外終止發生死鎖)。
  6. Redis 經過 Lua 腳原本達到原子性刪除鎖。
  7. Lua 腳本中會判斷字符串的內容是否與參數中的擁有者一致,一致才執行刪除操做。
相關文章
相關標籤/搜索