redis可以說是除MySQL之外用的最多的一種數據庫了!眾所周知,它是一個非關系型數據庫(NoSQL),當然它的出現也絕不是為了取代MySQL。非關系型數據庫有很多種類型:面向列的NoSQL、基于圖的NoSQL、文檔型NoSQL ...... Redis是一種Key-Value型的NoSQL。Redis所有的key(鍵)都是字符串。我們在談基礎數據結構時,討論的是value(值)的數據類型,主要包括常見的5種數據類型,分別是:String、List、Set、Zset、Hash。
一、String
數據結構
String是Redis最基本的類型,你可以理解成與Memcached一模一樣的類型,一個Key對應一個Value。String類型是二進制安全的。意味著Redis的String可以包含任何數據。比如jpg圖片或者序列化的對象。String類型是Redis最基本的數據類型,Redis中一個String類型的Value最多可以是512M。
String的數據結構為簡單動態字符串(SimpleDynamicString,縮寫SDS)。是可以修改的字符串,內部結構實現上類似于JAVA的ArrayList,采用預分配冗余空間的方式來減少內存的頻繁分配。
常用命令
使用場景
緩存:經典使用場景,把常用信息,字符串,圖片或者視頻等信息放到Redis中,Redis作為緩存層,MySQL做持久化層,降低MySQL的讀寫壓力
計數器:Redis是單線程模型,一個命令執行完才會執行下一個,同時數據可以一步落地到其他的數據源。
Session:常見方案Spring Session + Redis實現Session共享。
字符串的Encoding有三種方式:
- 如果一個字符串String保存的是整數值,如:set age 13,那么這個整數值可以用long類型標識。那么該字符串的redisObject會把13這個整數值保存在ptr屬性中,并將Encoding設置為int。
- 如果字符串String保存的是一個字符串值,并且字符串大于39個字節,那么這個字符串對象將使用一個簡單動態字符串(SDS)來保存這個字符串,并將redisObject的Encoding設置為raw。
- 如果字符串String保存的是一個字符串值,并且字符串小于39個字節那么字符串將使用embstr編碼的方式來儲存這個字符串。
embstr對別raw的優點:
- embstr創建字符串對象(redisObject)的次數只需要一次,而raw是兩次(redisObjcet和SDS分開分配)。
- embstr調用釋放內存的函數一次,rew編碼的字符串對象要少一次。
- 由于embstr編碼是內存的連續性的,而raw不是連續的,因此embstr存,取速度比較快
二、List
數據結構
Redis 列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。它的底層實際是個雙向鏈表,這意味著List的插入和刪除操作效率會比較快,時間復雜度是O(1)。使用List結構,我們可以輕松地實現最新消息排隊功能,List的另一個應用就是消息隊列,可以利用List的 PUSH 操作,將任務存放在List中,然后工作線程再用 POP 操作將任務取出進行執行。
常用命令
使用場景
- 消息隊列:List類型的lpop和rpush(或者反過來,lpush和rpop)能實現隊列的功能,故而可以用Redis的List類型實現簡單的點對點的消息隊列。
- 排行榜:List類型的range命令可以分頁查看隊列中的數據,但是只有頂式計算的排行榜才適合使用List類型存儲。
- 最新列表:List類型的lpush命令和range命令能實現最新列表的功能.每次通過lpush的命令往列表里插入新的元素,然后通過lrange命令讀取最新元素列表,如朋友圈的點贊列表、評論列表。
redisObject如何表示List
列表對象List的編碼方式encoding有兩種,分別是:Ziplist(壓縮列表)、Linkedlist。
Ziplist:
優點:節省內存
缺點:比其他結構要消耗跟多的時間,所以Redis在數據量少的時候使用壓縮列表儲存。列表元素數量少于512,并且所有元素的長度都少于64字節時,使用Ziplist(壓縮列表)儲存,相反會使用Linkedlist。
Ziplist節省內存的原理:
普通數據能夠支持隨機訪問的原因是儲存的內存是連續的,但是有一個問題,就是數組中每一個元素的大小都是必須相同的,如果大小不一樣的話,那么該元素的內存就必須按照數組中最大的元素(假設是五個字節)的內存存放,那么儲存少于5個字節的元素就會存在內存浪費問題。
Linkedlist:
列表元素數量少于512,并且所有元素的長度都少于64字節時,使用Ziplist(壓縮列表)儲存,相反會使用LindedList。
三、Set
數據結構
Redis Set對外提供的功能與List類似是一個列表的功能,特殊之處在于Set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重復數據時,Set是一個很好的選擇,并且Set提供了判斷某個成員是否在一個Set集合內的重要接口,這個也是List所不能提供的。Redis的Set是String類型的無序集合。它底層其實是一個Value為Null的Hash表,所以添加,刪除,查找的復雜度都是O(1)。一個算法,隨著數據的增加,執行時間的長短,如果是O(1),數據增加,查找數據的時間不變。
常用命令
使用場景
推薦:通過sinter命令計算交集,比如美團給你推薦附近外賣時就可以根據你的外賣記錄與附近商家計算交集推送安全提示:微信群成員保存在一個set中,用戶好友也保存在Set中。當用戶加入群聊時可以提醒非好友用戶注意安全。
編碼格式
Set集合值set 的encoding編碼方式有:intset、hashtable。
intset:當 集合的長度少于 512 時,并且所有元素都是整數,使用 intset存儲。否則使用 hashtable。
Hashtable:Hashtable編碼的底層實現是字典,字典的每個鍵是字符串對象,只不過值都是空(NULL)。
四、Hash
數據結構
Redis hash 是一個鍵值對集合。Redis hash是一個string類型的field和value的映射表,hash特別適合用于存儲對象。類似Java里面的Map<String,Object>。
以用戶ID為查找的key為例,存儲的value用戶對象包含姓名,年齡,生日等信息,若用普通的key/value結構來存儲。
每次修改用戶的某個屬性需要,先反序列化改好后再序列化回去。開銷較大。
用戶ID數據冗余。
通過 key(用戶ID) + field(屬性標簽) 就可以操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和并發修改控制的問題。
常用命令
使用場景
商品對象、用戶對象。這個場景需要驗證性對待,如果商品對象、用戶對象信息每次都需要全量的話不妨存string,但是僅僅部分使用就可以考慮使用hash結構SKU等信息,這個場景hash就比較合適了。一個hash結構中存儲某個商品所有sku。
編碼格式
Hash 的Encoding 編碼方式共有兩種:Ziplist、Hashtable
Ziplist:當哈希對象保存的鍵值對數量少于512,且所有鍵值對的長度都少于64字節時,使用壓縮列表保存。在壓縮列表中,每當有新的鍵值對要加入到哈希對象時, 程序會先將保存了 鍵的壓縮列表節點 推入到壓縮列表表尾, 然后再將 保存了值的壓縮列表節點 推入到壓縮列表表尾, 因此:保存了同一鍵值對的兩個節點總是緊挨在一起。
Hashtable:若哈希對象保存的鍵值對個數大于512,并且其中有鍵值對大于64個字節,就使用Hashtable 保存。
五、sorted set
數據結構
Redis有序集合Zset與普通集合Set非常相似,是一個沒有重復元素的字符串集合。不同之處是有序集合的每個成員都關聯了一個評分(score),這個評分(score)被用來按照從最低分到最高分的方式排序集合中的成員。集合的成員是唯一的,但是評分是可以重復的 。因為元素是有序的, 所以你也可以很快的根據評分(score)或者次序(position)來獲取一個范圍的元素。訪問有序集合的中間元素也是非常快的,因此你能夠使用有序集合作為一個沒有重復成員的智能列表。
Zset是Redis提供的一個非常特別的數據結構,一方面它等價于Java的數據結構Map<String, Double>,可以給每一個元素value賦予一個權重score,另一方面它又類似于TreeSet,內部的元素會按照權重score進行排序,可以得到每個元素的名次,還可以通過score的范圍來獲取元素的列表。
Zset底層使用了兩個數據結構:
- Hash:hash的作用就是關聯元素value和權重score,保障元素value的唯一性,可以通過元素value找到相應的score值。
- 跳躍表,跳躍表的目的在于給元素value排序,根據score的范圍獲取元素列表。
舉個例子:在有序鏈表和跳躍表中,查詢出51。
有序鏈表:
要查找值為51的元素,需要從第一個元素開始依次查找、比較才能找到。共需要6次比較。
跳躍表:
從第2層開始,1節點比51節點小,向后比較。21節點比51節點小,繼續向后比較,后面就是NULL了,所以從21節點向下到第1層在第1層,41節點比51節點小,繼續向后,61節點比51節點大,所以從41向下在第0層,51節點為要查找的節點,節點被找到,共查找4次。從此可以看出跳躍表比有序鏈表效率要高。
常用命令
使用場景
排行榜:美團要做一個銷量排行榜,就可以使用店家的訂單做score,這個查詢出來的結果就是有序的權重隊列:score作為優先級,這樣取出來的數據權重都是最大優先執行的延時任務score作為任務啟動執行時間,取值時判斷該值執行即可。
編碼格式
Zset的Encoding 編碼有兩種,分別是:Ziplist、Skiplist。
Ziplist:與Hash有異曲同工之妙,都是使用相鄰兩個節點存儲Score和Memberskiplist。
Skiplist:redis 的 Skiplist 是由字典Dict 和 跳表構成的。
總結
在開發中,字符串類型是用的最多的數據類型,導致我們忽視了redis的其他四種數據類型,在具體場景下選擇具體的數據類型對提升redis性能有非常大的幫助。redis雖然支持消息隊列的實現,但是并不支持ack。所以redis實現的消息隊列不能保證消息的可靠性,除非自己實現消息確認機制,不過這非常麻煩,所以如果是重要的消息還是推薦使用專門的消息隊列去做。