標籤:資料 this localhost timeout ram 不同 測試 網路 exception
1.並發訪問限制問題
對於一些需要限制同一個使用者並發訪問的情境,如果使用者並發請求多次,而伺服器處理沒有加鎖限制,使用者則可以多次請求成功。
例如換領優惠券,如果使用者同一時間並發提交換領碼,在沒有加鎖限制的情況下,使用者則可以使用同一個換領碼同時兌換到多張優惠券。
虛擬碼如下:
if A(可以換領)
B(執行換領)
C(更新為已換領)
D(結束)
如果使用者並發提交換領碼,都能通過可以換領(A)的判斷,因為必須有一個執行換領(B)後,才會更新為已換領(C)。因此如果使用者在有一個更新為已換領之前,有多少次請求,這些請求都可以執行成功。
2.並發訪問限制方法
使用檔案鎖可以實現並發訪問限制,但對於分布式架構的環境,使用檔案鎖不能保證多台伺服器的並發訪問限制。
Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。
本文將使用其setnx方法實現分布式鎖功能。setnx即Set it N**ot eX**ists。
當索引值不存在時,插入成功(擷取鎖成功),如果索引值已經存在,則插入失敗(擷取鎖失敗)
2.簡單案例
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 start14 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 Boolean32 */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 Boolean56 */57 public function unlock($key){58 return $this->_redis->del($key);59 }60 61 /**62 * 建立redis串連63 * @return Link64 */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 end81 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
保證同一時間只有一個訪問有效,有效限制並發訪問。
為了避免系統突然出錯導致死結,所以在擷取鎖的時候增加一個到期時間,如果已超過到期時間,即使是鎖定狀態都會釋放鎖,避免死結導致的問題
(執行個體篇)php 使用redis鎖限制並發訪問類樣本