redis內(nèi)存空間效率問(wèn)題的深入探究
前言
在使用redis時(shí),我們會(huì)遇到一個(gè)問(wèn)題,數(shù)據(jù)刪除后,數(shù)據(jù)量已經(jīng)不大了,但是使用top命令查看,還會(huì)發(fā)現(xiàn)redis占用了很對(duì)內(nèi)存。實(shí)際上,因?yàn)閿?shù)據(jù)刪除后,redis釋放內(nèi)存由內(nèi)存分配器管理,不會(huì)立刻返回給操作系統(tǒng)。所以,操作系統(tǒng)仍然記錄著給redis分配了大量的內(nèi)存
這往往會(huì)伴隨一個(gè)潛在的風(fēng)險(xiǎn)點(diǎn):Redis 釋放的內(nèi)存空間可能并不是連續(xù)的,那么,這些不連續(xù)的內(nèi)存空間很有可能處于一種閑置的狀態(tài)。這就會(huì)導(dǎo)致一個(gè)問(wèn)題:雖然有空閑空間,Redis 卻無(wú)法用來(lái)保存數(shù)據(jù),不僅會(huì)減少 Redis 能夠?qū)嶋H保存的數(shù)據(jù)量,還會(huì)降低 Redis 運(yùn)行機(jī)器的成本回報(bào)率。
什么是內(nèi)存碎片
通常情況下,內(nèi)存空間利用率低,往往是因?yàn)椴僮飨到y(tǒng)發(fā)生了比較嚴(yán)重的內(nèi)存碎片,那么什么是內(nèi)存碎片呢?可以將內(nèi)存看成是高鐵上的作為,連續(xù)的空間相當(dāng)于連座,內(nèi)存碎片可以看成一個(gè)個(gè)零散的作為,如果你是3個(gè)人出行,火車(chē)上沒(méi)有三個(gè)座位連著的,那么你就沒(méi)法買(mǎi)到合適的作為,可能需要換一輛車(chē)
內(nèi)存類(lèi)似,如果需要申請(qǐng)一個(gè)N字節(jié)的連續(xù)空間,但是沒(méi)有這么大的連續(xù)空間,那么,這些剩余空間就是內(nèi)存碎片,redis內(nèi)存碎片是什么原因?qū)е碌哪?,了解了原因才有可能比較好的解決
內(nèi)存碎片形成的原因
一般來(lái)說(shuō)內(nèi)存碎片形成的原因有兩個(gè),內(nèi)因是操作系統(tǒng)的內(nèi)存分配機(jī)制,外因是redis的負(fù)載特征
內(nèi)因:內(nèi)存分配器策略
內(nèi)存分配器的分配策略就決定了操作系統(tǒng)無(wú)法做到“按需分配”。這是因?yàn)?,?nèi)存分配器一般是按固定大小來(lái)分配內(nèi)存,而不是完全按照應(yīng)用程序申請(qǐng)的內(nèi)存空間大小給程序分配。
Redis 可以使用 libc、jemalloc、tcmalloc 多種內(nèi)存分配器來(lái)分配內(nèi)存,默認(rèn)使用 jemalloc。接下來(lái),我就以 jemalloc 為例,來(lái)具體解釋一下。其他分配器也存在類(lèi)似的問(wèn)題。
jemalloc 的分配策略之一,是按照一系列固定的大小劃分內(nèi)存空間,例如 8 字節(jié)、16 字節(jié)、32 字節(jié)、48 字節(jié),…, 2KB、4KB、8KB 等。當(dāng)程序申請(qǐng)的內(nèi)存最接近某個(gè)固定值時(shí),jemalloc 會(huì)給它分配相應(yīng)大小的空間。
外因:鍵值對(duì)大小不一樣和刪改操作
redis通常作為公共緩存和鍵值數(shù)據(jù)庫(kù)對(duì)外提供服務(wù),所以對(duì)于不同大小的數(shù)據(jù),redis申請(qǐng)內(nèi)存空間大小不一,這是一個(gè)外因。
因?yàn)閮?nèi)存分配是按照固定大小分配,所以內(nèi)存空間一般都會(huì)比申請(qǐng)的空間大一些,所以本身就會(huì)有一些內(nèi)存碎片,降低內(nèi)存空間存儲(chǔ)效率。
第二個(gè)外因是,這些數(shù)據(jù)會(huì)被刪除和修改,會(huì)導(dǎo)致空間空間擴(kuò)充和釋放,具體來(lái)說(shuō),一方面,如果修改后的鍵值對(duì)變大或變小了,就需要占用額外的空間或者釋放不用的空間。另一方面,刪除的鍵值對(duì)就不再需要內(nèi)存空間了,此時(shí),就會(huì)把空間釋放出來(lái),形成空閑空間
一開(kāi)始,應(yīng)用 A、B、C、D 分別保存了 3、1、2、4 字節(jié)的數(shù)據(jù),并占據(jù)了相應(yīng)的內(nèi)存空間。然后,應(yīng)用 D 刪除了 1 個(gè)字節(jié),這個(gè) 1 字節(jié)的內(nèi)存空間就空出來(lái)了。緊接著,應(yīng)用 A 修改了數(shù)據(jù),從 3 字節(jié)變成了 4 字節(jié)。為了保持 A 數(shù)據(jù)的空間連續(xù)性,操作系統(tǒng)就需要把 B 的數(shù)據(jù)拷貝到別的空間,比如拷貝到 D 剛剛釋放的空間中。此時(shí),應(yīng)用 C 和 D 也分別刪除了 2 字節(jié)和 1 字節(jié)的數(shù)據(jù),整個(gè)內(nèi)存空間上就分別出現(xiàn)了 2 字節(jié)和 1 字節(jié)的空閑碎片。如果應(yīng)用 E 想要一個(gè) 3 字節(jié)的連續(xù)空間,顯然是不能得到滿足的。因?yàn)?,雖然空間總量夠,但卻是碎片空間,并不是連續(xù)的。
好了,到這里,我們就知道了造成內(nèi)存碎片的內(nèi)外因素,其中,內(nèi)存分配器策略是內(nèi)因,而 Redis 的負(fù)載屬于外因,包括了大小不一的鍵值對(duì)和鍵值對(duì)修改刪除帶來(lái)的內(nèi)存空間變化。
如何判斷是否有內(nèi)存碎片
Redis 是內(nèi)存數(shù)據(jù)庫(kù),內(nèi)存利用率的高低直接關(guān)系到 Redis 運(yùn)行效率的高低。為了讓用戶能監(jiān)控到實(shí)時(shí)的內(nèi)存使用情況,Redis 自身提供了 INFO 命令,可以用來(lái)查詢內(nèi)存使用的詳細(xì)信息,命令如下:
INFO memory # Memory used_memory:1073741736 used_memory_human:1024.00M used_memory_rss:1997159792 used_memory_rss_human:1.86G … mem_fragmentation_ratio:1.86O memory INFO memory # Memory used_memory:1073741736 used_memory_human:1024.00M used_memory_rss:1997159792 used_memory_rss_human:1.86G … mem_fragmentation_ratio:1.86
這里有一個(gè) mem_fragmentation_ratio 的指標(biāo),它表示的就是 Redis 當(dāng)前的內(nèi)存碎片率。那么,這個(gè)碎片率是怎么計(jì)算的呢?其實(shí),就是上面的命令中的兩個(gè)指標(biāo) used_memory_rss 和 used_memory 相除的結(jié)果。
mem_fragmentation_ratio = used_memory_rss/ used_memory
used_memory_rss 是操作系統(tǒng)實(shí)際分配給 Redis 的物理內(nèi)存空間,里面就包含了碎片;而 used_memory 是 Redis 為了保存數(shù)據(jù)實(shí)際申請(qǐng)使用的空間。
我簡(jiǎn)單舉個(gè)例子。例如,Redis 申請(qǐng)使用了 100 字節(jié)(used_memory),操作系統(tǒng)實(shí)際分配了 128 字節(jié)(used_memory_rss),此時(shí),mem_fragmentation_ratio 就是 1.28。
那么,知道了這個(gè)指標(biāo),我們?cè)撊绾问褂媚??在這兒,我提供一些經(jīng)驗(yàn)閾值:
- mem_fragmentation_ratio大于1小于1.5。這種情況是合理的。這是因?yàn)?,剛才我介紹的那些因素是難以避免的。畢竟,內(nèi)因的內(nèi)存分配器是一定要使用的,分配策略都是通用的,不會(huì)輕易修改;而外因由 Redis 負(fù)載決定,也無(wú)法限制。所以,存在內(nèi)存碎片也是正常的。
- mem_fragmentation_ratio大于1.5。 這表明內(nèi)存碎片率已經(jīng)超過(guò)了 50%。一般情況下,這個(gè)時(shí)候,我們就需要采取一些措施來(lái)降低內(nèi)存碎片率了。
如何清理內(nèi)存碎片
當(dāng) Redis 發(fā)生內(nèi)存碎片后,一個(gè)“簡(jiǎn)單粗暴”的方法就是重啟redis實(shí)例,當(dāng)然這并不是一個(gè)優(yōu)雅的方法,重啟會(huì)帶來(lái)一些問(wèn)題
- 如果數(shù)據(jù)沒(méi)有持久化,那么數(shù)據(jù)會(huì)丟失
- 如果數(shù)據(jù)持久化了,我們需要通過(guò)AOF或RDB進(jìn)行恢復(fù),恢復(fù)時(shí)長(zhǎng)取決于AOF或RDB的大小,如果只有一個(gè)實(shí)例,在恢復(fù)階段無(wú)法提供服務(wù)。
幸運(yùn)的是,從 4.0-RC3 版本以后,Redis 自身提供了一種內(nèi)存碎片自動(dòng)清理的方法,我們先來(lái)看這個(gè)方法的基本機(jī)制。還是通過(guò)一張圖來(lái)看下
在進(jìn)行碎片清理前,這段 10 字節(jié)的空間中分別有 1 個(gè) 2 字節(jié)和 1 個(gè) 1 字節(jié)的空閑空間,只是這兩個(gè)空間并不連續(xù)。操作系統(tǒng)在清理碎片時(shí),會(huì)先把應(yīng)用 D 的數(shù)據(jù)拷貝到 2 字節(jié)的空閑空間中,并釋放 D 原先所占的空間。然后,再把 B 的數(shù)據(jù)拷貝到 D 原來(lái)的空間中。這樣一來(lái),這段 10 字節(jié)空間的最后三個(gè)字節(jié)就是一塊連續(xù)空間了。到這里,碎片清理結(jié)束。
需要注意:碎片清理事由代價(jià)的,操作系統(tǒng)需要把多份數(shù)據(jù)拷貝到新位置,把原有空間釋放出來(lái),這會(huì)帶來(lái)時(shí)間開(kāi)銷(xiāo)。因?yàn)?Redis 是單線程,在數(shù)據(jù)拷貝時(shí),Redis 只能等著,這就導(dǎo)致 Redis 無(wú)法及時(shí)處理請(qǐng)求,性能就會(huì)降低。而且,有的時(shí)候,數(shù)據(jù)拷貝還需要注意順序,就像剛剛說(shuō)的清理內(nèi)存碎片的例子,操作系統(tǒng)需要先拷貝 D,并釋放 D 的空間后,才能拷貝 B。這種對(duì)順序性的要求,會(huì)進(jìn)一步增加 Redis 的等待時(shí)間,導(dǎo)致性能降低。
那么,有什么辦法可以盡量緩解這個(gè)問(wèn)題嗎?這就要提到,Redis 專(zhuān)門(mén)為自動(dòng)內(nèi)存碎片清理功機(jī)制設(shè)置的參數(shù)了。我們可以通過(guò)設(shè)置參數(shù),來(lái)控制碎片清理的開(kāi)始和結(jié)束時(shí)機(jī),以及占用的 CPU 比例,從而減少碎片清理對(duì) Redis 本身請(qǐng)求處理的性能影響。
首先,Redis 需要啟用自動(dòng)內(nèi)存碎片清理,可以把 activedefrag 配置項(xiàng)設(shè)置為 yes,命令如下:
config set activedefrag yes
這個(gè)命令只是啟用了自動(dòng)清理功能,但是,具體什么時(shí)候清理,會(huì)受到下面這兩個(gè)參數(shù)的控制。這兩個(gè)參數(shù)分別設(shè)置了觸發(fā)內(nèi)存清理的一個(gè)條件,如果同時(shí)滿足這兩個(gè)條件,就開(kāi)始清理。在清理的過(guò)程中,只要有一個(gè)條件不滿足了,就停止自動(dòng)清理。
- active-defrag-ignore-bytes 100mb:表示內(nèi)存碎片數(shù)量達(dá)到100MB時(shí),開(kāi)始清理
- active-defrag-threshold-lower 10:表示內(nèi)存碎片空間占操作系統(tǒng)給redis分配空間的10%時(shí)開(kāi)始清理
為了盡可能減少碎片清理對(duì) Redis 正常請(qǐng)求處理的影響,自動(dòng)內(nèi)存碎片清理功能在執(zhí)行時(shí),還會(huì)監(jiān)控清理操作占用的 CPU 時(shí)間,而且還設(shè)置了兩個(gè)參數(shù),分別用于控制清理操作占用的 CPU 時(shí)間比例的上、下限,既保證清理工作能正常進(jìn)行,又避免了降低 Redis 性能。這兩個(gè)參數(shù)具體如下:
- active-defrag-cycle-min 25:表示自動(dòng)清理過(guò)程cpu時(shí)間筆記不低于25%,保證清理能正常開(kāi)展
- active-defrag-cycle-max 75:表示自動(dòng)清理過(guò)程所用 CPU 時(shí)間的比例不高于 75%,一旦超過(guò),就停止清理,從而避免在清理時(shí),大量的內(nèi)存拷貝阻塞 Redis,導(dǎo)致響應(yīng)延遲升高。
自動(dòng)內(nèi)存碎片清理機(jī)制在控制碎片清理啟停的時(shí)機(jī)上,既考慮了碎片的空間占比、對(duì) Redis 內(nèi)存使用效率的影響,還考慮了清理機(jī)制本身的 CPU 時(shí)間占比、對(duì) Redis 性能的影響。而且,清理機(jī)制還提供了 4 個(gè)參數(shù),讓我們可以根據(jù)實(shí)際應(yīng)用中的數(shù)據(jù)量需求和性能要求靈活使用,建議你在實(shí)踐中好好地把這個(gè)機(jī)制用起來(lái)。
總結(jié)
到此這篇關(guān)于redis內(nèi)存空間效率問(wèn)題的文章就介紹到這了,更多相關(guān)redis內(nèi)存空間效率內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用高斯Redis實(shí)現(xiàn)二級(jí)索引的方法
本文介紹了如何通過(guò)高斯Redis搭建二級(jí)索引,二級(jí)索引在電商、圖(hexastore)、游戲等領(lǐng)域具有廣泛的應(yīng)用場(chǎng)景,高斯redis現(xiàn)網(wǎng)亦有很多類(lèi)似應(yīng)用,需要的朋友跟隨小編一起看看吧2022-07-07Redis實(shí)現(xiàn)分布式鎖的幾種方法總結(jié)
這篇文章主要介紹了Redis實(shí)現(xiàn)分布式鎖的幾種方法總結(jié)的相關(guān)資料, Redis實(shí)現(xiàn)與Zookeeper實(shí)現(xiàn)和數(shù)據(jù)庫(kù)實(shí)現(xiàn),需要的朋友可以參考下2017-07-07redis?手機(jī)驗(yàn)證碼實(shí)現(xiàn)示例
本文主要介紹了redis?手機(jī)驗(yàn)證碼實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11redis實(shí)現(xiàn)sentinel哨兵架構(gòu)的方法
哨兵是一個(gè)分布式系統(tǒng),可以在一個(gè)架構(gòu)中運(yùn)行多個(gè)哨兵(sentinel) 進(jìn)程,這些進(jìn)程使用流言協(xié)議(gossip protocols)來(lái)接收關(guān)于Master主服務(wù)器是否下線的信息,這篇文章主要介紹了redis實(shí)現(xiàn)sentinel哨兵架構(gòu),需要的朋友可以參考下2022-11-11簡(jiǎn)單粗暴的Redis數(shù)據(jù)備份和恢復(fù)方法
這里我們來(lái)講解一個(gè)簡(jiǎn)單粗暴的Redis數(shù)據(jù)備份和恢復(fù)方法,有一個(gè)在不同主機(jī)上遷移Redis數(shù)據(jù)的示例,還有一個(gè)備份腳本實(shí)現(xiàn)的關(guān)鍵點(diǎn)提示,一起來(lái)看一下:2016-06-06如何使用Redis實(shí)現(xiàn)電商系統(tǒng)的庫(kù)存扣減
在日常開(kāi)發(fā)中有很多地方都有類(lèi)似扣減庫(kù)存的操作,本文主要介紹了如何使用Redis實(shí)現(xiàn)電商系統(tǒng)的庫(kù)存扣減,具有一定的參考價(jià)值,感興趣的可以了解一下2022-01-01Mac中Redis服務(wù)啟動(dòng)時(shí)錯(cuò)誤信息:NOAUTH Authentication required
這篇文章主要介紹了Mac中使用Redis服務(wù)啟動(dòng)時(shí)錯(cuò)誤信息:"NOAUTH Authentication required"問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Redis不同數(shù)據(jù)類(lèi)型使用場(chǎng)景代碼實(shí)例
這篇文章主要介紹了Redis不同數(shù)據(jù)類(lèi)型使用場(chǎng)景代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12