細談Redis五大數據類型


文章原創於公眾號:程序猿周先森。本平台不定時更新,喜歡我的文章,歡迎關注我的微信公眾號。
file

上一篇文章有提到,Redis中使用最頻繁的有5種數據類型:String、List、Hash、Set、SortSet。上一篇文章只是單純介紹了下這5種數據類型使用到的指令以及常用場景,本篇文章會談談5種數據類型的底層數據結構以及各自常用的操作命令來分別進行解析。Redis作為目前最流行的Key-Value型內存數據庫,不僅數據庫操作在內存中進行,並且可定期的將數據持久化到磁盤中,所以性能相對普通數據庫高很多,而在Redis中,每個Value實際上都是以一個redisObject結構來表示:
typedef struct redisObject{
unsigned type:4;
unsigned encoding:4;
void *ptr;
int refCount;
unsigned lru:
}
我們可以看看這幾個參數分別的含義:

  • type:對象的數據類型,一般情況就是5大數據類型。

  • encode:redisObject對象底層編碼實現,主要編碼類型有簡單動態字符串,鏈表,字典,跳躍表,整數集合及壓縮列表。

  • *ptr:指向底層實現數據結構的指針。

  • refCount:計數器,當引用計數值為0將會釋放對象。

  • lru:最后一次訪問本對象的時間。

String數據類型

String 數據結構是簡單的 Key-Value 類型,是Redis中最常用的一種數據類型,Value 可以是string或者數字。String數據類型實際上可以存儲字符串、整數、浮點數三種不同類型的值,Redis是如何做到自動識別字符串、整數、浮點數三種不同類型的值。Redis是使用C實現的,但是並未使用C中的字符串,實際上Redis自己實現了一個結構體SDS來替代String類型:
struct sdshdr{
//記錄buf數組中已使用字節的長度
int len;
//記錄buf數組中剩余空間的長度
int free;
//字節數組,用於存儲字符串
char buf[];
};

我們可以看到free參數是用來判斷剩余可使用空間的長度,len表示字符串的長度,buf存儲字符串的每一個字符以及結尾的'\0'。為什么Redis要自己實現SDS結構體呢?因為SDS結構體有幾個優點:

  • 由於len保存了當前字符串的實際長度,所以獲取長度時間復雜度為O(1)。
    
  • SDS在拼接之前會對當前字符串的空間進行自動調整和擴展,防止當前字符串數據溢出。
    
  • 減少內存分配次數,SDS拼接字符串發生時,如果此時的字符串長度len小於1M,則SDS會分配和len大小相同的未使用空間給free,如果此時的字符串長度len大於1M,則SDS會分配和1M的未使用空間給free,當字符串縮短時,縮短的空間會疊加到free中,用於后續的拼接使用。
    

String數據類型常用命令:

  • 常用命令:set、get、decr、incr、mget 等。

String數據類型適用場景:

  • 分布式鎖

  • 分布式session:將分布式應用session存儲到Redis中

  • 商品秒殺

  • 常規計數:博客數,閱讀數

List數據類型
List數據結構是用來存儲多個有序的字符串,List中的每個字符串成為元素,List提供了節點重排和節點順序訪問的能力,在Redis中,List可以在兩端push和pop元素,還可以獲取指定范圍的元素列表,獲取指定索引下標的元素等,List數據結構主要有zipList(壓縮鏈表)和LinkedList(雙向鏈表)兩種實現方式。首先我們可以先看看LinkedList的結構:
type struct list{
//表頭節點
listNode *head;
//表尾節點
listNode *tail;
//包含的節點總數
unsigned long len;
};

可以看到每個LinkedList中都會包含一個表頭節點head和一個表尾結點tail,在LinkedList中每個節點都會有一個prev指向前一個元素,同時還有一個next指向后一個元素,每個節點的value就是節點的值。從而實現雙向鏈表,理解起來實際上和C中的雙向鏈表有很大程度的相似性。而另一種實現方式zipList是基於連續內存實現,有點類似於數組方式,但是和數組有點不一致的是zipList的每一個entry的大小可能不一致,需要特殊方法去控制解決,但是在執行push,pop操作時會有數據的遷移,時間復雜度為O(n), 所以一般只有在元素較少時才會使用zipList,我們可以看看zipList的結構:

type struct ziplist{
//整個壓縮列表的字節數
uint32_t zlbytes;
//記錄壓縮列表尾節點到頭結點的字節數,直接可以求節點的地址
uint32_t zltail_offset;
//記錄了節點數,有多種類型,默認如下
uint16_t zllength;
//節點
List entryX;
}

zipList中每個節點都會有以下幾個參數信息:

  • previous_entry_length:記錄前一個節點的字節長度

  • content:節點所存儲的內容,可以是一個字節數組或者整數

  • encoding:記錄content屬性中所保存的數據類型以及長度

*** List數據類型適用場景**

在渲染文章列表時可以使用List數據類型,一般情況下每個用戶都會有自己發布的文章列表,如果需要展示文章列表,就可以使用List數據類型,不但可以有序而且可以按照索引范圍去查詢文章列表。

Set數據類型

Set數據類型和List數據類型有點類似,也可以用來保存多個元素,但最大的一點區別在於Set數據類型不允許出現重復的元素,並且Set中的元素是無序的,所以沒辦法和List一樣通過索引下標獲取元素,但是Set類型支持多個Set集合取交集、並集、差集,所以合理使用Set數據類型,可以在實際項目開發中解決很多問題。Set數據類型有兩種數據結構:IntSet和HashTable。首先我們來看看IntSet的結構:

typedef struct intset {
// 編碼方式
uint32_t enconding;
// 集合包含的元素數量
uint32_t length;
// 保存元素的數組
int8_t contents[];
} intset;

當Set集合中所有元素都為整型時,Redis才會使用IntSet數據結構。有一點需要格外注意的是:IntSet數據結構是有序的。因為為了減輕性能的消耗,Redis在Set集合元素都為整型時,會使用一種基於動態數組的結構體,同時在push元素的時候控制元素的大小順序,這樣就可以使用二分查找算法來對元素進行push及pop操作,這樣時間復雜度僅為O(logN)。在Set集合中元素存在非整型數據時,Redis這時會自動采用HashTable數據結構來存放數據,在HashTable中,存放的只有key值而沒有value值,所以說在HashTable中,鍵值永遠為null。我們可以看下HashTable的結構:

typedef struct dict{
//類型特定函數
dictType *type;
//哈希表 兩個,一個用於實時存儲,一個用於rehash
dictht ht[2];
//rehash索引 數據遷移時使用
unsigned rehashidx;
}

Set數據類型使用場景:

  • 記錄唯一值:比如登錄ip,身份證號

  • 添加標簽:可以通過標簽的交並集計算用戶喜好程度等數據。

Hash數據類型
在Redis中哈希類型是指鍵本身又是一種鍵值對結構,也就是我們所說的對象,所以Hash數據類型用來存儲對象是最合適的數據類型。Hash數據類型的編碼可以是zipList或HashTable。當哈希對象保存的所有鍵值對長度小於64字節並且元素數量少於512時使用zipList,否則使用HashTable。zipList與剛才List數據類型中講到的zipList實際上基本一致,唯一區別在於Hash存儲entry數量成對增加,所以長度一定為2的整數倍。當然,使用zipList剛才已經說過push和pop時間復雜度為O(n),所以只能在數據量少的情況下才允許使用。而HashTable其實有點類似於Java中的HashTable,HashTTable主要依賴於三個結構:dict、dictht、entry。三個結構的關系可以表示為如下這幅圖:
file

Hash數據類型適用場景:

  • 存儲對象數據。

  • 結合Json描述對象集合。

SortSet數據類型

有序集合是在Set集合的基礎上,保留了Set集合中不能存在重復元素的特性,但是不同的是,SortSet集合中元素是可以排序的,SortSet排序和List排序都可以使用索引下標作為排序依據,所以說SortSet實現了數據有序且鍵值對唯一的集合,SortSet的數據結構有兩種:zipList和skipList + HashTable,zipList都不用多少了,是用於數據量較少的情況,默認排序為元素從小到大。而采用skipList + HashTable的數據結構,skipList會在保證集合有序的情況下優化范圍查找的時間復雜性,而HashTable剛才已經提到過它可以優化push和pop元素時的時間復雜性。skipList基於有序鏈表,可以創建多層索引,實現以空間復雜度來換取時間復雜度的做法,最終實現時間復雜度為O(logN)的元素查詢過程,當需要push或者pop元素時,則使用HashTable實現時間復雜度僅為O(1).

SortSet數據類型適用場景

  • 積分排行榜:根據積分排序從小到大

  • 獲取某個范圍的數據:考試80-100分的數據

歡迎關注公眾號:程序員周先森
file


免責聲明!

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



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