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

1.併發訪問限制問題

對於一些須要限制同一個用戶併發訪問的場景,若是用戶併發請求屢次,而服務器處理沒有加鎖限制,用戶則能夠屢次請求成功。php

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

僞代碼以下:數據庫

if A(能夠換領)瀏覽器

    B(執行換領)服務器

    C(更新爲已換領)網絡

D(結束)架構

若是用戶併發提交換領碼,都能經過能夠換領(A)的判斷,由於必須有一個執行換領(B)後,纔會更新爲已換領(C)。所以若是用戶在有一個更新爲已換領以前,有多少次請求,這些請求均可以執行成功。 
 併發

2.併發訪問限制方法

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

Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。 
本文將使用其setnx方法實現分佈式鎖功能。setnxSet 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>';
}

?>
//設置鎖,防止多個用戶併發操做連麥超出數量限制 
        $lock = $redis->lock($lockKey); 
        if(!$lock) { 
            for($i=0;$i<3;$i++){ //重試3次,若是3次還未獲取倒鎖提示繁忙 
                $lock = $redis->lock($lockKey); 
                if($lock){ 
                    break; 
                } 
                sleep(1); 
            } 
            if(!$lock){ 
                return; 
            } 
        } 
  
        doAction..... //獲取到了鎖,作本身的業務

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

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

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

爲了不繫統忽然出錯致使死鎖,因此在獲取鎖的時候增長一個過時時間,若是已超過過時時間,即便是鎖定狀態都會釋放鎖,避免死鎖致使的問題。 

轉載:https://blog.csdn.net/fdipzone/article/details/51793837

相關文章
相關標籤/搜索