Redis深度歷險筆記01 Redis數據結構

Redis所有的數據結構都是以唯一的key字符串作爲名稱,然後通過這個唯一的key來獲取響應的value數據。value有5種基礎數據結構,分別是string(字符串)、list(列表)、set(集合)、hash(字典)、zset(有序集合)。

數據類型的應用場景

string:粉絲數

list:存儲關注列表、粉絲列表

hash:用於存儲用戶信息,比如一個person對象,有姓名,年齡,生日等信息,就適合用hash來存儲

set:存儲關注人 粉絲等,redis實現了對兩個set求交併差集,可以計算出兩個人的共同關注等

zset:存儲一個班級所以同學的考試成績

string(字符串)

  1. Redis字符串是動態字符串,是可以修改的字符串,結構上類似於Java中的ArrayList,採用預分配冗餘空間的方式來減少內存的頻繁分配。Redis 的字符串是一種簡單動態字符串(SDS),因爲普通的 String 字符串不記錄長度信息,如果想知道它的長度,需要遍歷整個字符串來得到,時間複雜度爲 O(n),而 redis 的簡單動態字符串中的 len 屬性記錄了長度信息,取得長度的時間複雜度僅爲 O(1),這樣確保了獲取字符串長度的操作不會成爲 redis 的性能瓶頸。

  2. 操作語句

    • set 進行寫:set name test(mset name1 test1 name2 test2 name3 test3)
    • get 進行讀:get name(mget name1 name2 name3)
    • expire 設置過期時間:expire name 5(表示5秒後過期)
    • setex 同時寫和設置過期時間:setex name 5 test
    • setnx 表示 name不存在時才進行set:setnx name test
  3. 如果value的值是一個整數,name可以對它進行自增操作,如果整數超過Long的最大最小值,會報錯

    • incr 表示自增1:incr age
    • incrby 表示自增指定值:incrby age 5

list(列表)

  1. Redis 列表相當於Java中的LinkedList,插入和刪除非常快,時間複雜度是O(1),但是查找數據很慢,時間複雜度爲O(n)。

  2. 可以用來做異步隊列:將需要延後處理的任務序列化成字符串,存到 Redis 的 list 列表中,另一個線程從這個列表中輪詢數據進行處理。

    • rpush 表示右邊進,lpop 表示左邊出,構成的是一個隊列。

    • rpush 表示右邊進,rpop表示右邊出,構成的是一個棧。

  3. list底層結構:

    • Redis 底層存儲的並不是一個簡單的LinkedList,而是快速鏈表quickList。
    • 在列表元素較少時,使用一塊連續的內存來儲存,這個結構叫做zipList,也就是壓縮鏈表,它將所有的元素緊挨着一起存儲。
    • 當數據量較多時,改爲quickList,這麼做的原因是:普通的鏈表需要附加指針會佔用空間,而且加重了內存的碎片化。所以Redis將鏈表和zipList結合組成了quickList,它將多個zipList通過雙向指針串起來,這樣既能滿足快速插入刪除性能,又不會出現嚴重的空間碎片化。

set(集合)

  1. Redis 的集合相當於Java 中的HashSet,它的內部實現相當於一個特殊的字典,字典中所有的value都是NULL。
  2. set集合中的元素時無序且唯一的,可以用來存儲活動中獎的用戶id,可以保證同一個用戶不會中獎兩次。
  3. 操作:
    • sadd book java(添加元素)
    • spop book(彈出一個元素)
    • scard book(獲取長度,類似count)
    • smembers book(查看所有元素)

hash(字典)

  1. Redis的字典相當於Java中的HashMap。

  2. Redis的字典和HashMap的異同點

    • 相同點:無序,採用數組+鏈表結構。數組位置碰撞時,就會將碰撞的元素使用鏈表串接起來。
    • 不同點:
      • Redis 字典的值只能是字符串,而HashMap的值可以是多種類型。
      • rehash的方式不一樣,因爲HashMap採用了一次性全部rehash。Redis爲了高性能,不能阻塞服務,採用了漸進式rehash策略。漸進式 rehash 就是同時保留舊數組和新數組,在後續對 hash 的操作中漸漸的將舊數組中的數據遷移到新數組中,所以在操作處於 rehash 過程的字典時,需要同時訪問新舊兩個哈希表,如果在舊哈希表中找不到元素,就需要去新哈希表中查找。
  3. Redis中的每個字典都帶有兩個哈希表:ht[0]和ht[1],一個作爲平時使用,一個在rehash時使用。

  4. Redis的哈希表示如何解決哈希衝突的:採用鏈地址法來解決鍵衝突問題,每個節點都有一個next指針指向另一個節點,通過指針將索引值相同的節點連接起來。

  5. rehash 當哈希表保存的鍵值對數量太多或者太少時,需要對哈希表的大小進行相應的擴展或者收縮。漸進式rehash步驟:

    • 爲ht[1]分配空間,讓字典同時持有ht[0]和ht[1]兩個表。
    • 在字典中維持一個索引計數器rehashidx,值設爲0,表示rehash工作正式開始。
    • 在rehash進行期間,每次對字典執行添加、刪除、查找或者更新操作時,程序就會順帶將ht[0]哈希表在rehashidx索引上所有的鍵值對rehash到ht[1],並將rehashidx的值加1。
    • 隨着字典操作的不斷執行,最終ht[0]的所有鍵值對都會被rehash到ht[1],這時程序將rehashidx的值設爲-1,表示rehash操作已完成。然後將ht[0]的空間釋放掉,將ht[1]設置爲ht[0]。

zset(有序集合)

  1. 一方面它是一個set,保證了元素的唯一性。

  2. 另一方面它給每個元素賦予一個score,代表這個value的排序權重。可以根據score對元素進行排序。

  3. 使用場景:可以用來存粉絲列表(粉絲id,關注時間),存學術成績(學生id,成績)

  4. 操作:

    • zadd test 99 math
    • zrange test 60 100(區間查找60到100分的元素)
    • zrank test math 看排名
    • zcard test 相當於count
  5. zset底層數據結構

    • zset底層數據結構包括ziplist和skiplist,當有序集合保存的元素數量小於128個並且所有元素的長度都小於64字節時,使用ziplist,否則使用zskiplist和dict。
    • ziplist 作爲zset底層數據結構時,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素的成員,第二個節點保存元素的分值。
    • zskiplist和dict作爲zset底層存儲結構時,dict保存key/value,便於通過key獲取score;zskiplist保存有序的元素列表,便於按照分值對元素排序,便於執行range之類的命令。

跳躍表

  1. 跳躍表是一種鏈表加多層索引的結構,支持快速插入、刪除、查找操作,平均查找複雜度是O(logN),空間複雜度是O(n)。

  2. 跳躍表由zskiplist和zskiplistNode兩個結構構成。zskiplist結構用於保存跳躍節點相關的信息,比如節點的數量、表頭表尾節點的指針等;zskiplistNode結構用於表示跳躍表節點。跳躍表節點中有一個level數組,代表層,數組中每個元素都包含一個指向其他節點的指針,可以通過這些層來加快訪問其他節點的速度。節點中還保存了元素的引用和分值,跳躍表中所有的節點都按照分值從小到大排序。

  3. 爲什麼要使用跳錶而不是紅黑樹來實現zset:

    • zset支持的操作有插入、刪除、查找、有序輸出所有元素、查找區間內所有元素。前面4項,兩者效率差不多,但是最後一項,紅黑樹沒有跳錶效率高。
    • 在跳錶中,要查找區間的元素,只需定位到兩個區間端點在最底層的位置,然後順序遍歷元素即可,非常高效。而紅黑樹定位到端點後,每次都要查找後續節點,比較耗時。
  4. 爲什麼有序集合zset需要同時使用跳躍表和字典來實現

    • 如果只用字典實現有序集合,它可以以O(1)的複雜度查找元素的分值,但是對於zrank、zrange命令,都需要對所有元素排序,完成這種排序至少需要O(NlogN)的時間複雜度,以及O(N)的空間內存(因爲要創建一個數組來保存排序後的元素)。
    • 如果只用跳躍表來實現有序集合的話,有高效執行範圍操作的優點,但是根據成員查找分值這一操作的複雜度將從O(1)上升爲O(N)。因此,爲了讓有序集合的查找分值和範圍查找操作都儘可能快,redis選擇同時使用字典和跳躍表來實現有序集合。兩種數據結構通過指針共享元素,不會浪費內存
  5. Redis只在兩個地方用到了跳躍表,一個是實現有序集合鍵,另一個是集羣節點中用作內部數據結構。跳錶是可以實現二分查找的有序鏈表(鏈表加上多級索引的結構,就是跳錶)

  6. 跳躍表的時間複雜度爲O(logn):標準化的跳錶每兩個元素提取出一個元素作爲上一級的索引,也就是開始是1/2,然後1/4,1/8 … ,每一級索引減少上一級一半的元素,那麼最後一級只有一個元素,也就是總元素個數 n/(2h) = 1 ,得到高度 h = logn,所以跳錶的時間複雜度爲 O(logn)。

    跳躍表的空間複雜度爲O(n):如果每兩個元素向上提取一個元素的索引,那麼最後額外需要的空間是一個等比數列,爲:n/2 + n/4 + n/8 … + 8 + 4 + 2 = n - 2 ,所以,跳錶的空間複雜度爲 O(n)。每三個節點抽取一個索引的話,額外需要的空間是n-1/2。

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