本文作者:繹推
背景
在雲音樂全面轉跨端的時代,H5 / RN 快取模組是非常重要的組成部分,對頁面的穩定性,頁面效能等都有非常大影響,目前雲音樂使用的快取庫已經“歷史悠久”,沒法在現有的基礎上來支撐日益龐大的跨端需求,面臨著當前架構沒法修復的問題:
- 後臺 wake up問題與後臺頻繁 I / O 操作導致的崩潰 - 據統計,最高50%以上的後臺崩潰是老快取庫導致
- 主執行緒偶現卡死問題 - 執行緒管理問題
- RN / H5 頁面偶現空白問題 - 資料不一致導致
- Fatal Exception,Bundler Error等降級錯誤率高
- RN unregister module 錯誤高
- 大量細散重複日誌,浪費網路資源
- 沒有整體日誌監控,難以定位問題
因此我們基於快取庫的可擴充套件架構,從問題出發,重新設計了一套新的跨端快取庫 - NEMichelinCache,全文以 RN 快取的角度來描述
快取
首先我們要知道快取的目的是什麼?目的是以空間換時間。說起快取,很多人會想到作業系統的快取設計以及快取中的直寫與回寫模式(Write Through and Write Back)。
直寫模式
CPU 將資料同時更新到 Cache 和 Memory 中
優點
- 有助於資料恢復(在停電或系統故障的情況下)
- Cache 和 Memory 資料始終保持一致
- 直接 I / O 訪問,可以獲取到最新資料
缺點
- 慢
- 寫操作多
回寫模式
CPU 將資料更新到 Cache 時,對 Cache 做一個標記,但不同步更新到 Memory 中(非同步更新)
優點
- 速度快
- 寫操作少
缺點
- 容易造成 Cache 和 Memory 資料不一致
- 直接 I / O 訪問,不能獲取到最新資料
思考
對於一個跨端快取庫方案,主要考慮以下幾個方面:
- 如何解決目前面臨的問題:收集快取庫相關問題,從問題出發設計解決方案
- 如何提高快取的穩定性:需要綜合快取的優缺點,在資料一致性,讀寫速度等方面考慮方案
- 錯誤快速定位能力:針對各個階段的錯誤,設計錯誤上報模組,需要做到不多報、不誤報、不漏報
- 完善的日誌模組:以本地回撈日誌(儲存於客戶端,需要時透過指令上報的debug日誌)為主,減少服務端壓力,儘量保證日誌的資訊量足
- 快取庫新老切換成本:AB 切換成本,新老快取遷移成本,各指標定義等
- 業務擴充性:針對資料來源,快取型別等,給業務提供擴充點
- 業務接入成本:內建通用方案,降低接入成本
透過各方調研,跨端快取方案有些類似回寫模式,但是需要著重關注回寫的缺點。
問題解決方案
從快取的回寫模式缺點出發
- 保證資料一致性:保證記憶體快取、引擎、磁碟快取資料一致性
- 不提供任何 I / O 直接訪問快取的方法給業務方
因快取庫導致的後臺崩潰 / 主執行緒卡死問題
- 執行緒模組設計 - 設計執行緒池,保證 I / O 操作/耗時操作都在次執行緒完成
- 下載更新模組 - 以保證資料一致性為核心,責任鏈模組設計,各節點功能原子化,保證耗時操作在次執行緒完成
- 資料庫模組設計 - 統一管理,FMDB Queue
降級錯誤 / 載入失敗 / 頁面空白 / 卡片模組消失空白 / unregister module等引擎錯誤
- 同步資料庫時機 - 完全成功後同步,保證磁碟快取必是可用的
- 資料庫模組 - 支援事務,可 Fallback,保證出錯時可回退
- 快取多版本並存 - 保證本地 Bundle 快取互不干擾
- 引用計數模組 - 用於清空快取,保證使用中的快取不被提前清空
介面修改
- 刪除對外提供清空快取的介面 - 避免業務方隨意刪除快取
- 刪除對外提供直接讀取本地磁碟的介面 - 避免業務方隨意讀取快取
- 責任鏈 Runner - 優先順序佇列,優先保證正在載入的頁面載入速度
- 資料庫/檔案遷移 - 保證新版本相容老版本資料,避免重複下載
- 介面 CDN 遷移
- 網路模組強行使用 https ,防攔截
有效快速定位問題
- 日誌模組設計
- 整體監控錯誤日誌 - 自定義 Domain ,方便區分各個階段,方便歸因
- 刪除冗餘日誌
- 結合載入流程做到異常資訊細化,形成閉環
方案設計
業務介面層
對業務方而言,主要是面向業務介面層開發,設計的初衷為了減少接入的難度,使介面可控,不讓業務方隨意訪問磁碟等,如何設計這一層非常關鍵,對業務方來說,他們只要知道他們需要做什麼,以及能夠得到什麼,我們的想法是這一層應該具備以下幾點:
- 初始化引數 CacheConfig :快取名,快取根目錄,其他自定義引數
@interface NEMichelinCacheConfig : NSObject
- (instancetype)initWithAppName:(NSString *)appName
cacheRootPath:(NSString *)cacheRootPath
xxx
@end
- DataProvider協議 - 業務方只需要實現一個介面即可正常使用快取功能
- (void)fetchBundleCacheResWithLocalApps:(NSArray<NEMCAppInfo *> *)apps
completionHandler:(void (^)(NSArray<NEMichelinResVersionInfo * > *infoList, NSError *error))completionHandler;
- 快取更新介面
- (void)updateResourceOfAppInfo:(id<NEMCAppInfo *>)appInfo
priority:(NEMichelinSerialChainPriority)priority
completeBlock:(void (^)(NSError *error, NSDictionary *result))completeBlock;
- 自定義快取 / 資料協議 - 只有在特殊自定義快取時,需要特殊實現
除了業務需要關心的以上介面外,此層中處理了:新老快取庫 AB 切換,內部協議定義,其他自定義介面預留等
責任鏈模組
- 拆分前置判斷,下載,MD5 校驗,zip / gz 解壓,合併,tar 解壓,更新快取節點等,顆粒度細化
自定義鏈路能力
- 可刪除,增加節點
- 支援暫停 pause,繼續 resume 能力
- 全域性 Context 傳遞
- 失敗異常丟擲能力 - 節點執行失敗後,中斷執行,用於收集異常
- 生命週期監聽能力 - 支援各個節點開始與結束生命週期監聽
- 節點職責單一(只負責自己模組,誰建立,誰釋放(包括本地臨時檔案))
- 邏輯內聚,只依賴資料 :節點自行判斷 Context 資料,節點間不相互依賴。
責任鏈模組的設計,為後續日誌模組,錯誤模組設計打下了良好基礎,可以方便在這個設計下收集各個模組的日誌,以及刪除冗餘日誌,錯誤也可以及時丟擲,不會出現重複丟擲的情況,也為後面跨端APM資料收集打下了基礎,可以方便的在各個節點間插樁,減少了APM建設的工作量。最重要的收益是提升了穩定性,降低的出錯可能性,各個節點完全掌控自己的臨時變數,不會出現漏刪檔案,變數等情況。
責任鏈 Runner
- 優先順序佇列能力
- 支援一個key對應多個責任鏈
- 責任鏈快取能力
主要為了支援優先順序佇列的能力,可以讓優先順序高的鏈插隊,有效提升快取速度。
解壓/合併模組
- 抽離 zip,tar,gz 解壓,壓縮,合併能力
- 可自定義配置 zip,tar,gz 壓縮包解壓庫能力
- 減少對三方庫的依賴,可任意替換三方庫
資料庫
- FMDB 替換 sqlite3
- 使用事務
- 資料遷移
- 資料校驗
- 出錯回滾
多版本並存
- AppInfo:相當於快取描述,裡面有 Bundle 檔案路徑
- Bundle檔案:RN 讀取的 JS Bundle檔案
為什麼要做多版本並存?
根據上圖可以看出,AppInfo 讀取時機跟引擎載入本地 Bundle 檔案的時機是不一致的,所以有可能讀取的 AppInfo 中的本地快取路徑已經被更改,從而導致不可預估的問題。
多版本
為了保證資料的一致性,就出現了多版本共存的情況,簡單理解是同一個版本,在使用期間,資料庫、記憶體、檔案都不會被刪除,也不會被覆蓋。這樣操作不就會導致磁碟快取無限放大麼?所以我們就想到了透過引用計數的方式刪除冗餘快取。
引用計數 - 本地 Bundle 快取清理時機
- Bridge 建立時,Bridge 對應的本地快取會被引用持有
- 直到所有的 Bridge 被釋放時,就會做本地快取清理操作
- 本地快取清理操作是悲觀操作,也會校驗是否是最新快取,是否在使用
總結
資料統計
CCCandyWebCache(老快取庫) | NEMichelinCache | 結論 | |
---|---|---|---|
md5 校驗成功率 | 97% | 100% | 上升3% |
降級錯誤 | * W | * W | 下降94% |
xcode 獲取 wakeup 導致的 crash | 22年6月份:最高到近 70% 的量;去年一年:Top10中佔了3個 | 0 | 下降 100% |
ANR | 抽樣卡死 104 次,影響 94 使用者 | 暫未發現 | 下降 100% |
卡頓 | 抽樣卡頓 46061 次,影響 2519 使用者 | 卡頓事件 3 個 | 下降 99% |
CPU 異常 | 抽樣數量 100+ | 暫未找到 | 下降 100% |
OOM | 抽樣錯誤量 236 ,影響使用者 67 | 暫未找到 | 下降 100% |
引擎錯誤 | 9000+ | 481 | 下降 94% |
24 小時升級率 | Vip(96.43%),Square(75.25%) | Vip(98.21%), Square(96.78%) | 升級率上升 2% 到 20% 不等 |
除了以上模組,我們對錯誤透過 Domain 定義進行了詳細分類,日誌模組以雲音樂自研的 Corona 平臺,本地回撈等手段進行了詳細監控,網路模組以網路庫作為基礎,支援了斷點續傳等能力。目前新庫已在雲音樂 RN 模組全量使用,錯誤率下降非常明顯,後面將持續替換H5快取,DSL 模版快取等。
參考資料
本文釋出自網易雲音樂技術團隊,文章未經授權禁止任何形式的轉載。我們常年招收各類技術崗位,如果你準備換工作,又恰好喜歡雲音樂,那就加入我們 grp.music-fe(at)corp.netease.com!