實際上每個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;
複製代碼
redisObject是這樣的結構主要是爲了方便redis靈活的切換對象的編碼及實時的回收redis內失效的內存,防止內存泄漏。算法
redis的五大數據對象分別是字符串、列表、集合、有序集合、哈希表,下面來分別介紹。緩存
字符串對象一共有三種實現——整數、SDS(簡單動態字符串)和embstr編碼的SDS。 當字符串的長度小於44時,會使用embstr編碼的SDS,大於44時會使用SDS。這裏主要有兩個問題,一是什麼是embstr編碼,二是爲何恰恰要選擇44爲臨界點。 <1> embstr編碼 首先須要瞭解redisObject對象,以下圖所示,通常的string的redisObject在內存中是以這樣的形式存在的,須要分配兩塊空間,且要分配兩次。 bash
<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 編碼:數據結構
當這兩個條件中的一個不知足時,將使用雙端列表,並且這種變化是動態的。app
當哈希表對象知足ziplist編碼的條件時,會使用ziplist做爲底層數據結構,當不知足條件時,會使用字典做爲底層數據結構。 大數據
當集合對象能夠同時知足如下兩個條件時, 對象使用 intset 編碼:ui
當集合對象能夠同時知足如下兩個條件時, 對象使用 ziplist編碼:編碼
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時,會配合回收算法計算要被回收的內存。