雲音樂 iOS 跨端快取庫 - NEMichelinCache

雲音樂技術團隊發表於2023-02-15
本文作者:繹推

背景

在雲音樂全面轉跨端的時代,H5 / RN 快取模組是非常重要的組成部分,對頁面的穩定性,頁面效能等都有非常大影響,目前雲音樂使用的快取庫已經“歷史悠久”,沒法在現有的基礎上來支撐日益龐大的跨端需求,面臨著當前架構沒法修復的問題:

  1. 後臺 wake up問題與後臺頻繁 I / O 操作導致的崩潰 - 據統計,最高50%以上的後臺崩潰是老快取庫導致
  2. 主執行緒偶現卡死問題 - 執行緒管理問題
  3. RN / H5 頁面偶現空白問題 - 資料不一致導致
  4. Fatal Exception,Bundler Error等降級錯誤率高
  5. RN unregister module 錯誤高
  6. 大量細散重複日誌,浪費網路資源
  7. 沒有整體日誌監控,難以定位問題

因此我們基於快取庫的可擴充套件架構,從問題出發,重新設計了一套新的跨端快取庫 - NEMichelinCache,全文以 RN 快取的角度來描述

快取

首先我們要知道快取的目的是什麼?目的是以空間換時間。說起快取,很多人會想到作業系統的快取設計以及快取中的直寫與回寫模式(Write Through and Write Back)。

直寫模式

zhixie

CPU 將資料同時更新到 Cache 和 Memory 中

優點

  • 有助於資料恢復(在停電或系統故障的情況下)
  • Cache 和 Memory 資料始終保持一致
  • 直接 I / O 訪問,可以獲取到最新資料

缺點

  • 寫操作多

回寫模式

huixie

CPU 將資料更新到 Cache 時,對 Cache 做一個標記,但不同步更新到 Memory 中(非同步更新)

優點

  • 速度快
  • 寫操作少

缺點

  • 容易造成 Cache 和 Memory 資料不一致
  • 直接 I / O 訪問,不能獲取到最新資料

思考

對於一個跨端快取庫方案,主要考慮以下幾個方面:

  • 如何解決目前面臨的問題:收集快取庫相關問題,從問題出發設計解決方案
  • 如何提高快取的穩定性:需要綜合快取的優缺點,在資料一致性,讀寫速度等方面考慮方案
  • 錯誤快速定位能力:針對各個階段的錯誤,設計錯誤上報模組,需要做到不多報、不誤報、不漏報
  • 完善的日誌模組:以本地回撈日誌(儲存於客戶端,需要時透過指令上報的debug日誌)為主,減少服務端壓力,儘量保證日誌的資訊量足
  • 快取庫新老切換成本:AB 切換成本,新老快取遷移成本,各指標定義等
  • 業務擴充性:針對資料來源,快取型別等,給業務提供擴充點
  • 業務接入成本:內建通用方案,降低接入成本

透過各方調研,跨端快取方案有些類似回寫模式,但是需要著重關注回寫的缺點。

問題解決方案

從快取的回寫模式缺點出發

  1. 保證資料一致性:保證記憶體快取、引擎、磁碟快取資料一致性
  2. 不提供任何 I / O 直接訪問快取的方法給業務方

因快取庫導致的後臺崩潰 / 主執行緒卡死問題

  • 執行緒模組設計 - 設計執行緒池,保證 I / O 操作/耗時操作都在次執行緒完成
  • 下載更新模組 - 以保證資料一致性為核心,責任鏈模組設計,各節點功能原子化,保證耗時操作在次執行緒完成
  • 資料庫模組設計 - 統一管理,FMDB Queue

降級錯誤 / 載入失敗 / 頁面空白 / 卡片模組消失空白 / unregister module等引擎錯誤

  • 同步資料庫時機 - 完全成功後同步,保證磁碟快取必是可用的
  • 資料庫模組 - 支援事務,可 Fallback,保證出錯時可回退
  • 快取多版本並存 - 保證本地 Bundle 快取互不干擾
  • 引用計數模組 - 用於清空快取,保證使用中的快取不被提前清空
  • 介面修改

    • 刪除對外提供清空快取的介面 - 避免業務方隨意刪除快取
    • 刪除對外提供直接讀取本地磁碟的介面 - 避免業務方隨意讀取快取
  • 責任鏈 Runner - 優先順序佇列,優先保證正在載入的頁面載入速度
  • 資料庫/檔案遷移 - 保證新版本相容老版本資料,避免重複下載
  • 介面 CDN 遷移
  • 網路模組強行使用 https ,防攔截

有效快速定位問題

  • 日誌模組設計
  • 整體監控錯誤日誌 - 自定義 Domain ,方便區分各個階段,方便歸因
  • 刪除冗餘日誌
  • 結合載入流程做到異常資訊細化,形成閉環

方案設計

fangan

業務介面層

對業務方而言,主要是面向業務介面層開發,設計的初衷為了減少接入的難度,使介面可控,不讓業務方隨意訪問磁碟等,如何設計這一層非常關鍵,對業務方來說,他們只要知道他們需要做什麼,以及能夠得到什麼,我們的想法是這一層應該具備以下幾點:

  • 初始化引數 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 切換,內部協議定義,其他自定義介面預留等

責任鏈模組

zerenlian

  • 拆分前置判斷下載MD5 校驗zip / gz 解壓合併tar 解壓更新快取節點等,顆粒度細化
  • 自定義鏈路能力

    • 可刪除,增加節點
  • 支援暫停 pause,繼續 resume 能力
  • 全域性 Context 傳遞
  • 失敗異常丟擲能力 - 節點執行失敗後,中斷執行,用於收集異常
  • 生命週期監聽能力 - 支援各個節點開始與結束生命週期監聽
  • 節點職責單一(只負責自己模組,誰建立,誰釋放(包括本地臨時檔案))
  • 邏輯內聚,只依賴資料 :節點自行判斷 Context 資料,節點間不相互依賴。

責任鏈模組的設計,為後續日誌模組,錯誤模組設計打下了良好基礎,可以方便在這個設計下收集各個模組的日誌,以及刪除冗餘日誌,錯誤也可以及時丟擲,不會出現重複丟擲的情況,也為後面跨端APM資料收集打下了基礎,可以方便的在各個節點間插樁,減少了APM建設的工作量。最重要的收益是提升了穩定性,降低的出錯可能性,各個節點完全掌控自己的臨時變數,不會出現漏刪檔案,變數等情況。

責任鏈 Runner

  • 優先順序佇列能力
  • 支援一個key對應多個責任鏈
  • 責任鏈快取能力

主要為了支援優先順序佇列的能力,可以讓優先順序高的鏈插隊,有效提升快取速度。

解壓/合併模組

jieya

  • 抽離 zip,tar,gz 解壓,壓縮,合併能力
  • 可自定義配置 zip,tar,gz 壓縮包解壓庫能力
  • 減少對三方庫的依賴,可任意替換三方庫

資料庫

shujuku

  • FMDB 替換 sqlite3
  • 使用事務
  • 資料遷移
  • 資料校驗
  • 出錯回滾

多版本並存

  • AppInfo:相當於快取描述,裡面有 Bundle 檔案路徑
  • Bundle檔案:RN 讀取的 JS Bundle檔案

為什麼要做多版本並存?

duobanben
根據上圖可以看出,AppInfo 讀取時機跟引擎載入本地 Bundle 檔案的時機是不一致的,所以有可能讀取的 AppInfo 中的本地快取路徑已經被更改,從而導致不可預估的問題。

多版本

duobanb
為了保證資料的一致性,就出現了多版本共存的情況,簡單理解是同一個版本,在使用期間,資料庫、記憶體、檔案都不會被刪除,也不會被覆蓋。這樣操作不就會導致磁碟快取無限放大麼?所以我們就想到了透過引用計數的方式刪除冗餘快取。

引用計數 - 本地 Bundle 快取清理時機

yinyongjishu

  • Bridge 建立時,Bridge 對應的本地快取會被引用持有
  • 直到所有的 Bridge 被釋放時,就會做本地快取清理操作
  • 本地快取清理操作是悲觀操作,也會校驗是否是最新快取,是否在使用

總結

資料統計

CCCandyWebCache(老快取庫)NEMichelinCache結論
md5 校驗成功率97%100%上升3%
降級錯誤* W* W下降94%
xcode 獲取 wakeup 導致的 crash22年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!

相關文章