php與Redis實現分佈式鎖

1、分佈式鎖的做用:php

redis寫入時不帶鎖定功能,爲防止多個進程同時進行一個操做,出現意想不到的結果,so...對緩存進行插入更新操做時自定義加鎖功能。laravel

2、Redis的NX後綴命令面試

  Redis有一系列的命令,其特色是以NX結尾,NX的意思能夠理解爲 NOT EXISTS(不存在),SETNX命令 (SET IF NOT EXISTS) 能夠理解爲若是不存在則插入,Redis分佈式鎖的實現主要就是使用SETNX命令。redis

3、實現原理sql

在進程請求執行操做前進行判斷,加鎖是否成功,加鎖成功容許執行下步操做;shell

若是不成功,則判斷鎖的值(時間戳)是否大於當前時間,若是大於當前時間,則獲取鎖失敗不容許執行下步操做;緩存

若是鎖的值(時間戳)小於當前時間,而且GETSET命令獲取到的鎖的舊值依然小於當前時間,則獲取鎖成功容許執行下步操做;服務器

若是鎖的值(時間戳)小於當前時間,而且GETSET命令獲取到的鎖的舊值大於當前時間,則獲取鎖失敗不容許執行下步操做;架構

4、$redis->setnx() 設置鎖併發

$expire = 10;//有效期10秒 $key = 'lock';//key $value = time() + $expire;//鎖的值 = Unix時間戳 + 鎖的有效期 $lock = $redis->setnx($key, $value);
//判斷是否上鎖成功,成功則執行下步操做 if(!empty($lock))
{
     //下步操做... }

若是返回 1 ,則表示當前進程得到鎖,並得到了當前插入/更新緩存的操做權限。

若是返回 0,表示鎖已被其餘進程獲取,這是進程能夠返回結果或者等待當前鎖失效再請求。

5、解決死鎖

  若是隻用SETNX命令設置鎖的話,若是當持有鎖的進程崩潰或刪除鎖失敗時,其餘進程將沒法獲取到鎖,問題就大了。

解決方法是在獲取鎖失敗的同時獲取鎖的值,並將值與當前時間進行對比,若是值小於當前時間說明鎖以過時失效,進程可運用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命令上鎖也會出現問題。好比,進程1得到鎖後崩潰或刪除鎖失敗,這時進程2檢測到鎖存在當已過時,用DEL命令刪除鎖並用SETNX命令設置鎖,進程3也檢測到鎖過時,也用DEL命令刪除鎖也用SETNX命令設置了鎖,這時進程2和進程3同時得到了鎖。問題大了!

  爲了解決這個問題,這裏用到了Redis的GETSET命令,GETSET命令在給鎖設置新值的同時返回鎖的舊值,這裏利用了GETSET命令同時獲取和賦值的特性,在此期間其餘進程沒法修改鎖的值。

  例如:

    進程1得到鎖後操做超時/崩潰/刪除鎖失敗,

    進程2檢測到鎖已存在,但獲取鎖的值對比當前時間發現鎖已過時,

    進程2經過GETSET命令從新給鎖賦予新的值,並獲取到的鎖的舊值,再次對比鎖的舊值與當前時間,若是鎖的舊值依然小於當前時間的話,這時進程2就能夠忽略進程1餘留下的廢鎖進行下步操做了。

    進程2完成下步操做後返回前應該刪除鎖,但在刪除鎖時能夠先檢測鎖是否還未過時,未過時才作刪除操做,已過時的就不必在去刪除鎖了,由於頗有可能其餘進程檢測到鎖過時時已經去獲取鎖了。

    這裏要說明的是,若是有其餘進程在進程2以前獲取到鎖,那麼進程2將獲取鎖失敗,可是進程2在用GETSET獲取鎖的舊值時也賦予了鎖新的值,改寫了其餘進程賦予鎖的超時值。看到這你們可能會有疑問了,進程2沒獲取到鎖怎麼能改變鎖的值呢?是的,進程2改變了鎖的原有值,但這一點小小的時間偏差帶來的影響是能夠忽略。

如下是Redis實現分佈式鎖的完整PHP代碼:

<?php
/**  * 實現Redis分佈鎖  */

$key        = 'test';       //要更新信息的緩存KEY $lockKey    = 'lock:'.$key; //設置鎖KEY $lockExpire = 10;           //設置鎖的有效期爲10秒 
//獲取緩存信息 $result = $redis->get($key);
//判斷緩存中是否有數據 if(empty($result))
{
    $status = TRUE;
    while ($status)
    {
        //設置鎖值爲當前時間戳 + 有效期         $lockValue = time() + $lockExpire;
        /**  * 建立鎖  * 試圖以$lockKey爲key建立一個緩存,value值爲當前時間戳  * 因爲setnx()函數只有在不存在當前key的緩存時纔會建立成功  * 因此,用此函數就能夠判斷當前執行的操做是否已經有其餘進程在執行了  * @var [type]  */
        $lock = $redis->setnx($lockKey, $lockValue);
        /**  * 知足兩個條件中的一個便可進行操做  * 一、上面一步建立鎖成功;  * 二、 1)判斷鎖的值(時間戳)是否小於當前時間 $redis->get()  * 2)同時給鎖設置新值成功 $redis->getset()  */
        if(!empty($lock) || ($redis->get($lockKey) < time() && $redis->getSet($lockKey, $lockValue) < time() ))
        {
            //給鎖設置生存時間             $redis->expire($lockKey, $lockExpire);
            //******************************             //此處執行插入、更新緩存操做...             //****************************** 
            //以上程序走完刪除鎖             //檢測鎖是否過時,過時鎖不必刪除             if($redis->ttl($lockKey))
                $redis->del($lockKey);
            $status = FALSE;
        }else{
            /**  * 若是存在有效鎖這裏作相應處理  * 等待當前操做完成再執行這次請求  * 直接返回  */
            sleep(2);//等待2秒後再嘗試執行操做         }
    }
}

 

實現分佈式鎖用到的Redis命令介紹:

setnx(key, value)

將key的值設爲value,當且僅當key不存在。

若給定的key已經存在,則SETNX不作任何動做。

SETNX是」SET if Not eXists」(若是不存在,則SET)的簡寫。

返回值:

設置成功,返回1。

設置失敗,返回0。

get(key)

返回key所關聯的字符串值。

若是key不存在則返回特殊值nil。

假如key儲存的值不是字符串類型,返回一個錯誤,由於GET只能用於處理字符串值。

返回值:

key的值。

若是key不存在,返回nil。

getset(key, value)

將給定key的值設爲value,並返回key的舊值。

當key存在但不是字符串類型時,返回一個錯誤。

返回值:

返回給定key的舊值(old value)。

當key沒有舊值時,返回nil。

expire(key, seconds)

爲給定key設置生存時間。

當key過時時,它會被自動刪除。

在Redis中,帶有生存時間的key被稱做「易失的」(volatile)。

在低於2.1.3版本的Redis中,已存在的生存時間不可覆蓋。

從2.1.3版本開始,key的生存時間能夠被更新,也能夠被PERSIST命令移除。(詳情參見 http://redis.io/topics/expire)。

返回值:

設置成功返回1。

當key不存在或者不能爲key設置生存時間時(好比在低於2.1.3中你嘗試更新key的生存時間),返回0。

ttl(key)

返回給定key的剩餘生存時間(time to live)(以秒爲單位)。

返回值:

key的剩餘生存時間(以秒爲單位)。

當key不存在或沒有設置生存時間時,返回-1 。

del(key)

移除給定的一個或多個key。

返回值:

被移除key的數量。

以上內容但願幫助到你們, 不少PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那裏入手去提高,對此我整理了一些資料,包括但不限於:分佈式架構、高可擴展、高性能、高併發、服務器性能調優、TP6,laravel,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階乾貨須要的能夠免費分享給你們 ,須要戳這裏     PHP進階架構師>>>實戰視頻、大廠面試文檔免費獲取

相關文章
相關標籤/搜索