Go map[int64]int64 寫入 redis 占用多少內(nèi)存
我們在系統(tǒng)設(shè)計面試或者在實(shí)際工作中,免不了要進(jìn)行一些估算。之前的文章里講過一些技巧,今天來個實(shí)戰(zhàn)。
這是我最近在做的一個工作,將內(nèi)存中的一個超大的 map[int64]int64 寫入到 redis,map 里的元素個數(shù)是千萬級的。設(shè)計方案的時候,需要對 redis 的容量做一個估算。
如果不了解 redis 的話,可能你的答案是用元素個數(shù)直接乘以 16B(key 和 value 各占 8B)。我們假設(shè)元素個數(shù)是 5kw,那估算結(jié)果就是:5kw * 16B=50kk * 16B = 800MB。
答案是錯的。
為了解決這個問題,需要深入地研究一下 redis 的數(shù)據(jù)結(jié)構(gòu)。
整個 redis 數(shù)據(jù)庫就是一個大的 map,它容納了所有的 key,我們都知道 key 都是 string 類型,而 value 則有 string, list, set, hashmap, zset……等類型。
Redis 中的一個 k-v 對用一個 entry 項(xiàng)表示,其中每個 entry 包含 key、value、next 三個指針,共 24 字節(jié)。由于 redis 使用 jemalloc 分配內(nèi)存,因此一個 entry 需要申請 32 字節(jié)的內(nèi)存。這里的 key, value 指針分別指向一個 RedisObject:

typedef?struct?redisObject?{
????unsigned?type:4;
????unsigned?encoding:4;
????unsigned?lru:LRU_BITS;?
????int?refcount;
????void?*ptr;
}?robj;
RedisObject 對應(yīng)前面提到的各種數(shù)據(jù)類型,其中最簡單的就是 redis 內(nèi)部的字符串了。它有如下幾種編碼格式:

圖中的元數(shù)據(jù)包括 type,encoding,lru, refcount,分別表示數(shù)據(jù)類型,編碼類型,最近一次訪問的時間戳,引用次數(shù)。
當(dāng)字符串是一個整型時,直接放在 ptr 位置,不用再分配新的內(nèi)存了,非常高效。
解析一下 44 字節(jié)的原因:元數(shù)據(jù)和 ptr 共占 16 字節(jié),加上 44 字節(jié),再加上字符串末尾的 '\0',共61 字節(jié)。因?yàn)樽址拈L度只有 44,因此 len 和 alloc 各用 1 個字節(jié)就夠了。再加上 1 個字節(jié)的 flags,剛好是 64 字節(jié)。超過了這個值,SDS 就需要單獨(dú)再申請一塊內(nèi)存,導(dǎo)致訪問的時候就多了一跳指針。
多提一句,redis 最大支持 512MB 大小的字符串。
回答本文的問題,恰好我們要寫入 redis 的 map 中的 key 和 value 都是整數(shù),因此直接將值寫入 ptr 處即可。
于是 map 的一個 key 占用的內(nèi)存大小為:32(entry)+16(value)+16(value)=64B。于是,5kw 個 key 占用的內(nèi)存大小是 5kw*64B = 50 kk * 64B = 3200MB ≈ 3G。
假如我們在 key 前面加上了前綴,那就會生成 SDS,占用的內(nèi)存會變大,訪問效率也會變差。
總之,我們根據(jù)要寫入 redis 中的字符串的長度可以很方便地估算占用內(nèi)存的總大小。如果 key 和 value 恰好都是 int64 類型的,那么盡量不要在 key 前加前綴,這樣可以直接使用 key 的個數(shù)乘以 64B 就能算出占用內(nèi)存的大小。
