測試Redis的原子性及實現Redis的鎖

這幾天都在學習Redis的相關知識,發現了一個問題,Redis雖然是單線程的,但是他有一個特點:IO多路複用,這樣的特點使2個請求同時對同一key進行操作時,會出現2個請求同時拿到該key的值,進行了重複的操作,在秒殺中的體現爲超賣;

具體代碼爲:

    public function redis1(){
        $redis = new \Redis();
        $redis->connect('127.0.0.1',6379);
        for ($i=1;$i<=500;$i++) {
            $num = $redis->get('val');
            $redis->set('val', $num+1);
            usleep(10000);
        }
    }

使用usleep是因爲redis速度太快,不睡眠的話,體現不出來2個請求同時進行。。。

我發現了一個很奇怪的現象,如果我分別請求兩次相同的接口,程序是同步進行的,第一個請求進行中的時候,第二個請求會阻塞。當兩個接口都執行完畢,查看val的值是1000。(大概因爲是在同一個瀏覽器執行同一個接口)

但我又寫了一個一模一樣的接口,redis2

    public function redis2(){
        $redis = new \Redis();
        $redis->connect('127.0.0.1',6379);
        for ($i=1;$i<=500;$i++) {
            $num = $redis->get('val');
            $redis->set('val', $num+1);
            usleep(10000);
        }
    }

我分別請求接口redis1和redis2,結果這兩個請求是異步的(不同瀏覽器請求同一個接口也可以),所以也導致了最後val的值是800多,結果是小於1000的,這說明了兩個請求在搶佔資源,而且可能會同時搶佔到同一個資源,然後進行操作。這就是爲什麼redis還是需要鎖機制的原因了。。。

要使用redis的鎖,我們需要用到的Redis方法是setnx,該方法的用法是setnx(key,value),嘗試設值,如果key已經存在值,則返回0,不存在則設值,並返回1。這個方法就很符合鎖的特點,代碼如下:

    public function redisTran1(){
        $redis = new \Redis();
        $redis->connect('127.0.0.1',6379);
        for ($i=1;$i<=500;$i++) {
            while (true){
                if(!$redis->setnx('lock',1))//拿鎖的權限,沒拿到則continue
                    continue;
                $num = $redis->get('val');
                $redis->set('val', $num+1);
                $redis->del('lock');//解鎖
                break;
            }
            usleep(10000);
        }
    }

我們也是和上面一樣,寫一個一模一樣的接口,redisTran2,然後分別請求接口redisTran1和redisTran2,最後的val的結果是1000,說明鎖機制成功生效了。

2020-2-22 注

對於redis的原子性,我有了更加深入的理解,redis的原子性,說的是redis的api具有原子性,像第一次測試,val的值小於1000,這樣的結果是不具備原子性的;事實上,redis的工作流程如下:

上面圖片中的箭頭都代表了redis的api,且它們是上一個執行完,下一個才繼續執行,api是具有原子性的。

如果想要業務也具有原子性,直接用一步到位的方法,如incr。

此外,根據我的 調查,redis的事務是不具備原子性的,即前一條語句執行失敗,後面的還是會繼續執行。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章