一、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分布式鎖,滿足了三個特性:
- 互斥性。同時只能又一個客戶端擁有鎖
- 不會發生死鎖。
- 加鎖和解鎖的必須是同一個客戶端。
童鞋們,有什么疑問,可以在地下留言哦。