匯入
之前一直在給大家寫docker相關的東西,當然docker的東西也會繼續的寫,在此插一篇《關於Redis分布式鎖的應用》開開葷腥。
背景
同一主機同一進程不同的線程,如何同步訪問一段代碼塊呢?
Java有synchronized
synchronized(this) {}
Golang有sync工具包
var mutex sync.Mutex//加鎖mutexmutex.Lock()do Something···//解鎖mutexmutex.Unlock()
PHP
因為PHP沒有多線程的概念,對PHP而言,普遍的是多進程,PHP的多個多進程之間同步訪問,可以通過檔案鎖來實現。
$fp = fopen("logs/lock.l", "a+");if (flock($fp, LOCK_EX)) { // 進行排它型鎖定 do something.... flock($fp, LOCK_UN); // 釋放鎖定} else { echo "鎖正在被其他程式佔用";}fclose($fp);
不同的語言對於同步訪問的問題,會有不同的解決方案,還可以通過共用記憶體來實現,但是這些解決方案的本質都是通過鎖來保持同步的。
現在企業對外提供的服務,一般都是以叢集的方式來做的,那麼多機之前該怎麼去協同同步呢?答案很簡單,用分布式鎖來解決。
接下來嘗試用Redis實現一個分布式鎖。
加鎖
加鎖的時候,對特定的資源加鎖,例如:秒殺情境為庫存餘額加鎖等等。lockKey就代表庫存,要標識這個鎖的身份,所以就要用一個唯一IDuniqLockValue來標識。如果一個任務過長,我們要設定鎖的到期時間$ttl。
//表示庫存 這把鎖$lockKey = "PRODUCT_LOCK_KEY";//擷取一個唯一ID,來表明這個把鎖是誰加的$uniqLockValue = getUniqueId();//加上一把鎖 setnx 是當key不存在的時候,可以添加成功。$ret = $redis->setnx($lockKey,$uniqLockValue);if($ret){ //加鎖成功 設定到期時間 $ttl = 3; // 單位:s $redis->expire($lockKey,$ttl);}else{ //表明鎖有其他人在使用}
解鎖
解鎖的邏輯如下:擷取鎖,驗證鎖的身份是否是自己加上的,如果是自己加上的並且沒有到期就解鎖,否則,那就解鎖失敗。
$lockKey = "PRODUCT_LOCK_KEY";$currentTime = time();if(($uniqLockValue == $redis->get($lockKey)) && $redis->ttl($lockKey) <= $currentTime){ $redis->del($key); return true;}else{ return false;}
範例程式碼:
class RedisLock { const ORDER_LOCK_KEY_PRE = 'PRODUCET_KEY_LOCK'; //鎖到期時間 const LOCK_KEY_EXPIRE = 5; //類靜態執行個體 private static $objInstance = null; // redis服務執行個體 protected $objRedis = null; private function __construct() { $this->objRedis = new Redis(); $this->objRedis->connect('127.0.0.1', 6379); } public static function getInstance() { if (is_null(self::$objInstance)) { self::$objInstance = new self(); } return self::$objInstance; } /** * 加鎖 * @param $strKey string redis key值 * @return bool */ public function lock($strKey, $force = true) { //表示庫存 這把鎖 $lockKey = "PRODUCT_LOCK_KEY"; //擷取一個唯一ID,來表明這個把鎖是誰加的 $uniqLockValue = uniqid(); //加上一把鎖 setnx 是當key不存在的時候,可以添加成功。 $ret = $this->objRedis->setnx($lockKey,$uniqLockValue); // 如果鎖存在 但是 已經到期了 if($ret || ($this->objRedis->ttl($lockKey) == -2 && $this->objRedis->getSet($lockKey,$uniqLockValue))){ //加鎖成功 設定到期時間 $this->objRedis->expire($lockKey,self::LOCK_KEY_EXPIRE); return true; } return false; } /** * 釋放鎖, 如果指定key則釋放指定key,否則全部釋放 * @param $strKey string */ public function unLock($strKey = '') { $lockKey = "PRODUCT_LOCK_KEY"; $currentTime = time(); if(($uniqLockValue == $this->objRedis->get($lockKey)) && $this->objRedis->ttl($lockKey) != -2){ $redis->del($key); return true; }else{ return false; } } private function __clone () { }}
其實這樣實現一把鎖是存在一些問題的,其中的很多操作都不能保證其原子性,在平常的情況下不容易顯現出來,接下來我帶大家再進一步最佳化這把鎖。