php 使用redis鎖限制併發訪問類

1.併發訪問限制問題

對於一些需要限制同一個用戶併發訪問的場景,如果用戶併發請求多次,而服務器處理沒有加鎖限制,用戶則可以多次請求成功。

例如換領優惠券,如果用戶同一時間併發提交換領碼,在沒有加鎖限制的情況下,用戶則可以使用同一個換領碼同時兌換到多張優惠券。

僞代碼如下:

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

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

2.併發訪問限制方法
使用文件鎖可以實現併發訪問限制,但對於分佈式架構的環境,使用文件鎖不能保證多臺服務器的併發訪問限制。

Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。
本文將使用其setnx方法實現分佈式鎖功能。setnx即Set it Not eXists。
當鍵值不存在時,插入成功(獲取鎖成功),如果鍵值已經存在,則插入失敗(獲取鎖失敗)

RedisLock.class.php

<?php
/**
 *  Redis鎖操作類
 *  Date:   2016-06-30
 *  Author: fdipzone
 *  Ver:    1.0
 *
 *  Func:
 *  public  lock    獲取鎖
 *  public  unlock  釋放鎖
 *  private connect 連接
 */
class RedisLock { // class start

    private $_config;
    private $_redis;

    /**
     * 初始化
     * @param Array $config redis連接設定
     */
    public function __construct($config=array()){
        $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 Link
     */
    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;
    }

} // class end

?>

demo.php

<?php
require 'RedisLock.class.php';

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

?>

測試方法:
打開兩個不同的瀏覽器,同時在A,B中訪問demo.php
如果先訪問的會獲取到鎖
輸出
get lock success
do sth…
success

另一個獲取鎖失敗則會輸出request too frequently

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

爲了避免系統突然出錯導致死鎖,所以在獲取鎖的時候增加一個過期時間,如果已超過過期時間,即使是鎖定狀態都會釋放鎖,避免死鎖導致的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章