Redis(1.19)redis內存消耗、redis內存優化


參考自《redis開發與運維》

1. 內存消耗

1.1 內存使用統計

    info memory指令,重點內容如下:

    used_memory:redis內部數據所占內存總量

    used_memory_rss:從操作系統角度看redis占用的內存總量

    used_memory_peak:used_memory的峰值

    mem_fragmentation_ratio:內存碎片率,used_memory_rss/info memory

    如果內存碎片率大於1很多,則說明碎片化嚴重,如果小於1表示redis把內存里的數據交換到了硬盤,需要特別注意這個參數。

1.2 內存組成

    (1)自身內存:很小,可忽略不計

    (2)對象內存:包括redis中所有的key和對應的值所占的內存空間,因此應當避免過長的鍵,而值需要根據具體類型選擇相應的編碼。

    (3)緩沖內存:

        客戶端緩沖:

        輸入輸出緩沖區,輸入緩沖區不可控,最大1G,輸出緩沖區可以通過client-output-buffer-limit 參數控制。

        普通客戶端輸出緩沖區:redis默認是不限制,一般普通客戶端的內存消耗可以忽略不計,但某些指令如monitor就需要特別關注。

        從客戶端輸出緩沖區:默認為client-output-buffer-limit slave256mb64mb60,當主從節點網絡不好(如跨機房)或者主節點掛載多個從節點時,可能導致輸出緩沖區過大。

        訂閱客戶端輸出緩沖區:默認為client-output-buffer-limit pubsub32mb8mb60,當消息生產者消息生成過快而消費速度過慢時,也有可能造成輸出緩沖區溢出

        復制積壓緩沖區:redis2.8后才有,在主節點中存在,和所有從節點共享,用於實現部分復制功能,可以通過參數repl-backlog-size適當調大(如100M),它可以有效避免全量復制,默認為1M。

        AOF緩沖區:用於redis在使用AOF方式時持久化數據到磁盤,無法控制,內存消耗取決於AOF持久時間和寫入命令量。一般較小。

    (4)內存碎片:根redis內存分配有關

        redis默認使用jemalloc進行內存分配。它將內存空間划分為小、中、大三個范圍,每個范圍又划分為多個小的內存塊單位,比如保存5k對象時,需要用8k的塊進行存儲,一定程度上會出現內存碎片。內存碎片率正常在1.03左右。

    內存碎片出現場景:頻繁做更新操作,如對已經存在的鍵做append、setrange操作;大量過期鍵刪除,釋放的空間不能有效利用時。

    內存碎片解決方案:安全重啟(單實例則直接重啟服務,有主從集群等關系就切換節點為從節點然后重啟服務)和數據對齊(數據盡量采用固定長度字符串或數字類型)

     

 

 

 

1.3 子進程內存消耗

    當redis在執行AOF或RDB持久化時,需要fork一個子進程來進行持久化,而子進程的內存消耗和父進程相同,因此Linux出現了寫時復制的技術,即父進程只有在寫的時候才會復制相關頁,而子進程讀取父進程的內存進行持久化,即子進程讀取的是fork時的父進程內存快照。

    THP問題:即父進程在復制相關內存時由原來的4k(內存頁單位)變成2M,如果父進程有大量寫,會加重內存拷貝,從而造成過度內存消耗。因此需要關閉THP功能。

2. 內存管理

2.1 設置內存上限

    maxmemory限制最大可用內存,可用防止redis的內存超過物理內存。

    注意:maxmemory是redis實際使用的內存量,也就是used_memory統計的內存,即對象內存區域,不包括緩沖區、內存碎片,因此需要注意這部分內存溢出。

2.2 動態調整內存上限

    通過config set maxmemory進行動態修改最大可用內存。

    2.3 內存回收策略

2.3.1 刪除過期鍵對象

    redis 會將每個設置了過期時間的 key 放入到一個獨立的字典中,以后會定時遍歷這個字典來刪除到期的 key。

    惰性刪除,當客戶端讀取帶有超時屬性的鍵時,如果該鍵已經過時,那么刪除該鍵值,並返回空。如果過期鍵一直沒有讀取,則不會及時釋放。因此有了定時任務刪除模式。

    定期任務刪除: Redis 默認會每秒進行10次(通過hz控制)過期掃描,過期掃描不會遍歷過期字典中所有的 key,而是采用了一種簡單的貪心策略。

        從過期字典中隨機 20 個 key;
        刪除這 20 個 key 中已經過期的 key;
        如果過期的 key 比率超過 1/4,那就重復步驟 1;

 該策略中采用了自適應快慢兩種算法運行模式來回收鍵,流程如下:

 1)定時任務在每個數據庫空間隨機檢查20個鍵,發現過期時刪除對應的鍵。

 2)如果檢索出來的鍵超過25%都是過期的鍵,那么將會循環執行回收邏輯操作 直到比例不足 25% 或運行到超時為止(為了避免過期掃描或者循環回收過度導致卡死,慢模式回收默認是25毫秒超時)

 3)如果之前的回收鍵邏輯超時,則在 Redis出發內部事件之前再次以快模式運行回收過期任務,快模式下超時時間為1毫秒,且 2 秒內只能運行一次。

 4)快慢模式的內部刪除回收邏輯相同,只是超時事件不同罷了。

    如果某一時刻,有大量key同時過期,Redis 會持續掃描過期字典,造成客戶端響應卡頓,因此設置過期時間時,就盡量避免這個問題,在設置過期時間時,可以給過期時間設置一個隨機范圍,避免同一時刻過期。

2.3.2 內存溢出控制策略

    (1)noeviction:默認策略,不刪除數據,也拒絕寫入,即redis變成只讀操作。

    (2)volatile-lru:根據LRU算法刪除設置了超時屬性的鍵,直到有足夠的空間為止。如果沒有可刪除對象,回退到默認策略。

    (3)allkeys-lru:根據lru算法刪除鍵,不管數據是否設置了超時屬性,直到騰出足夠空間為止

    (4)allkeys-random:隨機刪除所有鍵,直到騰出足夠空間為止。

    (5)volatile-random:隨機刪除過期鍵,直到騰出足夠空間為止。

    (6)volatile-ttl:根據鍵值對象的ttl屬性刪除最近將要過期數據,如果沒有回退到默認策略

    內存溢出控制策略可以采用config set maxmemory-policy{policy}動態配置。當Redis一直工作在內存溢出(used_memory>maxmemory)的狀態下且設置非noeviction策略時,會頻繁地觸發回收內存的操作,影響Redis的性能。建議線上Redis內存工作在maxmemory>used_memory狀態下,避免頻繁內存回收開銷。

3.內存優化

3.1 redisObject對象

Redis存儲的所有值對象在內部定義為redisObject結構體,如下圖所示:

   

 

 

 

    高並發寫入場景中,在條件允許的情況下,建議字符串長度控制在39字節以內,減少創建redisObject內存分配次數,從而提高性能。

3.2 縮減鍵值對象

    降低Redis內存使用最直接的方式就是縮減鍵(key)和值(value)的長度。

    ·key長度:如在設計鍵時,在完整描述業務情況下,鍵值越短越好。

    ·value長度:值對象縮減比較復雜,一般為二進制數組或格式化存儲(如json)。

    在業務上精簡對象,去掉不必要的屬性

    二進制數組在序列化時,選擇更高效的序列化工具來降低字節數組大小

    通用格式存儲數據時,應考慮壓縮速度和計算成本,如Google的Snappy壓縮工具,降低存儲空間。

3.3 共享對象池

    共享對象池是指Redis內部維護[0-9999]的整數對象池。創建大量的整數類型redisObject存在內存開銷,每個redisObject內部結構至少占16字節,甚至超過了整數自身空間消耗。所以Redis內存維護一個[0-9999]的整數對象池,用於節約內存。除了整數值對象,其他類型如list、hash、set、zset內部元素也可以使用整數對象池。因此開發中在滿足需求的前提下,盡量使用整數對象以節省內存。

    可以通過object refcount key 命令查看對象引用數。假設set foo 100,set bar 100這2個指令執行完后,這內存變成如下圖所示:

     

 

 

    注意:以下情況無法使用共享對象

    內存回收策略為maxmemory+LRU時

    值對象編碼為ziplist時,因為ziplist使用壓縮且內存連續的結構,對象共享判斷成本過高

3.4 字符串優化

    redis沒有采用原生C的字符串類型,而是進行了封裝,包括字符串已用長度、空閑長度、字符數組3個部分組成。內部實現空間預分配機制。第一次set時,正常分配,第二次使用append或setrange時,會進行空間的動態擴展,類似Java的ArrayList動態擴容機制,造成內存空間的浪費和內存碎片化加劇。因此在開發時,不要使用append或setrange指令。直接使用 set 

    字符串重構,對於json作為值時,可以使用hash結構進行重構優化。這樣能提高內存使用率,可以使用hmget和hmset讀取和修改部分字段,降低網絡開銷。

3.5 編碼優化

    在redis中,每種類型都對應一個或多個底層數據結構,我們稱之為值的編碼,編碼不同將直接影響數據的內存占用和讀寫效率。可通過object encoding key來查詢。

    編碼類型在redis寫入數據時自動完成,這個過程是不可逆的,轉化規則只能從小內存編碼向大內存編碼轉換。

   

 

 

    3.5.1 ziplist編碼

    ziplist編碼主要目的是為了節約內存,因此所有數據都是采用線性連續的內存結構。ziplist編碼是應用范圍最廣的一種,可以分別作為hash、list、zset類型的底層數據結構實現。

    

 

 

    ziplist壓縮編碼的性能表現跟值長度和元素個數密切相關,正因為如此Redis提供了{type}-max-ziplist-value和{type}-max-ziplist-entries相關參數來做控制ziplist編碼轉換。最后再次強調使用ziplist壓縮編碼的原則:追求空間和時間的平衡,即減少內存空間的占用,但讀寫性能會降低。

    針對性能要求較高的場景使用ziplist,建議長度不要超過1000,每個元素大小控制在512字節以內。

    命令平均耗時使用info Commandstats命令獲取,包含每個命令調用次數、總耗時、平均耗時,單位為微秒。

 3.6 控制鍵的數量

    當使用Redis存儲大量數據時,通常會存在大量鍵,過多的鍵同樣會消耗大量內存。Redis本質是一個數據結構服務器,它為我們提供多種數據結構,如hash、list、set、zset等。使用Redis時不要進入一個誤區,大量使用get/set這樣的API,把Redis當成Memcached使用。對於存儲相同的數據內容利用Redis的數據結構降低外層鍵的數量,也可以節省大量內存。

    使用hash重構后節省內存量效果非常明顯,特別對於存儲小對象的場景,下面分析這種內存優化技巧的關鍵點:

    (1)hash類型節省內存的原理是使用ziplist編碼,如果使用hashtable編碼方式反而會增加內存消耗。

    (2)ziplist長度需要控制在1000以內,否則讀寫長列表會導致CPU消耗嚴重,得不償失。

    (3)ziplist適合存儲小對象,對於大對象不但內存優化效果不明顯還會增加命令操作耗時。

    (4)需要預估鍵的規模,從而確定每個hash結構需要存儲的元素數量。

    (5)根據hash長度和元素大小,調整hash-max-ziplist-entries和hash-max-ziplist-value參數,確保hash類型使用ziplist編碼。

    缺點:

        hash重構后所有的鍵無法再使用超時(expire)和LRU淘汰機制自動刪除,需要手動維護刪除。

        對於大對象,如1KB以上的對象,使用hash-ziplist結構控制鍵數量反而得不償失。

    優點:

        對於大量小對象的存儲場景,非常適合使用ziplist編碼的hash類型控制鍵的規模來降低內存。

 

轉自:https://blog.csdn.net/zhaocuit/article/details/90548716


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM