PHP使用redis實現分佈式鎖的示例詳解
最近在做一個領券功能的時候,發現在一定並發下會出現重復領券的問題。使用度娘一頓搜索操作之後,發現可以使用分佈式鎖來解決這個問題。
什麼是分佈式鎖
分佈式鎖是控制分佈式系統之間同步訪問共享資源的一種方式。在分佈式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享瞭一個或一組資源,那麼訪問這些資源的時候,往往需要互斥來防止彼此幹擾來保證一致性,這個時候,便需要使用到分佈式鎖。
實現原理
實現分佈式鎖的原理很簡單,就是需要有一把鎖,多個服務同時去獲取鎖,但是隻有一個服務能獲取到鎖。獲取到鎖的服務就可以執行自己的業務,沒有獲取到鎖的其他服務需要等待獲取到鎖的服務業務執行完成後釋放鎖,然後再次嘗試獲取鎖。
實現分佈式的方案有很多種。如下
- 基於數據庫實現分佈式鎖,比如mysql
- 基於緩存實現分佈式鎖,比如redis
- 基於Zookeeper實現分佈式鎖
這裡我們使用redis來實現分佈式鎖,在執行業務之前先獲取一個key,如果key存在就說明已經有其他服務獲得鎖,這個時候需要等待或者返回系統繁忙。如果key不存在,說明沒有其他服務獲取鎖,把這個key保存到redis,然後執行業務,等待業務執行完就從redis中刪除這個key。
php實現代碼
<?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) { //已有鎖,直接返回,不往下執行瞭 return false; } //沒有鎖,加鎖 $redisLock->setLock($key,$value); //todo 執行業務邏輯 sleep(5); // 解鎖 $redisLock->delLock($key);
使用ab進行測試
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
執行業務
解鎖
解鎖
執行業務
解鎖
執行業務
解鎖
執行業務
解鎖
解鎖
執行業務
解鎖
加鎖
執行業務
解鎖
加鎖
執行業務
解鎖
從測試結果來看,發現有多個執行業務,並沒有完全鎖住。這個是因為我們用的是redis的set命令。set 命令用於設置給定 key 的值。如果 key 已經存儲其他值, SET 就覆寫舊值,且無視類型。這樣會導致很多服務都能加鎖成功,而我們想要的是隻有一個服務能加鎖成功。
要解決這個問題,需要瞭解redis的另一個命令setnx。setnx 命令在指定的 key 不存在時,為 key 設置指定的值。
<?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) { //已有鎖,直接返回,不往下執行瞭 return false; } //沒有鎖,加鎖 $setLock = $redisLock->setLock($key,$value); if(!$setLock) { //加鎖失敗 return false; } //todo 執行業務邏輯 sleep(5); // 解鎖 $redisLock->delLock($key);
再次使用ab進行測試
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖
加鎖失敗
加鎖失敗
加鎖失敗
加鎖失敗
加鎖失敗
已鎖
已鎖
已鎖
執行業務
解鎖
從測試結果來看,在未加鎖的狀態下,有多個服務同時獲取加鎖,但是隻有一個加鎖成功, 其他的都是返回加鎖失敗,再後面的服務更是直接返回已鎖。由此可見,加鎖成功。
那麼到此就結束瞭嗎?其實並不是的。假如在已加鎖的情況執行業務,在業務過程中因為一些原因出現異常導致退出而沒有進行解鎖,那麼將造成死鎖,後面的所有服務都無法再次獲取鎖。為瞭解決這個問題,我們需要對鎖設置一個過期的時間,防止死鎖的發生。
<?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) { //已有鎖,直接返回,不往下執行瞭 return false; } //沒有鎖,加鎖 $second = 5; $setLock = $redisLock->setLock($key,$value,$second); if(!$setLock) { //加鎖失敗 return false; } //todo 執行業務邏輯 sleep(5); // 解鎖 $redisLock->delLock($key);
以上就是PHP使用redis實現分佈式鎖的示例詳解的詳細內容,更多關於PHP redis分佈式鎖的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 如何用redis setNX命令來加鎖
- Go 語言下基於Redis分佈式鎖的實現方式
- 用Go+Redis實現分佈式鎖的示例代碼
- 關於SpringBoot 使用 Redis 分佈式鎖解決並發問題
- Redis分佈式鎖升級版RedLock及SpringBoot實現方法