Redis所有的數(shù)據(jù)都存在內(nèi)存中岖是,當(dāng)前內(nèi)存雖然越來(lái)越便宜豺撑,但跟廉價(jià)的硬盤(pán)相比成本還是比較昂貴,因此如何高效利用Redis內(nèi)存變得非常重要荧止。高效利用Redis內(nèi)存首先需要理解Redis內(nèi)存消耗在哪里阶剑,如何管理內(nèi)存牧愁,最后才能考慮如何優(yōu)化內(nèi)存猪半。掌握這些知識(shí)后能夠?qū)崿F(xiàn)用更少的內(nèi)存存儲(chǔ)更多的數(shù)據(jù)兔朦,從而減低成本。本章主要內(nèi)容如下:
- 內(nèi)存消耗分析磨确。
- 管理內(nèi)存的原理與方法沽甥。
- 內(nèi)存優(yōu)化技巧。
-
內(nèi)存消耗
理解Redis內(nèi)存乏奥,首先需要掌握Redis內(nèi)存消耗在哪些方面摆舟。有些內(nèi)存消耗是必不可少的,而有些可以通過(guò)參數(shù)調(diào)整和合理使用使用來(lái)規(guī)避內(nèi)存浪費(fèi)邓了。內(nèi)存消耗可以分為進(jìn)程自身消耗和子進(jìn)程消耗恨诱。
-
內(nèi)存使用統(tǒng)計(jì)
首先需要了解Redis自身使用內(nèi)存的統(tǒng)計(jì)數(shù)據(jù),可通過(guò)執(zhí)行info memory命令獲取內(nèi)存相關(guān)指標(biāo)骗炉。讀懂每個(gè)指標(biāo)有助于分析Redis內(nèi)存使用情況照宝,下表列舉出內(nèi)存統(tǒng)計(jì)指標(biāo)和對(duì)應(yīng)解釋。
屬性名 屬性說(shuō)明旨别、 used_memory Redis分配器分配的內(nèi)存總量,也就是內(nèi)部存儲(chǔ)的所有數(shù)據(jù)內(nèi)存占用量 used_memory_human 以可讀的格式返回used_memory used_memory_rss 從操作系統(tǒng)的角度顯示Redis進(jìn)程占用的物理內(nèi)存總量 used_memory_peak 內(nèi)存使用的最大值递览,表示used_memory的峰值 used_memory_peak_human 以可讀的格式返回used_memory_peak used_memory_lua Lua引擎所消耗的內(nèi)存大小 mem_fragmentation_ratio used_memory_rss/used_memory比值嫂侍,表示內(nèi)存碎片率 mem_allocator Redis所使用的內(nèi)存分配器菲盾。默認(rèn)為jemalloc 需要重點(diǎn)關(guān)注的指標(biāo)有:used_memory_rss和used_memory以及它們的比值mem_fragmentation_ratio碎浇。
當(dāng)mem_fragmentation_ratio > 1時(shí)吴裤,說(shuō)明used_memory_rss-used_memory多出的部分內(nèi)存并沒(méi)有用于數(shù)據(jù)存儲(chǔ),而是被內(nèi)存碎片所消耗,如果兩者相差很大吱晒,說(shuō)明碎片率嚴(yán)重叹话。
當(dāng)mem_fragmentation_ratio < 1時(shí),這種情況一般出現(xiàn)在操作系統(tǒng)吧Redis內(nèi)存交換(Swap)到硬盤(pán)導(dǎo)致,出現(xiàn)這種請(qǐng)時(shí)要格外關(guān)注般妙,由于硬盤(pán)速度遠(yuǎn)遠(yuǎn)慢于內(nèi)存,Redis性能會(huì)變得很差苫拍,甚至僵死浆洗。
-
內(nèi)存消耗劃分
Redis進(jìn)程內(nèi)存消耗主要包括:自身內(nèi)存 + 對(duì)象內(nèi)存 + 緩沖內(nèi)存 + 內(nèi)存碎片抠刺,其中Redis空進(jìn)程自身內(nèi)存消耗非常少聪黎,通常used_memory_rss在3MB左右锦秒,used_memory在800KB左右侣姆,一個(gè)空的Redis進(jìn)程消耗內(nèi)存可以忽略不計(jì)川蒙。Redis主要內(nèi)存消耗如下圖所示。
2019-04-29-21-15-48.png下面介紹另外三種內(nèi)存消耗:
-
對(duì)象內(nèi)存
對(duì)象內(nèi)存是Redis內(nèi)存占用最大的一塊斤斧,存儲(chǔ)著用戶(hù)所以的數(shù)據(jù)。Redis所有的數(shù)據(jù)都采用key-value數(shù)據(jù)類(lèi)型甘苍,每次創(chuàng)建鍵值對(duì),至少創(chuàng)建兩個(gè)類(lèi)型對(duì)象:key對(duì)象和value對(duì)象。對(duì)象內(nèi)存消耗可以簡(jiǎn)單理解為sizeof(keys) + sizeof(values)。鍵對(duì)象都是字符串谓松,在使用Redis時(shí)很容易忽略鍵對(duì)內(nèi)存消耗的影響拧簸,應(yīng)當(dāng)避免使用過(guò)長(zhǎng)的鍵贾富。value對(duì)象更復(fù)雜些牺六,主要包含5種基本數(shù)據(jù)類(lèi)型:字符串、列表、哈希票灰、集合冯键、有序集合。其它數(shù)據(jù)類(lèi)型都是建立在這5種數(shù)據(jù)結(jié)構(gòu)之上實(shí)現(xiàn)的晓淀,如:Bitmaps和HyperLogLog使用字符串實(shí)現(xiàn),GEO使用有序集合實(shí)現(xiàn)等。每種value對(duì)象類(lèi)型根據(jù)使用規(guī)模不同,占用內(nèi)存不同冒萄。在使用時(shí)一定要合理預(yù)估并監(jiān)控vlaue對(duì)象占用情況,避免內(nèi)存溢出。
-
緩沖內(nèi)存
緩沖內(nèi)存主要包括:客戶(hù)端緩沖、復(fù)制積壓緩沖區(qū)、AOF緩沖區(qū)咕幻。
客戶(hù)端緩沖指的是所有接入到Redis服務(wù)器TCP連接的輸入輸出緩沖蓝厌。輸入緩沖無(wú)法控制隧膘,最大空間為1G,如果超過(guò)將斷開(kāi)連接。輸出緩沖通過(guò)參數(shù)client-output-buffer-limit控制,如下所示:
普通客戶(hù)端:除了復(fù)制和訂閱的客戶(hù)端之外的所有連接锰镀,Redis的默認(rèn)配置是:client-output-buffer-limit normal 0 0 0氧腰,Redis并沒(méi)有對(duì)普通客戶(hù)端的輸出緩沖區(qū)做限制真友,一般普通客戶(hù)端的內(nèi)存消耗可以忽略不計(jì)挺尾,但是當(dāng)有大量滿連接客戶(hù)端接入時(shí)這部分內(nèi)存消耗就不能忽略了掂僵,可以設(shè)置maxclients做限制。特別是當(dāng)使用大量數(shù)據(jù)輸出的命令且數(shù)據(jù)無(wú)法及時(shí)推送給客戶(hù)端是舱卡,如monitor命令,容易造成Redis服務(wù)器內(nèi)存突然飆升既绩。
從客戶(hù)端:主節(jié)點(diǎn)會(huì)為每個(gè)從節(jié)點(diǎn)單獨(dú)建立一條連接用于命令復(fù)制颜矿,默認(rèn)配置是:client-output-buffer-limit slave 256mb 64mb 60.當(dāng)主從節(jié)點(diǎn)之間網(wǎng)絡(luò)延遲較高或主節(jié)點(diǎn)掛載大量從節(jié)點(diǎn)時(shí)這部分內(nèi)存消耗將張勇很大一部分箍铭,建議主節(jié)點(diǎn)掛載的從節(jié)點(diǎn)不要多于2個(gè)拍摇,主從節(jié)點(diǎn)不要部署在較差的網(wǎng)絡(luò)環(huán)境下混卵,如異地跨機(jī)房環(huán)境,防止復(fù)制客戶(hù)端連接緩慢造成溢出踏拜。
訂閱客戶(hù)端吧:當(dāng)使用發(fā)布訂閱功能時(shí),連接客戶(hù)端使用單獨(dú)的輸出緩沖區(qū)笋妥,默認(rèn)配置為:client-output-buffer-limit pubsub 32mb 8mb 60,當(dāng)訂閱服務(wù)的消息生產(chǎn)快于消費(fèi)速度時(shí)油挥,輸出緩沖區(qū)會(huì)產(chǎn)生積壓造成輸出緩沖區(qū)空間溢出惋鹅。
輸入輸出緩沖區(qū)在大流量的場(chǎng)景中容易失控闰集,造成Redis內(nèi)存的不穩(wěn)定挚瘟,需要重點(diǎn)監(jiān)控。
復(fù)制積壓緩沖區(qū):Redis在2.8版本之后提供了一個(gè)可重用的固定大小緩沖區(qū)用于實(shí)現(xiàn)部分復(fù)制功能泽谨,根據(jù)repl-backlog-size參數(shù)控制妒潭,默認(rèn)1MB。對(duì)于復(fù)制積壓緩沖區(qū)整個(gè)主節(jié)點(diǎn)只有一個(gè)揣钦,所有的從節(jié)點(diǎn)共享此緩沖區(qū)雳灾,因此可以設(shè)置較大的緩沖區(qū)空間,如100MB冯凹,這部分內(nèi)存投入是有價(jià)值的谎亩,可以有效避免全量復(fù)制。
AOF緩沖區(qū):這部分空間用于在Redis重寫(xiě)期間保存最近的寫(xiě)入命令宇姚。AOF緩沖區(qū)空間消耗用戶(hù)無(wú)法控制匈庭,消耗的內(nèi)存取決于AOF重寫(xiě)時(shí)間和寫(xiě)入命令量,這部分空間占用通常很小浑劳。
-
內(nèi)存碎片
Redis默認(rèn)的內(nèi)存分配器采用jemalloc阱持,可選的分配器還有:glibc、tcmalloc魔熏。內(nèi)存分配器為了更好地管理和重復(fù)利用內(nèi)存衷咽,分配內(nèi)存策略一般采用固定范圍的內(nèi)存塊進(jìn)行分配。例如jemalloc在64位系統(tǒng)中將內(nèi)存空間劃分為:小道逗、大兵罢、巨大三個(gè)范圍。每個(gè)范圍內(nèi)又劃分為多個(gè)小的內(nèi)存塊單位滓窍,如下所示:
新舸省:[8byte], [16byte, 32byte, 48byte, ..., 128byte], [192byte, 256byte, ..., 512byte], [768byte, 1024byte, ..., 3840byte]
大:[4KB, 8KB, 12KB, ..., 4072KB]
巨大:[4MB, 8MB, 12MB, ...]
比如當(dāng)保存5KB對(duì)象時(shí)jemalloc可能會(huì)采用8KB的塊存儲(chǔ),而剩下的3KB空間變?yōu)榱藘?nèi)存碎片不能再分配給其他對(duì)象存儲(chǔ)吏夯。內(nèi)存碎片雖然是所有內(nèi)存服務(wù)的通病此蜈,但是jemalloc針對(duì)碎片化問(wèn)題做了優(yōu)化,一般不會(huì)存在過(guò)度碎片化的問(wèn)題噪生,正常的碎片率(mem_fragmentation_ratio)在1.03左右裆赵。但是當(dāng)存儲(chǔ)的數(shù)據(jù)長(zhǎng)短差異較大時(shí),以下場(chǎng)景容易出現(xiàn)高內(nèi)存碎片問(wèn)題:
頻繁做更新操作跺嗽,例如頻繁對(duì)已存在的鍵執(zhí)行append战授、setrange等更新操作。
大量過(guò)期鍵刪除桨嫁,鍵對(duì)象過(guò)期刪除后植兰,釋放的空間無(wú)法得到充分利用,導(dǎo)致碎片率上升璃吧。
出現(xiàn)高內(nèi)存碎片問(wèn)題是常見(jiàn)的解決方式如下:
數(shù)據(jù)對(duì)齊:在條件允許的情況下盡量做數(shù)據(jù)對(duì)齊楣导,比如數(shù)據(jù)盡量采用數(shù)字類(lèi)型或者固定長(zhǎng)度字符串等,但是這要視具體的業(yè)務(wù)而定畜挨,有些場(chǎng)景無(wú)法做到筒繁。
安全重啟:重啟節(jié)點(diǎn)可以做到內(nèi)存碎片重新整理噩凹,因此可以利用高可用架構(gòu),如Sentinel或Cluster毡咏,將碎片率過(guò)高的主節(jié)點(diǎn)轉(zhuǎn)換為從節(jié)點(diǎn)驮宴,進(jìn)行安全重啟。
-
-
-
子進(jìn)程內(nèi)存消耗
子進(jìn)程內(nèi)存消耗主要指執(zhí)行AOF/RDB重寫(xiě)時(shí)Redis創(chuàng)建的子進(jìn)程內(nèi)存消耗血当。Redis執(zhí)行fork操作產(chǎn)生的子進(jìn)程內(nèi)存占用量對(duì)外表現(xiàn)為與父進(jìn)程相同幻赚,理論上需要一倍的物理內(nèi)存來(lái)完成重寫(xiě)操作禀忆。但Linux具有寫(xiě)時(shí)復(fù)制技術(shù)(copy-on-write)臊旭,父子進(jìn)程會(huì)共享相同的物理內(nèi)存頁(yè),當(dāng)父進(jìn)程處理寫(xiě)請(qǐng)求時(shí)會(huì)需要修改的頁(yè)復(fù)制出一份副本完成寫(xiě)操作箩退,而子進(jìn)程依然讀取fork時(shí)整個(gè)父進(jìn)程的內(nèi)存快照离熏。
Linux Kernel在2.6.38內(nèi)核增加了Transparent Huge pages(THP)機(jī)制而有些Linux發(fā)行版及時(shí)內(nèi)核達(dá)不到2.6.38也會(huì)默認(rèn)加入并開(kāi)啟這個(gè)功能,如RedHat Enterprise Linux在6.0以上版本默認(rèn)會(huì)引入THP戴涝。雖然開(kāi)啟THP可以降低fork子進(jìn)程的速度滋戳,但之后copy-on-write期間復(fù)制內(nèi)存頁(yè)的單位從4KB變?yōu)?MB,如果父進(jìn)程有大量寫(xiě)命令啥刻,會(huì)加重內(nèi)存拷貝量奸鸯,從而造成過(guò)度內(nèi)存消耗。例如可帽,以下兩個(gè)執(zhí)行AOF重寫(xiě)時(shí)的內(nèi)存消耗日志:
// 開(kāi)啟 THP: C * AOF rewrite: 1039 MB of memory used by copy-on-write // 關(guān)閉THP: C * AOF rewrite: 9 MB of memory used by copy-on-write
這兩個(gè)日志出資同一Redis進(jìn)程娄涩,used_memory總量為1.5GB,子進(jìn)程執(zhí)行期間每秒寫(xiě)命令量都在200左右映跟。當(dāng)分別開(kāi)啟和關(guān)閉THP時(shí)蓄拣,子進(jìn)程內(nèi)存消耗有天壤之別。如果在高并發(fā)寫(xiě)的場(chǎng)景下開(kāi)啟THP努隙,子進(jìn)程內(nèi)存消耗可能是父進(jìn)程的數(shù)倍球恤,極易造成機(jī)器物理內(nèi)存溢出,從而觸發(fā)SWAP或OOM killer荸镊。
子進(jìn)程內(nèi)存消耗總結(jié)如下:
Redis產(chǎn)生的子進(jìn)程并不需要消耗1倍的父進(jìn)程內(nèi)存咽斧,實(shí)際消耗根據(jù)期間寫(xiě)入命令量決定,但是依然要預(yù)留出一些內(nèi)存防止溢出躬存。
需要設(shè)置sysctl vm.overcommit_memory=1 允許內(nèi)核可以分配所有的物理內(nèi)存张惹、防止Redis進(jìn)程執(zhí)行fork時(shí)因系統(tǒng)剩余內(nèi)存不足而失敗。
排查當(dāng)前系統(tǒng)是否支持并開(kāi)啟THP优构,如果開(kāi)啟建議關(guān)閉诵叁,防止copy-on-write期間內(nèi)存過(guò)度消耗。