Redis全方位詳解--數據類型使用場景和redis分布式鎖的正確姿勢


 

一、Redis數據類型

  1.string

    string是Redis的最基本數據類型,一個key對應一個value,每個value最大可存儲512M。string一半用來存圖片或者序列化的數據。

  2.hash

    相當於一個string類型的映射表。特別適合用來存儲對象。例如可以存儲用戶信息,用戶ID作為hash類型里的每一個key。

    案例:我們這邊需要對接微信粉絲的數據到我們自己的平台上,但微信提供的接口只支持單天查詢,那么如果我們想要查看最近一個月微信粉絲的狀況,就需要循環30次調用微信的接口。一個月勉強還可以接受,那么如果想要查半年,甚至一年呢?那么我們的接口里就需要循環365次調微信的接口,這就會使我們的接口變得非常慢,甚至超時。還有這些數據,比如單天新增粉絲數,是不會變得,而且每天都有一個數據,這樣就特別適合存在redis的hash類型里,以日期(2018-10-10)作為hash的key。

  3.list

    list類型是簡單的字符串列表,每個列表可以存儲232 - 1 個值。可以從頭部或者尾部順序插入數據。list類型可以用來做電商里的秒殺營銷系統或關注列表。

  4.set

    set是string類型的無序集合。該集合是通過哈希實現的,添加、刪除的復雜度都是O(1),所以查找非常快。

    案例:我們這邊是以手機號為唯一標示符,防止重復用戶注冊,會判斷該手機號有沒有注冊過,那么如果用set類型存儲注冊過的用戶手機號,就會很快判斷出該用戶是否注冊過,而不用去查數據庫了。

  5.zset

    和set一樣,但zset多了一個score來讓set變得有序,且不允許有重復的成員。

    案例:我們這邊有一個賬戶記錄需要按記錄時間排序,那么就可以將時間戳當作score存儲zset中。

 

二、redis分布式鎖

  網上很多redis分布式鎖的實現方式不能說錯誤的,但至少不夠嚴謹,在某些極端情況下是會出問題的。一旦出現問題,還是挺麻煩的事情,所以我們要知道redis分布式鎖的正確姿勢。

  其實很簡單,利用redis的原子性。關於原子性,官方的一段描述為:

  大概意思就是redis執行lua腳本的時候,會被當成一條命令執行,在此期間,不會執行其他命令,所以lua腳本盡量是快腳本而不是慢腳本。

  所以,正確的姿勢是:

    public function getDistributeLock($redis, string $key, int $userId, int $expire)
    {
        $luaScript = <<<LUA
if (redis.call('exists', KEYS[1]) == 0) and redis.call('setex', KEYS[1], ARGV[1], ARGV[2]) 
then
    return 1
else
    return 0
end
LUA;

        return $redis->eval($luaScript, 1, $key, $expire, $userId) > 0 ? true : false;
    }

 

  這里我們把一段lua腳本放到redis的eval方法里執行,這樣就可以保證這一段命令的原子性。

  那么如果不用lua腳本,姿勢應該是這樣的:

    public function wrongGetDistributeLock($redis, string $key, int $userId, int $expire)
    {
        
        // 若鎖不存在,則加鎖
        if(!$redis->exists($key) && ($redis->setex($key, $expire, $userId) == 'OK')) {
            return true;
        }

        return false;
    }

  前面提到過,這種姿勢在某些情況下會出問題:如果同時好幾個客戶端同時請求,同時通過了上面if條件的第一層,那么這時候就會出現多個同時拿到鎖,並且前面人的鎖會被覆蓋。

 

  然后,正確的解鎖姿勢是:

    public function releaseDistributeLock($redis, string $key, int $userId)
    {
        $luaScript = <<<LUA
if redis.call('get', KEYS[1]) == ARGV[1]
then
    return redis.call('del',  KEYS[1])
else
    return 0
end
LUA;
        $redis->eval($luaScript, 1, $key, $userId);
    }

  同樣需要使用lua腳本來達到原子性。那么如果不使用lua腳本的姿勢是:

    public function wrongReleaseLock($redis, string $key, int $userId)
    {
        if($userId == $redis->get($key)) {
            $redis->del($key);
        }
    }

  會有這樣一種情況:A請求通過if語句后,這時候這個redis的key剛好過期了,然后B客戶端加鎖成功,這時候A請求就會把客戶端B剛加的鎖給解除了。

  雖然我上面提到的兩種情況都是很極端、很少出現的。但如果可以用很簡單的方法避免掉,so why not?

  上面提到的redis分布式鎖,滿足了三個特性:

  • 互斥性。同時只能又一個客戶端擁有鎖
  • 不會發生死鎖。 
  • 加鎖和解鎖的必須是同一個客戶端。

  童鞋們,有什么疑問,可以在地下留言哦。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM