共計 4709 個字符,預計需要花費 12 分鐘才能閱讀完成。
這篇文章主要介紹“Redis 內存滿了然后去優化”的相關知識,丸趣 TV 小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Redis 內存滿了然后去優化”文章能幫助大家解決問題。
Redis 內存滿了怎么辦?怎么優化內存?MySQL 里有 2000w 數據,redis 中只存 20w 的數據,如何保證 redis 中的數據都是熱點數據
redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。
Redis 主要消耗什么物理資源?
內存。
Redis 的內存用完了會發生什么?
如果達到設置的上限,Redis 的寫命令會返回錯誤信息(但是讀命令還可以正常返回。)或者你可以配置內存淘汰機制,當 Redis 達到內存上限時會沖刷掉舊的內容。
談談緩存數據的淘汰機制
Redis 緩存有哪些淘汰策略?
不進行數據淘汰的策略,只有 noeviction 這一種。
會進行淘汰的 7 種策略,我們可以再進一步根據淘汰候選數據集的范圍把它們分成兩類:
在設置了過期時間的數據中進行淘汰,包括 volatile-random、volatile-ttl、volatile-lru、volatile-lfu 四種。
在所有數據范圍內進行淘汰,包括 allkeys-lru、allkeys-random、allkeys-lfu 三種。
策略規則 volatile-ttl 在篩選時,會針對設置了過期時間的鍵值對,根據過期時間的先后進行刪除,越早過期的越先被刪除。volatile-random 在設置了過期時間的鍵值對中,進行隨機刪除。volatile-lru 使用 LRU 算法篩選設置了過期時間的鍵值對 volatile-lfu 使用 LFU 算法選擇設置了過期時間的鍵值對策略規則 allkeys-random 從所有鍵值對中隨機選擇并刪除數據;allkeys-lru 使用 LRU 算法在所有數據中進行篩選 vallkeys-lfu 使用 LFU 算法在所有數據中進行篩選談談 LRU 算法
是按照最近最少使用的原則來篩選數據,最不常用的數據會被篩選出來,而最近頻繁使用的數據會留在緩存中。
那具體是怎么篩選的呢?LRU 會把所有的數據組織成一個鏈表,鏈表的頭和尾分別表示 MRU 端和 LRU 端,分別代表最近最常使用的數據和最近最不常用的數據。
LRU 算法背后的想法非常樸素:它認為剛剛被訪問的數據,肯定還會被再次訪問,所以就把它放在 MRU 端;長久不訪問的數據,肯定就不會再被訪問了,所以就讓它逐漸后移到 LRU 端,在緩存滿時,就優先刪除它。
問題:LRU 算法在實際實現時,需要用鏈表管理所有的緩存數據,這會帶來額外的空間開銷。而且,當有數據被訪問時,需要在鏈表上把該數據移動到 MRU 端,如果有大量數據被訪問,就會帶來很多鏈表移動操作,會很耗時,進而會降低 Redis 緩存性能。
解決:
在 Redis 中,LRU 算法被做了簡化,以減輕數據淘汰對緩存性能的影響。具體來說,Redis 默認會記錄每個數據的最近一次訪問的時間戳(由鍵值對數據結構 RedisObject 中的 lru 字段記錄)。然后,Redis 在決定淘汰的數據時,第一次會隨機選出 N 個數據,把它們作為一個候選集合。接下來,Redis 會比較這 N 個數據的 lru 字段,把 lru 字段值最小的數據從緩存中淘汰出去。
當需要再次淘汰數據時,Redis 需要挑選數據進入第一次淘汰時創建的候選集合。這兒的挑選標準是:能進入候選集合的數據的 lru 字段值必須小于候選集合中最小的 lru 值。當有新數據進入候選數據集后,如果候選數據集中的數據個數達到了 maxmemory-samples,Redis 就把候選數據集中 lru 字段值最小的數據淘汰出去。
使用建議:
優先使用 allkeys-lru 策略。這樣,可以充分利用 LRU 這一經典緩存算法的優勢,把最近最常訪問的數據留在緩存中,提升應用的訪問性能。如果你的業務數據中有明顯的冷熱數據區分,我建議你使用 allkeys-lru 策略。
如果業務應用中的數據訪問頻率相差不大,沒有明顯的冷熱數據區分,建議使用 allkeys-random 策略,隨機選擇淘汰的數據就行。
如果你的業務中有置頂的需求,比如置頂新聞、置頂視頻,那么,可以使用 volatile-lru 策略,同時不給這些置頂數據設置過期時間。這樣一來,這些需要置頂的數據一直不會被刪除,而其他數據會在過期時根據 LRU 規則進行篩選。
如何處理被淘汰的數據?
一旦被淘汰的數據選定后,如果這個數據是干凈數據,那么我們就直接刪除;如果這個數據是臟數據,我們需要把它寫回數據庫。
那怎么判斷一個數據到底是干凈的還是臟的呢?
干凈數據和臟數據的區別就在于,和最初從后端數據庫里讀取時的值相比,有沒有被修改過。干凈數據一直沒有被修改,所以后端數據庫里的數據也是最新值。在替換時,它可以被直接刪除。
而臟數據就是曾經被修改過的,已經和后端數據庫中保存的數據不一致了。此時,如果不把臟數據寫回到數據庫中,這個數據的最新值就丟失了,就會影響應用的正常使用。
即使淘汰的數據是臟數據,Redis 也不會把它們寫回數據庫。所以,我們在使用 Redis 緩存時,如果數據被修改了,需要在數據修改時就將它寫回數據庫。否則,這個臟數據被淘汰時,會被 Redis 刪除,而數據庫里也沒有最新的數據了。
Redis 怎么優化內存?
1、控制 key 的數量:當使用 Redis 存儲大量數據時,通常會存在大量鍵,過多的鍵同樣會消耗大量內存。Redis 本質是一個數據結構服務器,它為我們提供多種數據結構,如 hash,list,set,zset 等結構。使用 Redis 時不要進入一個誤區,大量使用 get/set 這樣的 API,把 Redis 當成 Memcached 使用。對于存儲相同的數據內容利用 Redis 的數據結構降低外層鍵的數量,也可以節省大量內存。
2、縮減鍵值對象,降低 Redis 內存使用最直接的方式就是縮減鍵(key)和值(value)的長度。
key 長度:如在設計鍵時,在完整描述業務情況下,鍵值越短越好。
value 長度:值對象縮減比較復雜,常見需求是把業務對象序列化成二進制數組放入 Redis。首先應該在業務上精簡業務對象,去掉不必要的屬性避免存儲無效數據。其次在序列化工具選擇上,應該選擇更高效的序列化工具來降低字節數組大小。
3、編碼優化。Redis 對外提供了 string,list,hash,set,zet 等類型,但是 Redis 內部針對不同類型存在編碼的概念,所謂編碼就是具體使用哪種底層數據結構來實現。編碼不同將直接影響數據的內存占用和讀寫效率。
1、redisObject 對象
type 字段:
利用集合類型數據,因為通常情況下很多小的 Key-Value 可以用更緊湊的方式存放到一起。盡可能使用散列表(hashes),散列表(是說散列表里面存儲的數少)使用的內存非常小,所以你應該盡可能的將你的數據模型抽象到一個散列表里面。比如你的 web 系統中有一個用戶對象,不要為這個用戶的名稱,姓氏,郵箱,密碼設置單獨的 key,而是應該把這個用戶的所有信息存儲到一張散列表里面。
encoding 字段:
采用不同的編碼實現內存占用存在明顯差異
lru 字段:
開發提示:可以使用 scan + object idletime 命令批量查詢哪些鍵長時間未被訪問,找出長時間不訪問的鍵進行清理降低內存占用。
refcount 字段:
當對象為整數且范圍在 [0-9999] 時,Redis 可以使用共享對象的方式來節省內存。
ptr 字段 :
開發提示:高并發寫入場景中,在條件允許的情況下建議字符串長度控制在 39 字節以內,減少創建 redisObject 內存分配次數從而提高性能。
2、縮減鍵值對象
降低 Redis 內存使用最直接的方式就是縮減鍵(key)和值(value)的長度。
可以使用通用壓縮算法壓縮 json,xml 后再存入 Redis,從而降低內存占用
3、共享對象池
對象共享池指 Redis 內部維護[0-9999] 的整數對象池。創建大量的整數類型 redisObject 存在內存開銷,每個 redisObject 內部結構至少占 16 字節,甚至超過了整數自身空間消耗。所以 Redis 內存維護一個 [0-9999] 的整數對象池,用于節約內存。除了整數值對象,其他類型如 list,hash,set,zset 內部元素也可以使用整數對象池。因此開發中在滿足需求的前提下,盡量使用整數對象以節省內存。
當設置 maxmemory 并啟用 LRU 相關淘汰策略如:volatile-lru,allkeys-lru 時,Redis 禁止使用共享對象池。
為什么開啟 maxmemory 和 LRU 淘汰策略后對象池無效?
LRU 算法需要獲取對象最后被訪問時間,以便淘汰最長未訪問數據,每個對象最后訪問時間存儲在 redisObject 對象的 lru 字段。對象共享意味著多個引用共享同一個 redisObject,這時 lru 字段也會被共享,導致無法獲取每個對象的最后訪問時間。如果沒有設置 maxmemory,直到內存被用盡 Redis 也不會觸發內存回收,所以共享對象池可以正常工作。
綜上所述,共享對象池與 maxmemory+LRU 策略沖突,使用時需要注意。
為什么只有整數對象池?
首先整數對象池復用的幾率最大,其次對象共享的一個關鍵操作就是判斷相等性,Redis 之所以只有整數對象池,是因為整數比較算法時間復雜度為 O(1),只保留一萬個整數為了防止對象池浪費。如果是字符串判斷相等性,時間復雜度變為 O(n),特別是長字符串更消耗性能(浮點數在 Redis 內部使用字符串存儲)。對于更復雜的數據結構如 hash,list 等,相等性判斷需要 O(n2)。對于單線程的 Redis 來說,這樣的開銷顯然不合理,因此 Redis 只保留整數共享對象池。
4、字符串優化
Redis 沒有采用原生 C 語言的字符串類型而是自己實現了字符串結構,內部簡單動態字符串,簡稱 SDS。
字符串結構:
特點:
O(1)時間復雜度獲取:字符串長度,已用長度,未用長度。
可用于保存字節數組,支持安全的二進制數據存儲。
內部實現空間預分配機制,降低內存再分配次數。
惰性刪除機制,字符串縮減后的空間不釋放,作為預分配空間保留。
預分配機制:
開發提示: 盡量減少字符串頻繁修改操作如 append,setrange, 改為直接使用 set 修改字符串,降低預分配帶來的內存浪費和內存碎片化。
字符串重構:基于 hash 類型的二級編碼方式。
二級編碼怎么用?
二級編碼方法中采用的 ID 長度是有講究的。
涉及到一個問題–Hash 類型底層結構小于設定值時使用壓縮列表,大于設定值時使用哈希表。
一旦從壓縮列表轉為了哈希表,Hash 類型會一直用哈希表進行保存,而不會再轉回壓縮列表。
在節省內存空間方面,哈希表就沒有壓縮列表那么高效。為能充分使用壓縮列表的精簡內存布局,一般要控制保存在 Hash 中的元素個數。
5. 編碼優化
使用壓縮列表 ziplist 編碼的 hash 類型依然比使用 hashtable 編碼的集合節省大量內存。
6. 控制 key 的數量
開發提示:使用 ziplist+hash 優化 keys 后,如果想使用超時刪除功能,開發人員可以存儲每個對象寫入的時間,再通過定時任務使用 hscan 命令掃描數據,找出 hash 內超時的數據項刪除即可。
當 Redis 內存不足時,首先考慮的問題不是加機器做水平擴展,應該先嘗試做內存優化。當遇到瓶頸時,再去考慮水平擴展。即使對于集群化方案,垂直層面優化也同樣重要,避免不必要的資源浪費和集群化后的管理成本。
關于“Redis 內存滿了然后去優化”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注丸趣 TV 行業資訊頻道,丸趣 TV 小編每天都會為大家更新不同的知識點。