天天看點

redis 記憶體優化

最近做的一個系統大量使用redis,我們将大量的使用者資訊存放在redis中,記憶體一申請就是幾百G,體量也是相當龐大。是以我們也在不斷的想方法優化減少redis的記憶體使用,把我們的優化實踐也分享出來。

采用Hash代替<K,V>鍵值對存儲

因為是存放使用者次元的資料,使用者id(uid)往往會作為key,而一個使用者會有多個資訊,比如年齡,生日等等,比較容易想到的存儲結構會采用Hash,将一個使用者的多個資訊作為hash裡的不同field來存放

善用Hash,List,ZSet的ziplist壓縮特性

Redis針對Hash,List,ZSet都實作了ziplist的壓縮存儲,可以通過配置最大元素不超過512,每個元素大小不超過64bytes,來判斷是否要采用 !ziplist壓縮格式 存儲。

注意:雖然這個ziplist是否啟用做成了配置參數,但對這個配置參數的修改要謹慎,因為ziplist是一個連續的數組空間,查找效率不是O(1)的,如果設定元素超過512太多,可能導緻查找效率降低,反而影響性能。那為什麼Redis會采用512*64bytes這樣的預設配置呢?據說是這個大小可以被加載進CPU的Cache裡,是以即使不是O(1),查找效率也是很快的。

優先使用數字類型,比String類型省空間

在Redis的内部,不管是數字類型,String類型,都會統一用一個叫redisObject的對象做一層封裝:

typedef struct redisObject {    unsigned type:4;    unsigned encoding:4;    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;    void *ptr;
} robj;      

可見,一個簡簡單單的”hello world”在redis裡都不是直接11個bytes就搞定的,還有很多附加的屬性,比如引用計數(記憶體回收)refcount,lru清理等資訊。

但如果使用了上面提到的ziplist,redis對ziplist裡元素做了裁剪,讓資料更緊湊,是以針對數字,做了一些特别處理:

* |11000000| - 1 byte* Integer encoded as int16_t (2 bytes).
* |11010000| - 1 byte* Integer encoded as int32_t (4 bytes).
* |11100000| - 1 byte* Integer encoded as int64_t (8 bytes).
* |11110000| - 1 byte* Integer encoded as 24 bit signed (3 bytes).
* |11111110| - 1 byte* Integer encoded as 8 bit signed (1 byte).
* |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.
* Unsigned integer from 0 to 12. The encoded value is actually from* 1 to 13 because 0000 and 1111 can not be used, so 1 should be
* subtracted from the encoded 4 bit value to obtain the right value.      

先用1byte來表示不同的encode,針對大小不同的數字,分别采用不一樣的記憶體空間來存儲,比如0-127就是2個位元組,128-32768就是4個位元組等等。是以算下來,和String相比,大部分情況下更省記憶體。

另外,如果不是采用ziplist的存儲方式,而是直接用redisObject這樣相對龐大的對象存儲呢?

如果能用數字,還是盡量使用數字類型,并且是小于10000的數字最好,因為:

#define OBJ_SHARED_INTEGERS 10000      

redis考慮到redisObject這個龐大的對象占用過多記憶體的因素,将10000以下數字的redisObject做了一個對象池,其他地方都通過指針(4/8bytes)引用這個池裡的redisObject,而不是各自存一份。

繼續閱讀