PHP利用Redis鎖解決併發訪問

Billy_liu發表於2021-11-02
  • 併發訪問限制問題

對於一些需要限制同一個使用者併發訪問的場景,如果使用者併發請求多次,而伺服器處理沒有加鎖限制,使用者則可以多次請求成功。例如換領優惠券,如果使用者同一時間併發提交換領碼,在沒有加鎖限制的情況下,使用者則可以使用同一個換領碼同時兌換到多張優惠券。

常見的業務邏輯程式碼如下:

if A(可以換領)
   B(執行換領)
   C(更新為已換領)
D(結束)

如果使用者併發提交換領碼,都能通過可以換領(A)的判斷,因為必須有一個執行換領(B)後,才會更新為已換領(C)。因此如果使用者在有一個更新為已換領之前,有多少次請求,這些請求都可以執行成功。

  • 併發訪問限制方法

使用檔案鎖可以實現併發訪問限制,但對於分散式架構的環境,使用檔案鎖不能保證多臺伺服器的併發訪問限制。

具體的 redis 加鎖類和示例程式碼如下:

<?php

/**
 *  Redis 操作類
 *  Func:
 *  public  lock    獲取鎖
 *  public  unlock  釋放鎖
 *  private connect 連線
 */
class RedisLock
{ // class start

    private $_config;
    private $_redis;

    /**
     * RedisLock constructor.
     * @param array $config
     * @throws Exception
     */
    public function __construct($config = [])
    {
        $this->_config = $config;
        $this->_redis = $this->connect();
    }

    /**
     * 獲取鎖
     * @param  String $key 鎖標識
     * @param  Int $expire 鎖過期時間
     * @return Boolean
     */
    public function lock($key, $expire = 5)
    {
        $is_lock = $this->_redis->setnx($key, time() + $expire);

        // 不能獲取鎖
        if (!$is_lock) {

            // 判斷鎖是否過期
            $lock_time = $this->_redis->get($key);

            // 鎖已過期,刪除鎖,重新獲取
            if (time() > $lock_time) {
                $this->unlock($key);
                $is_lock = $this->_redis->setnx($key, time() + $expire);
            }
        }

        return $is_lock ? true : false;
    }

    /**
     * 釋放鎖
     * @param  String $key 鎖標識
     * @return Boolean
     */
    public function unlock($key)
    {
        return $this->_redis->del($key);
    }

    /**
     * 連結redis
     * @return bool|Redis
     * @throws Exception
     */
    private function connect()
    {
        try {
            $redis = new \Redis();
            $redis->connect($this->_config['host'], $this->_config['port'], $this->_config['timeout'], $this->_config['reserved'], $this->_config['retry_interval']);
            if (empty($this->_config['auth'])) {
                $redis->auth($this->_config['auth']);
            }
            $redis->select($this->_config['index']);
        } catch (RedisException  $e) {
            throw new Exception($e->getMessage());
            return false;
        }
        return $redis;
    }
}

$config = array(
    'host' => 'localhost',
    'port' => 6379,
    'index' => 0,
    'auth' => '',
    'timeout' => 1,
    'reserved' => NULL,
    'retry_interval' => 100,
);

// 建立redislock物件
$oRedisLock = new RedisLock($config);

// 定義鎖標識
$key = 'mylock';

// 獲取鎖
$is_lock = $oRedisLock->lock($key, 10);

if ($is_lock) {
    echo 'get lock success<br>';
    echo 'do sth..<br>';
    sleep(5);
    echo 'success<br>';
    //釋放鎖
    $oRedisLock->unlock($key);

// 獲取鎖失敗
} else {
    echo 'request too frequently<br>';
}

保證同一時間只有一個訪問有效,有效限制併發訪問。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章