通過 CONFIG SET maxmemory 100mb
或者在 redis.conf
配置文件設置 maxmemory 100mb
Redis 內存佔用限制。當達到內存最大值值,會觸發內存淘汰策略刪除數據。
除此之外,當 key 達到過期時間,Redis 會有以下兩種刪除過期數據的策略:
- 後臺定時任務選取部分數據刪除;
- 惰性刪除。
具體原理請移步《Redis 的過期數據刪除那些事》。
假設 Redis 實例保存了 5GB 的數據,現在刪除了 2GB 數據,Redis 進程佔用的內存一定會降低麼?(也叫做 RSS,進程消耗內存頁數)。
答案是:可能依然佔用了大約 5GB 的內存,即使 Redis 的數據只佔用了 3GB 左右。
大家一定要設置maxmemory
,否則 Redis 會繼續爲新寫入的數據分配內存,無法分配就會導致應用程序報錯,當然不會導致宕機。
釋放的內存去哪了
明明刪除了數據,使用 top 命令查看,爲何還是佔用了那麼多內存?
內存都去哪了?使用 info memory
命令獲取 Redis 內存相關指標,我列舉了幾個重要的數據:
127.0.0.1:6379> info memory
# Memory
used_memory:1132832 // Redis 存儲數據佔用的內存量
used_memory_human:1.08M // 人類可讀形式返回內存總量
used_memory_rss:2977792 // 操作系統角度,進程佔用的物理總內存
used_memory_rss_human:2.84M // used_memory_rss 可讀性模式展示
used_memory_peak:1183808 // 內存使用的最大值,表示 used_memory 的峯值
used_memory_peak_human:1.13M // 以可讀的格式返回 used_memory_peak的值
used_memory_lua:37888 // Lua 引擎所消耗的內存大小。
used_memory_lua_human:37.00K
maxmemory:2147483648 // 能使用的最大內存值,字節爲單位。
maxmemory_human:2.00G // 可讀形式
maxmemory_policy:noeviction // 內存淘汰策略
// used_memory_rss / used_memory 的比值,代表內存碎片率
mem_fragmentation_ratio:2.79
Redis 進程內存消耗主要由以下部分組成:
- Redis 自身啓動所佔用的內存;
- 存儲對象數據內存;
- 緩衝區內存:主要由 client-output-buffer-limit 客戶端輸出緩衝區、複製積壓緩衝區、AOF 緩衝區。
- 內存碎片。
Redis 自身空進程佔用的內存很小可以忽略不計,對象內存是佔比對打的一塊,裏面存儲着所有的數據。
緩衝區內存在大流量場景容易失控,造成 Redis 內存不穩定,需要重點關注。
內存碎片過大會導致明明有空間可用,但是卻無法存儲數據。
碎片 = used_memory_rss 實際使用的物理內存(RSS 值)除以 used_memory 實際存儲數據內存。
什麼是內存碎片
內存碎片會造成明明有內存空間空閒,可是卻無法存儲數據。舉個例子,你跟漂亮小姐姐去電影院看電影,肯定想連在一塊坐。
假設現在有 8 個座位,已經賣出了 4 張票,還有 4 張可以買。可是好巧不巧,買票的人很奇葩,分別間隔一個座位買票。
即使還有 4 個座位空閒,可是你卻買不到兩個座位連在一塊的票,厚禮蟹!
內存碎片形成原因
內存碎片是什麼原因導致呢?
主要有兩個原因:
- 內存分配器的分配策略。
- 鍵值對的大小不一樣和刪改操作:Redis 頻繁做更新操作、大量過期數據刪除,釋放的空間(不夠連續)無法得到複用,導致碎片率上升。
接下來我分別探討實際發生的原因……
內存分配器的分配策略
Redis 默認的內存分配器採用 jemalloc,可選的分配器還有:glibc、tcmalloc。
內存分配器並不能做到按需分配,而是採用固定範圍的內存塊進行分配。
例如 8 字節、16 字節…..,2 KB,4KB,當申請內存最近接某個固定值的時候,jemalloc 會給它分配最接近固定值大小的空間。
這樣就會出現內存碎片,比如程序只需要 1.5 KB,內存分配器會分配 2KB 空間,那麼這 0.5KB 就是碎片。
這麼做的目的是減少內存分配次數,比如申請 22 字節的空間保存數據,jemalloc 就會分配 32 字節,如果後邊還要寫入 10 字節,就不需要再向操作系統申請空間了,可以使用之前申請的 32 字節。
刪除 key 的時候,Redis 並不會立馬把內存歸還給操作系統,出現這個情況是因爲底層內存分配器管理導致,比如大多數已經刪除的 key 依然與其他有效的 key分配在同一個內存頁中。
另外,分配器爲了複用空閒的內存塊,原有 5GB 的數據中刪除了 2 GB 後,當再次添加數據到實例中,Redis 的 RSS 會保持穩定,不會增長太多。
因爲內存分配器基本上覆用了之前刪除釋放出來的 2GB 內存。
鍵值對大小不一樣和刪改操作
由於內存分配器是按照固定大小分配內存,所以通常分配的內存空間比實際數據佔用的大小多一些,會造成碎片,降低內存的存儲效率。
另外,鍵值對的頻繁修改和刪除,導致內存空間的擴容和釋放,比如原本佔用 32 字節的字符串,現在修改爲佔用 20 字節的字符串,那麼釋放出的 12 字節就是空閒空間。
如果下一個數據存儲請求需要申請 13 字節的字符串,那麼剛剛釋放的 12 字節空間無法使用,導致碎片。
碎片最大的問題:空間總量足夠大,但是這些內存不是連續的,可能大致無法存儲數據。
內存碎片解決之道
那該如何解決呢?
首先要確定是否發生了內存碎片,重點關注前面 INFO memory
命令提示的 mem_fragmentation_ratio
指標,表示內存碎片率:
mem_fragmentation_ratio = used_memory_rss/ used_memory
如果 1 < 碎片率 < 1.5,可以認爲是合理的,而大於 1.5 說明碎片已經超過 50%,我們需要採取一些手段解決碎片率過大的問題。
重啓大法
最簡單粗暴的方式就是重啓,如果沒有開啓持久化,數據會丟失。
開啓持久化的話,需要使用 RDB 或者 AOF 恢復數據,如果只有一個實例,數據大的話會導致恢復階段長時間無法提供服務,高可用大打折扣。
咋辦呢?碼哥靚仔
自動清理內存碎片
既然你都叫我靚仔了,就傾囊相助告訴你終極殺招:Redis 4.0 版本後,自身提供了一種內存碎片清理機制。
怎麼清理呢?
很簡單,還是上面的例子,想要買兩張連在一塊的電影票。與與別人溝通調換下位置,就實現了。
對於 Redis 來說,當一塊連續的內存空間被劃分爲好幾塊不連續的空間的時候,操作系統先把數據以依次挪動拼接在一塊,並釋放原來數據佔據的空間,形成一塊連續空閒內存空間。。
如下圖所示:
自動清理內存碎片的代價
自動清理雖好,可不要肆意妄爲,操作系統把數據移動到新位置,再把原有空間釋放是需要消耗資源的。
Redis 操作數據的指令是單線程,所以在數據複製移動的時候,只能等待清理碎片完成才能處理請求,造成性能損耗。
如何避免清理碎片對性能的影響又能實現自動清理呢?
好問題,通過以下兩個參數來控制內存碎片清理和結束時機,避免佔用 CPU 過多,減少清理碎片對 Redis 處理請求的性能影響。
開啓自動內存碎片清理
CONFIG SET activedefrag yes
這只是開啓自動清理,何時清理要同時滿足以下兩個條件纔會觸發清理操作。
清理的條件
active-defrag-ignore-bytes 200mb
:內存碎片佔用的內存達到 200MB,開始清理;
active-defrag-threshold-lower 20
:內存碎片的空間佔慚怍系統分配給 Redis 空間的 20% ,開始清理。
避免對性能造成影響
清理時間有了,還需要控制清理對性能的影響。由一項兩個設置先分配清理碎片佔用的 CPU 資源,保證既能正常清理碎片,又能避免對 Redis 處理請求的性能影響。
active-defrag-cycle-min 20
:自動清理過程中,佔用 CPU 時間的比例不低於 20%,從而保證能正常展開清理任務。
active-defrag-cycle-max 50
:自動清理過程佔用的 CPU 時間比例不能高於 75%,超過的話就立刻停止清理,避免對 Redis 的阻塞,造成高延遲。
總結
如果你發現明明 Redis 存儲數據的內存佔用遠小於操作系統分配給 Redis 的內存,而又無法保存數據,那可能出現大量內存碎片了。
通過 info memory
命令,看下內存碎片mem_fragmentation_ratio
指標是否正常。
那麼我們就開啓自動清理併合理設置清理時機和 CPU 資源佔用,該機制涉及到內存拷貝,會對 Redis 性能造成潛在風險。
如果遇到 Redis 性能變慢,排查下是否由於清理碎片導致,如果是,那就調小 active-defrag-cycle-max
的值。
碼哥創建了技術羣摸魚羣,想要與各個城市的程序員一起摸魚打屁聊人生學技術就加羣吧。
最後,可以我叫我一聲靚仔麼?你有什麼問題想對碼哥說麼?在留言區留言吧,知無不言。
點贊、收藏、分享走起!
參考
[1].Redis 核心技術與實戰
[2].https://juejin.cn/post/6844903967298682893#heading-4
[3].https://redis.io/docs/reference/optimization/memory-optimization/#memory-allocation