php 使用redis鎖定限制並發存取類

黄舟
發布: 2023-03-05 19:22:01
原創
1715 人瀏覽過


1.並發存取限制問題


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

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

偽代碼如下:

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[&#39;host&#39;],
            $this->_config[&#39;port&#39;],$this->_config[&#39;timeout&#39;],
            $this->_config[&#39;reserved&#39;],$this->_config[&#39;retry_interval&#39;]);            
            if(empty($this->_config[&#39;auth&#39;])){
                $redis->auth($this->_config[&#39;auth&#39;]);
            }
            $redis->select($this->_config[&#39;index&#39;]);
        }catch(RedisException $e){            
        throw new Exception($e->getMessage());            
        return false;
        }        
        return $redis;
    }

} // class end?>
登入後複製

<?php
require &#39;RedisLock.class.php&#39;;
$config = array(    &#39;host&#39; => &#39;localhost&#39;,    &#39;port&#39; => 6379,    &#39;index&#39; => 0,    &#39;auth&#39; => &#39;&#39;,    &#39;timeout&#39; => 1,    
&#39;reserved&#39; => NULL,    &#39;retry_interval&#39; => 100,
);
// 创建redislock对象$oRedisLock = new RedisLock($config);
// 定义锁标识$key = &#39;mylock&#39;;
// 获取锁$is_lock = $oRedisLock->lock($key, 10);
if($is_lock){    
echo &#39;get lock success<br>&#39;;    
echo &#39;do sth..<br>&#39;;
    sleep(5);    
    echo &#39;success<br>&#39;;    
    $oRedisLock->unlock($key);
    // 获取锁失败
    }
    else{    
    echo &#39;request too frequently<br>&#39;;
}?>
登入後複製



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

另一個獲取鎖定失敗則會輸出

request too frequently

保證同一時間只有一個存取有效,有效限制並發存取。



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


源碼下載地址:點擊查看
1.並發訪問限制問題

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

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

偽代碼如下:

if A(可以换领)
    B(执行换领)
    C(更新为已换领)D(结束)
登入後複製
登入後複製
如果用戶並發提交換領碼,都能通過可以換領(A)的判斷,因為必須有一個執行換領(B)後,才會更新為已換領(C)。因此如果使用者在有一個更新為已換領之前,有多少次請求,這些請求都可以執行成功。



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[&#39;host&#39;],
            $this->_config[&#39;port&#39;],
            $this->_config[&#39;timeout&#39;],
            $this->_config[&#39;reserved&#39;],
            $this->_config[&#39;retry_interval&#39;]);            
            if(empty($this->_config[&#39;auth&#39;])){
                $redis->auth($this->_config[&#39;auth&#39;]);
            }
            $redis->select($this->_config[&#39;index&#39;]);
        }catch(RedisException $e){            throw new Exception($e->getMessage());            
        return false;
        }        return $redis;
    }

} // class end?>
登入後複製

<?phprequire &#39;RedisLock.class.php&#39;;
$config = array(    &#39;host&#39; => &#39;localhost&#39;,    &#39;port&#39; => 6379,    &#39;index&#39; => 0,    &#39;auth&#39; => &#39;&#39;,    &#39;timeout&#39; => 1,   
 &#39;reserved&#39; => NULL,    &#39;retry_interval&#39; => 100,
);
// 创建redislock对象$oRedisLock = new RedisLock($config);
// 定义锁标识$key = &#39;mylock&#39;;
// 获取锁$is_lock = $oRedisLock->lock($key, 10);
if($is_lock){    
echo &#39;get lock success<br>&#39;;    
echo &#39;do sth..<br>&#39;;
    sleep(5);    
    echo &#39;success<br>&#39;;    
    $oRedisLock->unlock($key);
    // 获取锁失败
    }
    else{    
    echo &#39;request too frequently<br>&#39;;
}?>
登入後複製

測試方法:

打開兩個不同的瀏覽器,同時在A,B中訪問demo.php 如果先訪問的會獲取到鎖輸出

get 5cc .
success

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

保證同一時間只有一個存取有效,有效限制並發存取。

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



 以上就是php 使用redis鎖定限制並發存取類的內容,更多相關內容請關注PHP中文網(www.php.cn)!


來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!