經驗之談:我為什麼選擇了這樣一個激進的快取大Key治理方案

James_Shangguan發表於2024-05-06

一、引言

本文將結合我的一次Redis大Key的治理經驗,來淺談一下快取大Key的治理方案選擇。文中主要包括快取大Key基礎知識大Key治理方案選擇大Key治理案例等,適合有一定開發經驗的開發者閱讀,希望對大家有幫助。

二、快取大Key基礎知識

2.1 大Key的標準

集合型別元素數量>5000或者單個value大於1M。當然這個標準不是絕對的,而是需要根據具體的業務場景和Redis叢集的實際情況來靈活調整

2.2 大Key帶來的風險

  • 大key發生熱點訪問,響應積壓在服務端後記憶體暴漲,最終導致服務端被殺造成資料丟失
  • 嚴重影響 QPS、TP99等指標,對大Key進行的慢操作會導致後續的命令被阻塞,從而導致一系列慢查詢。
  • hgetall、smembers 等時間複雜度O(N)的命令使用不當,容易造成CPU使用率過高。
  • 叢集各分片記憶體使用不均。某個分片佔用記憶體較高或OOM,傳送快取區增大等,導致該分片其他Key被逐出,同時也會造成其他分片的資源浪費。
  • 叢集各分片的頻寬使用不均。某個分片被流控,其他分片則沒有這種情況,且影響宿主機上的其它應用。
  • 資料遷移失敗 過大的Key(如超過1G),在遷移、縮容、擴容,主從全量同步在序列化過程中,記憶體上漲,資料同步失敗,且存在資料丟失風險。

三、快取大Key治理方案

3.1事前預防

事前預防的基本思路是在快取設計的時候,快取中只儲存必要的資料,且需要考慮將儲存空間變小,具體的手段有:

  1. 對於JSON型別,可以刪除不使用的Field;或者使用@JsonProperty 註解讓 FiledName 字符集縮小;
  2. 採用Protobuf等壓縮演算法,利用時間換空間;
  3. 對於集合型別,設計上儘量避免整存整取;

3.2 事中監控

事中做好監控,對於快取大Key,早發現,早治理;

3.3 事後

快取大Key已經存在,那麼應該怎麼辦?分2種情況進行考慮:

  1. 評估這些大Key是否還有存在的意義和價值,是否可以刪除,對於可以刪除的可以使用SCAN命令進行循序漸進式刪除大Key;對於不可以直接刪除的這種,是否可以將資料轉移到其他的介質進行儲存;
  2. 對於不能直接刪除的大Key,進行“分而治之”,再通俗一點就是“拆”,將大Key進行拆分
  • String型別的大Key:可以嘗試將物件分拆成幾個Key-Value,使用MGET或者多個GET組成的pipeline獲取值,分拆單次操作的壓力,對於叢集來說可以將操作壓力平攤到多個分片上,降低對單個分片的影響。
  • 集合型別的大Key,每次只需操作部分元素:將集合型別中的元素分拆。以Hash型別為例,可以在客戶端定義一個分拆Key的數量N,每次對HGET和HSET操作的field計算雜湊值並取模N,確定該field落在哪個Key上。

四、快取大Key治理案例

4.1 系統架構和業務場景說明

有這樣一個電商活動管理系統,系統有2個應用:後臺管理端、運營端;(後臺管理端是管理人員進行操作;運營端可以建立營銷活動)儲存使用的是MySQL和Redis;

系統中有一個這樣的場景,簡單來說就是,後臺管理端可以新增sku白名單,這個白名單是以“商家ID + skuId”為一條記錄寫入MySQL中的;運營端在建立營銷活動,需要上傳sku,上傳的時候需要讀取sku白名單進行校驗。

系統現狀是,在查詢sku白名單的時候使用了Redis快取。快取模式採用的是旁路快取,新增SKU白名單的流程是,寫入資料庫,並刪除快取;讀取SKU白名單的流程是,先從快取讀取,快取中沒有則讀取資料庫,並將結果寫入快取。快取的資料型別是String,value為資料庫中的全量SKU白名單記錄,是以JSON字串儲存的。

旁路快取:讀取快取、讀取資料、更新快取和操作都是在應用程式中完成的。

但是隨著業務的發展,新增的SKU白名單逐漸增多,發展成為了快取大Key。截止治理之前,資料庫的配置表中存在1w+條sku白名單記錄,也就是說Redis的一個String結果儲存了1w+條的白名單記錄。

這裡也許有人問,一張表中儲存1w+條資料,也是沒有壓力的啊,其實這張配置表中不僅有商家SKU白名單配置,還包括系統中其他所有的配置資訊儲存,資料量還是可觀的。

4.2 一種激進的治理方案

該業務場景中,快取大Key是不可以直接進行刪除處理的,處理思路是將其拆分為一些小的Key。

分析場景,商家可以登入運營端進行建立營銷活動,在建立營銷活動和上傳SKU的過程中,只需要查詢該商家的SKU白名單,那麼儲存和查詢全部都是不必要的,那麼可以將全量白名單資料按照商家維度進行拆分儲存和查詢。

關於快取結構的選擇,我使用的是Hash結構(新快取),key與原快取String結構 key 相同,field為商家ID,value為sku白名單列表。大概流程圖如下:

新快取Hash結構key與原快取String結構 key 完全相同,意味著新的Hash結構快取和舊的String快取是無法共存的,只能選擇其一,意味著不存在百分比切量的過程,一步到位切量,你就說是否激進?

這樣激進的方案是基於什麼的考量呢?

  1. 夜間流量幾乎為0,且快取切換操作秒級完成,風險可控。運營端系統,商家建立促銷活動基本都是工作時間,晚上和凌晨幾乎沒有流量;而且,新舊快取切換可以秒級完成:①運營端新增測試SKU白名單,刪除快取;②將開關切到新快取;風險可控。
  2. 方案支援秒級回滾.①運營端新增測試SKU白名單,刪除快取;②將開關切到舊快取。
  3. 程式碼改動和上線部署工作量小。這個方案可以保持管理後端刪除快取的邏輯不變,也就是在新增SKU白名單的時候還是刪除所有的白名單快取;只需要改動運營後端程式碼,且上線部署只需要部署運營端。
  4. 雖然管理後端刪除快取為全量刪除,存在管理端新增商家A的SKU白名單,導致快取中的商家B、商家C等的白名單資料也被刪除(其實是無需刪除的),綜合評估可以暫時保持現狀,後續最佳化

4.3 更加通用的治理方案

4.3.1 三個階段

更加通用的快取大key治理方案,包括雙寫、雙讀對比和讀寫新key等階段:

  1. 雙寫階段

在不影響現有業務的情況下,將新資料同時寫入舊key和新key;

  1. 雙讀對比階段

驗證新key的資料與舊key的資料是否一致,並準備切換讀操作到新key;

  1. 讀寫新key階段

將所有讀寫操作都切換到新key,並廢棄舊key;需要注意刪除大key時要避免阻塞Redis服務。

4.3.2 注意事項

  • 資料一致性:在整個治理過程中,需要確保資料的一致性。特別是在雙寫和雙讀對比階段,要仔細驗證資料是否一致。
  • 效能影響:在雙寫和雙讀對比階段,由於需要同時操作舊key和新key,可能會對效能產生一定影響。因此,需要在業務低峰期進行這些操作,並監控系統的效能指標。
  • 錯誤處理:在治理過程中,可能會遇到各種錯誤情況(如資料不一致、網路問題等)。需要制定完善的錯誤處理機制來確保系統的穩定性和可用性。
  • 備份和恢復:在執行治理操作之前,建議對Redis資料進行備份。以便在出現問題時可以快速恢復資料。

五、小結

本文是一個真實的線上快取大Key治理案例,區別於通用的治理方案,我選擇了一種激進和簡單的治理策略。對於快取大Key進行簡單總結一下,對於使用快取,需要考慮快取大Key問題,設計和開發過程中儘量避免;如果已經出現,考慮刪除,如果不可以刪除,考慮拆分。可以在權衡之下,選擇最適合自己的方案。

一起學習

歡迎各位在評論區或者私信我一起交流討論,或者加我主頁weixin,備註技術渠道(如部落格園),進入技術交流群,我們一起討論和交流,共同進步!

也歡迎大家關注我的部落格園、公眾號(碼上暴富),點贊、留言、轉發。你的支援,是我更文的最大動力!

相關文章