Redis 是一個(gè)開源、高性能的 Key-Value 數(shù)據(jù)庫,被廣泛應(yīng)用在服務(wù)器各種場(chǎng)景中。Redis 是一種內(nèi)存數(shù)據(jù)庫,將數(shù)據(jù)保存在內(nèi)存中,讀寫效率要比傳統(tǒng)的將數(shù)據(jù)保存在磁盤上的數(shù)據(jù)庫要快很多。所以,監(jiān)控 Redis 的內(nèi)存消耗并了解 Redis 內(nèi)存模型對(duì)高效并長期穩(wěn)定使用 Redis 至關(guān)重要。
在介紹之前先說明下,一般生產(chǎn)環(huán)境下,對(duì)開發(fā)同事不會(huì)開放直連 redis 集群的權(quán)限,一般是提供 daas 平臺(tái),通過可視化命令窗口,輸入 redis 命令,一般只有 read 權(quán)限;對(duì)于 write 操作,需要提 redis 數(shù)據(jù)變更單,而對(duì)于 redis 內(nèi)存、大 key、慢命令,一般都會(huì)將信息集成及中顯示在監(jiān)控看板,而不需要開發(fā)同事自己去輸入命令;但是基本的相關(guān)知識(shí)還是要具備的。
reids 內(nèi)存分析
redis 內(nèi)存使用情況:info memory
示例:
可以看到,當(dāng)前節(jié)點(diǎn)內(nèi)存碎片率為 226893824/209522728 ≈ 1.08,使用的內(nèi)存分配器是 jemalloc。
used_memory_rss 通常情況下是大于 used_memory 的,因?yàn)閮?nèi)存碎片的存在。
但是當(dāng)操作系統(tǒng)把 redis 內(nèi)存 swap 到硬盤時(shí),memory_fragmentation_ratio 會(huì)小于 1。redis 使用硬盤作為內(nèi)存,因?yàn)橛脖P的速度,redis 性能會(huì)受到極大的影響。
redis 內(nèi)存使用
redis 的內(nèi)存使用分布:自身內(nèi)存,鍵值對(duì)象占用、緩沖區(qū)內(nèi)存占用及內(nèi)存碎片占用。
redis 空進(jìn)程自身消耗非常的少,可以忽略不計(jì),優(yōu)化內(nèi)存可以不考慮此處的因素。
對(duì)象內(nèi)存
對(duì)象內(nèi)存,也即真實(shí)存儲(chǔ)的數(shù)據(jù)所占用的內(nèi)存。
redis k-v 結(jié)構(gòu)存儲(chǔ),對(duì)象占用可以簡單的理解為 k-size + v-size。
redis 的鍵統(tǒng)一都為字符串類型,值包含多種類型:string、list、hash、set、zset五種基本類型及基于 string 的 Bitmaps 和 HyperLogLog 類型等。
在實(shí)際的應(yīng)用中,一定要做好 kv 的構(gòu)建形式及內(nèi)存使用預(yù)期,。
緩沖內(nèi)存
緩沖內(nèi)存包括三部分:客戶端緩存、復(fù)制積壓緩存及?AOF 緩沖區(qū)。
客戶端緩存
接入redis服務(wù)器的TCP連接輸入輸出緩沖內(nèi)存占用,TCP 輸入緩沖占用是不受控制的,最大允許空間為 1G。輸出緩沖占用可以通過 client-output-buffer-limit 參數(shù)配置。
redis 客戶端主要分為從客戶端、訂閱客戶端和普通客戶端。
從客戶端連接占用
也就是我們所說的 slave,主節(jié)點(diǎn)會(huì)為每一個(gè)從節(jié)點(diǎn)建立一條連接用于命令復(fù)制,緩沖配置為:client-output-buffer-limit slave 256mb 64mb 60。
主從之間的間絡(luò)延遲及掛載的從節(jié)點(diǎn)數(shù)量是影響內(nèi)存占用的主要因素。因此在涉及需要異地部署主從時(shí)要特別注意,另外,也要避免主節(jié)點(diǎn)上掛載過多的從節(jié)點(diǎn)(<=2);
訂閱客戶端內(nèi)存占用
發(fā)布訂閱功能連接客戶端使用單獨(dú)的緩沖區(qū),默認(rèn)配置:client-output-buffer-limit pubsub 32mb 8mb 60。
當(dāng)消費(fèi)慢于生產(chǎn)時(shí)會(huì)造成緩沖區(qū)積壓,因此需要特別注意消費(fèi)者角色配比及生產(chǎn)、消費(fèi)速度的監(jiān)控。
普通客戶端內(nèi)存占用
除了上述之外的其它客戶端,如我們通常的應(yīng)用連接,默認(rèn)配置:client-output-buffer-limit normal 1000。
可以看到,普通客戶端沒有配置緩沖區(qū)限制,通常一般的客戶端內(nèi)存消耗也可以忽略不計(jì)。
但是當(dāng) redis 服務(wù)器響應(yīng)較慢時(shí),容易造成大量的慢連接,主要表現(xiàn)為連接數(shù)的突增,如果不能及時(shí)處理,此時(shí)會(huì)嚴(yán)重影響 redis 服務(wù)節(jié)點(diǎn)的服務(wù)及恢復(fù)。
關(guān)于此,在實(shí)際應(yīng)用中需要注意幾點(diǎn):
maxclients 最大連接數(shù)配置必不可少。
合理預(yù)估單次操作數(shù)據(jù)量(寫或讀)及網(wǎng)絡(luò)時(shí)延 ttl。
禁止線上大吞吐量命令操作,如 keys 等。
高并發(fā)應(yīng)用情景下,redis內(nèi)存使用需要有實(shí)時(shí)的監(jiān)控預(yù)警機(jī)制。
復(fù)制積壓緩沖區(qū)
v2.8 之后提供的一個(gè)可重用的固定大小緩沖區(qū),用以實(shí)現(xiàn)向從節(jié)點(diǎn)的部分復(fù)制功能,避免全量復(fù)制。配置單數(shù):repl-backlog-size,默認(rèn) 1M。單個(gè)主節(jié)點(diǎn)配置一個(gè)復(fù)制積壓緩沖區(qū)。
AOF緩沖區(qū)
AOF重寫期間增量的寫入命令保存,此部分緩存占用大小取決于 AOF 重寫時(shí)間及增量。
內(nèi)存碎片內(nèi)存占用
固定范圍內(nèi)存塊兒分配。redis默認(rèn)使用jemalloc內(nèi)存分配器,其它包括glibc、tcmalloc。
內(nèi)存分配器會(huì)首先將可管理的內(nèi)存分配為規(guī)定不同大小的內(nèi)存塊以備不同的數(shù)據(jù)存儲(chǔ)需求,但是,我們知道實(shí)際應(yīng)用中需要存儲(chǔ)的數(shù)據(jù)大小不一,規(guī)范不一,內(nèi)存分配器只能選擇最接近數(shù)據(jù)需求大小的內(nèi)存塊兒進(jìn)行分配,這樣就伴隨著“占不滿”空間的碎片浪費(fèi)。
jemalloc針對(duì)內(nèi)存碎片有相應(yīng)的優(yōu)化策略,正常碎片率為mem_fragmentation_ratio在1.03左右。
第二部分我們說過,對(duì)string值得頻繁append及range操作會(huì)會(huì)導(dǎo)致內(nèi)存碎片問題,另外,第七部分,SDS惰性內(nèi)存回收也會(huì)導(dǎo)致內(nèi)存碎片,同時(shí)過期鍵內(nèi)存回收也伴隨著所釋放空間的無法充分利用,導(dǎo)致內(nèi)存碎片率上升的問題。
碎片處理:
應(yīng)用層面:盡量避免差異化的鍵值使用,做好數(shù)據(jù)對(duì)齊。
redis服務(wù)層面:可以通過重啟服務(wù),進(jìn)行碎片整理。
maxmemory 及 maxmemory-policy
redis 基于以上配置控制 redis 最大可用內(nèi)存及內(nèi)存回收。需要注意的是內(nèi)存回收?qǐng)?zhí)行影響redis的性能,避免頻繁的內(nèi)存回收開銷。
redis 子進(jìn)程內(nèi)存消耗
子進(jìn)程即 redis 執(zhí)行持久化(RDB/AOF)時(shí) fork 的子任務(wù)進(jìn)程。
關(guān)于 linux 系統(tǒng)的寫時(shí)復(fù)制機(jī)制
父子進(jìn)程會(huì)共享相同的物理內(nèi)存頁,父進(jìn)程處理寫請(qǐng)求時(shí)會(huì)對(duì)需要修改的頁復(fù)制一份副本進(jìn)行修改,子進(jìn)程讀取的內(nèi)存則為fork時(shí)的父進(jìn)程內(nèi)存快照,因此,子進(jìn)程的內(nèi)存消耗由期間的寫操作增量決定。
關(guān)于 linux 的透明大頁機(jī)制THP(Transparent Huge Page)
THP 機(jī)制會(huì)降低 fork 子進(jìn)程的速度:寫時(shí)復(fù)制內(nèi)存頁由 4KB 增大至 2M。高并發(fā)情境下,寫時(shí)復(fù)制內(nèi)存占用消耗影響會(huì)很大,因此需要選擇性關(guān)閉。
關(guān)于linux配置
一般需要配置 linux 系統(tǒng) vm.overcommit_memory = 1,以允許系統(tǒng)可以分配所有的物理內(nèi)存。防止fork任務(wù)因內(nèi)存而失敗。
redis 內(nèi)存管理
redis 的內(nèi)存管理主要分為兩方面:內(nèi)存上限控制及內(nèi)存回收管理。
內(nèi)存上限:maxmemory
目的:緩存應(yīng)用內(nèi)存回收機(jī)制觸發(fā) + 防止物理內(nèi)存用盡(redis 默認(rèn)無限使用服務(wù)器內(nèi)存) + 服務(wù)節(jié)點(diǎn)內(nèi)存隔離(單服務(wù)器上部署多個(gè) redis 服務(wù)節(jié)點(diǎn))
在進(jìn)行內(nèi)存分配及限制時(shí)要充分考慮內(nèi)存碎片占用影響。動(dòng)態(tài)調(diào)整,擴(kuò)展redis服務(wù)節(jié)點(diǎn)可用內(nèi)存:config set maxmemory {}
內(nèi)存回收
回收時(shí)機(jī):鍵過期、內(nèi)存占用達(dá)到上限
過期鍵刪除
redis 鍵過期時(shí)間保存在內(nèi)部的過期字典中,redis 采用惰性刪除機(jī)制+定時(shí)任務(wù)刪除機(jī)制。
惰性刪除
即讀時(shí)刪除,讀取帶有超時(shí)屬性的鍵時(shí),如果鍵已過期,則刪除然后返回空值。這種方式存在問題是,觸發(fā)時(shí)機(jī),加入過期鍵長時(shí)間未被讀取,那么它將會(huì)一直存在內(nèi)存中,造成內(nèi)存泄漏。
定時(shí)任務(wù)刪除
redis 內(nèi)部維護(hù)了一個(gè)定時(shí)任務(wù)(默認(rèn)每秒10次,可配置),通過自適應(yīng)法進(jìn)行刪除。
刪除邏輯如下:
需要說明的一點(diǎn)是,快慢模式執(zhí)行的刪除邏輯相同,這是超時(shí)時(shí)間不同。
內(nèi)存溢出控制
當(dāng)內(nèi)存達(dá)到 maxmemory,會(huì)觸發(fā)內(nèi)存回收策略,具體策略依據(jù) maxmemory-policy 來執(zhí)行。
noevication:默認(rèn)不回收,達(dá)到內(nèi)存上限,則不再接受寫操作,并返回錯(cuò)誤。
volatile-lru:根據(jù)LRU算法刪除設(shè)置了過期時(shí)間的鍵,如果沒有則不執(zhí)行回收。
allkeys-lru:根據(jù)LRU算法刪除鍵,針對(duì)所有鍵。
allkeys-random:隨機(jī)刪除鍵。
volatitle-random:隨機(jī)刪除設(shè)置了過期時(shí)間的鍵。
volatilte-ttl:根據(jù)鍵ttl,刪除最近過期的鍵,同樣如果沒有設(shè)置過期的鍵,則不執(zhí)行刪除。
動(dòng)態(tài)配置:config set maxmemory-policy {}
在設(shè)置了maxmemory情況下,每次的redis操作都會(huì)檢查執(zhí)行內(nèi)存回收,因此對(duì)于線上環(huán)境,要確保所這只的 maxmemory > used_memory。
另外,可以通過動(dòng)態(tài)配置 maxmemory 來主動(dòng)觸發(fā)內(nèi)存回收。
內(nèi)存回收策略
內(nèi)存回收觸發(fā)有兩種情況,也就是內(nèi)存使用達(dá)到maxmemory上限時(shí)候觸發(fā)的溢出回收,還有一種是我們?cè)O(shè)置了過期的對(duì)象到期的時(shí)候觸發(fā)的到期釋放的內(nèi)存回收。
Redis內(nèi)存使用達(dá)到maxmemory上限時(shí)候觸發(fā)的溢出回收;Redis 提供了幾種策略 (maxmemory-policy) 來讓用戶自己決定該如何騰出新的空間以繼續(xù)提供讀寫服務(wù):
(1)volatile-lru:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
(2)volatile-ttl:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
(3)volatile-random:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
(4)allkeys-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最近最少使用的key(這個(gè)是最常用的)
(5)allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
(6)no-eviction:禁止驅(qū)逐數(shù)據(jù),也就是說當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),新寫入操作會(huì)報(bào)錯(cuò)。這個(gè)應(yīng)該沒人使用吧!
4.0版本后增加以下兩種:
(7)volatile-lfu:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最不經(jīng)常使用的數(shù)據(jù)淘汰
(8)allkeys-lfu:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最不經(jīng)常使用的key
redis默認(rèn)的策略就是noeviction策略,如果想要配置的話,需要在配置文件中寫這個(gè)配置:
maxmemory-policy?volatile-lru
Redis 的 LRU 算法
LRU是Least Recently Used 近期最少使用算法,很多緩存策略都使用了這種策略進(jìn)行空間的釋放,在學(xué)習(xí)操作系統(tǒng)的內(nèi)存回收的時(shí)候也用到了這種機(jī)制進(jìn)行內(nèi)存的回收,類似的還有LFU(Least Frequently Used)最不經(jīng)常使用算法,這種算法。
我們?cè)谏厦娴拿枋鲋幸部梢粤私獾?,redis使用的是一種類似LRU的算法進(jìn)行內(nèi)存溢出回收的,其算法的代碼:
/*?volatile-lru?and?allkeys-lru?policy?*/ else?if?(server.maxmemory_policy?==?REDIS_MAXMEMORY_ALLKEYS_LRU?|| ?server.maxmemory_policy?==?REDIS_MAXMEMORY_VOLATILE_LRU) { ?struct?evictionPoolEntry?*pool?=?db->eviction_pool; ? ?while(bestkey?==?NULL)?{ ??evictionPoolPopulate(dict,?db->dict,?db->eviction_pool); ??/*?Go?backward?from?best?to?worst?element?to?evict.?*/ ??for?(k?=?REDIS_EVICTION_POOL_SIZE-1;?k?>=?0;?k--)?{ ???if?(pool[k].key?==?NULL)?continue; ???de?=?dictFind(dict,pool[k].key); ? ???/*?Remove?the?entry?from?the?pool.?*/ ???sdsfree(pool[k].key); ???/*?Shift?all?elements?on?its?right?to?left.?*/ ???memmove(pool+k,pool+k+1, ????sizeof(pool[0])*(REDIS_EVICTION_POOL_SIZE-k-1)); ???/*?Clear?the?element?on?the?right?which?is?empty ????*?since?we?shifted?one?position?to?the?left.??*/ ???pool[REDIS_EVICTION_POOL_SIZE-1].key?=?NULL; ???pool[REDIS_EVICTION_POOL_SIZE-1].idle?=?0; ? ???/*?If?the?key?exists,?is?our?pick.?Otherwise?it?is ????*?a?ghost?and?we?need?to?try?the?next?element.?*/ ???if?(de)?{ ????bestkey?=?dictGetKey(de); ????break; ???}?else?{ ????/*?Ghost...?*/ ????continue; ???} ??} ?} } Redis會(huì)基于server.maxmemory_samples配置選取固定數(shù)目的key,然后比較它們的lru訪問時(shí)間,然后淘汰最近最久沒有訪問的key,maxmemory_samples的值越大,Redis的近似LRU算法就越接近于嚴(yán)格LRU算法,但是相應(yīng)消耗也變高。所以,頻繁的進(jìn)行這種內(nèi)存回收是會(huì)降低redis性能的,主要是查找回收節(jié)點(diǎn)和刪除需要回收節(jié)點(diǎn)的開銷。
所以一般我們?cè)谂渲胷edis的時(shí)候,盡量不要讓它進(jìn)行這種內(nèi)存溢出的回收操作,redis是可以配置maxmemory,used_memory指的是redis真實(shí)占用的內(nèi)存,但是由于操作系統(tǒng)還有其他軟件以及內(nèi)存碎片還有swap區(qū)的存在,所以我們實(shí)際的內(nèi)存應(yīng)該比redis里面設(shè)置的maxmemory要大,具體大多少視系統(tǒng)環(huán)境和軟件環(huán)境來定。maxmemory也要比used_memory大,一般由于碎片的存在需要做1~2個(gè)G的富裕。
編輯:黃飛
?
評(píng)論
查看更多