緩存 --- Redis性能瓶頸和大Key問題
- 內存瓶頸
- 網絡瓶頸
- CPU 瓶頸
- 持久化瓶頸
- 大key問題
- 優化方案
- Redis 是一個高性能的內存數據庫,但在實際使用中,可能會在內存、網絡、CPU、持久化、大鍵值對等方面遇到性能瓶頸。下面從這些方面詳細分析 Redis 的性能瓶頸,并給出相應的解決方法。
內存瓶頸
問題:
- Redis 是一個基于內存的數據庫,所有數據都存儲在內存中,因此內存容量是 Redis 的主要限制。
- 當數據量超過可用內存時,Redis 可能會觸發內存淘汰策略(如 LRU、LFU),導致部分數據被刪除。
- 大鍵值對(如大哈希表、大列表)會占用大量內存,進一步加劇內存壓力。
解決方法:
- 優化數據結構:使用更高效的數據結構(如壓縮列表、整數集合)來減少內存占用。
- 啟用內存淘汰策略:根據業務需求配置合適的內存淘汰策略(如
volatile-lru
、allkeys-lru
)。 - 分片存儲:使用 Redis Cluster 將數據分布到多個節點,分散內存壓力。
- 數據壓縮:在客戶端對數據進行壓縮后再存儲到 Redis 中。
- 監控內存使用:定期監控 Redis 的內存使用情況,及時發現和解決內存問題。
網絡瓶頸
問題:
- Redis 的性能高度依賴于網絡,尤其是在高并發或跨地域部署的場景下。
- 網絡延遲和帶寬限制會影響 Redis 的響應速度。
- 大量小請求可能導致網絡擁塞,增加 Redis 的負載。
解決方法:
- 減少網絡請求:使用批量操作(如
MGET
、MSET
)減少網絡請求次數。 - 優化Redis客戶端連接池:配置合理的客戶端連接池,避免連接數過多或過少。
- 使用內存緩存: 可考慮將一些數據量不大,并且對一致性要求不高的數據移至內存緩存
- 就近部署:將 Redis 部署在離客戶端較近的位置,減少網絡延遲。
- 監控網絡流量:定期監控 Redis 的網絡流量,及時發現和解決網絡瓶頸。
CPU 瓶頸
問題:
- Redis 的核心處理邏輯是單線程的(Redis 6.0 引入了多線程 I/O,但命令執行仍然是單線程的)。
- 復雜命令(如
SORT
、ZUNIONSTORE
)或長耗時操作(如KEYS *
、FLUSHALL
)會占用大量 CPU 資源,阻塞其他命令的執行。
解決方法:
- 避免復雜命令:使用高效命令替代復雜命令(如
SCAN
替代KEYS *
)。 - 使用多線程 I/O:在 Redis 6.0 及以上版本中,啟用多線程 I/O 提升網絡處理能力。
- 分散計算邏輯:將復雜計算邏輯移到客戶端或應用層,減少 Redis 的 CPU 負擔。
- 監控 CPU 使用率:定期監控 Redis 的 CPU 使用率,及時發現和解決 CPU 瓶頸。
持久化瓶頸
問題:
- Redis 提供了兩種持久化方式:RDB(快照)和 AOF(追加日志)。
- RDB 在生成快照時可能會占用大量 CPU 和內存,導致性能下降。
- AOF 的日志追加操作會增加磁盤 I/O 壓力,尤其是在高寫入場景下。
解決方法:
- 選擇合適的持久化方式:根據業務需求選擇合適的持久化方式(如 RDB 適合備份,AOF 適合數據安全)。
- 調整持久化配置:優化 RDB 和 AOF 的配置(如
save
參數、appendfsync
參數)以平衡性能和數據安全。 - 使用混合持久化:在 Redis 4.0 及以上版本中,啟用混合持久化(RDB + AOF)以兼顧性能和數據安全。
- 監控持久化性能:定期監控 Redis 的持久化性能,及時發現和解決持久化瓶頸。
大key問題
大key定義:
- 所謂的大key問題是某個key的value比較大,所以本質上是大value問題
- String類型的Key,它的值為5MB(數據過大)
- List類型的Key,它的列表數量為20000個(列表數量過多)
- ZSet類型的Key,它的成員數量為10000個(成員數量過多)
- Hash格式的Key,它的成員數量雖然只有1000個但這些成員的value總大小為100MB(成員體積過大)
- 在實際業務中,大Key的判定仍然需要根據Redis的實際使用場景、業務場景來進行綜合判斷。通常都會以數據大小與成員數量來判定。
- 一般認為string類型控制在10KB以內,hash、list、set、zset元素個數不要超過10000個。
大 Key 的問題
- 內存占用過高:
大 Key 會占用大量內存,可能導致 Redis 內存不足,觸發內存淘汰策略(如 LRU、LFU),甚至導致 OOM(Out Of Memory)錯誤。- 網絡以及操作性能下降:
對大 Key 的操作(如 HGETALL、LRANGE、SMEMBERS)會消耗大量 CPU 和網絡資源,導致 Redis 響應變慢。- 大 Key 的操作可能會阻塞 Redis 的單線程模型,影響其他命令的執行。
- 持久化性能問題:
在生成 RDB 快照或 AOF 日志時,大 Key 會導致持久化操作變慢,增加 Redis 的負載。
數據遷移困難:
在 Redis Cluster 中,大 Key 的數據遷移會占用大量網絡帶寬,影響集群的穩定性。- 故障恢復時間長:
如果 Redis 實例發生故障,大 Key 的恢復時間會顯著增加,影響服務的可用性。
大key的產生:
- 大key的產生往往是業務方設計不合理,沒有預見vaule的動態增長問題
- redis數據結構使用不合理,易造成Key的value過大,如使用String類型的Key存放大體積二進制文件型數據
- 業務上線前規劃設計不足,沒有對Key中的成員進行合理的拆分,造成個別Key中的成員數量過多
- 沒有對無效數據進行定期清理,造成如HASH類型Key中的成員持續不斷的增加。即一直往value塞數據,卻沒有刪除機制,value只會越來越大
- 在實際業務中,可能會發生的大key場景:
- 社交類:粉絲列表,如果某些明星或者大v不精心設計下,必是bigkey;
- 統計類:例如按天存儲某項功能或者網站的用戶集合,除非沒幾個人用,否則必是bigkey;
- 緩存類:將數據從數據庫load出來序列化放到Redis里,這個方式非常常用,但有兩個地方需要注意,第一,是不是有必要把所有字段都緩存,第二,有沒有相關關聯的數據。
優化方案
刪除大key
- 首先考慮刪除大key,如果發現某些大key并非熱key就可以在DB中查詢使用,則可以在Redis中刪掉:
- Redis 4.0及之后版本:您可以通過UNLINK命令安全地刪除大Key甚至特大Key,該命令能夠以非阻塞的方式,逐步地清理傳入的Key。 Redis UNLINK 命令類似與 DEL 命令,表示刪除指定的 key,如果指定 key 不存在,命令則忽略。 UNLINK 命令不同與 DEL
命令在于它是異步執行的,因此它不會阻塞。 UNLINK 命令是非阻塞刪除,非阻塞刪除簡言之,就是將刪除操作放到另外一個線程去處理。- Redis 4.0之前的版本:建議先通過SCAN命令讀取部分數據,然后進行刪除,避免一次性刪除大量key導致Redis阻塞。 Redis Scan 命令用于迭代數據庫中的數據庫鍵。 SCAN 命令是一個基于游標的迭代器,每次被調用之后, 都會向用戶返回一個新的游標,
用戶在下次迭代時需要使用這個新游標作為 SCAN 命令的游標參數, 以此來延續之前的迭代過程。
壓縮大key
- 可以采用壓縮法, 考慮到使用合適的序列化框架、壓縮算法:
- 當vaule是string時,比較難拆分,則使用序列化、壓縮算法將key的大小控制在合理范圍內,但是序列化和反序列化都會帶來更多時間上的消耗, 如果壓縮之后仍然是大key,則需要考慮進行拆分
拆分大key
- 將一個Big Key拆分為多個key-value這樣的小Key,并確保每個key的成員數量或者大小在合理范圍內,然后再進行存儲,通過get不同的key或者使用mget批量獲取。
- 當value是list/set等集合類型時,根據預估的數據規模來進行分片,不同的元素計算后分到不同的片
本地緩存(Caffeine )
- 可以考慮本地緩存(Caffeine )》Redis》數據庫
Case Study
某電商平臺的商品評論數據存儲在 Redis 中,使用列表(List)數據結構存儲每個商品的評論 ID。由于某些熱門商品的評論數量高達數百萬條,導致這些商品的評論列表成為大 Key,引發以下問題:
- 內存占用過高,導致 Redis 內存不足。
- 獲取評論列表(LRANGE)時,響應時間過長。
- 持久化操作變慢,影響 Redis 的性能。
將每個商品的評論列表拆分為多個小列表
- 例如:將商品 A 的評論列表拆分為 product:A:comments:1、product:A:comments:2、…、product:A:comments:N,每個小列表存儲 1 萬條評論。
- 在客戶端或應用層實現分頁邏輯,按需獲取評論列表
- 將評論列表從列表(List)改為有序集合(ZSet),以支持按時間排序和分頁查詢
限制 Key 的大小:
- 在寫入評論時,檢查評論列表的大小,如果超過閾值(如 1 萬條),則創建新的小列表。
定期清理大 Key:
- 使用 SCAN 命令定期掃描 Redis 中的大 Key,并進行清理或拆分。