真·Redis快取最佳化—97%的最佳化率你見過嘛?

京東雲開發者發表於2023-09-27

本文透過一封618前的R2M(公司內部快取元件,可以認為等同於Redis)告警,由淺入深的分析了該告警的直接原因與根本原因,並根據原因提出相應的解決方法,希望能夠給大家在排查類似問題時提供相應的思路。

一、問題排查

1.1 郵件告警

正值618值班前夕,某天收到了郵件告警,告警內容如下:

您好,R2M監控報警,請您及時追蹤一下! 報警資訊:告警ID:6825899, 應用:zr_credit_portal, 負責人:zhangsan, 告警型別:記憶體使用率, 時間:2023-06-15 16:00:04。例項:(10.0.0.0:5011-slave), 當前:9212MB 超過警戒值:8748MB 例項最大記憶體:10800 MB,記憶體使用率:85 % ;例項:(10.0.0.0:5023-master), 當前:9087MB 超過警戒值:8748MB 例項最大記憶體:10800 MB,記憶體使用率:84 % ;例項:(10.0.0.0:5017-master), 當前:9214MB 超過警戒值:8748MB 例項最大記憶體:10800 MB,記憶體使用率:85 % ;

大概內容是說,R2M叢集使用率已經達到85%,需要緊急處理下。

我們的快取叢集配置如下,總共32400MB容量,三主三從,每個主節點10800M容量,目前使用最高的已經達到9087M。R2M使用叢集模式進行部署。

image-20230713164727082

首先的思路就是使用大key統計,檢視是哪些快取佔用了容量。因為大key統計是從節點進行掃描,所以不用擔心會影響線上主流程。

image-20230713170916595

1.2 程式碼分析

大key主要分為兩類,一類是xxx_data,一類是xxx_interfacecode_01,按照此規律去程式碼中尋找存放key的地方

String dataKey = task.getTaskNo() + "_data";
cacheClusterClient.setex(dataKey.getBytes(), EXPIRATION, DataUtil.objectToByte(paramList));

key = task.getTaskNo() + "_" + item.getInterfaceCode() + "_" + partCount;
cacheClusterClient.setex(key.getBytes(), EXPIRATION,DataUtil.objectToByte(dataList));


找到了程式碼位置後,分析其業務流程:

伏羲運營後臺插入資料最佳化-第 3 頁.drawio

1.3 告警原因

綜合上圖分析,此次佔用率過高的原因可以分為直接原因與根本原因:

1.3.1 直接原因

檢視運營後臺確實發現有使用者在此前三天建立了大量的跑批任務,導致快取中樣本與結果數量增加,從而導致快取使用率過高。

1.3.2 根本原因

分析程式碼後,根據上文描述快取中主要有兩塊資料:樣本與結果

  • 首先是樣本在快取中存了一下隨機又取出,本操作毫無意義,只會佔用快取容量。

  • 結果分批分片儲存,此步驟有意義,主要是為了防止在多工並行處理時,如果不將資料分片存入快取,很有可能導致資料在JVM中佔用大量空間,進而導致FULL GC的問題。(之前文章已分析)

  • 跑批結束後,中間資料正常來說已經無用,但是業務流程並沒有主動刪除無用資料,而是等待超時後自動刪除,本操作會導致資料在快取中額外儲存較長時間。

至此,已經分析出了本次快取使用率過高的原因(其實還沒有,直接原因只分析出了表象,直接原因的“根本原因”還未有結論)。

二、問題解決

上文分析了本次告警的排查過程,以下是如何解決問題,也是分為如何解決直接原因與解決根本原因。

2.1 直接原因

2.1.1 原因分析

正值618前夕,最好不考慮操作會對系統產生的影響,因此只能先考慮讓對應的使用者暫時停止建立跑批,以免繼續佔用記憶體導致影響線上業務。

此時觀察監控圖又發現:
image.png

使用者是從三天前就開始建立跑批任務的(對應快取開始增長的時間點),但是快取的有效期只有一天,按道理來說從第二天開始每天的快取都應該下降不少才對(因為前一天的已經過期了),為什麼看監控圖這三天的快取使用率近乎直線上升呢?

此處可以思考30s,與Redis特性有關。

根據之前剛系統的學完Redis的相關特性,關注到此問題點後,開始思考有沒有可能是雖然我們設定了超時時間是一天,但是實際上資料並沒有被物理刪除呢(Redis的快取淘汰策略)?

隨後檢視R2M相關文件:

image-20230907145129672

其中:

如果帶有生存時間的鍵非常多的話, 那麼在鍵的生存時間變為0, 直到鍵真正被刪除這中間, 可能會有一段比較顯著的時間間隔。

這不就是我們的特性嗎,從剛剛的我們搜尋大key的圖中可以看到,我們有很多帶超時的key並且size都很大,很有可能雖然已經超時了(即TTL變為0)但該資料並沒有訪問,並且由於R2M漸進式刪除,某一個Key可能會在超時後很久才會被真正的物理刪除

至此,直接原因的根本原因已經找到了。

2.1.2 解決

那麼如何解決呢?根據一個Key過期時被物理刪除的兩種策略:

注意:

Redis 使用以下兩種方式刪除過期的鍵

  • 當一個鍵被訪問時,程式會對這個鍵進行檢查,如果鍵已經過期,那麼該鍵將被刪除。

  • 底層系統會在後臺漸進地查詢並刪除那些過期的鍵,從而處理那些已經過期、但是不會被訪問到的鍵。

首先透過訪問的形式去刪除資料肯定是愚蠢且沒必要的(都能訪問並且知道要過期了不如直接刪除),那麼可以選擇提高漸進的查詢速率。從而將那些超時的資料物理刪除

於是我們聯絡了R2M對應運維:

image.png

根據上述聊天記錄可知,確實有引數可以調整漸進式物理刪除的頻率,而我們的快取叢集則之前因為不知名原因(專案團隊做過更換)被調整為了10,大約降低了六倍,此結果也符合我們的預期,從側面印證了我們的猜想是正確的。

當時處於618前夕,我們沒有並沒有修改該引數,在618之後,我們隨即提了工單修改該引數,將該引數從10提高到80:

image-20230713183643785

審批透過之後,我們觀察r2m的下降速率:

image-20230713183917668

可以看到,在6.20號我們調整了引數後,在沒有大批次資料新增後,r2m使用率的下降速率明顯變快

至此,快取使用率告警的直接原因已經解決完畢,真正的原因就是有大量的key過期後並沒有被刪除,觀察後續快取使用率都沒有太高。此外,即時有大量的跑批任務,如果不是在同一天內直接新增,一般不會造成使用率過高的問題。

2.2 根本原因

上文在調整引數後,基本可以滿足使用者的日常業務需求,但是如果使用者確實有一天之內有大量跑批任務的需求,那麼此方案仍不能解決根本問題,還會造成使用率過高有可能影響線上業務的風險。

那麼要從根本上解決此問題,就需要對跑批流程進行最佳化,按照1.2中流程示意以及原因分析:

  1. 樣本就已經完全沒有必要儲存在快取中,所以在程式碼中直接採用引數傳遞的方式傳入給下一步流程。

  2. 結果分片肯定是有意義的,原因上文中也提到了,但是redis快取的空間(即記憶體)是比較寶貴的,而oss的空間成本(硬碟)則是比較廉價的,並且考慮本身業務就是離線業務,時效性以及查詢速率並不是最關鍵的因素,因此綜合考慮將跑批的結果分片資料儲存至oss

  3. 在跑批流程結束後,主動刪除oss中的結果分片資料,避免資料無用後仍佔用儲存空間。此外在oss端設定7天自動刪除,防止系統原因異常導致資料未刪除而永久存在

綜上,結合以上的最佳化思想,重新設計的流程圖如下:

伏羲運營後臺插入資料最佳化-第 4 頁.drawio

至此,即時後續使用者資料量再大,也無論是分一天建立還是多天建立,都不會導致快取使用率告警而有可能帶來的線上業務問題。

2.3 最佳化率

上線完成後的快取使用情況:

image-20230911155108232

可以看到幾乎是斷崖式下降。

快取最佳化率:

改造前

image-20230911155042608

改造後

image-20230911155540703

(8.35-0.17)/8.35≈97.96%

三、總結

本文主要透過一封告警郵件,由淺入深的將系統中存在的快取問題與流程最佳化。在解決完實際問題後,我們應該都會有一些心得與總結,從而下次自己在開發過程中避免再犯這樣的問題,也能夠自己對自己再做一次總結與歸檔。要做到能夠知其然更要知其所以然。以下總結是自己的由淺入深的一點點心得。

3.1 不同中介軟體應該負責不同的事

“韓信點兵,多多益善”,一名好的將軍就是能將不同計程車兵分配不同的職責,從而讓士兵能夠在自己擅長的領域內各盡其職。對我們研發來說,選擇不同的中介軟體完成不同的功能則是能夠反應我們研發的技術水平。

像本文來說,可供儲存中間資料的有好多中介軟體,除了Redis、Oss,還有Mysql、Hive、ES、CK等,我們需要根據不同的業務需求選擇不同的中介軟體完成對應的功能。本案例中很明顯資料的特性為大量的、不要求速度的,而Redis的儲存特性為少量的、快速的,很明顯這兩個是背道而馳的需求與業務,因此我們在選用時應該選擇正確的中介軟體。

3.2 學習技術細節有沒有用

其實之前也有學了很多的技術、框架的實現細節,但是絕大多數都是學完就學完了,並沒有太多實踐的環節。而這次案例分析正好處於之前剛剛學完Redis的相關細節,沒隔多久就能夠應用到本次的實踐環節,算是理論與實踐結合。此外這次案例也能夠很大程度上提升自己的學習興趣。

作者:京東科技 韓國凱

來源:京東雲開發者社群 轉載請註明來源

相關文章