本篇內(nèi)容主要講解“php與redis如何實(shí)現(xiàn)分布式鎖”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“php與Redis如何實(shí)現(xiàn)分布式鎖”吧!
創(chuàng)新互聯(lián)建站不只是一家網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司;我們對營銷、技術(shù)、服務(wù)都有自己獨(dú)特見解,公司采取“創(chuàng)意+綜合+營銷”一體化的方式為您提供更專業(yè)的服務(wù)!我們經(jīng)歷的每一步也許不一定是最完美的,但每一步都有值得深思的意義。我們珍視每一份信任,關(guān)注我們的網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)質(zhì)量和服務(wù)品質(zhì),在得到用戶滿意的同時,也能得到同行業(yè)的專業(yè)認(rèn)可,能夠?yàn)樾袠I(yè)創(chuàng)新發(fā)展助力。未來將繼續(xù)專注于技術(shù)創(chuàng)新,服務(wù)升級,滿足企業(yè)一站式網(wǎng)絡(luò)營銷推廣需求,讓再小的高端網(wǎng)站設(shè)計(jì)也能產(chǎn)生價值!
一、分布式鎖的作用:
redis寫入時不帶鎖定功能,為防止多個進(jìn)程同時進(jìn)行一個操作,出現(xiàn)意想不到的結(jié)果,so...對緩存進(jìn)行插入更新操作時自定義加鎖功能。
二、Redis的NX后綴命令
Redis有一系列的命令,其特點(diǎn)是以NX結(jié)尾,NX的意思可以理解為 NOT EXISTS(不存在),SETNX命令 (SET IF NOT EXISTS) 可以理解為如果不存在則插入,Redis分布式鎖的實(shí)現(xiàn)主要就是使用SETNX命令。
三、實(shí)現(xiàn)原理
在進(jìn)程請求執(zhí)行操作前進(jìn)行判斷,加鎖是否成功,加鎖成功允許執(zhí)行下步操作;
如果不成功,則判斷鎖的值(時間戳)是否大于當(dāng)前時間,如果大于當(dāng)前時間,則獲取鎖失敗不允許執(zhí)行下步操作;
如果鎖的值(時間戳)小于當(dāng)前時間,并且GETSET命令獲取到的鎖的舊值依然小于當(dāng)前時間,則獲取鎖成功允許執(zhí)行下步操作;
如果鎖的值(時間戳)小于當(dāng)前時間,并且GETSET命令獲取到的鎖的舊值大于當(dāng)前時間,則獲取鎖失敗不允許執(zhí)行下步操作;
四、$redis->setnx() 設(shè)置鎖
$expire = 10;//有效期10秒 $key = 'lock';//key $value = time() + $expire;//鎖的值 = Unix時間戳 + 鎖的有效期 $lock = $redis->setnx($key, $value); //判斷是否上鎖成功,成功則執(zhí)行下步操作 if(!empty($lock)) { //下步操作... }
如果返回 1 ,則表示當(dāng)前進(jìn)程獲得鎖,并獲得了當(dāng)前插入/更新緩存的操作權(quán)限。
如果返回 0,表示鎖已被其他進(jìn)程獲取,這是進(jìn)程可以返回結(jié)果或者等待當(dāng)前鎖失效再請求。
五、解決死鎖
如果只用SETNX命令設(shè)置鎖的話,如果當(dāng)持有鎖的進(jìn)程崩潰或刪除鎖失敗時,其他進(jìn)程將無法獲取到鎖,問題就大了。
解決方法是在獲取鎖失敗的同時獲取鎖的值,并將值與當(dāng)前時間進(jìn)行對比,如果值小于當(dāng)前時間說明鎖以過期失效,進(jìn)程可運(yùn)用Redis的DEL命令刪除該鎖。
$expire = 10;//有效期10秒 $key = 'lock';//key $value = time() + $expire;//鎖的值 = Unix時間戳 + 鎖的有效期 $status = true; while($status) { $lock = $redis->setnx($key, $value); if(empty($lock)) { $value = $redis->get($key); if($value < time()) { $redis->del($key); } }else{ $status = false; //下步操作.... } }
但是,簡單粗暴的用DEL命令刪除鎖再SETNX命令上鎖也會出現(xiàn)問題。比如,進(jìn)程1獲得鎖后崩潰或刪除鎖失敗,這時進(jìn)程2檢測到鎖存在當(dāng)已過期,用DEL命令刪除鎖并用SETNX命令設(shè)置鎖,進(jìn)程3也檢測到鎖過期,也用DEL命令刪除鎖也用SETNX命令設(shè)置了鎖,這時進(jìn)程2和進(jìn)程3同時獲得了鎖。問題大了!
為了解決這個問題,這里用到了Redis的GETSET命令,GETSET命令在給鎖設(shè)置新值的同時返回鎖的舊值,這里利用了GETSET命令同時獲取和賦值的特性,在此期間其他進(jìn)程無法修改鎖的值。
例如:
進(jìn)程1獲得鎖后操作超時/崩潰/刪除鎖失敗,
進(jìn)程2檢測到鎖已存在,但獲取鎖的值對比當(dāng)前時間發(fā)現(xiàn)鎖已過期,
進(jìn)程2通過GETSET命令重新給鎖賦予新的值,并獲取到的鎖的舊值,再次對比鎖的舊值與當(dāng)前時間,如果鎖的舊值依然小于當(dāng)前時間的話,這時進(jìn)程2就可以忽略進(jìn)程1余留下的廢鎖進(jìn)行下步操作了。
進(jìn)程2完成下步操作后返回前應(yīng)該刪除鎖,但在刪除鎖時可以先檢測鎖是否還未過期,未過期才做刪除操作,已過期的就沒必要在去刪除鎖了,因?yàn)楹苡锌赡芷渌M(jìn)程檢測到鎖過期時已經(jīng)去獲取鎖了。
這里要說明的是,如果有其他進(jìn)程在進(jìn)程2之前獲取到鎖,那么進(jìn)程2將獲取鎖失敗,但是進(jìn)程2在用GETSET獲取鎖的舊值時也賦予了鎖新的值,改寫了其他進(jìn)程賦予鎖的超時值??吹竭@大家可能會有疑問了,進(jìn)程2沒獲取到鎖怎么能改變鎖的值呢?是的,進(jìn)程2改變了鎖的原有值,但這一點(diǎn)小小的時間誤差帶來的影響是可以忽略。
以下是Redis實(shí)現(xiàn)分布式鎖的完整PHP代碼:
get($key); //判斷緩存中是否有數(shù)據(jù) if(empty($result)) { $status = TRUE; while ($status) { //設(shè)置鎖值為當(dāng)前時間戳 + 有效期 $lockValue = time() + $lockExpire; /** * 創(chuàng)建鎖 * 試圖以$lockKey為key創(chuàng)建一個緩存,value值為當(dāng)前時間戳 * 由于setnx()函數(shù)只有在不存在當(dāng)前key的緩存時才會創(chuàng)建成功 * 所以,用此函數(shù)就可以判斷當(dāng)前執(zhí)行的操作是否已經(jīng)有其他進(jìn)程在執(zhí)行了 * @var [type] */ $lock = $redis->setnx($lockKey, $lockValue); /** * 滿足兩個條件中的一個即可進(jìn)行操作 * 1、上面一步創(chuàng)建鎖成功; * 2、 1)判斷鎖的值(時間戳)是否小于當(dāng)前時間 $redis->get() * 2)同時給鎖設(shè)置新值成功 $redis->getset() */ if(!empty($lock) || ($redis->get($lockKey) < time() && $redis->getSet($lockKey, $lockValue) < time() )) { //給鎖設(shè)置生存時間 $redis->expire($lockKey, $lockExpire); //****************************** //此處執(zhí)行插入、更新緩存操作... //****************************** //以上程序走完刪除鎖 //檢測鎖是否過期,過期鎖沒必要刪除 if($redis->ttl($lockKey)) $redis->del($lockKey); $status = FALSE; }else{ /** * 如果存在有效鎖這里做相應(yīng)處理 * 等待當(dāng)前操作完成再執(zhí)行此次請求 * 直接返回 */ sleep(2);//等待2秒后再嘗試執(zhí)行操作 } } }
實(shí)現(xiàn)分布式鎖用到的Redis命令介紹:
setnx(key, value)
將key的值設(shè)為value,當(dāng)且僅當(dāng)key不存在。
若給定的key已經(jīng)存在,則SETNX不做任何動作。
SETNX是”SET if Not eXists”(如果不存在,則SET)的簡寫。
返回值:
設(shè)置成功,返回1。
設(shè)置失敗,返回0。
get(key)
返回key所關(guān)聯(lián)的字符串值。
如果key不存在則返回特殊值nil。
假如key儲存的值不是字符串類型,返回一個錯誤,因?yàn)镚ET只能用于處理字符串值。
返回值:
key的值。
如果key不存在,返回nil。
getset(key, value)
將給定key的值設(shè)為value,并返回key的舊值。
當(dāng)key存在但不是字符串類型時,返回一個錯誤。
返回值:
返回給定key的舊值(old value)。
當(dāng)key沒有舊值時,返回nil。
expire(key, seconds)
為給定key設(shè)置生存時間。
當(dāng)key過期時,它會被自動刪除。
在Redis中,帶有生存時間的key被稱作“易失的”(volatile)。
在低于2.1.3版本的Redis中,已存在的生存時間不可覆蓋。
從2.1.3版本開始,key的生存時間可以被更新,也可以被PERSIST命令移除。(詳情參見 http://redis.io/topics/expire)。
返回值:
設(shè)置成功返回1。
當(dāng)key不存在或者不能為key設(shè)置生存時間時(比如在低于2.1.3中你嘗試更新key的生存時間),返回0。
ttl(key)
返回給定key的剩余生存時間(time to live)(以秒為單位)。
返回值:
key的剩余生存時間(以秒為單位)。
當(dāng)key不存在或沒有設(shè)置生存時間時,返回-1 。
del(key)
移除給定的一個或多個key。
返回值:
被移除key的數(shù)量。
到此,相信大家對“php與Redis如何實(shí)現(xiàn)分布式鎖”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!