對於一些須要限制同一個用戶併發訪問的場景,若是用戶併發請求屢次,而服務器處理沒有加鎖限制,用戶則能夠屢次請求成功。php
例如換領優惠券,若是用戶同一時間併發提交換領碼,在沒有加鎖限制的狀況下,用戶則可使用同一個換領碼同時兌換到多張優惠券。html
僞代碼以下:redis
1 if A(能夠換領) 2 B(執行換領) 3 C(更新爲已換領) 4 D(結束)
若是用戶併發提交換領碼,都能經過能夠換領(A)的判斷,由於必須有一個執行換領(B)後,纔會更新爲已換領(C)。所以若是用戶在有一個更新爲已換領以前,有多少次請求,這些請求均可以執行成功。 數據庫
使用文件鎖能夠實現併發訪問限制,但對於分佈式架構的環境,使用文件鎖不能保證多臺服務器的併發訪問限制。瀏覽器
Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。
本文將使用其setnx方法實現分佈式鎖功能。setnx即Set it Not eXists。 服務器
語法:網絡
SETNX key value
將 key
的值設爲 value
,當且僅當 key
不存在。架構
若給定的 key
已經存在,則 SETNX 不作任何動做。併發
SETNX 是『SET if Not eXists』(若是不存在,則 SET)的簡寫。分佈式
1
。
0
。
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 ?>
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