分佈式鎖的實現方式有不少,本篇文章講述一下使用Redis實現分佈式鎖。網上有不少使用Redis實現分佈式鎖的代碼,可是這些代碼或多或少都有問題。這篇文章會寫一個實現,同時標明一些注意點。php
爲了便於闡述,這裏假設一個遊戲場景,用戶A有開山斧一把,價值500元寶,用戶B有800元寶,想買A的開山斧,這些數據都存在Redis中。須要編寫代碼成功的實現該筆交易。html
Redis實現分佈式鎖,須要考慮以下問題:程序員
實現一個最低保障的分佈式鎖,須要具有三個特性redis
使用Redis實現分佈式鎖,通常使用SETNX或者SET命令,SETNX不能同時設置過時時間,若是使用的版本大於等於2.6.12,可使用SET命令,可使用這個命令原子性的實現SETNX和EXPIRE的功能,下面是兩個命令的簡介算法
命令格式:SETNX key value緩存
時間複雜度:O(1)安全
說明:將key
設置值爲value
,若是key
不存在,這種狀況下等同SET命令。 當key
存在時,什麼也不作。SETNX
是」SET if Not eXists」的簡寫。網絡
返回值框架
1
若是key被設置了0
若是key沒有被設置命令格式:SET key value [EX seconds] [PX milliseconds] [NX|XX]編輯器
時間複雜度:O(1)
說明:將鍵key
設定爲指定的「字符串」值。若是 key 已經保存了一個值,那麼這個操做會直接覆蓋原來的值,而且忽略原始類型。當set
命令執行成功以後,以前設置的過時時間都將失效。
選項
從2.6.12版本開始,redis爲SET
命令增長了一系列選項:
EX
seconds – 設置鍵key的過時時間,單位時秒PX
milliseconds – 設置鍵key的過時時間,單位是毫秒NX
– 只有鍵key不存在的時候纔會設置key的值XX
– 只有鍵key存在的時候纔會設置key的值此處使用SETNX實現,畢竟有的公司Redis版本可能較低,使用SETNX能夠實現,SET更加沒有問題。
代碼以下:
<?php
function uuid($prefix = '') {
$chars = md5(uniqid(mt_rand(), true));
$uuid = substr($chars, 0, 8) . '-';
$uuid .= substr($chars, 8, 4) . '-';
$uuid .= substr($chars, 12, 4) . '-';
$uuid .= substr($chars, 16, 4) . '-';
$uuid .= substr($chars, 20, 12);
$ret = $prefix . $uuid;
return strtoupper($ret);
}
function acquireLock($redis,$lockName, $acquireTime = 10, $lockTime = 10) {
$lockKey = 'lock:' + $lockName;
$identifier = uuid('identify');
$end = time() + $acquireTime;
while (time() < $end) {
if ($redis->setnx($lockKey, $identifier)) {
$redis->expire($lockKey, $lockTime);
return $identifier;
} elseif ($redis->ttl($lockKey) == -1) {
$redis->expire($lockKey, $lockTime);
}
usleep(1000);
}
return false;
}
function process(){
$redis = new Redis();
$lockName = 'market';
//1.獲取鎖
$locked = acquireLock($redis,$lockName);
if($locked === false){
return false;
}
//2.進行交易
//判斷A和B是否知足交易條件
//使用管道,對A和B進行操做
//3.釋放鎖
$releaseRes = releaseLock($redis,$lockName,$locked);
if($releaseRes === false){
return false;
}
}
function releaseLock($redis,$lockName,$identifier){
$lockKey = 'lock:' + $lockName;
$redis->watch($lockKey);
if($redis->get($lockKey) === $identifier){
$redis->multi();
$redis->del($lockKey);
$redis->exec();
return true;
}
$redis->unwatch();
return false;
}
複製代碼
說明:
若是基於Redis單實例,假設這個單實例老是可用,這種方法已經足夠安全。
但有兩種特殊狀況你們須要關注一下:
主從結構中存在明顯的競態:
在Redis的分佈式環境中,有N個Redis master
這種狀況可使用Redlock算法
本文講述了怎樣用Redis實現分佈式鎖,並寫了具體實現和相關的分析。要用Redis實現分佈式鎖,有不少細節須要思考,你們能夠根據本身的業務形態設計符合本身要求的鎖,在複雜度和安全性上作好折中。
你們若是喜歡個人文章,能夠關注個人公衆號(程序員麻辣燙)
往期文章回顧: