本篇文章主要是對方案性能優化2.0中,所做的緩存設計的過程、方案、結果做一個總結。 一、前言 對於方案中心,核心業務場景之一是物流場景下的物流費用計算。而部分業務場景下,對於物流費用計算的性能有較高要求,如ICBU網站運費模板鏈路,通方案中心計算快遞、海拼物流費用。在接入新的流量場景的背景下(ICB ...
本篇文章主要是對方案性能優化2.0中,所做的緩存設計的過程、方案、結果做一個總結。
一、前言
二、優化2.0面臨什麼問題
2.1 1.0優化做了什麼
-
降低CPU資源消耗
-
規則引擎表達式預先編譯並且緩存 -
減少大對象深度複製,快遞場景可以完全不複製 -
避免通過JSON序列化,再反序列化實現創建對象並且賦值 -
底層元數據查詢方法,避免使用第三方封裝的校驗框架 -
日誌列印優化,debug添加isDebug判斷
-
tair使用優化
-
mget替代tair get方法使用,降低網路資源消耗
2.2 計費流程的核心問題--嵌套迴圈查詢
-
匹配運力線方案 1<=n<=10,查DB or localCache -
計算物流方案 1<=n<=?,視服務商報價而定,如有多條方案,則計算多條。 -
計算銷售價/計算成本價,分別計算兩個報價。 -
匹配報價版本,匹配當前生效的報價版本。查DB 或 tair -
匹配費用項報價1<=n<=30+,視不同運力線而定。查DB 或 localCache
以上只是簡化流程,依然有很深的嵌套迴圈。
2.3 計費數據涉及模型簡述
2.4 當前緩存模型
2.4.1 Tair緩存
-
為什麼緩存?要獲取 當前報價配置,從DB查詢獲取,查詢涉及表較多,不做緩存,那肯定頂不住。
-
為什麼用Tair?現在看來,是因為獲取 當前報價配置 模型的查詢複雜性,通過分散式緩存,儘可能降級查詢DB次數。
優點:
-
使用Tair緩存當前報價配置,批量讀取的方式,可以一定程度緩解DB查詢壓力
-
目前Tair緩存模型設計,沒有把最核心、查詢量級最大的方案和報價進行緩存,沒解決真正痛點;
-
在計費過程中仍需要根據每個方案+費用項構造相應的緩存key,需要費用項多多情況下,仍然需要多次查詢tair;
2.4.2 本地緩存
-
實現簡單,不用對數據做新的聚合設計,調mapper介面級別緩存。於前期臨時、快速解決性能問題的本地緩存方案;
-
大面積、細粒度使用本地緩存,集群機器本地緩存數據還不一致,易造成客戶體驗割裂問題(測試有時候都搞不清是bug還是緩存)
-
粒度太細,計費流程與數據存儲層的交互還是嵌套分散在深層次迴圈流程的內部,當緩存失效,依然會有大量DB查詢(特別是迴圈嵌套最深的報價查詢)
-
不太能支持水平擴容(嘗試過,DB扛不住)
-
緩存數據無法預熱,面對大流量場景,程式重啟易出現成功率下跌(優化前每次發佈基本都會發生)
三、新的緩存
3.1 為什麼需要新的緩存設計
3.2 對詢價計費流程的重新定義
-
前置費率匹配:小二啟用運力線新報價的時候,通過精衛監聽報價表變更,為每個物流方案提前匹配費用項報價,並且匹配結果保存在清洗出來的產品線路費率表中。客戶側查價時,根據from-to線路信息即可獲取到該線路所有的費用項的報價,不需要在運行時逐個費用項取匹配費率,大幅減少查價運行時匹配報價的tair請求以及邏輯運算。
-
減少DB訪問:配置型數據通過方案中心本地緩存框架訪問獲取,數據量大的費率模型數據,從tair緩存獲取。通過此方案,可以大幅減少DB訪問。
-
減少網路開銷:在費用計算前組裝好計費所需的數據上下文,通過批量tair查詢讀取費率,避免在核心流程迴圈訪問獲取費率數據,減少RPC請求次數。
圖3.2-3 報價緩存失效後的查詢高峰
3.3 緩存模型
3.3.1 Tair緩存模型
Tair緩存模型,這裡指的是方案和報價的緩存。這裡的Tair緩存模型的設計,需要滿足兩個關鍵點:可批量查和高速查。貼合業務場景來進行設計,提高匹配方案報價的效率以及命中率。
圖3.3-1 Tair緩存模型設計
prefixGets
之前考慮的使用mget可能會受限於key分片問題導致查詢緩存性能不穩定。對於方案/報價查詢,改用prefixPut,prefixGets進行批量緩存存取,一個主key,多個子key的情況下,以獲得更好的批量讀取性能。通過prefixGets,可以高效批量獲取一次查價中多個sku的方案的緩存數據 或者 報價的緩存數據。
prefixPut
prefixPut發生在查詢不到報價、或者方案的時候,進行惰性載入
緩存key設計
-
主key:對於方案/報價,都可以用SPU維度區分主key,如上圖所示。 -
方案查詢子key:
-
對於國際快遞,匹配線路方案的核心條件,只有一個destinationCountry,以destinationCountry作為緩存key,可以緩存每次對應目的國的方案查詢結果。 -
同時在查方案的時候,已經指定了sku_id和生效的報價version_id,因此設計查詢方案緩存key由 sku_id+version_id+destinationCountry組成。如上圖所示。另外需要註意使用version_id作為緩存key一部分,可以對數據做較長時間的緩存,避免了頻繁失效要重新查詢方案數據,並且將Tair緩存的實時性控制,轉移為對version_id的實時性控制。
-
報價查詢子key:
-
查價流程,先根據destinationCountry查詢方案緩存, 得到方案線路列表,一條線路信息包含【destinationCountry、warehouseCode】。匹配報價的查詢條件是warehouseCode + destinationCountry,相似地,設計查詢報價的緩存key由 sku_id+version_id+destinationCountry+warehouseCode組成。 -
為什麼不直接用destinationCountry?報價數據對象相對而言比較大,受限於業務場景與value大小限制,緩存粒度拆分相對需要更小. -
同樣地,可以對數據做較長時間的緩存,避免了頻繁失效要重新查詢方案數據,並且將Tair緩存的實時性控制,轉移為對version_id的實時性控制。
value設計
-
方案value:沒啥特殊的,結構比較簡單
圖3.3-2 方案緩存模型value
-
報價value:結構類似方案,核心是多了報價信息result【JSON結構數據】
圖3.3-3 報價緩存模型value
-
報價記錄value結構
3.3.2 本地緩存模型
詢價計費時依賴的配置型數據,分成3大類,按照sku、resource、spu做聚合緩存。
key設計
key以 類型+ 大類的主鍵構成:sku+sku_id, resource+resource_id, spu+spu_id
value設計
圖3.3-4 本地緩存模型value
通過這樣的聚合模型設計,詢價過程中,通過用skuID可以在本地緩存中檢索到任意想要的服務表達定義相關的數據。另外這裡緩存的實時性和寫邏輯控制,在後面展開。
聚合模型每個子屬性更新,需要更新整個模型的數據,這裡為什麼考慮要做聚合,而不採用每個表的數據都單獨一個key緩存的實現方式呢?
-
每個表的單獨key,緩存結構零散,需要管理更多的Key。 -
每個表的單獨key,緩存結構零散,在讀取緩存的業務層,不同業務場景訴求下,需要實現比較複雜的關聯組裝邏輯。 -
配置數據並不多變,少更新,聚合模型更新讀取DB的次數有限,較少。
綜上,相關的配置數據聚合管理的好處大於缺點。
四、緩存讀寫
4.1 本地緩存
4.1.1 緩存預熱
本地緩存預熱,程式啟動時,根據程式內置邏輯定義的本地緩存Key集合,提前載入緩存到應用記憶體,保證提供服務時,緩存已經載入。
4.1.2 緩存更新
簡單來說就是通過監聽精衛,藉助廣播能力,通知集群更新本地緩存。這裡的緩存是一直存在於堆記憶體,不會失效,只會廣播刷新。每次刷新緩存,按照圖3.3-4 本地緩存模型value描述的聚合模型,每次更新最小粒度為一個ConfigDTO。
4.1.3 解決了什麼
-
解決應用伺服器本地緩存方案緩存實時性問題,實現應用伺服器集群本地緩存方案的準實時刷新。 -
通過廣播數據實體變更,觸發本地緩存刷新,解決應用伺服器集群多節點本地緩存不一致的問題。例如之前經常出現,因為本地緩存問題,查詢方案多次不一致的問題。 -
數據啟動時預熱,解決了之前每次發佈,程式重啟都會出現服務成功率下跌的問題。 -
對於已緩存數據,在數據使用的業務流程中,可完全屏蔽資料庫查詢,對水平擴容友好。可以解決擴容時,DB瓶頸問題。
4.2 tair緩存
查詢沒啥輸的,按照 tair緩存模型設計 的key-value,進行prefixGets,prefixPut。需要註意的是,key設計的粒度、報價value大小限制。
4.2.1 緩存預熱
這裡需要做預熱的場景,基本就只有新運力線上線了,一般日常還是沒問題的。新運力上線,目前要求是分批灰度,等Tair緩存命中率上去了,繼續開啟灰度,這是比較保守的做法。
4.1.2 緩存更新
前面有提到,Tair緩存數據的實時性控制,是依靠version_id的實時性控制,方案或者報價的version_id通過本地緩存準實時更新,能夠保證version_id的準實時性,從而保證每次查詢Tair緩存數據的實時正確性。因為每次獲取到的version_id是最新的,拼接出來的Key自然也是查詢最新的緩存的Key
4.3 緩存讀寫總結
圖4.3-1 緩存讀寫總結
-
配置型數據:穩定,量不大,查價計費時需要經常讀取的數據。例如運力線配置、運力線資源、報價版本、費用項、運力資源關聯關係等。 -
方案報價型數據:量大,無法本地緩存,具備版本特性,可以長時間存儲在tair。
五、緩存臟數據處理
5.1 本地緩存
儘管精衛很強大,但也不是100%保證沒有意外,為避免臟數據產生,因此會採用定時任務刷新的方式來定時更新本地緩存。
5.2 tair緩存
前面的設計有提到,目前的方案/報價緩存子key,是帶版本號的,只要版本號正確,就不存在緩存臟數據的問題,而版本號數據實時性,依賴於本方案中的本地緩存實現,二者相互結合,保證查詢Tair緩存數據的正確性。另外使用版本號作為緩存key還可以對數據做較長時間的緩存,避免了頻繁失效要重新查詢報價數據。
六、單點資源瓶頸
6.1 Tair瓶頸
對整個應用集群來說,支撐更大的流量,繞不開單點資源瓶頸,水平擴容更加繞不開單點資源瓶頸。不巧,最近在接入更大的流量場景的時候,就遇到Tair瓶頸問題
圖6.1-1 切流Tair出現瓶頸監控視圖
圖6.1-2 緩存穿透後DB的QPS視圖
可以看到出現大量的Tair限流,解決處理方向有幾種,簡單說一下
方向1
很簡單,如果是原本Tair的限流閾值很低,那麼可以申請擴容。需要註意的是,申請擴容的容量評估,需要結合我們查詢緩存方式來評估,鷹眼上看的僅僅是對Tair發起RPC請求的統計,服務端限流統計是按照真正的key個數統計的。例如使用到prefixGet,那麼就按Skey個數統計。
方向2
如果擴容不能滿足,那麼就需要回到代碼中,看看有沒有什麼不必要的Tair查詢,進行優化。
方向3
針對熱點Key做一層本地緩存,如果應用伺服器的熱點本地緩存中包含key,那麼就不需要查詢Tair了,可以直接返回結果,降低對Tair的壓力。熱點key的識別可依賴Tair內嵌的LocalCache功能,或者我們自己實現,動態配置熱點Key。
方向4
使用RDB。對持久化有需求,並且緩存QPS確實很高,如果當前使用的是LDB,那麼可以考慮使用RDB,LDB成本比較高,沒那麼多資源。RDB成本相對較低,可以有更多資源。
方案中心怎麼做
這次遇到Tair瓶頸,方案中心是先從簡單的方向1、2入手。
首先申請擴容,一開始評估預計QPS,按照的鷹眼平臺展示的來估,因為方案中心使用了prefixGet,因此估少了,擴容完成後發現還是限流。
無奈,但也沒繼續申請擴容,而是到業務、代碼中,分析可以減少的查詢Tair的點。
七、總結
7.1 數據
通過本地緩存配置型數據 + tair緩存方案報價型數據的組合,緩存命中的場景下,查價計費鏈路已經可以實現無DB查詢。目前線上穩定支持水平擴容,按照壓測數據預估,單機支持180QPS,單集群50台機器支撐9000+qps
結合新的緩存組合,代碼路徑實現調整如下:
-
獲取N個運力線方案版本+報價版本:查詢本地緩存
-
批量獲取N個運力線方案:查詢tair or DB
-
批量獲取N個方案的報價:查詢tair or DB
作者|申懷
本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Talk-about-cache-design-in-performance-optimization-of-the-solution-center.html