本頁面是進(jìn)程內(nèi)的一個(gè)工作岛蚤。目前它僅是一個(gè)當(dāng)你的內(nèi)存出現(xiàn)問題時(shí)的檢查清單邑狸。
小型聚合數(shù)據(jù)類型的特殊編碼
從Redis2.2開始,許多數(shù)據(jù)類型被優(yōu)化為在一定大小下使用更少的空間涤妒。哈希单雾,列表,僅由整數(shù)組成的集合她紫,和有序集合硅堆,當(dāng)元素?cái)?shù)量小于一個(gè)給定數(shù)字,并且到元素最大尺寸贿讹,使用一個(gè)非常有效率的內(nèi)存編碼方式可以達(dá)到最高減少10倍內(nèi)存(平均節(jié)省5倍內(nèi)存)渐逃。
從用戶和API的視角這是完全是透明的。因?yàn)檫@是CPU/memory權(quán)衡民褂,它可能會(huì)使用下面的redis.conf指令調(diào)優(yōu)特殊編碼的元素最大數(shù)字和最大尺寸茄菊。
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512
如果特殊編碼的值超過了配置中的最大尺寸,Redis將會(huì)自動(dòng)將其轉(zhuǎn)換為普通編碼赊堪。這個(gè)操作對(duì)于小的值非趁嬷常快,但如果你為了特別大的聚合類型使用特殊編碼更改配置哭廉,建議是運(yùn)行一些基準(zhǔn)測(cè)試脊僚,并且測(cè)試檢查下轉(zhuǎn)換時(shí)間。
使用32位實(shí)例
Redis為了每個(gè)鍵使用更少的內(nèi)存而使用32位編譯遵绰,因?yàn)橹羔樖叫〉牧苫希且粋€(gè)實(shí)例的最大內(nèi)存使用將會(huì)限制在4GB。為了以32位編譯Redis街立,使用make 32bit舶衬。RDB和AOF文件兼容32位和64位實(shí)例(同樣也兼容小端字節(jié)序和大端字節(jié)序)埠通,因此你可以從32切換到64赎离,或者相反,都沒有問題端辱。
位和字節(jié)層級(jí)的操作
Redis2.2引入新的bit和byte等級(jí)的操作:GETRANGE, SETRANGE, GETBIT 和 SETBIT梁剔。使用這些命令虽画,你可以將Redis字符串類型看成是隨機(jī)訪問的數(shù)組。例如荣病,如果你有一個(gè)應(yīng)用码撰,使用唯一漸增的證書標(biāo)識(shí),你可以使用位圖保存用戶的郵件列表訂閱信息个盆,在用戶訂閱和取消訂閱時(shí)設(shè)置位脖岛,或者相反方向。保存1億用戶的這種信息僅需要占用12M的Redis RAM空間颊亮。你同樣可以使用GETRANGE 和 SETRANGE為每位用戶保存單字節(jié)的信息柴梆。這僅僅是一個(gè)例子,但是它確實(shí)可以使用新的基元在非常小的空間內(nèi)去建模一系列的問題终惑。
盡可能使用哈希
小的哈希被編碼在非常小的空間內(nèi)绍在,因此你需要在可能的情況下去嘗試使用哈希來代表你的數(shù)據(jù)。例如雹有,如果在網(wǎng)頁應(yīng)用中使用對(duì)象代表用戶偿渡,使用包含所有必須字段的一個(gè)哈希取代分別為name,surname,email,password字段設(shè)置一個(gè)不同的key霸奕。
如果你想要知道更多溜宽,閱讀下面的部分。
使用哈希將存儲(chǔ)在Redis上的普通鍵-值抽象成非常有內(nèi)存效率的
我知道這個(gè)部分的標(biāo)題很嚇人质帅,但是我將會(huì)詳細(xì)解釋它們包含什么坑质。
基本上可以使用Redis建模一個(gè)普通的鍵-值存儲(chǔ),其中值僅允許是字符串临梗,它不僅比普通的Redis鍵更有存儲(chǔ)效率涡扼,同時(shí)也比memcached更有存儲(chǔ)效率。
讓我們從一些事實(shí)開始:幾個(gè)鍵比一個(gè)包含一些字段哈希的鍵占用更多的內(nèi)存盟庞。這怎么可能呢吃沪?我們使用了一個(gè)技巧。在理論上什猖,為了確保能實(shí)現(xiàn)我們?cè)诔A繒r(shí)間內(nèi)執(zhí)行查找(在大O記法中也稱為O(1))票彪,需要使用一個(gè)在平均情況下時(shí)間復(fù)雜度是常量的數(shù)據(jù)結(jié)構(gòu),例如哈希表不狮。
但是很多時(shí)候哈希僅包含少量字段降铸。當(dāng)一個(gè)哈希很小的時(shí),我們可以使用一個(gè)O(N)的數(shù)據(jù)結(jié)構(gòu)編碼它摇零,像一個(gè)使用長(zhǎng)度前綴的鍵值對(duì)的線性數(shù)組一樣推掸。因?yàn)槲覀冎挥性贜很小時(shí)才會(huì)這么使用,因此HGET和HSET命令的均攤時(shí)間依舊是O(1):當(dāng)它包含的元素的數(shù)量增長(zhǎng)到很大時(shí),我們會(huì)將它轉(zhuǎn)換成一個(gè)真正的哈希(你可以在redis.conf里配置限制)谅畅。
這不僅僅從時(shí)間復(fù)雜度視角運(yùn)行的很好登渣,但也從常量時(shí)間視角看,因?yàn)橐粋€(gè)鍵值對(duì)的線性數(shù)組能很好的處理CPU緩存(相比哈希表毡泻,它是一個(gè)更好的緩存地方)胜茧。
無論如何,因?yàn)楣W侄魏椭挡豢偸谴硭刑匦缘腞edis對(duì)象仇味,哈希字段不能像一個(gè)真實(shí)的鍵一樣關(guān)聯(lián)一個(gè)生存時(shí)間(live)呻顽,并且只能包含字符串。但我們是沒問題的丹墨,這是我們?cè)O(shè)計(jì)哈希這種數(shù)據(jù)類型API的意圖(我們相信簡(jiǎn)單比特性重要芬位,所以嵌套的數(shù)據(jù)結(jié)構(gòu)不被允許,就像單個(gè)字段的過期時(shí)間不被允許一樣)带到。
所以昧碉,哈希是有內(nèi)存效率的。當(dāng)我們使用哈希代表一個(gè)對(duì)象或建模其他有關(guān)聯(lián)的字段分組的問題時(shí)揽惹,這是非常有用被饿。如果我們使用普通的鍵值業(yè)務(wù)時(shí)呢?
想象一下我們想要使用Redis來緩存很多小的對(duì)象搪搏,那些可以使用JSON編碼的對(duì)象狭握,小的HTML片段,簡(jiǎn)單的鍵key->布爾值等等疯溺÷勐基本上任何事物都是一個(gè)小的鍵和值的string->string映射。
現(xiàn)在囱嫩,讓我們假設(shè)我們想要緩存的對(duì)象是有序的恃疯,例如:
- object:102393
- object:1234
- object:5
這是我們能做的。每次我們只行一個(gè)SET操作來設(shè)置一個(gè)新的值墨闲,我們實(shí)際上將鍵拆分為兩部分今妄,一部分像key一樣使用,另外一部分作為哈希的字段名鸳碧。例如盾鳞,名為"object:1234"的對(duì)象實(shí)際上拆分為:
- 一個(gè)名為 object:12 的鍵
- 一個(gè)名為34的字段
所以,我們使用所有的的字符瞻离,但是最后兩位是鍵腾仅,并且最后兩個(gè)字符是哈希字段的名字。我們使用下面的命令設(shè)置鍵套利。
HSET object:12 34 somevalue
就像你看到的推励,每個(gè)哈希將包含100個(gè)字段結(jié)束鹤耍,那是一個(gè)在CPU和內(nèi)存節(jié)省之間最優(yōu)的折衷。
有另外一件非常重要的事情需要注意吹艇,使用這個(gè)模式,不管我們緩存的對(duì)象的數(shù)字昂拂,每個(gè)哈希將會(huì)擁有100個(gè)左右的字段受神。這是因?yàn)椋覀兊膶?duì)象總將以一個(gè)數(shù)字結(jié)尾格侯,不是一個(gè)隨機(jī)字符串鼻听。從某種意義上說,最后的數(shù)字可以被認(rèn)為是隱式預(yù)分片的形式联四。
小數(shù)字呢撑碴?像對(duì)象:2?我們只使用"object:"來處理這個(gè)案例朝墩,像一個(gè)鍵名醉拓,并且整個(gè)數(shù)字作為哈希字段名。因此object:2和object:10都將在"object:"鍵內(nèi)結(jié)束收苏,但是一個(gè)以鍵名”2"亿卤,另外一個(gè)以”10"。
這個(gè)方法我們節(jié)省了多少內(nèi)存鹿霸?
我用下面的Ruby程序來測(cè)試這是如何工作的:
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
}
這是一個(gè)使用Redis2.2的64位實(shí)例的結(jié)果:
- UseOptimization set to true: 1.7 MB of used memory
- UseOptimization設(shè)置為真(true):使用1.7MB
- UseOptimization set to false; 11 MB of used memory
- UseOptimization設(shè)置為假(false):使用11MB內(nèi)存
這個(gè)是一個(gè)數(shù)量級(jí)排吴,我認(rèn)為,這使Redis或多或少以內(nèi)存效率最高的存儲(chǔ)普通鍵值懦鼠。
注意:為了使這個(gè)能工作钻哩,確認(rèn)在你的redis.conf里面包含像這樣的內(nèi)容:
hash-max-zipmap-entries 256
也要記得根據(jù)你的鍵和值的最大大小設(shè)置的下面的字段:
hash-max-zipmap-value 1024
每次一個(gè)哈希超過指定的元素?cái)?shù)量或元素尺寸,它將會(huì)轉(zhuǎn)化為一個(gè)真正的哈希表肛冶,并且內(nèi)存節(jié)省將會(huì)丟失街氢。
你可能會(huì)問了,為什么你不在正常鍵空間隱式的做呢睦袖?這樣我就不必?fù)?dān)心了阳仔。有兩個(gè)原因:其一是我們傾向于做一個(gè)顯式的權(quán)衡,并且這是一個(gè)在很多事情之間清晰的權(quán)衡:CPU扣泊,內(nèi)存近范,最大元素尺寸。其二是頂級(jí)的鍵空間必須支持大量有趣的事情延蟹,比如過期時(shí)間评矩,LRU數(shù)據(jù)等等,因此使用一般的方式做這些是不太現(xiàn)實(shí)的阱飘。
但是Redis的做法是斥杜,用戶必須理解事物如何運(yùn)作虱颗,這樣他能夠挑選最好的這種方案,并且理解系統(tǒng)將如何精確的運(yùn)轉(zhuǎn)蔗喂。
內(nèi)存分配
為了保存用戶的鍵忘渔,Redis分配了maxmemory
設(shè)置中允許的盡可能多的內(nèi)存(然而還有少量的額外分配可能性)。
精確的值可以在配置文件里面設(shè)置缰儿,或者稍后通過CONFIG SET設(shè)置(參見Using memory as an LRU cache for more info)畦粮。關(guān)于Redis如何管理內(nèi)存還有一些事情需要注意。
當(dāng)鍵被移除時(shí)乖阵,Redis并不總是會(huì)釋放(返回)內(nèi)存給OS宣赔。這并不是Redis特有的,但這是大多數(shù)malloc()實(shí)現(xiàn)的工作方式瞪浸。例如儒将,如果你將5GB的數(shù)據(jù)填充到一個(gè)實(shí)例,然后移除2GB的數(shù)據(jù)对蒲,駐留集(也被稱為RSS钩蚊,進(jìn)程消耗的內(nèi)存頁數(shù))將可能仍然是5GB左右,甚至當(dāng)Redis宣稱用戶內(nèi)存是3GB左右蹈矮。發(fā)生此事的原因是底層的分配器不能夠輕易的釋放內(nèi)存两疚。例如,通常當(dāng)大部分被移除的鍵被分配在與其他仍然存在的鍵相同的頁上含滴。
前一點(diǎn)意味著诱渤,你需要在峰值內(nèi)存的基礎(chǔ)上提供內(nèi)存。如果你的工作負(fù)載時(shí)不時(shí)需要10GB內(nèi)存谈况,即使大部分時(shí)間5GB就可以了勺美,你仍需要提供10GB內(nèi)存。
然而碑韵,分配器是聰明的并且能夠復(fù)用空閑的內(nèi)存碎片赡茸,因此在你釋放5GB數(shù)據(jù)集中的2GB后,當(dāng)你再次開始添加更多的鍵時(shí)祝闻,你將會(huì)看到RSS(駐留集)保持穩(wěn)定占卧,當(dāng)你添加最多2GB額外的鍵時(shí),不會(huì)增加更多联喘。內(nèi)存分配器基本上會(huì)嘗試復(fù)用之前(邏輯上)釋放的2GB內(nèi)存华蜒。
由于這些原因,當(dāng)你內(nèi)存使用量峰值遠(yuǎn)大于當(dāng)前已使用的內(nèi)存時(shí)碎片率是不可靠的豁遭。內(nèi)存碎片是計(jì)算實(shí)際使用的物理內(nèi)存(RSS值)除以當(dāng)前在用的內(nèi)存總量(Redis分配執(zhí)行的總和)叭喜。因?yàn)镽SS反應(yīng)的是峰值內(nèi)存,當(dāng)(虛擬的)已使用內(nèi)存低的時(shí)候蓖谢,是因?yàn)榇罅康逆I/值被釋放捂蕴,但是RSS是高的譬涡,
RSS/mem_used
率將會(huì)非常高。
如果maxmemory
沒有設(shè)置啥辨,Redis將會(huì)一直分配內(nèi)存直到發(fā)現(xiàn)合適涡匀,因此它可以(階梯式)吃掉你所有的空閑內(nèi)存。因此溉知,通常建議配置一些限制陨瘩。你可能也想設(shè)置maxmemory-policy
到noeviction
(在一些老版本的Redis中,這不是一個(gè)默認(rèn)值)着倾。
當(dāng)Redis的寫命令達(dá)到內(nèi)存限制時(shí)拾酝,它會(huì)返回一個(gè)內(nèi)存不足錯(cuò)誤--從而會(huì)引起應(yīng)用程序錯(cuò)誤燕少,但是不會(huì)因?yàn)閮?nèi)存耗盡而導(dǎo)致整個(gè)機(jī)器掛掉卡者。
工作進(jìn)展
工作進(jìn)展...更多提示很快就會(huì)添加。