Redis內存優化之: 小體量彙集類型數據的編碼優化(譯)

聚合類數據類型的內存優化

從Redis2.2開始,許多數據類型都被優化到必定大小,從而減小了佔用內存空間。Hash,List,由整數組成的Set,和Sorted set,當他們的元素數量和單個元素所佔內存分別小於給定值時,這些彙集類型會被以很是搞笑利用內存的方式存儲,最高能夠節省10倍內存(平都可以節省5倍內存)。html

從用戶和API的角度來看,這些優化是徹底透明的。由於這是CPU/內存的這種,因此咱們能夠在redis.conf配置文件中以下指令,經過修改某個特定類型下元素最大數量和元素自身最大內存的值來調優。redis

hash-max-zipmap-entries 512 (hash-max-ziplist-entries for Redis >= 2.6)
hash-max-zipmap-value 64  (hash-max-ziplist-value for Redis >= 2.6)
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512
複製代碼

當一個特殊優化的數據超過配置的最大值時,Redis會自動將其編碼類型轉爲該類型數據的常規編碼類型。json

使用32位實例

使用32位target編譯的redis下,每一個key能少用不少內存,由於指針會小不少,可是最大使用內存也會被限制在4G。要想編譯成32位二進制的redis,可使用make 32bitRDBAOF在32位和64位實例上是兼容的(固然在大端和小段之間上也是兼容的),因此你能夠從32位切換到64位,反過來也同樣。數組

位和字節級別的操做

Redis 2.2 引進了新的字節和位級別的操做: GETRANGE ,SETRANGE, GETBIT,SETBIT。使用這些指令,你能夠把string類型當作能夠隨機訪問的數組。好比說,你有一個應用,應用裏用戶經過一個惟一的漸進整數來識別,你可使用位圖來保存用戶的性別信息,能夠在位上女性置1,男性置0,或者用其餘方式。在Redis實例裏,10億用戶的性別數據只須要12M RAM。你可使用GETRANGE ,SETRANGE來存儲一個字節長度的每一個用戶信息。這裏只是個例子,可是咱們有可能使用這些新指令解決不少小空間時面臨的問題。緩存

若有可能,儘可能使用Hash

小的hash會使用佔用很小空間的編碼格式,因此你應該可能的使用hash來存儲你的數據。好比說你有一個網站用戶對象,你應該使用單個hash來存儲全部的字段,而非把用戶的名字,性別,年齡等都存儲成不一樣的k-v。ruby

在redis上使用哈希抽象出一個很是節省內存的鍵值存儲

我理解這節的話題有點嚇人,可是我會詳細解釋它。bash

基本上,可使用redis對普通的鍵值存儲進行建模,其中的值能夠只是字符串,這不只比redis普通的鍵值更節省內存,並且比memcached更節省內存。數據結構

讓咱們從一個事實開始:少許的鍵比包含少許字段的哈希的單個鍵使用更多的內存。這怎麼可能?咱們使用了一個技巧。從理論上講,爲了保證咱們在常數時間內執行查找(在大O符號中也稱爲O(1)),須要在平均狀況下使用具備常數時間複雜度的數據結構,例如哈希表。memcached

可是不少時候,哈希只包含不多的字段。當哈希比較小的時候,咱們可使用O(N)複雜度的數據結構對其編碼,好比帶有長度前綴的鍵值對線性數組。由於咱們只在N比較小的時候才這麼作,HGETHSET的均攤時間仍然是O(1),當哈希包含的元素個數增加到足夠大時 (你能夠在redis.conf裏配置這個限制),它將被轉換爲真正的哈希表。測試

從時間複雜度的角度看,這並不能很好的工做,但從常量時間的角度來看卻相反,由於經過CPU緩存,一個鍵值對的線性數組剛好可以工做的很好。

可是,由於哈希的字段(field)和值(value)(一般)不能像Redis對象那樣表現出全部的特性,哈希的字段沒有相似Redis鍵那樣與之關聯的生存時間(過時時間),僅僅包含一個字符串。但這對咱們來講是沒問題的,這就是設計哈希數據結構API時的意圖(咱們相信簡單優於特性,因此不運行內嵌數據結構,單個字段的過時屬性也是不支持的)。

因此,哈希是內存高效的。因此,在表示對象或者對包含一組相關字段的問題建模時使用哈希是頗有用的。可是對於只有普通鍵值對的業務又該如何呢?

好比咱們使用Redis存儲一些小對象,能夠是json編碼的對象、小的HTML塊,簡單的鍵->布爾值對等等。本質上,都是小型鍵和小型值的字符串->字符串的映射。

如今咱們假設要緩存的對象是數字的,好比:

  • object:102393
  • object:1234
  • object:5 這就是咱們能夠作的。每當咱們執行一個set操做來設置一個新value的時候,咱們實際上把鍵分爲兩部分,一個用來作鍵,一個用來看成哈希的字段名稱。好比對象的名稱爲Ojbect:1234最終會被分爲:
  • 一個鍵名: Ojbect:12
  • 字段名稱爲: 34

因此咱們用處理最後兩個字符之外的字符做爲key,而最後兩個字符做爲哈希的字段名,爲了設置咱們的鍵,可使用以下命令: HSET object:12 34 somevalue

正如你看到的,全部的哈希最終都能包含100個(00-99)字段(field),這是CPU和內存之間的最佳折中。

還有另一個很重要的事須要注意,使用這個模式時,不管咱們有多少數量的對象要緩存,咱們都只能緩存100個左右的字段。這是由於,咱們的對象最終都是以數字結尾,而不是隨機字符串。在某種程度上,最後的數字能夠看做是隱式預分片(pre-sharding)的一種方式。

那麼小數字呢?好比Object:2? 咱們能夠把Object:看成鍵名,整個數字看成哈希的字段名。因此Object:10Object:2都以Ojbect:爲鍵名,可是前者以``0看成字段名,後者則是2`。

這種方式下咱們能節省多少內存?

我使用下面的Ruby程序來測試這是如何工做的:

require 'rubygems'
require 'redis'

UseOptimization = true

def hash_get_key_field(key)
    s = key.split(":")
    if s[1].length > 2
        {:key => s[0]+":"+s[1][0..-3], :field => s[1][-2..-1]}
    else
        {:key => s[0]+":", :field => s[1]}
    end
end

def hash_set(r,key,value)
    kf = hash_get_key_field(key)
    r.hset(kf[:key],kf[:field],value)
end

def hash_get(r,key,value)
    kf = hash_get_key_field(key)
    r.hget(kf[:key],kf[:field],value)
end

r = Redis.new
(0..100000).each{|id|
    key = "object:#{id}"
    if UseOptimization
        hash_set(r,key,"val")
    else
        r.set(key,"val")
    end
}
複製代碼

這是在一個Redis2.2版本的64位實例上運行的結果:

  • UseOptimization 選項爲true的時候: 使用了1.7 MB的內存
  • UseOptimization 選項爲false的時候: 使用了11 MB的內存

這是一個數量級(an order of magnitude)的差距,我認爲這或多或少的使Redis成爲最省內存的普通鍵值存儲。

警告 爲了使其工做,你須要保證在redis.conf文件裏存在以下設置:

hash-max-zipmap-entries 256

還要記得根據你鍵值對的最大值來設置下面的字段:

hash-max-zipmap-value 1024

每當哈希超出設置的最大數量,或者值超出了設置的最大致量,哈希都會被轉化爲真正的哈希表,內存節省的特性也就隨之丟失。

你也許會問,爲何不在普通鍵空間上顯示的作這些,有兩個緣由:一個是咱們傾向於顯式地權衡,而這裏正是一個顯式地權衡:CPU,內存,最大元素大小。另外一個緣由就是頂級的鍵空間須要支持許多有趣的特性,好比過時時間,LRU數據等等,因此普世地使用這種方法並不實用。

可是redis的方式是用戶必須理解Reids是如何工做的,從而可以更好的選擇折衷方案,以及可以更好的瞭解系統確切行爲方式。

原文

Special encoding of small aggregate data types

補充知識

  • redis 底層數據結構 壓縮列表 ziplist

  • 內存消耗分析

    • 指令 info memory
    • 指標:
      • used_memory_rss: redis進程所佔用內存
      • used_memory: redis內部存儲的全部數據內存佔有量
      • used_fragmentation_ratio: used_memory_rss/used_memory,表示內存碎片率,大於1表示有碎片,小於1則存在內存交互到硬盤的狀況
    • 內存消耗劃分
      • used_memory
        • 自身內存
        • 對象內存(存儲着用戶全部的數據)
        • 緩衝內存
      • use_memory_rss-use_memory
        • 內存碎片
相關文章
相關標籤/搜索