內(nèi)存優(yōu)化

本頁面是進(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, GETBITSETBIT梁剔。使用這些命令虽画,你可以將Redis字符串類型看成是隨機(jī)訪問的數(shù)組。例如荣病,如果你有一個(gè)應(yīng)用码撰,使用唯一漸增的證書標(biāo)識(shí),你可以使用位圖保存用戶的郵件列表訂閱信息个盆,在用戶訂閱和取消訂閱時(shí)設(shè)置位脖岛,或者相反方向。保存1億用戶的這種信息僅需要占用12M的Redis RAM空間颊亮。你同樣可以使用GETRANGESETRANGE為每位用戶保存單字節(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-policynoeviction(在一些老版本的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ì)添加。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末客们,一起剝皮案震驚了整個(gè)濱河市崇决,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌底挫,老刑警劉巖恒傻,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異建邓,居然都是意外死亡盈厘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門官边,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沸手,“玉大人,你說我怎么就攤上這事注簿∑跫” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵诡渴,是天一觀的道長(zhǎng)捐晶。 經(jīng)常有香客問我,道長(zhǎng)妄辩,這世上最難降的妖魔是什么惑灵? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮眼耀,結(jié)果婚禮上泣棋,老公的妹妹穿的比我還像新娘。我一直安慰自己畔塔,他們只是感情好潭辈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布鸯屿。 她就那樣靜靜地躺著,像睡著了一般把敢。 火紅的嫁衣襯著肌膚如雪寄摆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天修赞,我揣著相機(jī)與錄音婶恼,去河邊找鬼。 笑死柏副,一個(gè)胖子當(dāng)著我的面吹牛勾邦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播割择,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼眷篇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了荔泳?” 一聲冷哼從身側(cè)響起蕉饼,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玛歌,沒想到半個(gè)月后昧港,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡支子,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年创肥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片值朋。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叹侄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吞歼,到底是詐尸還是另有隱情圈膏,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布篙骡,位于F島的核電站稽坤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏糯俗。R本人自食惡果不足惜尿褪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望得湘。 院中可真熱鬧杖玲,春花似錦、人聲如沸淘正。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至囤采,卻和暖如春述呐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕉毯。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工乓搬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人代虾。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓进肯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棉磨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子江掩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來自《Redis開發(fā)與運(yùn)維》一書第八章,如轉(zhuǎn)載請(qǐng)聲明含蓉。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 18,892評(píng)論 2 29
  • 內(nèi)存優(yōu)化 Redis所有的數(shù)據(jù)都在內(nèi)存中馅扣,而內(nèi)存又是非常寶貴的資源。如何優(yōu)化內(nèi)存的使用一直是Redis用戶非常關(guān)注...
    linuxzw閱讀 419評(píng)論 0 2
  • 參考來源 Redis的內(nèi)存優(yōu)化 Redis所有的數(shù)據(jù)都在內(nèi)存中着降,而內(nèi)存又是非常寶貴的資源差油。對(duì)于如何優(yōu)化內(nèi)存使用一直...
    秦漢郵俠閱讀 1,290評(píng)論 0 2
  • 聲明:本文內(nèi)容來自《Redis開發(fā)與運(yùn)維》一書第八章,如轉(zhuǎn)載請(qǐng)聲明任洞。Redis所有的數(shù)據(jù)都在內(nèi)存中蓄喇,而內(nèi)存又是非常...
    yoqu閱讀 1,500評(píng)論 0 2
  • Redis所有的數(shù)據(jù)都存在內(nèi)存中,當(dāng)前內(nèi)存雖然越來越便宜交掏,但跟廉價(jià)的硬盤相比成本還是比較昂貴妆偏,因此如何高效利用Re...
    handsomemao666閱讀 360評(píng)論 0 2