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

1.併發訪問限制問題

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

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

僞代碼以下:redis

1 if A(能夠換領)
2     B(執行換領)
3     C(更新爲已換領)
4 D(結束)

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

2.併發訪問限制方法

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

Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。 
本文將使用其setnx方法實現分佈式鎖功能。setnxSet it Not eXists。 服務器

語法:網絡

SETNX key value

將 key 的值設爲 value ,當且僅當 key 不存在。架構

若給定的 key 已經存在,則 SETNX 不作任何動做。併發

SETNX 是『SET if Not eXists』(若是不存在,則 SET)的簡寫。分佈式

時間複雜度:
O(1)
返回值:
設置成功,返回  1 。
設置失敗,返回  0 。
 
RedisLock.class.php
 1 <?php
 2 /**
 3  *  Redis鎖操做類
 4  *  Date:   2016-06-30
 5  *  Author: fdipzone
 6  *  Ver:    1.0
 7  *
 8  *  Func:
 9  *  public  lock    獲取鎖
10  *  public  unlock  釋放鎖
11  *  private connect 鏈接
12  */
13 class RedisLock { // class start
14 
15     private $_config;
16     private $_redis;
17 
18     /**
19      * 初始化
20      * @param Array $config redis鏈接設定
21      */
22     public function __construct($config=array()){
23         $this->_config = $config;
24         $this->_redis = $this->connect();
25     }
26 
27     /**
28      * 獲取鎖
29      * @param  String  $key    鎖標識
30      * @param  Int     $expire 鎖過時時間
31      * @return Boolean
32      */
33     public function lock($key, $expire=5){
34         $is_lock = $this->_redis->setnx($key, time()+$expire);
35 
36         // 不能獲取鎖
37         if(!$is_lock){
38 
39             // 判斷鎖是否過時
40             $lock_time = $this->_redis->get($key);
41 
42             // 鎖已過時,刪除鎖,從新獲取
43             if(time()>$lock_time){
44                 $this->unlock($key);
45                 $is_lock = $this->_redis->setnx($key, time()+$expire);
46             }
47         }
48 
49         return $is_lock? true : false;
50     }
51 
52     /**
53      * 釋放鎖
54      * @param  String  $key 鎖標識
55      * @return Boolean
56      */
57     public function unlock($key){
58         return $this->_redis->del($key);
59     }
60 
61     /**
62      * 建立redis鏈接
63      * @return Link
64      */
65     private function connect(){
66         try{
67             $redis = new Redis();
68             $redis->connect($this->_config['host'],$this->_config['port'],$this->_config['timeout'],$this->_config['reserved'],$this->_config['retry_interval']);
69             if(empty($this->_config['auth'])){
70                 $redis->auth($this->_config['auth']);
71             }
72             $redis->select($this->_config['index']);
73         }catch(RedisException $e){
74             throw new Exception($e->getMessage());
75             return false;
76         }
77         return $redis;
78     }
79 
80 } // class end
81 
82 ?>
 
demo.php
 1 <?php
 2 require 'RedisLock.class.php';
 3 
 4 $config = array(
 5     'host' => 'localhost',
 6     'port' => 6379,
 7     'index' => 0,
 8     'auth' => '',
 9     'timeout' => 1,
10     'reserved' => NULL,
11     'retry_interval' => 100,
12 );
13 
14 // 建立redislock對象
15 $oRedisLock = new RedisLock($config);
16 
17 // 定義鎖標識
18 $key = 'mylock';
19 
20 // 獲取鎖
21 $is_lock = $oRedisLock->lock($key, 10);
22 
23 if($is_lock){
24     echo 'get lock success<br>';
25     echo 'do sth..<br>';
26     sleep(5);
27     echo 'success<br>';
28     $oRedisLock->unlock($key);
29 
30 // 獲取鎖失敗
31 }else{
32     echo 'request too frequently<br>';
33 }
34 
35 ?>

 

測試方法: 

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

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

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


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

 

 

 

 

 

 

本文轉自:http://blog.csdn.net/fdipzone/article/details/51793837

相關文章
相關標籤/搜索