Redis數據結構及對象(下)

  在上一篇文章中分析了一下redis的底層數據結構,這一篇繼續來分析redis的對象,redis的對象都會用到一到三個底層數據結構,redis會在不一樣的應用場景下采用相應合適的數據結構,以達到平衡時間效率和空間效率的目的。另外redis對於對象還會有類型檢查,內存回收等操做以輔助系統的運行。

redisObject結構


實際上每個redis都是一個redisObject對象。redis對象的類型檢查,內存回收,對象共享都是基於redisObject完成的,下面來看一下redisObject的結構。redis

typedef struct redisObject {
    // 類型
    unsigned type:4;

    // 對齊位
    unsigned notused:2;

    // 編碼方式
    unsigned encoding:4;

    // LRU 時間(相對於 server.lruclock)
    unsigned lru:22;

    // 引用計數
    int refcount;

    // 指向對象的值
    void *ptr;
} robj;
複製代碼
  • type:對象類型。
  • encoding: 對象編碼方式。
  • lru: 對象的最近使用時間,配合內存回收使用。
  • refcount: 引用次數。配合內存回收使用。
  • ptr: 指向具體底層數據結構的指針。

redisObject是這樣的結構主要是爲了方便redis靈活的切換對象的編碼及實時的回收redis內失效的內存,防止內存泄漏。算法

五大類型對象


redis的五大數據對象分別是字符串、列表、集合、有序集合、哈希表,下面來分別介紹。緩存

字符串

  字符串對象一共有三種實現——整數、SDS(簡單動態字符串)和embstr編碼的SDS。   當字符串的長度小於44時,會使用embstr編碼的SDS,大於44時會使用SDS。這裏主要有兩個問題,一是什麼是embstr編碼,二是爲何恰恰要選擇44爲臨界點。 <1> embstr編碼   首先須要瞭解redisObject對象,以下圖所示,通常的string的redisObject在內存中是以這樣的形式存在的,須要分配兩塊空間,且要分配兩次。 bash

string在內存中的圖示
  而embstr編碼在內存中的分佈是這樣的,直接和redisObject的頭部連在一塊兒,只有一塊空間,且只用分配一次內存。因此相較於分開兩塊內存保存,embstr編碼更可以發揮緩存的優點。
embstr

<2> 44字節   從2.4版本開始,redis開始使用jemalloc內存分配器,jemalloc會分配8,16,32,64等字節的內存。embstr由redisObject和sds組成,其中redisObject有16個字節,若是embstr有44個字節,則sds的長度爲44+3+1=48,加起來恰好爲64字節。服務器

列表

  當列表對象能夠同時知足如下兩個條件時, 列表對象使用 ziplist 編碼:數據結構

  • 列表對象保存的全部字符串元素的長度都小於 64 字節;
  • 列表對象保存的元素數量小於 512 個;

  當這兩個條件中的一個不知足時,將使用雙端列表,並且這種變化是動態的。app

哈希表

  當哈希表對象知足ziplist編碼的條件時,會使用ziplist做爲底層數據結構,當不知足條件時,會使用字典做爲底層數據結構。 大數據

哈希表ziplist斌嗎

集合

  當集合對象能夠同時知足如下兩個條件時, 對象使用 intset 編碼:ui

  • 集合對象保存的全部元素都是整數值;
  • 集合對象保存的元素數量不超過 512 個;   當這兩個條件中的一個不知足時,將使用hashtable,並且這種變化是動態的。

有序集合

  當集合對象能夠同時知足如下兩個條件時, 對象使用 ziplist編碼:編碼

  • 列表對象保存的全部字符串元素的長度都小於 64 字節;
  • 列表對象保存的元素數量小於 512 個;
    有序集合ziplist編碼
      當這兩個條件中的一個不知足時,將使用跳躍表和hashtable兩種數據結構。
    有序集合使用跳躍表和哈希表
      爲何要將數據多冗餘出一份呢?其實單憑跳躍表或哈希表中的一個數據結構均可以完成有序集合的需求,可是隻有使用哈希表時才能以O(1)的複雜度查找成員的分值,可是在使用zrange等範圍性操做時,相比於使用跳躍表,會使得複雜度從O(N),上升到O(NLogN),而光使用跳躍表時,本來的以O(1)的複雜度查找成員的分值,會變成O(N),爲了這兩種類型的操做都能有良好的表現,redis使用了兩種數據結構來實現有序集合。

類型檢查和命令多態


  redis會對命令進行檢查,以確保命令被正確的運用到正確的對象之上,當命令和對象不匹配時,會向客戶端返回一個錯誤,例如當要對一個字符串執行自增操做時,就會返回一個錯誤。下表顯示了這種匹配關係。

SET 、 GET 、 APPEND 、 STRLEN 等命令只能對字符串鍵執行; HDEL 、 HSET 、 HGET 、 HLEN 等命令只能對哈希鍵執行; RPUSH 、 LPOP 、 LINSERT 、 LLEN 等命令只能對列表鍵執行; SADD 、 SPOP 、 SINTER 、 SCARD 等命令只能對集合鍵執行; ZADD 、 ZCARD 、 ZRANK 、 ZSCORE 等命令只能對有序集合鍵執行;

  上面說過,redis的對象都是redisObject,類型檢查就是經過redisObject的type屬性完成的。同時依賴於type屬性,redis還能夠實現命令的多態,即相同的命令做用的不一樣的對象上,執行不一樣的操做。例如當對整數型字符串執行append操做時,redis會首先將值轉化爲sds,以後執行append操做,而對sds型的字符串執行append操做時,則直接執行append操做。

內存回收


  redis採用了引用計數法來回收內存,在redisObject結構中有一個refCount屬性即是爲此服務,當建立對象時,refCount置爲1,當對象被引用則refCount+1,不被引用則refCount-1,當refCount爲0時,對象所佔用的內存就會被回收。

內存共享


  redis 會在初始化服務器時, 建立一萬個字符串對象, 這些對象包含了從 0 到 9999 的全部整數值, 當服務器須要用到值爲 0到 9999 的字符串對象時, 服務器就會使用這些共享對象, 而不是新建立對象。

對象空轉時間


  redisObject的lru屬性記錄了對象最後一次被命令程序訪問的時間,此屬性主要是爲了配合redis的回收內存算法,例如volatile-lru 或者 allkeys-lru,當內存超過設置的maxmemory時,會配合回收算法計算要被回收的內存。

相關文章
相關標籤/搜索