為什麼伺服器上的 Redis 記憶體會暴漲

在真實的伺服器租用環境中,Redis 記憶體暴漲很少意味著「伺服器突然抽風了」。它通常指向某種可以被定位的模式:例如 key 數量成長、淘汰行為異常、持久化開銷、配置器重用,或客戶端壓力升高。對於在日本基礎設施上執行低延遲業務的工程師來說,這一點尤其關鍵,因為 Redis 記憶體暴漲往往會進一步引發 swap 活動、尾延遲上升、寫入失敗,以及同一節點上其他服務被連帶干擾。好消息是,只要按照正確順序檢查記憶體計數器、TTL 紀律、key 結構與工作負載時序,大多數故障都能解釋清楚。
Redis 之所以快,是因為它把工作資料保存在記憶體中;但也正因為如此,記憶體行為會被放大得非常明顯。關聯式資料庫往往還能藉助儲存延遲暫時掩蓋低效設計,而 Redis 不行。當記憶體占用突然升高時,根因通常並非某一個巨大的錯誤,而是若干較小的設計選擇在錯誤的時間疊加到了一起:某處漏設過期時間、某個集合異常膨脹、一次背景重寫恰逢流量高峰,或者冷啟動部署後的快取回填風暴。根據官方文件,如果沒有設定記憶體上限,Redis 會持續按需申請記憶體;而且即便 key 被刪除,記憶體也不會總是立刻歸還給作業系統,因為配置器是以頁粒度管理記憶體的。
記憶體突然跳升通常會呈現什麼樣子
從外部現象看,這類故障往往很簡單:行程體積變大、可用記憶體下降、應用回應時間變得不穩定;如果涉及記憶體上限或淘汰策略,寫入還可能失敗。但在 Redis 內部,不同指標講述的故事並不總是一致。官方說明明確區分了邏輯記憶體使用量與常駐記憶體集大小,後者即便在 key 被刪除後仍可能維持高位,因為記憶體頁並不會總是立刻返還給系統。
- 邏輯成長:key 變多、value 變大,或兩者同時發生。
- 物理成長:常駐記憶體上升速度快於應用層可見的實際使用量。
- 瞬時成長:背景持久化或高寫入命令把記憶體推高到穩態之上。
- 策略型症狀:淘汰數量增加,或者寫命令開始回傳記憶體不足錯誤。
這個區別很重要。如果你把配置器保留記憶體誤判為洩漏,重新啟動實例也解決不了根因;如果你把快取回填高峰視為「正常波動」,卻忽略了此時背景重寫正在進行,就可能踩中完全可以避免的故障窗口。
Redis 記憶體暴漲最常見的原因
大多數問題其實都能歸入少數幾類技術原因,而不是神祕故障。在生產級伺服器租用環境中,下面這些模式反覆出現。
- 新快取條目在短時間內大量湧入。 流量高峰、爬蟲風暴、發布事件,或快取未命中洪峰,都可能使 key 數量迅速上升。如果應用程式碼在發現快取缺失後進行激進式回填,記憶體成長速度往往會比請求量看起來更誇張。
- Big Key。 一個異常龐大的 string、hash、set、list 或 sorted set,就足以扭曲整體記憶體輪廓。官方關於 key 空間使用的說明提到,key 長度與 value 結構都會影響記憶體成本,因此糟糕的 key 設計會隨著規模擴大而迅速惡化。
- 缺少 TTL。 沒有嚴格過期機制的快取,本質上只是「野心勃勃的記憶體資料庫」。Redis 支援 TTL 和過期控制,但如果寫入 key 時沒有附帶過期時間,陳舊物件就會不斷累積,直到記憶體壓力肉眼可見。
- 過期回收滯後。 即便設定了 TTL,也不等於記憶體會立刻釋放。大量「接近死亡」的 key 可能在清理真正完成前持續抬高記憶體水位。
- 碎片化與配置器重用。 Redis 官方文件明確提醒,刪除後的記憶體不一定會馬上返還給作業系統,因此即便完成清理,RSS 也可能維持高位。
- 持久化開銷。 快照與追加日誌維護在高寫入時期都可能暫時增加記憶體壓力。官方關於淘汰策略的說明也建議,在啟用持久化時預留額外 RAM 以容納緩衝區。
- 沒有有效的記憶體上限。 如果
maxmemory沒有設定,或者設定得不切實際,Redis 就會持續吞噬可用 RAM,直到操作環境整體變得不穩定。官方文件建議明確設定記憶體上限,而不是放任行程無限成長。 - 客戶端連線過多。 客戶端狀態同樣占用記憶體。官方關於客戶端處理的說明指出,大量連線會明顯增加記憶體消耗,甚至可能進一步誘發淘汰或記憶體不足問題。
如何判斷到底是哪一種原因擊中了你的伺服器
最快的方法不是盯著單一指標發呆,而是建立一條簡短但有效的證據鏈。先看 Redis 記憶體計數器,再檢查 key 的形態,最後把這些發現與業務負載發生的時間點對齊。
- 檢查
INFO memory,查看邏輯記憶體使用量、RSS、碎片化指標以及設定的記憶體上限。 - 觀察淘汰行為,以及實例是否已經逼近設定上限。
- 抽樣檢查 TTL 覆蓋率,確認所謂「快取」key 是否真的會過期。
- 查找超大的集合或意外肥胖的 value。
- 把故障時間點與發布窗口、流量波峰或持久化任務對應起來。
如果邏輯記憶體和 RSS 同時上升,那麼你的資料集大概率確實在成長;如果邏輯記憶體趨於穩定,但 RSS 長時間居高不下,那麼更應懷疑碎片化或配置器保留;如果記憶體是在某些會產生臨時大結果集的命令執行期間突然跳高,官方關於淘汰的說明指出,Redis 可能在淘汰動作把記憶體拉回之前,短暫超過已設定的上限。
Big Key 的危險往往比看上去更大
工程師通常習慣先看總 key 數,但 key 的分布同樣重要。上百萬個適中的 key,往往比少數幾個病態巨型 key 更容易管理。Big Key 之所以危險,是因為它會從多個維度放大風險:
- 它會快速抬高記憶體占用。
- 它會加重複製與持久化負擔。
- 它會增加序列化與網路傳輸延遲。
- 它會讓淘汰行為變得更難預測。
解決它通常靠架構調整,而不是表面修補。把超大聚合拆開,避免把單個 key 作為持續成長的「大桶」,並根據讀取模式選擇匹配的資料結構,而不是把混雜負載一股腦塞進一個地方。另外,key 名稱也應盡量緊湊;官方關於 key 空間的說明提到,較短的 key 確實能節省一部分記憶體,儘管可讀性仍然重要。
TTL 紀律往往是很多快取系統悄悄失效的地方
紙面上說「我們把 Redis 當快取來用」聽起來很安全;但到了程式碼裡,團隊經常無法在所有寫入路徑上統一執行過期控制。一個介面會設定 TTL,另一個介面卻跳過它;一個批次處理任務為了方便直接寫永久 key;六週之後,這個實例表現出來的更像是一個封存系統,而不是快取層。官方關於 TTL 和快取模式的文件強調,過期機制是控制記憶體行為的核心手段之一。
好的 TTL 策略並不只是「隨便設一個值」。它應當反映物件變化頻率、回填成本和故障容忍度。短生命週期的衍生資料應該更積極地過期;昂貴但可重建的物件可以適當存活更久;接近永久的運行狀態則應隔離存放,避免扭曲整個快取層。如果你的 Redis 記憶體暴漲總是週期性重演,那麼 TTL 覆蓋不一致幾乎一定是重點嫌疑對象。
淘汰策略既可能救命,也可能掩蓋問題
Redis 提供了多種淘汰策略,官方說明解釋了:當記憶體越過設定上限時,不同策略將決定實例如何處理後續壓力。基於全部 key 的策略與僅基於帶過期 key 的策略,在壓力場景下表現完全不同;而 noeviction 會把記憶體壓力直接轉化為明確寫入失敗,而不是靜默地清掉資料。
對除錯而言,這個差異至關重要:
- 如果啟用了淘汰,伺服器表面上可能「穩定」,但實際上正在默默丟棄有價值的資料。
- 如果禁用了淘汰,應用會更快暴露錯誤;這雖然痛苦,卻足夠誠實。
- 如果只有帶 TTL 的 key 能被淘汰,那麼那些沒有過期時間的持久 key 就可能把實例困在持續高壓循環中。
換句話說,淘汰策略不能替代記憶體衛生。它是最後一道控制線,而不是為不良設計開脫的藉口。
持久化會製造短暫但真實存在的記憶體壓力
很多團隊低估了背景持久化任務與流量高峰相互疊加時的影響。在生成快照或重寫日誌期間,記憶體表現往往會比穩態預期更糟。官方文件建議,在啟用持久化或複製時,為緩衝區預留足夠空閒 RAM,因為記憶體核算絕不只有使用者 key 本身。
這意味著,如果一台伺服器僅按平均資料集容量來規劃資源,那麼它其實處在危險邊緣。對於寫入負載具有突發特徵的業務,一次背景持久化就可能與快取回填事件撞在一起,從而製造出一次劇烈但完全可以解釋的記憶體暴漲。
客戶端記憶體同樣是故事的一部分
Redis 故障往往被全部歸咎於資料本身,但連線模式也可能是隱蔽的放大器。官方關於客戶端的說明指出,客戶端連線會消耗記憶體,而大量客戶端完全可能對總記憶體使用造成實質影響。
- 過大的連線池
- 跨多個服務長期閒置卻不關閉的連線
- 帶有大量客戶端狀態的發布訂閱或阻塞模式
- 慢消費者導致輸出緩衝區膨脹
如果你的資料集看起來並不離譜,但記憶體仍然持續攀升,那麼在怪罪配置器之前,先檢查客戶端側。
一套適合工程師的實戰除錯流程
當生產級伺服器租用節點上出現 Redis 記憶體暴漲時,速度固然重要,但盲目調參同樣危險。更穩妥的流程通常如下:
- 確認成長形態。 到底是資料集成長、RSS 保留、客戶端記憶體,還是短暫的持久化事件?
- 檢查護欄是否存在。 是否設定了
maxmemory?淘汰模式是否與工作負載匹配?官方文件建議明確設定上限。 - 抽樣 key 類別。 找出在故障期間擴張的是哪些前綴、物件類型或集合。
- 稽核 TTL 覆蓋率。 確認原本應該帶過期時間的快取寫入路徑是否真的統一附帶了過期設定。
- 對齊時間線。 把暴漲與發布、排程任務、故障切換或流量波峰進行比對。
- 複核連線壓力。 統計客戶端數量,並檢查是否存在會放大單連線記憶體成本的模式。
這樣的順序可以避免過度反應。重新啟動實例可能暫時壓低 RSS,但如果真正的問題是不受控的 key 成長,那麼下一次暴漲其實已經在路上了。
如何降低下一次故障再次發生的機率
預防問題的關鍵,並不是某一個「神級參數」,而是樸素而持續的一致性。
- 設定合理的記憶體上限,並保留維運餘量。
- 選擇與快取語意相匹配的淘汰策略,而不是基於一廂情願的假設。
- 在應用邊界強制 TTL,而不是只靠團隊約定。
- 在程式碼審查階段就拒絕 big key 模式。
- 同時監控邏輯記憶體與 RSS,使碎片化問題可見。
- 為持久化與複製預留成本,而不是把它們當成「免費功能」。
- 關注客戶端數量及緩衝區行為。
對於在日本基礎設施上運行延遲敏感型服務的團隊而言,這些做法尤其有價值,因為它們能讓快取層在區域性流量集中的場景下依然保持可預測表現。良好的 Redis 衛生習慣看起來並不炫技,但它確實能阻止一個吵雜的快取層演變成整台節點的故障源。
結論
Redis 記憶體暴漲通常只是某種設計或維運問題浮出水面的結果,而不是隨機事件。應先從記憶體計數器著手,比對邏輯使用量與 RSS,檢查 TTL 覆蓋率,排查 big key,並把持久化與客戶端開銷納入分析範圍之後再做調整。在紀律良好的伺服器租用環境中,Redis 記憶體暴漲事件其實既更容易解釋,也更容易預防。最有效的修復方案往往並不戲劇化:更嚴格的過期控制、更合理的 key 建模、更現實的記憶體上限,以及在快取最繁忙時仍然留有餘量的資源規劃。Redis 記憶體暴漲。
