本文介紹了php 使用redis鎖定限制並發存取類,並詳細的介紹了並發存取限制方法。
1.並發訪問限制問題
對於一些需要限制同一個用戶並發訪問的場景,如果用戶並發請求多次,而伺服器處理沒有加鎖限制,用戶則可以多次請求成功。
例如換領優惠券,如果用戶同一時間並發提交換領碼,在沒有加鎖限制的情況下,用戶則可以使用同一個換領碼同時兌換到多張優惠券。
偽代碼如下:
if A(可以換領)
B(執行換領)
C(更新為已換領)
D(結束)
通過可以換領(A)的判斷,因為必須有一個執行換領(B)後,才會更新為已換領(C)。因此如果使用者在有一個更新為已換領之前,有多少次請求,這些請求都可以執行成功。
2.並發存取限制方法
使用檔案鎖可以實現並發存取限制,但對於分散式架構的環境,使用檔案鎖不能保證多台伺服器的並發存取限制。
Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌類型、Key-Value資料庫,並提供多種語言的API。
本文將使用其setnx方法來實現分散式鎖定功能。 setnx即Set it N**ot eX**ists。
當鍵值不存在時,插入成功(獲取鎖定成功),如果鍵值已經存在,則插入失敗(獲取鎖定失敗)
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 ?>
<?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>'; } ?>
do sth..
success
另一個獲取鎖定失敗則會輸出request too frequently
為了避免系統突然出錯導致死鎖,所以在獲取鎖的時候增加一個過期時間,如果已超過過期時間,即使是鎖定狀態都會釋放鎖,避免死鎖導致的問題。