Redis 分佈式鎖--PHP

Redis 分佈式鎖的做用

在單機環境下,有個秒殺商品的活動,在短期內,服務器壓力和流量會陡然上升。這個就會存在併發的問題。想要解決併發須要解決如下問題mysql

一、提升系統吞吐率也就是qps 每秒處理的請求書
二、避免商品在高併發的狀況下,出現資源爭搶致使的超買超買問題

解決問題一:採用內存型數據庫提升系統的qps
解決問題二:就要用到常常會遇到的鎖,例如MySQL 有讀鎖、寫鎖、排他鎖、悲觀鎖、樂觀鎖。不過這裏只討論redis來實現鎖redis

簡單版設置鎖

$redis = new Redis();
$redis->connect('127.0.0.1', 6379); //鏈接Redis

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

若是以這樣的簡單版設置鎖就能解決全部問題,未免也過小看在程序中應用了。sql

按正常的操做示例基本上都是這樣寫的。可是這樣寫有一些問題數據庫

一、假若有10000 個請求訪問了redis 不存在的鍵,這樣請求就是指接到了MySQL數據,形成CPU短期內達到100%甚至宕機。這樣場景俗稱緩存擊穿形成的緩存雪崩。
二、 假如CPU太高或者網絡延時問題,形成鎖沒有被刪除掉或者緩存鍵過時沒有被回收的狀況,就會造成死鎖。

解決問題:引用reids setnx 方法的做用是,當設置的key 不存在時,設置新的值。這樣就避免了緩存擊穿的問題。檢測鍵的過時時間,避免產生死鎖緩存

解決死鎖問題

$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;
            //下步操做....
        }
    }
一、按理說這樣解決單機版鎖競爭問題已是沒有多大問題了。那麼特殊狀況來了,若是這個設置鎖的鍵因爲意外狀況沒有被刪除,這樣一樣會有死鎖的狀況發生。

二、分佈式集羣業務業務場景下,每臺服務器是獨立存在的。多臺服務器怎麼經過一個標識來相互競爭鎖呢。這裏就用到了分佈式鎖服務器

這裏簡單介紹一下,以MYSQL 的事務機制來延生。事務四個特性ACID,有四種隔離級別:未提交讀、已提交讀、可重複讀、串行化。這些特性都只在單臺服務器上生效。到了分佈式集羣了,數據在不一樣的服務器上,緊靠事務很難保持數據的一致性及隔離性,事務的做用就意義不大了。Redis也是如此。網絡

正確的分佈式鎖的打開方式

/**
     * 實現Redis分佈鎖
     */
    $key        = 'demo';       //要更新信息的緩存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秒後再嘗試執行操做
            }
        }
    }

結尾

文章從知識面的廣度(mysql)、示例代碼優缺點的簡介及應用的場景,區別於其餘博客文章。嘿嘿~併發

相關文章
相關標籤/搜索