本文作者:绎推

布景

在云音乐全面转跨端的年代,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)。

直写形式

云音乐 iOS 跨端缓存库 - NEMichelinCache

CPU 将数据同时更新到 Cache 和 Memory 中

长处

  • 有助于数据恢复(在停电或系统故障的状况下)
  • Cache 和 Memory 数据始终保持一致
  • 直接 I / O 访问,能够获取到最新数据

缺点

  • 写操作多

回写形式

云音乐 iOS 跨端缓存库 - NEMichelinCache

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 ,便利区分各个阶段,便利归因
  • 删去冗余日志
  • 结合加载流程做到反常信息细化,形成闭环

计划规划

云音乐 iOS 跨端缓存库 - NEMichelinCache

业务接口层

对业务方而言,首要是面向业务接口层开发,规划的初衷为了减少接入的难度,使接口可控,不让业务方随意访问磁盘等,怎么规划这一层非常关键,对业务方来说,他们只要知道他们需求做什么,以及能够得到什么,咱们的主意是这一层应该具有以下几点:

  • 初始化参数 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 切换,内部协议界说,其他自界说接口预留等

职责链模块

云音乐 iOS 跨端缓存库 - NEMichelinCache

  • 拆分前置判断下载MD5 校验zip / gz 解压兼并tar 解压更新缓存节点等,颗粒度细化
  • 自界说链路才能
    • 可删去,添加节点
  • 支撑暂停 pause,继续 resume 才能
  • 全局 Context 传递
  • 失利反常抛出才能 – 节点履行失利后,中止履行,用于搜集反常
  • 生命周期监听才能 – 支撑各个节点开端与结束生命周期监听
  • 节点职责单一(只负责自己模块,谁创立,谁开释(包含本地暂时文件))
  • 逻辑内聚,只依靠数据 :节点自行判断 Context 数据,节点间不相互依靠。

职责链模块的规划,为后续日志模块,过错模块规划打下了良好根底,能够便利在这个规划下搜集各个模块的日志,以及删去冗余日志,过错也能够及时抛出,不会呈现重复抛出的状况,也为后边跨端APM数据搜集打下了根底,能够便利的在各个节点间插桩,减少了APM建设的作业量。最重要的收益是提高了稳定性,降低的犯错可能性,各个节点完全掌控自己的暂时变量,不会呈现漏删文件,变量等状况。

职责链 Runner

  • 优先级行列才能
  • 支撑一个key对应多个职责链
  • 职责链缓存才能

首要为了支撑优先级行列的才能,能够让优先级高的链插队,有用提高缓存速度。

解压/兼并模块

云音乐 iOS 跨端缓存库 - NEMichelinCache

  • 抽离 zip,tar,gz 解压,压缩,兼并才能
  • 可自界说装备 zip,tar,gz 压缩包解压库才能
  • 减少对三方库的依靠,可任意替换三方库

数据库

云音乐 iOS 跨端缓存库 - NEMichelinCache

  • FMDB 替换 sqlite3
  • 运用业务
  • 数据搬迁
  • 数据校验
  • 犯错回滚

多版别并存

  • AppInfo:相当于缓存描述,里边有 Bundle 文件途径
  • Bundle文件:RN 读取的 JS Bundle文件

为什么要做多版别并存?

云音乐 iOS 跨端缓存库 - NEMichelinCache
根据上图能够看出,AppInfo 读取机遇跟引擎加载本地 Bundle 文件的机遇是不一致的,所以有可能读取的 AppInfo 中的本地缓存途径已经被更改,从而导致不可预估的问题。

多版别

云音乐 iOS 跨端缓存库 - NEMichelinCache
为了确保数据的一致性,就呈现了多版别共存的状况,简单了解是同一个版别,在运用期间,数据库、内存、文件都不会被删去,也不会被掩盖。这样操作不就会导致磁盘缓存无限放大么?所以咱们就想到了经过引证计数的方式删去冗余缓存。

引证计数 – 本地 Bundle 缓存整理机遇

云音乐 iOS 跨端缓存库 - NEMichelinCache

  • 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 模版缓存等。

参考资料

  • Write Through and Write Back in Cache

本文发布自网易云音乐技术团队,文章未经授权制止任何形式的转载。咱们常年招收各类技术岗位,如果你准备换作业,又恰好喜欢云音乐,那就参加咱们 grp.music-fe(at)corp.netease.com!