1、 背景

Nresource服務日均4.5億流量,考慮到未來流量急增場景,我們打算對大流量介面進行快取化處理;根據服務管理平臺數據統計顯示getUsableResoureCount介面呼叫量很大,接近40%,故對此介面進行快取化處理。

2、 方案調研

getUsableResoureCount介面用途:獲取使用者的可用資源數,契約為:Map<String, Integer> getUsableResoureCount(long userId, int resourceType, ResourceCountTypeEnum typeEnum, List<Integer> cities, List<Integer> caties) throws Exception;支援傳入資源數型別(全國/本地)多城市和多類目按照包含的策略進行資源過濾;

由於資源庫存表設計欄位:城市和類目寫入格式為:1,2,3,即以逗號組合的字串,所以如果快取使用者對應城市和類目的所有餘量,實現難度大且意義不大。

通過對介面返回值的觀察,我們發現getUsableResoureCount介面返回值很多都是查詢使用者所有資源餘量,不區分城市和類目,且查詢結果都為0,所以我們切換思路:是不是可以把這些無餘量的資料快取起來?

通過2021-07-27 10:00:00資料統計我們發現,單節點一分鐘getUsableResoureCount介面呼叫量為395,其中查詢所有資源餘量的微287,返回值為0的為183,接近46%的請求是無資料的,所以快取使用者無餘量的資料是有意義的,能擋住接近50%的無用請求。

快取使用者無餘量的資料,我們需要在所有涉及餘量增減的操作,同時維護redis中餘量變化,如何維護db與快取中資料的一致性,也是面臨的問題,所以我們想:可不可以不快取餘量,只快取一個餘量標識,這樣既能擋住無用請求,又不需要維護db與快取的資料一致性呢?參考布隆過濾器,我們決定對此介面進行快取餘量標識,只記錄有還是沒有,即1和0。

3、 方案實施

getUsableResoureCount介面先查詢快取,如果返回值為0,則放入快取,所有增加餘量的操作均需要更新redis標識為1。基本目標就是:保證更新標識為有的操作一定成功。考慮到服務併發,寫操作(更新redis標識為1)可能會被讀操作(更新redis標識為0)覆蓋,所以更新redis標識為0的時候,先增加時間校驗即晚一分鐘更新,因為redis標識更新為0或null,不會影響最終查詢,而是會查詢db,晚一分鐘又可以避免讀寫覆蓋的問題。最終採用lua指令碼實現讀操作,而寫操作採用同步set非同步重試保證最終成功;

lua指令碼如下:

4、最終效果

介面響應耗時:

2021.9.2 VS 2021.9.9

2021.9.8 VS 2021.9.9

效能提升(ms):1.4 -> 0.9 提升超過30%

整體服務響應耗時:

2021.9.8 VS 2021.9.9

效能提升(ms):1.8 -> 1.6 提升超過10%

快取命中率統計:

服務節點:10.***.60.***

命中率接近80%

5、未來考量

1)快取的擴充套件性,未來新的寫介面需要更新redis標識為1;

2)快取的介面適配性,考慮更多查詢介面應用快取;

ps: 整體思路,由於業務原因,沒有采用傳統快取用redis存有效資料的思路,我們是反其道行之,用redis來擋住無效流量