PHP使用redis實(shí)現(xiàn)分布式鎖的示例詳解
最近在做一個(gè)領(lǐng)券功能的時(shí)候,發(fā)現(xiàn)在一定并發(fā)下會(huì)出現(xiàn)重復(fù)領(lǐng)券的問(wèn)題。使用度娘一頓搜索操作之后,發(fā)現(xiàn)可以使用分布式鎖來(lái)解決這個(gè)問(wèn)題。
什么是分布式鎖
分布式鎖是控制分布式系統(tǒng)之間同步訪問(wèn)共享資源的一種方式。在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動(dòng)作。如果不同的系統(tǒng)或是同一個(gè)系統(tǒng)的不同主機(jī)之間共享了一個(gè)或一組資源,那么訪問(wèn)這些資源的時(shí)候,往往需要互斥來(lái)防止彼此干擾來(lái)保證一致性,這個(gè)時(shí)候,便需要使用到分布式鎖。
實(shí)現(xiàn)原理
實(shí)現(xiàn)分布式鎖的原理很簡(jiǎn)單,就是需要有一把鎖,多個(gè)服務(wù)同時(shí)去獲取鎖,但是只有一個(gè)服務(wù)能獲取到鎖。獲取到鎖的服務(wù)就可以執(zhí)行自己的業(yè)務(wù),沒有獲取到鎖的其他服務(wù)需要等待獲取到鎖的服務(wù)業(yè)務(wù)執(zhí)行完成后釋放鎖,然后再次嘗試獲取鎖。
實(shí)現(xiàn)分布式的方案有很多種。如下
- 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖,比如mysql
- 基于緩存實(shí)現(xiàn)分布式鎖,比如redis
- 基于Zookeeper實(shí)現(xiàn)分布式鎖
這里我們使用redis來(lái)實(shí)現(xiàn)分布式鎖,在執(zhí)行業(yè)務(wù)之前先獲取一個(gè)key,如果key存在就說(shuō)明已經(jīng)有其他服務(wù)獲得鎖,這個(gè)時(shí)候需要等待或者返回系統(tǒng)繁忙。如果key不存在,說(shuō)明沒有其他服務(wù)獲取鎖,把這個(gè)key保存到redis,然后執(zhí)行業(yè)務(wù),等待業(yè)務(wù)執(zhí)行完就從redis中刪除這個(gè)key。
php實(shí)現(xiàn)代碼
<?php class RedisLock { protected $redis; public function __construct(){ $redis = new Redis(); $redis->connect('127.0.0.1',6379); $this->redis = $redis; } public function getLock($key){ $value = $this->redis->get($key); return $value; } public function setLock($key,$value){ $this->redis->set($key,$value); } public function delLock($key){ $lineNumber = $thid->redis->del($key); return $lineNumber; } } $key = 'your_lock_key'; $value = time(); $redisLock = new RedisLock(); $isLock = $redisLock->get($key); if($isLock) { //已有鎖,直接返回,不往下執(zhí)行了 return false; } //沒有鎖,加鎖 $redisLock->setLock($key,$value); //todo 執(zhí)行業(yè)務(wù)邏輯 sleep(5); // 解鎖 $redisLock->delLock($key);
使用ab進(jìn)行測(cè)試
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
執(zhí)行業(yè)務(wù)
解鎖
解鎖
執(zhí)行業(yè)務(wù)
解鎖
執(zhí)行業(yè)務(wù)
解鎖
執(zhí)行業(yè)務(wù)
解鎖
解鎖
執(zhí)行業(yè)務(wù)
解鎖
加鎖
執(zhí)行業(yè)務(wù)
解鎖
加鎖
執(zhí)行業(yè)務(wù)
解鎖
從測(cè)試結(jié)果來(lái)看,發(fā)現(xiàn)有多個(gè)執(zhí)行業(yè)務(wù),并沒有完全鎖住。這個(gè)是因?yàn)槲覀冇玫氖莚edis的set命令。set 命令用于設(shè)置給定 key 的值。如果 key 已經(jīng)存儲(chǔ)其他值, SET 就覆寫舊值,且無(wú)視類型。這樣會(huì)導(dǎo)致很多服務(wù)都能加鎖成功,而我們想要的是只有一個(gè)服務(wù)能加鎖成功。
要解決這個(gè)問(wèn)題,需要了解redis的另一個(gè)命令setnx。setnx 命令在指定的 key 不存在時(shí),為 key 設(shè)置指定的值。
<?php class RedisLock { protected $redis; public function __construct(){ $redis = new Redis(); $redis->connect('127.0.0.1',6379); $this->redis = $redis; } public function getLock($key){ $value = $this->redis->get($key); return $value; } public function setLock($key,$value){ return $this->redis->setnx($key,$value); } public function delLock($key){ $lineNumber = $thid->redis->del($key); return $lineNumber; } } $key = 'your_lock_key'; $value = time(); $redisLock = new RedisLock(); $isLock = $redisLock->get($key); if($isLock) { //已有鎖,直接返回,不往下執(zhí)行了 return false; } //沒有鎖,加鎖 $setLock = $redisLock->setLock($key,$value); if(!$setLock) { //加鎖失敗 return false; } //todo 執(zhí)行業(yè)務(wù)邏輯 sleep(5); // 解鎖 $redisLock->delLock($key);
再次使用ab進(jìn)行測(cè)試
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖失敗
加鎖失敗
加鎖失敗
加鎖失敗
加鎖失敗
已鎖
已鎖
已鎖
執(zhí)行業(yè)務(wù)
解鎖
從測(cè)試結(jié)果來(lái)看,在未加鎖的狀態(tài)下,有多個(gè)服務(wù)同時(shí)獲取加鎖,但是只有一個(gè)加鎖成功, 其他的都是返回加鎖失敗,再后面的服務(wù)更是直接返回已鎖。由此可見,加鎖成功。
那么到此就結(jié)束了嗎?其實(shí)并不是的。假如在已加鎖的情況執(zhí)行業(yè)務(wù),在業(yè)務(wù)過(guò)程中因?yàn)橐恍┰虺霈F(xiàn)異常導(dǎo)致退出而沒有進(jìn)行解鎖,那么將造成死鎖,后面的所有服務(wù)都無(wú)法再次獲取鎖。為了解決這個(gè)問(wèn)題,我們需要對(duì)鎖設(shè)置一個(gè)過(guò)期的時(shí)間,防止死鎖的發(fā)生。
<?php class RedisLock { protected $redis; public function __construct(){ $redis = new Redis(); $redis->connect('127.0.0.1',6379); $this->redis = $redis; } public function getLock($key){ $value = $this->redis->get($key); return $value; } public function setLock($key,$value,$second){ $setnx = $this->redis->setnx($key,$value); if(!$setnx) { return $setnx; } $expire = $this->redis->expire($key,$second); if(!$expire) { $this->redis->del($key); } return $expire; } public function delLock($key){ $lineNumber = $thid->redis->del($key); return $lineNumber; } } $key = 'your_lock_key'; $value = time(); $redisLock = new RedisLock(); $isLock = $redisLock->get($key); if($isLock) { //已有鎖,直接返回,不往下執(zhí)行了 return false; } //沒有鎖,加鎖 $second = 5; $setLock = $redisLock->setLock($key,$value,$second); if(!$setLock) { //加鎖失敗 return false; } //todo 執(zhí)行業(yè)務(wù)邏輯 sleep(5); // 解鎖 $redisLock->delLock($key);
以上就是PHP使用redis實(shí)現(xiàn)分布式鎖的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于PHP redis分布式鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PHP 獲取文件路徑(靈活應(yīng)用__FILE__)
__FILE__ ,是返回文件的完整路徑和文件名。如果用在包含文件中,則返回包含文件名。自 PHP 4.0.2 起,__FILE__ 總是包含一個(gè)絕對(duì)路徑,而在此之前的版本有時(shí)會(huì)包含一個(gè)相對(duì)路徑2013-02-02PHP實(shí)現(xiàn)的memcache環(huán)形隊(duì)列類實(shí)例
這篇文章主要介紹了PHP實(shí)現(xiàn)的memcache環(huán)形隊(duì)列類,實(shí)例分析了基于memcache實(shí)現(xiàn)環(huán)形隊(duì)列的方法,涉及memcache緩存及隊(duì)列的相關(guān)技巧,需要的朋友可以參考下2015-07-07PHP操作MySQL的mysql_fetch_* 函數(shù)的常見用法教程
這篇文章主要介紹了PHP中操作MySQL的mysql_fetch函數(shù)的常見用法教程,文中提到了其下fetch_array和mysql_fetch_row以及mysql_fetch_object函數(shù)的使用,需要的朋友可以參考下2015-12-12PHP文件讀寫操作相關(guān)函數(shù)總結(jié)
這篇文章主要介紹了PHP文件讀寫操作相關(guān)函數(shù)總結(jié),本文總結(jié)了fwrite()、fread()、fgets()、fgetc()、file()、readfile() 等函數(shù)的介紹及使用例子,需要的朋友可以參考下2014-11-11php中array_unshift()修改數(shù)組key注意事項(xiàng)分析
這篇文章主要介紹了php中array_unshift()修改數(shù)組key注意事項(xiàng),實(shí)例分析了array_unshift()函數(shù)在處理鍵值為數(shù)字類型時(shí)自動(dòng)轉(zhuǎn)換鍵值的情況,需要的朋友可以參考下2016-05-05