對於一些須要限制同一個用戶併發訪問的場景,若是用戶併發請求屢次,而服務器處理沒有加鎖限制,用戶則能夠屢次請求成功。php
例如換領優惠券,若是用戶同一時間併發提交換領碼,在沒有加鎖限制的狀況下,用戶則可使用同一個換領碼同時兌換到多張優惠券。redis
僞代碼以下:數據庫
if A(能夠換領)瀏覽器
B(執行換領)服務器
C(更新爲已換領)網絡
D(結束)架構
若是用戶併發提交換領碼,都能經過能夠換領(A)的判斷,由於必須有一個執行換領(B)後,纔會更新爲已換領(C)。所以若是用戶在有一個更新爲已換領以前,有多少次請求,這些請求均可以執行成功。
併發
使用文件鎖能夠實現併發訪問限制,但對於分佈式架構的環境,使用文件鎖不能保證多臺服務器的併發訪問限制。分佈式
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['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