一:什麼是分佈式鎖。php
- 通俗來講的話,就是在分佈式架構的redis中,使用鎖。redis
二:分佈式鎖的使用選擇。算法
- 當 Redis 的使用場景很少,並且也只是單個在用的時候,能夠構建本身使用的 鎖。架構
- 若是在公司裏落地生產環境用分佈式鎖的時候,通常是會用開源類庫。併發
- Redis分佈式鎖,通常就是用 Redisson 框架就行了,很是的簡便易用。 框架
三:Redisson 實現Redis分佈式鎖的原理。異步
- 總體流程圖分佈式
- this
- 詳解spa
- 當某個請求的客戶端須要加鎖時候。
- 若是該客戶端面對的是一個redis cluster集羣,他首先會根據hash算法選擇一臺機器。(一致hash算法)
- 隨後會向Redis發送加鎖請求(原理爲Lua腳本)
- Lua 腳本會保證業務執行的原子性,因此採用 Lua 的方式爲加鎖命令。
-
- 這個Lua 腳本的大體意思爲(這個Key的鎖是否存在,若是存在,則一直循環等待,如不存在,則上鎖,並設置過時時間。)
- watch dog 提供的鎖自動延期的能力
- 加鎖的鎖key默認生存時間才30秒,若是超過了30秒,客戶端1還想一直持有這把鎖,怎麼辦呢?
- RedisSon 只要加鎖成功,就會啓動一個watch dog看門狗, 他是一個後臺線程,會每隔10秒檢查一下 ,若是還持有鎖key,那麼就會不斷的延長鎖key的生存時間。
四:分佈式鎖鎖帶來的問題
- 在分佈式架構中的問題,就是若是你對某個redis master實例,寫入了key的鎖,此時會異步複製給對應的master slave實例。
- 可是這個過程當中一旦發生redis master宕機,主備切換,redis slave變爲了redis master。
- 接着就會致使,客戶端2來嘗試加鎖的時候,在新的redis master上完成了加鎖,而客戶端1也覺得本身成功加了鎖。
- 此時就會致使多個客戶端對一個分佈式鎖完成了加鎖。
- 這時系統在業務語義上必定會出現問題, 致使各類髒數據的產生 。
- 這個就是redis cluster/redis master-slave架構的 主從異步複製 致使的redis分佈式鎖的問題:在redis master實例宕機的時候,可能致使多個客戶端同時完成加鎖。
五:簡單實現的redis鎖
- 原理
- 經過 redis 的 setnx 功能進行加鎖
- 實現(PHP)
<?php
/**
* 建立 Redis 單例
* @return mixed
*/
class redisInstance { static $redis; private function __construct() { } public static function getInstance() { if (self::$redis) { return self::$redis; } self::$redis = new \Redis(); return self::$redis; } } class redisLock { public $reids; public function __construct() { $this->reids = redisInstance::getInstance(); } /** * 鍵加鎖,默認加鎖時間爲 1分鐘 = 60 * 1000(毫秒) * @param $key * @param int $ttl * @return bool */ public function lock($key, $ttl = 60000) { $lockKey = $key . '_lock'; // 經過setNx命令拿到鎖 $lock = $this->reids->set($lockKey, 1, ['NX', 'PX' => $ttl]); // 拿到鎖則直接返回 if ($lock) { return true; } // 沒有拿到鎖,則一直循環等待鎖資源釋放 while (!$lock) { $lock = $this->reids->set($key, 1, ['NX', 'PX' => $ttl]); } return $lock; } /** * 釋放鎖 * @param $key * @return bool */ public function unLock($key) { $lockKey = $key . '_lock'; $lock = false; // 釋放鎖 while (!$lock) { $lock = $this->reids->del($lockKey); } return true; } } $redis = redisInstance::getInstance(); $redisLock = new redisLock(); /** * 例如,買商品,進行庫存遞減 * 1:對庫存加鎖 * 2:遞減 * 3:釋放鎖 * 影響 * 加鎖致使的併發度下降 */ $key = 'stock'; $redisLock->lock($key); $stock = $redis->get($key); if ($redis->get($key) <= 0) { $redisLock->unLock($key); return false; } $redis->decr($key); $redisLock->unLock($key);