1,redis如何管理內(nèi)存結(jié)構(gòu)
redis內(nèi)存模型:
【used_memory】:Redis內(nèi)存占用中最主要的部分瞎饲,Redis分配器分配的內(nèi)存總量(單位是KB)(在編譯時(shí)指定編譯器开缎,默認(rèn)是jemalloc)捏境,主要包含自身內(nèi)存(字典姐浮、元數(shù)據(jù))、對(duì)象內(nèi)存檐迟、緩存,lua內(nèi)存码耐。
【自身內(nèi)存】:自身維護(hù)的一些數(shù)據(jù)字典及元數(shù)據(jù)追迟,一般占用內(nèi)存很低。
【對(duì)象內(nèi)存】:所有對(duì)象都是Key-Value型骚腥,Key對(duì)象都是字符串敦间,Value對(duì)象則包括5種類(String,List桦沉,Hash每瞒,Set,Zset)纯露,5.0還支持stream類型剿骨。
【緩存】:客戶端緩沖區(qū)(普通 + 主從復(fù)制 + pubsub)以及aof緩沖區(qū)。
【Lua內(nèi)存】:主要是存儲(chǔ)加載的 Lua 腳本埠褪,內(nèi)存使用量和加載的 Lua 腳本數(shù)量有關(guān)浓利。
【used\_memory\_rss】:Redis 主進(jìn)程占據(jù)操作系統(tǒng)的內(nèi)存(單位是KB),是從操作系統(tǒng)角度得到的值钞速,如top贷掖、ps等命令。
ps 和 top 命令常用來(lái)查看Linux系統(tǒng)進(jìn)程相關(guān)信息渴语。
ps命令:可以查看進(jìn)程的瞬間信息苹威。
top命令:可以持續(xù)的監(jiān)視進(jìn)程的信息。
【內(nèi)存碎片】:如果對(duì)數(shù)據(jù)的更改頻繁驾凶,可能導(dǎo)致redis釋放的空間在物理內(nèi)存中并沒(méi)有釋放牙甫,但redis又無(wú)法有效利用掷酗,這就形成了內(nèi)存碎片。
【運(yùn)行內(nèi)存】:運(yùn)行時(shí)消耗的內(nèi)存窟哺,一般占用內(nèi)存較低泻轰,在10M內(nèi)。
【子進(jìn)程內(nèi)存】:主要是在持久化的時(shí)候且轨,aof rewrite或者rdb產(chǎn)生的子進(jìn)程消耗的內(nèi)存浮声,一般也是比較小。
2旋奢,對(duì)象內(nèi)存存儲(chǔ) Redis 所有的key-value型數(shù)據(jù)類型泳挥,key對(duì)象都是 string 類型,value對(duì)象主要有五種數(shù)據(jù)類型String黄绩、List羡洁、Hash、Set爽丹、Zset筑煮,不同類型的對(duì)象通過(guò)對(duì)應(yīng)的編碼各種封裝,對(duì)外定義為RedisObject結(jié)構(gòu)體粤蝎,RedisObject都是由字典(Dict)保存的真仲,而字典底層是通過(guò)哈希表來(lái)實(shí)現(xiàn)的。通過(guò)哈希表中的節(jié)點(diǎn)保存字典中的鍵值對(duì)初澎,結(jié)構(gòu)如下:
此圖來(lái)源于《redis的設(shè)計(jì)與實(shí)現(xiàn)》
為了達(dá)到極大的提高 Redis 的靈活性和效率秸应,Redis 根據(jù)不同的使用場(chǎng)景來(lái)對(duì)一個(gè)對(duì)象設(shè)置不同的編碼,從而優(yōu)化某一場(chǎng)景下的效率碑宴。?
【int】:(整數(shù)且數(shù)字長(zhǎng)度小于20软啼,直接記錄在ptr*里面)
【embstr】: (連續(xù)分配的內(nèi)存(字符串長(zhǎng)度小于等于44字節(jié)的字符串))
【raw】: 動(dòng)態(tài)字符串(大于44個(gè)字節(jié)的字符串,同時(shí)字符長(zhǎng)度小于?512M(512M是字符串的大小限制))
list (列表)
【ziplist】:(元素個(gè)數(shù)小于hash-max-ziplist-entries配置(默認(rèn)512個(gè))延柠,同時(shí)所有值都小于hash-max-ziplist-value配置(默認(rèn)64個(gè)字節(jié)))
【linkedlist】:(當(dāng)列表類型無(wú)法滿足ziplist的條件時(shí)祸挪,Redis會(huì)使用linkedlist作為列表的內(nèi)部實(shí)現(xiàn))
【quicklist】:(Redis 3.2 版本引入了 quicklist 作為 list 的底層實(shí)現(xiàn),不再使用 linkedlist 和 ziplist 實(shí)現(xiàn))
list (列表)
【ziplist】:(元素個(gè)數(shù)小于hash-max-ziplist-entries配置(默認(rèn)512個(gè))贞间,同時(shí)所有值都小于hash-max-ziplist-value配置(默認(rèn)64個(gè)字節(jié)))
【linkedlist】:(當(dāng)列表類型無(wú)法滿足ziplist的條件時(shí)贿条,Redis會(huì)使用linkedlist作為列表的內(nèi)部實(shí)現(xiàn))
【quicklist】:(Redis 3.2 版本引入了 quicklist 作為 list 的底層實(shí)現(xiàn),不再使用 linkedlist 和 ziplist 實(shí)現(xiàn))
list (列表)
【ziplist】:(元素個(gè)數(shù)小于hash-max-ziplist-entries配置(默認(rèn)512個(gè))增热,同時(shí)所有值都小于hash-max-ziplist-value配置(默認(rèn)64個(gè)字節(jié)))
【linkedlist】:(當(dāng)列表類型無(wú)法滿足ziplist的條件時(shí)整以,Redis會(huì)使用linkedlist作為列表的內(nèi)部實(shí)現(xiàn))
【quicklist】:(Redis 3.2 版本引入了 quicklist 作為 list 的底層實(shí)現(xiàn),不再使用 linkedlist 和 ziplist 實(shí)現(xiàn))
zset(有序集合)
【ziplist】:(元素個(gè)數(shù)小于zset-max-ziplist-entries配置(默認(rèn)128個(gè))同時(shí)每個(gè)元素的value小于zset-max-ziplist-value配置(默認(rèn)64個(gè)字節(jié)))
【skiplist】:(當(dāng)ziplist條件不滿足時(shí)峻仇,有序集合會(huì)使用skiplist作為內(nèi)部實(shí)現(xiàn))
2.2? 緩沖內(nèi)存
2.2 1 客戶端緩存
客戶端緩沖指的是所有接入 Redis 服務(wù)的 TCP 連接的輸入輸出緩沖公黑。有普通客戶端緩沖、主從復(fù)制緩沖、訂閱緩沖帆调,這些都由對(duì)應(yīng)的參數(shù)緩沖控制大械旖尽(輸入緩沖無(wú)參數(shù)控制,最大空間為1G)番刊,若達(dá)到設(shè)定的最大值,客戶端將斷開(kāi)影锈。
【client-output-buffer-limit】:?限制客戶端輸出緩存的大小芹务,后面接客戶端種類(normal、slave鸭廷、pubsub)及限制大小枣抱,默認(rèn)是0,不做限制辆床,如果做了限制佳晶,達(dá)到閾值之后,會(huì)斷開(kāi)鏈接讼载,釋放內(nèi)存轿秧。
【repl-backlog-size】:默認(rèn)是1M,backlog是一個(gè)主從復(fù)制的緩沖區(qū)咨堤,是一個(gè)環(huán)形buffer,假設(shè)達(dá)到設(shè)置的閾值菇篡,不存在溢出的問(wèn)題,會(huì)循環(huán)覆蓋,比如slave中斷過(guò)程中同步數(shù)據(jù)沒(méi)有被覆蓋一喘,執(zhí)行增量同步就可以驱还。backlog設(shè)置的越大,slave可以失連的時(shí)間就越長(zhǎng)凸克,受參數(shù)maxmemory限制议蟆,正常不要設(shè)置太大。
AOF 緩沖
當(dāng)我們開(kāi)啟了 AOF 的時(shí)候萎战,先將客戶端傳來(lái)的命令存放在AOF緩沖區(qū)咐容,再去根據(jù)具體的策略(always、everysec撞鹉、no)去寫入磁盤中的 AOF 文件中疟丙,同時(shí)記錄刷盤時(shí)間。
AOF 緩沖沒(méi)法限制鸟雏,也不需要限制享郊,因?yàn)橹骶€程每次進(jìn)行 AOF會(huì)對(duì)比上次刷盤成功的時(shí)間;如果超過(guò)2s孝鹊,則主線程阻塞直到fsync同步完成炊琉,主線程被阻塞的時(shí)候,aof\_delayed\_fsync狀態(tài)變量記錄會(huì)增加。因此 AOF 緩存只會(huì)存幾秒時(shí)間的數(shù)據(jù)苔咪,消耗內(nèi)存比較小
2.3 內(nèi)存碎片
程序出現(xiàn)內(nèi)存碎片是個(gè)很常見(jiàn)的問(wèn)題锰悼,Redis的默認(rèn)分配器是jemalloc ,它的策略是按照一系列固定的大小劃分內(nèi)存空間团赏,例如 8 字節(jié)箕般、16 字節(jié)、32 字節(jié)舔清、…, 4KB丝里、8KB 等。當(dāng)程序申請(qǐng)的內(nèi)存最接近某個(gè)固定值時(shí)体谒,jemalloc 會(huì)給它分配比它大一點(diǎn)的固定大小的空間杯聚,所以會(huì)產(chǎn)生一些碎片,另外在刪除數(shù)據(jù)的時(shí)候抒痒,釋放的內(nèi)存不會(huì)立刻返回給操作系統(tǒng)幌绍,但redis自己又無(wú)法有效利用,就形成碎片故响。
內(nèi)存碎片不會(huì)被統(tǒng)計(jì)在used\_memory中傀广,內(nèi)存碎片比率在redis info里面記錄了一個(gè)動(dòng)態(tài)值mem\_fragmentation\_ratio,該值是used\_memory\_rss / used\_memory的比值被去,mem\_fragmentation\_ratio越接近1主儡,碎片率越低,正常值在1~1.5內(nèi)惨缆,超過(guò)了說(shuō)明碎片很多糜值。
備注:JeMalloc 是一款內(nèi)存分配器,與其它內(nèi)存分配器相比坯墨,它最大的優(yōu)勢(shì)在于多線程情況下的高性能以及內(nèi)存碎片的減少寂汇。
2.4子進(jìn)程內(nèi)存
前面提到子進(jìn)程主要是為了生成 RDB 和 AOF rewrite產(chǎn)生的子進(jìn)程,也會(huì)占用一定的內(nèi)存捣染,但是在這個(gè)過(guò)程中寫操作不頻繁的情況下內(nèi)存占用較少骄瓣,寫操作很頻繁會(huì)導(dǎo)致占用內(nèi)存較多。
3耍攘,redis優(yōu)化
內(nèi)存優(yōu)化的對(duì)象主要是對(duì)象內(nèi)存榕栏、客戶端緩沖、內(nèi)存碎片蕾各、子進(jìn)程內(nèi)存等幾個(gè)方面扒磁,因?yàn)檫@幾個(gè)內(nèi)存消耗比較大或者有的時(shí)候不穩(wěn)定,我們優(yōu)化內(nèi)存的方向分為如:
(1)減少內(nèi)存使用
(2)提高性能
(3)減少內(nèi)存異常發(fā)生式曲。
3.1對(duì)象內(nèi)存優(yōu)化
對(duì)象內(nèi)存優(yōu)化可以降低內(nèi)存使用率妨托,提高性能缸榛,優(yōu)化點(diǎn)主要針對(duì)不同對(duì)象不同編碼的選擇上做優(yōu)化
(1)首先是字符串類型的3種編碼,int編碼除了自身object無(wú)需分配內(nèi)存兰伤,object 的指針不需要指向其他內(nèi)存空間内颗,無(wú)論是從性能還是內(nèi)存使用都是最優(yōu)的,embstr是會(huì)分配一塊連續(xù)的內(nèi)存空間敦腔,但是假設(shè)這個(gè)value有任何變化均澳,那么value對(duì)象會(huì)變成raw編碼,而且是不可逆的符衔。
(2)ziplist 存儲(chǔ) list 時(shí)每個(gè)元素會(huì)作為一個(gè) entry; 存儲(chǔ) hash 時(shí) key 和 value 會(huì)作為相鄰的兩個(gè) entry; 存儲(chǔ) zset 時(shí) member 和 score 會(huì)作為相鄰的兩個(gè)entry负懦,當(dāng)不滿足上述條件時(shí),ziplist 會(huì)升級(jí)為 linkedlist, hashtable 或 skiplist 編碼柏腻。
(3)在任何情況下大內(nèi)存的編碼都不會(huì)降級(jí)為 ziplist。
(4)linkedlist 系吭、hashtable 便于進(jìn)行增刪改操作但是內(nèi)存占用較大五嫂。
(5)ziplist 內(nèi)存占用較少,但是因?yàn)槊看涡薷亩伎赡苡|發(fā) realloc 和 memcopy, 可能導(dǎo)致連鎖更新(數(shù)據(jù)可能需要挪動(dòng))肯尺。因此修改操作的效率較低沃缘,在 ziplist 的條目很多時(shí)這個(gè)問(wèn)題更加突出。
(6)由于目前大部分redis運(yùn)行的版本都是在3.2以上则吟,所以 List 類型的編碼都是quicklist,它是 ziplist 組成的雙向鏈表linkedlist 槐臀,它的每個(gè)節(jié)點(diǎn)都是一個(gè)ziplist,考慮了綜合平衡空間碎片和讀寫性能兩個(gè)維度所以使用了個(gè)新編碼quicklist氓仲,quicklist有個(gè)比較重要的參數(shù)list-max-ziplist-size水慨,當(dāng)它取正數(shù)的時(shí)候,正數(shù)表示限制每個(gè)節(jié)點(diǎn)ziplist中的entry數(shù)量敬扛,如果是負(fù)數(shù)則只能為-1~-5晰洒,限制ziplist大小,從-1~-5的限制分別為4kb啥箭、8kb谍珊、16kb、32kb急侥、64kb砌滞,默認(rèn)是-2,也就是限制不超過(guò)8kb坏怪。
(7)【rehash】: redis存儲(chǔ)底層很多是hashtable贝润,客戶端可以根據(jù)key計(jì)算的hash值找到對(duì)應(yīng)的對(duì)象,但是當(dāng)數(shù)據(jù)量越來(lái)越大的時(shí)候陕悬,可能就會(huì)存在多個(gè)key計(jì)算的hash值相同题暖,這個(gè)時(shí)候這些相同的hash值就會(huì)以鏈表的形式存放,如果這個(gè)鏈表過(guò)大,那么遍歷的時(shí)候性能就會(huì)下降胧卤,所以Redis定義了一個(gè)閾值(負(fù)載因子 loader_factor = 哈希表中鍵值對(duì)數(shù)量 / 哈希表長(zhǎng)度)唯绍,會(huì)觸發(fā)漸進(jìn)式的rehash,過(guò)程是新建一個(gè)更大的新hashtable枝誊,然后把數(shù)據(jù)逐步移動(dòng)到新hashtable中况芒。
(8)【bigkey】:bigkey一般指的是value的值占用內(nèi)存空間很大,但是這個(gè)大小其實(shí)沒(méi)有一個(gè)固定的標(biāo)準(zhǔn)叶撒,我們自己定義超過(guò)10M就可以稱之為bigkey绝骚。
優(yōu)化建議:
key盡量控制在44個(gè)字節(jié)數(shù)內(nèi),走embstr編碼祠够,embstr比raw編碼減少一次內(nèi)存分配压汪,同時(shí)因?yàn)槭沁B續(xù)內(nèi)存存儲(chǔ),性能會(huì)更好古瓤。
多個(gè)string類型可以合并成小段hash類型去維護(hù)止剖,小的hash類型走ziplist是有很好的壓縮效果,節(jié)約內(nèi)存落君。
非string的類型的value對(duì)象的元素個(gè)數(shù)盡量不要太多穿香,避免產(chǎn)生大key。
在value的元素較多且頻繁變動(dòng)绎速,不要使用ziplist編碼皮获,因?yàn)閦iplist是連續(xù)的內(nèi)存分配,對(duì)頻繁更新的對(duì)象并不友好纹冤,性能損耗反而大洒宝。
hash類型對(duì)象包含的元素不要太多,避免在rehash的時(shí)候消耗過(guò)多內(nèi)存赵哲。
盡量不要修改ziplist限制的參數(shù)值待德,因?yàn)閦iplist編碼雖然可以對(duì)內(nèi)存有很好的壓縮,但是如果元素太多使用ziplist的話枫夺,性能可能會(huì)有所下降将宪。
3.2? 客戶端緩沖優(yōu)化
客戶端緩存是很多內(nèi)存異常增長(zhǎng)的罪魁禍?zhǔn)祝蟛糠侄际瞧胀蛻舳溯敵鼍彌_區(qū)異常增長(zhǎng)導(dǎo)致橡庞,我們先了解下執(zhí)行命令的過(guò)程较坛,客戶端發(fā)送一個(gè)或者通過(guò)piplie發(fā)送一組請(qǐng)求命令給服務(wù)端,然后等待服務(wù)端的響應(yīng)扒最,一般客戶端使用阻塞模式來(lái)等待服務(wù)端響應(yīng)丑勤,數(shù)據(jù)在被客戶端讀取前,數(shù)據(jù)是存放在客戶端緩存區(qū)吧趣,命令執(zhí)行的簡(jiǎn)易流程圖如下:
異常增長(zhǎng)原因可能如下幾種:
客戶端訪問(wèn)大key 導(dǎo)致客戶端輸出緩存異常增長(zhǎng)法竞。
客戶端使用monitor命令訪問(wèn)Redis耙厚,monitor命令會(huì)把所有訪問(wèn)redis的命令持續(xù)存放到輸出緩沖區(qū),導(dǎo)致輸出緩沖區(qū)異常增長(zhǎng)岔霸。
客戶端為了加快訪問(wèn)效率薛躬,使用pipline封裝了大量命令,導(dǎo)致返回的結(jié)果集異常大(pipline的特性是等所有命令全部執(zhí)行完才返回呆细,返回前都是暫存在輸出緩存區(qū))型宝。
從節(jié)點(diǎn)應(yīng)用數(shù)據(jù)較慢,導(dǎo)致輸出主從復(fù)制輸出緩存有很多數(shù)據(jù)積壓絮爷,最后導(dǎo)致緩沖區(qū)異常增長(zhǎng)趴酣。
異常表現(xiàn):
在Redis的info命令返回的結(jié)果里面,client部分client\_recent\_max\_output\_buffer的值很大坑夯。
在執(zhí)行client list命令返回的結(jié)果集里面岖寞,omem不為0且很大,omem代表該客戶端的輸出代表緩存使用的字節(jié)數(shù)柜蜈。
在集群中慎璧,可能少部分used_memory在監(jiān)控顯示存在異常增長(zhǎng),因?yàn)椴还苁莔onitor或者pipeline都是針對(duì)單個(gè)實(shí)例的下發(fā)的命令跨释。
優(yōu)化建議:
應(yīng)用不要設(shè)計(jì)大key,大key盡量拆分。
服務(wù)端的普通客戶端輸出緩存區(qū)通過(guò)參數(shù)設(shè)置厌处,因?yàn)閮?nèi)存告警的閾值大部分是使用率80%開(kāi)始鳖谈,實(shí)際建議參數(shù)可以設(shè)置為實(shí)例內(nèi)存的5%~15%左右,最好不要超過(guò)20%阔涉,避免OOM缆娃。
非特殊情況下避免使用monitor命令或者rename該命令。
在使用pipline的時(shí)候瑰排,pipeline不能封裝過(guò)多的命令贯要,特別是一些返回結(jié)果集較多的命令更應(yīng)該少封裝。
主從復(fù)制輸出緩沖區(qū)大小設(shè)置參考:?緩沖區(qū)大小=(主庫(kù)寫入命令速度 * 操作大小 - 主從庫(kù)間網(wǎng)絡(luò)傳輸命令速度 * 操作大型肿 )* 2崇渗。
注:Redis MONITOR 是一個(gè)調(diào)試命令,返回服務(wù)器處理的每一個(gè)命令京郑,它能幫助我們了解在數(shù)據(jù)庫(kù)上發(fā)生了什么操作宅广,可以通過(guò) redis-cli 和 telnet 命令使用。 當(dāng)使用 Redis 作為數(shù)據(jù)庫(kù)和分布式緩存系統(tǒng)時(shí)些举,能夠查看服務(wù)器處理的所有請(qǐng)求對(duì)于發(fā)現(xiàn)應(yīng)用程序中的錯(cuò)誤非常有用跟狱。
3.3??碎片優(yōu)化
碎片優(yōu)化可以降低內(nèi)存使用率,提高訪問(wèn)效率户魏,在4.0以下版本驶臊,我們只能使用重啟恢復(fù)挪挤,重啟加載rdb或者重啟通過(guò)高可用主從切換實(shí)現(xiàn)數(shù)據(jù)的重新加載可以減少碎片,在4.0以上版本关翎,Redis提供了自動(dòng)和手動(dòng)的碎片整理功能扛门,原理大致是把數(shù)據(jù)拷貝到新的內(nèi)存空間,然后把老的空間釋放掉笤休,這個(gè)是有一定的性能損耗的尖飞。
【a. redis手動(dòng)整理碎片】:執(zhí)行memory purge命令即可。
【b.redis自動(dòng)整理碎片】:通過(guò)如下幾個(gè)參數(shù)控制
【activedefrag yes 】:?jiǎn)⒂米詣?dòng)碎片清理開(kāi)關(guān)
【active-defrag-ignore-bytes 100mb】:內(nèi)存碎片空間達(dá)到多少才開(kāi)啟碎片整理
【active-defrag-threshold-lower 10】:碎片率達(dá)到百分之多少才開(kāi)啟碎片整理
【active-defrag-threshold-upper 100?】:內(nèi)存碎片率超過(guò)多少店雅,則盡最大努力整理(占用最大資源去做碎片整理)
【active-defrag-cycle-min 25 】:內(nèi)存自動(dòng)整理占用資源最小百分比
【active-defrag-cycle-max 75】:內(nèi)存自動(dòng)整理占用資源最大百分比
3.4子進(jìn)程內(nèi)存優(yōu)化
前面談到 AOF rewrite和 RDB 生成動(dòng)作會(huì)產(chǎn)生子進(jìn)程政基,正常在兩個(gè)動(dòng)作執(zhí)行的過(guò)程中,Redis 寫操作沒(méi)有那么頻繁的情況下fork出來(lái)的子進(jìn)程是不會(huì)消耗很多內(nèi)存的闹啦,這個(gè)主要是因?yàn)?Redis 子進(jìn)程使用了 Linux 的 copy on write 機(jī)制沮明,簡(jiǎn)稱COW。
COW的核心是在fork出子進(jìn)程后窍奋,與父進(jìn)程共享內(nèi)存空間荐健,只有在父進(jìn)程發(fā)生寫操作修改內(nèi)存數(shù)據(jù)時(shí),才會(huì)真正去分配內(nèi)存空間琳袄,并復(fù)制內(nèi)存數(shù)據(jù)江场。
但是有一點(diǎn)需要注意,不要開(kāi)啟操作系統(tǒng)的大頁(yè)THP(Transparent Huge Pages)窖逗,開(kāi)啟 THP 機(jī)制后址否,本來(lái)頁(yè)的大小由4KB變?yōu)?2MB了。它雖然可以加快 fork 完成的速度( 因?yàn)橐截惖捻?yè)的數(shù)量減少 )碎紊,但是會(huì)導(dǎo)致 copy-on-write 復(fù)制內(nèi)存頁(yè)的單位從 4KB 增大為 2MB佑附,如果父進(jìn)程有大量寫命令,會(huì)加重內(nèi)存拷貝量仗考,從而造成過(guò)度內(nèi)存消耗音同。
4,內(nèi)存優(yōu)化案例
線上業(yè)務(wù) Redis 集群出現(xiàn)內(nèi)存告警秃嗜,內(nèi)存使用率增長(zhǎng)很快達(dá)到100%权均,值班人員先進(jìn)行了緊急擴(kuò)容,同時(shí)反饋至業(yè)務(wù)群是否有大量新數(shù)據(jù)寫入锅锨,業(yè)務(wù)反饋并無(wú)大量新數(shù)據(jù)寫入螺句,且同時(shí)擴(kuò)容后的內(nèi)存還在漲,很快又要觸發(fā)告警了橡类,業(yè)務(wù) DBA 去查監(jiān)控看看具體原因蛇尚。
首先我們看used_memory增長(zhǎng)只是集群的少數(shù)幾個(gè)實(shí)例,同時(shí)內(nèi)存異常的實(shí)例的key的數(shù)量并沒(méi)有異常增長(zhǎng)顾画,說(shuō)明沒(méi)有寫入大批量數(shù)據(jù)導(dǎo)致取劫。
我們?cè)偻路治龃殷裕赡苁强蛻舳说膬?nèi)存占用異常比較大,查看實(shí)例 info 里面的客戶端相關(guān)指標(biāo)谱邪,觀察發(fā)現(xiàn)output\_list的增長(zhǎng)曲線和used\_memory一致炮捧,可以判定是客戶端的輸出緩沖異常導(dǎo)致。
接下來(lái)我們?cè)偃ネㄟ^(guò)client list查看是什么客戶端導(dǎo)致output增長(zhǎng)惦银,客戶端在執(zhí)行什么命令咆课,同時(shí)去分析是否訪問(wèn)大key。
執(zhí)行 client list |grep -i ?omem=0? 發(fā)現(xiàn)如下:
id=12593807 addr=192.168.101.1:52086 fd=10767 name=? age=15301 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0? qbuf-free=32768? obl=16173? oll=341101? omem=5259227504? events=rw? cmd=get
說(shuō)明下相關(guān)的幾個(gè)重點(diǎn)的字段的含義:
【id】:就是客戶端的唯一標(biāo)識(shí)扯俱,經(jīng)常用于我們kill客戶端用到id书蚪;
【addr】:客戶端信息;
【obl】:固定緩沖區(qū)大小(字節(jié))迅栅,默認(rèn)是16K殊校;
【oll】:動(dòng)態(tài)緩沖區(qū)大小(對(duì)象個(gè)數(shù))读存,客戶端如果每條命令的響應(yīng)結(jié)果超過(guò)16k或者固定緩沖區(qū)寫滿了會(huì)寫動(dòng)態(tài)緩沖區(qū)为流;
【omem】:?指緩沖區(qū)的總字節(jié)數(shù);
【cmd】: 最近一次的操作命令让簿。
可以看到緩沖區(qū)內(nèi)存占用很大敬察,最近的操作命令也是get,所以我們先看看是否大key導(dǎo)致(我們是直接分析RDB發(fā)現(xiàn)并沒(méi)有大key)尔当,但是發(fā)現(xiàn)并沒(méi)有大key静汤,而且get對(duì)應(yīng)的肯定是string類型,string類型的value最大是512M居凶,所以單個(gè)key也不太可能產(chǎn)生這么大的緩存,所以斷定是客戶端緩存了多個(gè)key藤抡。
這個(gè)時(shí)候?yàn)榱吮M快恢復(fù)侠碧,和業(yè)務(wù)溝通臨時(shí)kill該連接,內(nèi)存釋放缠黍,然后為了避免防止后面還產(chǎn)生異常弄兜,和業(yè)務(wù)方溝通設(shè)置普通客戶端緩存限制,因?yàn)樽畲髢?nèi)存是25G瓷式,我們把緩存設(shè)置了2G-4G替饿, 動(dòng)態(tài)設(shè)置參數(shù)如下:
config set client-output-buffer-limit normal?4096mb 2048mb 120
因?yàn)閰?shù)限制也只是針對(duì)單個(gè)client的輸出緩沖這么大,所以還需要檢查客戶端使用使用 pipline 這種管道命令或者類似實(shí)現(xiàn)了封裝大批量命令導(dǎo)致結(jié)果統(tǒng)一返回之前被阻塞贸典,后面確定確實(shí)會(huì)有這個(gè)操作视卢,業(yè)務(wù)層就需要去逐步優(yōu)化,不然我們限制了輸出緩沖廊驼,達(dá)到了上限据过,會(huì)話會(huì)被kill, 所以業(yè)務(wù)不改的話還是會(huì)有拋錯(cuò)惋砂。
業(yè)務(wù)方反饋用的是 C++ 語(yǔ)言 brpc 自帶的 Redis客戶端,第一次直接搜索沒(méi)有pipline的關(guān)鍵字绳锅,但是現(xiàn)象又指向使用的管道西饵,所以繼續(xù)仔細(xì)看了下代碼,發(fā)現(xiàn)其內(nèi)部是實(shí)現(xiàn)了pipline類似的功能鳞芙,也是會(huì)對(duì)多個(gè)命令進(jìn)行封裝去請(qǐng)求redis眷柔,然后統(tǒng)一返回結(jié)果;
總結(jié):pipline 在 Redis 客戶端中使用的挺多的原朝,因?yàn)榇_實(shí)可以提供訪問(wèn)效率驯嘱,但是使用不當(dāng)反而會(huì)影響訪問(wèn),應(yīng)該控制好訪問(wèn)竿拆,生產(chǎn)環(huán)境也盡量加這些內(nèi)存限制宙拉,避免部分客戶端的異常訪問(wèn)影響全局使用。
4.2從節(jié)點(diǎn)內(nèi)存異常增長(zhǎng)案例
線上 Redis 集群出現(xiàn)內(nèi)存使用率超過(guò) 95% 的災(zāi)難告警丙笋,但是該集群是有190個(gè)節(jié)點(diǎn)的集群觸發(fā)異常內(nèi)存告警的只有3個(gè)節(jié)點(diǎn)谢澈。所以查看集群對(duì)應(yīng)信息以及監(jiān)控指標(biāo)發(fā)現(xiàn)如下有用信息:
1,3個(gè)從節(jié)點(diǎn)對(duì)應(yīng)的主節(jié)點(diǎn)內(nèi)存沒(méi)有變化御板,從節(jié)點(diǎn)的內(nèi)存是逐步增長(zhǎng)的锥忿。
2,發(fā)現(xiàn)集群整體ops比較低怠肋,說(shuō)明業(yè)務(wù)變化并不大敬鬓,沒(méi)有發(fā)現(xiàn)有效命令突增。
3笙各,主從節(jié)點(diǎn)的最大內(nèi)存不一致钉答,主節(jié)點(diǎn)是6G,從節(jié)點(diǎn)是5G,這個(gè)是導(dǎo)致災(zāi)難告警的重要原因杈抢。
4数尿,在出問(wèn)題前,主節(jié)點(diǎn)比從節(jié)點(diǎn)的內(nèi)存大概多出1.3G惶楼,后面從節(jié)點(diǎn)used_memory逐步增長(zhǎng)到超過(guò)主節(jié)點(diǎn)內(nèi)存,但是rss內(nèi)存是最后保持了一樣右蹦。
5,主從復(fù)制出現(xiàn)延遲也內(nèi)存增長(zhǎng)的那個(gè)時(shí)間段歼捐。
處理過(guò)程:
首先想到的應(yīng)該是保持主從節(jié)點(diǎn)最大內(nèi)存一致何陆,但是因?yàn)橹鳈C(jī)內(nèi)存使用率比較高暫時(shí)沒(méi)法擴(kuò)容,因?yàn)橄氲降氖菑墓?jié)點(diǎn)可能什么原因阻塞豹储,所以和業(yè)務(wù)方溝通是重啟下2從節(jié)點(diǎn)緩解下贷盲,重啟后從節(jié)點(diǎn)內(nèi)存釋放,降到發(fā)生問(wèn)題前的水平剥扣,如上圖晃洒,后面主機(jī)空出了內(nèi)存資源慨灭,所以優(yōu)先把內(nèi)存調(diào)整一致。
內(nèi)存調(diào)整好了一周后球及,這3個(gè)從節(jié)點(diǎn)內(nèi)存又告警了氧骤,因?yàn)楝F(xiàn)在主從內(nèi)存是一致的,所以觸發(fā)的是嚴(yán)重告警(>85%)吃引,查看監(jiān)控發(fā)現(xiàn)情況是和之前一樣筹陵,猜測(cè)這個(gè)是某些操作觸發(fā)的,所以還是決定問(wèn)問(wèn)業(yè)務(wù)方這 兩個(gè)時(shí)間段都有哪些操作镊尺,業(yè)務(wù)反饋這段時(shí)間就是在寫業(yè)務(wù)朦佩,那2個(gè)時(shí)間段都是在寫入,也看了寫redis的那段代碼庐氮,用了一個(gè)比較少見(jiàn)的命令append语稠,append是對(duì)string類型的value進(jìn)行追加。
這里就得提下string類型在 Redis 里面是怎么分配內(nèi)存的:string類型都是都是sds存儲(chǔ)弄砍,當(dāng)前分配的sds內(nèi)存空間不足存儲(chǔ)且小于1M時(shí)候仙畦,Redis會(huì)重新分配一個(gè)2倍之前內(nèi)存大小的內(nèi)存空間。
根據(jù)上面到知識(shí)點(diǎn)音婶,所以可以大致可以解析上述一系列的問(wèn)題慨畸,大概是當(dāng)時(shí)做 append 操作,從節(jié)點(diǎn)需要分配空間從而發(fā)生內(nèi)存膨脹衣式,而主節(jié)點(diǎn)不需要分配空間寸士,因?yàn)閮?nèi)存重新分配設(shè)計(jì)malloc和free操作,所以當(dāng)時(shí)有l(wèi)ag也是正常的碴卧。
Redis的主從本身是一個(gè)邏輯復(fù)制弱卡,加載 RDB 的過(guò)程其實(shí)也是拿到kv不斷的寫入到從節(jié)點(diǎn),所以主從到內(nèi)存大小也經(jīng)常存在不相同的情況住册,特別是這種values大小經(jīng)常改變的場(chǎng)景婶博,主從存儲(chǔ)的kv所用的空間很多可能是不一樣的。
為了證明這一猜測(cè)界弧,我們可以通過(guò)獲取一個(gè)key(value大小要比較大)在主從節(jié)點(diǎn)占用空間的大小,因?yàn)槭?.0以上版本搭综,所以我們可以使用memory USAGE 去獲取大小,看看差異有多少垢箕,我們隨機(jī)找了幾個(gè)稍微大點(diǎn)的key去查看,發(fā)現(xiàn)在有些key從庫(kù)占用空間是主庫(kù)的近2倍兑巾,有的差不多条获,有的也是1倍多,rdb解析出來(lái)的這個(gè)key空間更小蒋歌,說(shuō)明從節(jié)點(diǎn)重啟后加載rdb進(jìn)行存放是最小的帅掘,然后因?yàn)槟扯螘r(shí)間大批量key操作委煤,導(dǎo)致從節(jié)點(diǎn)的大批量的key分配的空間不足,需要擴(kuò)容1倍空間修档,導(dǎo)致內(nèi)存出現(xiàn)增長(zhǎng)碧绞。
到這就分析的其實(shí)差不多了,因?yàn)閍ppend的特性吱窝,為了避免內(nèi)存再次出現(xiàn)內(nèi)存告警讥邻,決定把該集群的內(nèi)存進(jìn)行擴(kuò)容,控制內(nèi)存使用率在70%以下(避免可能發(fā)生的大量key使用內(nèi)存翻倍的情況)院峡。
最后還有1個(gè)問(wèn)題:上面的used\_memory為什么會(huì)比memory\_rss的值還大呢兴使?(swap是關(guān)閉的)。
這是因?yàn)閖emalloc內(nèi)存分配一開(kāi)始其實(shí)分配的是虛擬內(nèi)存照激,只有往分配的page頁(yè)里面寫數(shù)據(jù)的時(shí)候才會(huì)真正分配內(nèi)存发魄,memory\_rss是實(shí)際內(nèi)存占用,used\_memory其實(shí)是一個(gè)計(jì)數(shù)器俩垃,在 Redis做內(nèi)存的malloc/free的時(shí)候励幼,對(duì)這個(gè)used_memory做加減法。
總結(jié):
在知曉 Redis內(nèi)存分配原理的情況下吆寨,數(shù)據(jù)庫(kù)的內(nèi)存異常問(wèn)題進(jìn)行分析會(huì)比較快速定位赏淌,另外可能某個(gè)問(wèn)題看起來(lái)和業(yè)務(wù)沒(méi)什么關(guān)聯(lián),但是我們還是應(yīng)該多和業(yè)務(wù)方溝通獲取一些線索排查問(wèn)題啄清,最后主從內(nèi)存一定按照規(guī)范保持一致六水。
5,總結(jié)
Redis在數(shù)據(jù)存儲(chǔ)辣卒、緩存都是做了很巧妙的設(shè)計(jì)和優(yōu)化掷贾,我們?cè)诹私饬怂膬?nèi)部結(jié)構(gòu)、存儲(chǔ)方式之后荣茫,我們可以提前在key的設(shè)計(jì)上做優(yōu)化想帅。我們?cè)谟龅絻?nèi)存異常或者性能優(yōu)化的時(shí)候啡莉,可以不再局限于表面的一些分析如:資源消耗港准、命令的復(fù)雜度、key的大小咧欣,還可以結(jié)合根據(jù)Redis的一些內(nèi)部運(yùn)行機(jī)制和內(nèi)存管理方式去深入發(fā)現(xiàn)是否還有可能哪些方面導(dǎo)致異城掣祝或者性能下降。