在我們探究和優(yōu)化redis性能的過程中,「Redis內(nèi)存碎片」是一個(gè)不可忽視的話題。
這篇文章將深入研究這個(gè)看似微不足道,但實(shí)際上對(duì)Redis運(yùn)行效率產(chǎn)生重要影響的問題。首先,讓我們揭開Redis內(nèi)存碎片的神秘面紗,理解它的本質(zhì)及其為何成為我們必須面對(duì)的挑戰(zhàn)。
內(nèi)存碎片如何產(chǎn)生的
Redis內(nèi)存碎片主要是因?yàn)镽edis數(shù)據(jù)存儲(chǔ)和回收過程中的內(nèi)存管理問題導(dǎo)致的。
Redis分配內(nèi)存時(shí),會(huì)根據(jù)需要申請(qǐng)一段連續(xù)的內(nèi)存空間。但當(dāng)Redis刪除或修改數(shù)據(jù)時(shí),釋放的內(nèi)存空間并不一定能被立即重新利用,尤其是當(dāng)這些空閑內(nèi)存空間大小不一致時(shí),就可能導(dǎo)致內(nèi)存碎片的出現(xiàn)。
為了提高內(nèi)存使用的效率,Redis內(nèi)部使用內(nèi)存分配器來對(duì)內(nèi)存的申請(qǐng)和釋放進(jìn)行管理。Redis使用的內(nèi)存分配器默認(rèn)是「jemalloc」。
而內(nèi)存分配器是按照固定大小來分配內(nèi)存的,并不是完全按照程序申請(qǐng)的內(nèi)存大小來進(jìn)行分配。
比如程序申請(qǐng)一個(gè)20字節(jié)的內(nèi)存,內(nèi)存分配器會(huì)分配一個(gè)32字節(jié)的內(nèi)存空間,這么做是為了減少分配次數(shù)。redis會(huì)申請(qǐng)不同大小的內(nèi)存空間來存儲(chǔ)不同業(yè)務(wù)不同類型的數(shù)據(jù),由于內(nèi)存按照固定大小分配且會(huì)比實(shí)際申請(qǐng)的內(nèi)存要大一些,這個(gè)過程中會(huì)產(chǎn)生內(nèi)存碎片。
舉個(gè)生活中的例子,幫助大家理解:
假設(shè)你正在整理一間圖書館。圖書館的書架就像是Redis儲(chǔ)存數(shù)據(jù)的內(nèi)存空間。每本書都代表不同大小的數(shù)據(jù)。剛開始時(shí),你把所有的書都按照大小放好。小書在一側(cè),大書在另一側(cè)。這樣你可以有效地利用書架的空間,也方便找書。
但是,如果你需要移除一些書(刪除某些數(shù)據(jù)),然后又加入新的書(新增數(shù)據(jù)),就可能出現(xiàn)問題了。例如,你移除了一些大書,把它們的位置空出來,然后把新的小書放進(jìn)去。這樣下來,原本屬于大書的空間,現(xiàn)在只被小書部分占用,剩余的空白就成了“內(nèi)存碎片”。
又或者你有一堆新的大書要放,但書架上只有分散的小書的空位,無法容納這些大書。這個(gè)時(shí)候你可能需要重新排列整個(gè)書架(類似于Redis的內(nèi)存整理)去騰出連續(xù)的大片空間來擺放這些新的大書。
總結(jié)來說:當(dāng)數(shù)據(jù)不斷刪除和新增時(shí),內(nèi)存中空出的位置可能無法完全匹配新數(shù)據(jù)的大小,導(dǎo)致產(chǎn)生未被利用的“碎片”空間,這就是內(nèi)存碎片。
內(nèi)存分配器
Redis 使用內(nèi)存分配器來管理其在運(yùn)行期間需要使用的內(nèi)存資源。可以是libc、jemalloc、tcmalloc。默認(rèn)是jemalloc。
要指定 Redis 使用哪個(gè)內(nèi)存分配器,你需要在編譯 Redis 時(shí)做出選擇。通常在執(zhí)行 make 命令時(shí)可以通過 MALLOC 參數(shù)來指定。例如,如果你想使用 jemalloc,你可以像這樣編譯 Redis:make MALLOC=jemalloc。
jemalloc在64位系統(tǒng)中,將內(nèi)存空間劃分為小、大、巨大三個(gè)范圍。每個(gè)范圍內(nèi)又劃分了許多小的內(nèi)存塊單位,存儲(chǔ)數(shù)據(jù)的時(shí)候,會(huì)選擇大小最合適的內(nèi)存塊進(jìn)行存儲(chǔ)。
jemalloc劃分的內(nèi)存單元如下圖所示:
也就是說Redis是以指定大小的塊為單位進(jìn)行連續(xù)內(nèi)存分配的,而不是按需分配的,Redis 會(huì)根據(jù)申請(qǐng)的內(nèi)存最接近的固定值分配相應(yīng)大小的空間。
這就像你有不同的箱子,為了裝東西,你需要找一個(gè)體積最接近的箱子來裝。但是裝進(jìn)去后,你發(fā)現(xiàn)還有空間可以放一些小東西,就無需再找箱子了。
但是,這種分配空間的方式會(huì)帶來一定程度的內(nèi)存碎片。我們可以把固定大小的劃分空間看成不同體積的箱子,每種箱子里的空間不同程度上都會(huì)有剩余。這些剩余的空間就是內(nèi)存碎片。
怎么看是否有內(nèi)存碎片
我們登陸到Redis服務(wù)器上,執(zhí)行以下命令,這會(huì)返回一段描述Redis內(nèi)存使用情況的文本。
redis> info memory
我們會(huì)看到類似如下的信息:
在這里,我們主要關(guān)注的是名為mem_fragmentation_ratio的字段,它顯示了Redis內(nèi)存碎片的比例。
如果mem_fragmentation_ratio大于1,那就表示存在內(nèi)存碎片。這個(gè)值越大,內(nèi)存碎片就越多。如果該值非常接近1或者小于1,則表示內(nèi)存碎片很少或者沒有。
計(jì)算公式為:
mem_fragmentation_ratio = used_memory_rss / used_memory
其中:
- used_memory_rss:代表Redis進(jìn)程占用的總物理內(nèi)存大小(包括碼區(qū)、數(shù)據(jù)區(qū)和堆棧等),單位是字節(jié)。
- used_memory:代表Redis分配器申請(qǐng)的內(nèi)存總量,也就是從操作系統(tǒng)角度看進(jìn)程實(shí)際使用的虛擬內(nèi)存空間,單位是字節(jié)。
碎片率的意義
mem_fragmentation_ratio的不同值,說明不同的情況。
- 大于1:說明內(nèi)存有碎片,通常在1到1.5之間是正常的。
- 大于1.5:說明內(nèi)存碎片率比較大,需要考慮是否要進(jìn)行內(nèi)存碎片清理,要引起重視。
- 小于1:說明已經(jīng)開始使用交換內(nèi)存,也就是使用硬盤了,正常的內(nèi)存不夠用了,需要考慮是否要進(jìn)行內(nèi)存的擴(kuò)容,使用swap是相當(dāng)影響性能的。
清理內(nèi)存碎片
1.低于4.0-RC3版本的Redis
Redis 4.0-RC3之前的版本并沒有內(nèi)置的內(nèi)存碎片整理工具。如果你想要清理內(nèi)存碎片,可以通過重啟的方式。
當(dāng)Redis重新啟動(dòng)時(shí),它會(huì)通過RDB持久化功能將數(shù)據(jù)存儲(chǔ)到磁盤,然后再從磁盤加載數(shù)據(jù)到內(nèi)存,這個(gè)過程可以有效地清理內(nèi)存碎片。但這種方法會(huì)導(dǎo)致服務(wù)的臨時(shí)中斷。
2.高于4.0-RC3版本的Redis
Redis4.0-RC3版本開始,引入了active-defrag 特性。可以在不重啟的情況下,自動(dòng)進(jìn)行碎片清理。
開啟配置如下,此選項(xiàng)的默認(rèn)值是關(guān)閉的,激活碎片整理可能會(huì)占據(jù)一些 CPU 時(shí)間。
redis> config set activedefrag yes
注意:自動(dòng)清理內(nèi)存碎片的功能需要該Redis的內(nèi)存分配器是jemalloc時(shí)才能啟用。
啟用后需要同時(shí)滿足下面2個(gè)參數(shù)的設(shè)置條件時(shí)才會(huì)觸發(fā)自動(dòng)清理:
active-defrag-ignore-bytes 100mb # 默認(rèn)100MB,表示內(nèi)存碎片空間達(dá)到100MB時(shí)
active-defrag-threshold-lower 10 # 默認(rèn)10,表示內(nèi)存碎片空間占OS分配給redis的物理內(nèi)存空間的比例達(dá)到10%時(shí)
redis是單進(jìn)程模型,內(nèi)存碎片自動(dòng)清理是通過主線程操作的,也會(huì)消耗一定的CPU資源。為了避免自動(dòng)清理降低Redis的處理性能,如下兩個(gè)參數(shù)可以控制清理動(dòng)作消耗的CPU時(shí)間比例的上下限:
active-defrag-cycle-min 5 # 默認(rèn)5,表示自動(dòng)清理過程所用 CPU 時(shí)間的比例不低于5%,保證清理能正常開展;
active-defrag-cycle-max 75 # 默認(rèn)75,表示自動(dòng)清理過程所用 CPU 時(shí)間的比例不高于 75%,一旦超過,就停止清理,從而避免在清理時(shí),大量的內(nèi)存拷貝阻塞 Redis,導(dǎo)致響應(yīng)延遲升高。
如果你對(duì)自動(dòng)清理的效果不滿意,可以使用如下命令,直接進(jìn)行手動(dòng)碎片清理:
redis > memory purge
需要注意的是,該命令會(huì)阻塞主進(jìn)程,并且目前也僅實(shí)現(xiàn)了jemalloc作為內(nèi)存分配器的內(nèi)存統(tǒng)計(jì),對(duì)其他分配器暫不支持。
本篇文章到這就結(jié)束了。在我們深入研究Redis內(nèi)存碎片管理和優(yōu)化策略后,可以明確一點(diǎn):理解并合理處理內(nèi)存碎片化對(duì)于保證Redis的性能及穩(wěn)定性至關(guān)重要。
不論是進(jìn)行內(nèi)存分配策略的調(diào)整,還是使用適當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu),都是對(duì)Redis內(nèi)存管理的優(yōu)化。
同時(shí),定期的監(jiān)控和審視也是必不可少的步驟。希望本文能為你在處理Redis內(nèi)存碎片問題上提供一些有價(jià)值的啟示。記住,每一個(gè)優(yōu)秀的工程師都應(yīng)該以理解其使用的工具為榮。讓我們持續(xù)關(guān)注和優(yōu)化Redis,使其更好地服務(wù)于我們的項(xiàng)目,推動(dòng)業(yè)務(wù)的發(fā)展。