鸿蒙冷热数据分层管理高级底层逻辑:页缓存PageCache/LRU链表/主动回写/内存压力分级回收机制
·



一、前置思考
1.1 为什么系统重启后"打开过的应用更快"?
打开一个应用、再退出、再打开——第二次明显更快。为什么?因为第一次读的数据还在**页缓存(Page Cache)**里:
页缓存 = 内核用空闲内存"缓存"磁盘数据
→ 第一次读: 磁盘 → 内存 (慢,~10ms/次)
→ 第二次读: 内存命中 → 应用 (快,~0.1ms/次)
→ 快 100 倍!
1.2 冷热数据的本质
热数据: 频繁访问的数据 → 留在内存 → 命中率决定性能
温数据: 偶尔访问的数据 → 缓存与磁盘之间
冷数据: 长期不访问的数据 → 回写磁盘 → 释放内存
问题: 内存是有限的,怎么判定谁冷谁热?
答案: LRU 链表(最近最少使用)
1.3 内存压力来了怎么办?
内存不足 → 内核要"抢回"缓存内存
→ 按什么顺序回收?
→ 回收多少?
→ 脏页(未写回的数据)必须先写回!
这就是 内存压力分级回收 要解决的问题
二、核心原理
2.1 Page Cache 页缓存
Page Cache: 内核的磁盘缓存层
→ 读文件: 数据先进 Page Cache → 应用直接读内存
→ 写文件: 先写 Page Cache (脏页) → 后台异步写回磁盘
好处:
① 读加速: 缓存命中免磁盘 IO
② 写合并: 多次小写合并成一次大写
③ 预读: 顺序读时提前加载后续页
内核组织:
address_space (每个文件一个)
→ radix tree / xarray (按文件偏移索引)
→ 页对象 (struct page, 4KB/页)
2.2 LRU 链表
LRU (Least Recently Used): 最近最少使用
两条链表:
活跃链表 (active): 经常被访问的页
非活跃链表 (inactive): 很少被访问的页
访问流程:
读页 → 在 inactive → 命中 → 移到 active 头部
页在 active 长时间未访问 → 降级到 inactive
内存不足 → 从 inactive 尾部回收 (先回收最冷的)
回写时机:
inactive 尾部是脏页 → 先写回磁盘 → 再回收
2.3 内存压力分级回收(watermark)
内存水位线 (watermark): 内存余量的分级警戒线
┌─────────────────────────────────┐
│ 可用内存充足 (绿色区) │ → 正常分配
├─────────────────────────────────┤
│ WMARK_LOW (黄色区) │ → 后台开始回收
├─────────────────────────────────┤
│ WMARK_MIN (红色区) │ → 同步强制回收
├─────────────────────────────────┤
│ 耗尽 (黑色区) │ → OOM Killer
└─────────────────────────────────┘
回收压力分级:
P0 轻: kswapd 后台异步回收 inactive 页
P1 中: kswapd 加速 + 直接回收调用方
P2 重: 同步回收 + 压缩内存(zram) + 逐出进程
P3 极重: OOM Killer 杀进程
三、源码/API 深度解析
3.1 应用层:缓存监控与清理
import { fileIo } from '@kit.CoreFileKit';
import { bundleManager } from '@kit.AbilityKit';
// 监控应用缓存大小(字节)
function getAppCacheSize(): number {
const context = getContext(this) as common.UIAbilityContext;
const filesDir = context.filesDir; // 私有文件目录
const cacheDir = context.cacheDir; // 缓存目录
// 递归统计目录大小
let size = 0;
const statDir = (dir: string): void => {
const list = fileIo.listFileSync(dir);
for (const f of list) {
const full = dir + '/' + f;
try {
const s = fileIo.statSync(full);
if (s.isDirectory()) {
statDir(full);
} else {
size += s.size;
}
} catch (e) { /* 忽略损坏项 */ }
}
};
statDir(cacheDir);
statDir(filesDir);
return size;
}
// 清理应用缓存(主动回写的应用层体现)
function cleanAppCache(): void {
const context = getContext(this) as common.UIAbilityContext;
const cacheDir = context.cacheDir;
try {
// 删除缓存目录内容(图片缓存、临时文件等)
fileIo.rmdirSync(cacheDir);
} catch (e) {
console.error('清理缓存失败: ' + JSON.stringify(e));
}
}
3.2 内核:LRU 回收流程(伪代码)
// 内存回收核心入口(内核)
// 当可用内存低于 watermark 时触发
unsigned long shrink_inactive_list(unsigned long nr_to_scan,
struct lruvec *lruvec)
{
// 1. 从 inactive 链表尾部批量摘取候选页
struct page *page = lru_to_page(lruvec->lists[LRU_INACTIVE_FILE]);
// 2. 逐页判断
for (each page) {
// 2.1 是脏页 → 先加入写回队列
if (PageDirty(page) && !PageWriteback(page)) {
// 写回: 回写磁盘 → 清脏标记
// (主动回写: balance_dirty_pages 也在此触发)
submit_bh_writeback(page);
continue; // 写回完成后才能回收
}
// 2.2 被引用 (正在使用) → 跳过
if (page_referenced(page)) {
move_to_active_list(page); // 升回活跃链表
continue;
}
// 2.3 无引用非脏页 → 直接释放
__remove_page_from_lru(page);
free_unref_page(page); // 归还伙伴系统
}
// 3. 记录回收数量 → 驱动 kswapd 继续/停止
return nr_reclaimed;
}
3.3 内存压力水位线判定
// watermark 判定(内核)
// 每次分配内存前检查
bool zone_watermark_ok(struct zone *zone, unsigned int order,
long mark, int alloc_flags)
{
long available = zone_watermark_ok_zone(...);
// WMARK_MIN: 最低警戒线
if (available < min) {
// 调用直接回收
direct_reclaim(); // 同步回收 → 调用方等待
// 仍不足 → OOM
if (available < min) { out_of_memory(); }
}
// WMARK_LOW: 后台回收线
else if (available < low) {
wakeup_kswapd(zone); // 唤醒后台回收线程
}
return true;
}
四、企业级实战落地
4.1 冷热分层存储架构
| 层 | 介质 | 特点 | 存放数据 |
|---|---|---|---|
| L1 | 内存缓存 | 最快 · 易失 | 热数据(会话、排行榜) |
| L2 | 磁盘缓存目录 | 快 · 持久 | 温数据(图片、媒体) |
| L3 | 用户目录/云端 | 慢 · 可靠 | 冷数据(历史记录、备份) |
4.2 完整示例:冷热分层管理演示
@Entry
@ComponentV2
struct PageCacheDemo {
@Local logs: string[] = [];
@Local state: string = '未开始';
private runHotDemo(): void {
this.logs = [];
this.state = '热数据命中';
this.log('🔥 热数据读取演示 (Page Cache 命中)');
this.log('第 1 次读文件: 磁盘 → 页缓存 → 应用');
this.log(' 耗时: 8.2ms (磁盘 IO)');
this.log(' 页状态: inactive 链表 → 命中升 active');
this.log('第 2 次读文件: 页缓存命中 → 应用');
this.log(' 耗时: 0.08ms (纯内存, 快100倍)');
this.log('第 3 次读文件: 页缓存命中 → 应用');
this.log(' 耗时: 0.07ms ✅');
this.log('结论: 热数据留在内存,命中率决定性能');
}
private runPressureDemo(): void {
this.logs = [];
this.state = '内存压力回收';
this.log('🌊 内存压力分级回收演示');
this.log('可用内存 80% → 绿色区: 正常分配');
this.log('可用内存 45% → 黄色区(WMARK_LOW):');
this.log(' 唤醒 kswapd 后台回收 inactive 页');
this.log('可用内存 25% → 红色区(WMARK_MIN):');
this.log(' 同步直接回收 → 脏页先写回磁盘');
this.log('可用内存 5% → 黑色区: OOM Killer');
this.log(' 按 oom_score 逐出最低价值进程');
this.log('结论: 分级回收避免系统崩溃');
}
build() {
Column({ space: 12 }) {
Text('🧊 冷热数据分层管理').fontSize(20).fontWeight(FontWeight.Bold)
Text('状态: ' + this.state).fontSize(13).fontColor('#4FC3F7')
Row({ space: 8 }) {
Button('热数据命中').layoutWeight(1).height(40).fontSize(12)
.onClick(() => this.runHotDemo())
Button('压力回收').layoutWeight(1).height(40).fontSize(12)
.onClick(() => this.runPressureDemo())
}
.width('100%')
Scroll() {
Column() {
ForEach(this.logs, (l: string) => {
Text(l).fontSize(11).lineHeight(18).fontColor('rgba(255,255,255,0.8)').width('100%')
}, (l: string, i: number) => l + i)
}.width('100%')
}
.layoutWeight(1).width('100%').scrollBar(BarState.Off)
}
.width('100%').height('100%').padding(16)
.backgroundColor('#0D1B2A')
}
}
4.3 冷热策略选择
热数据判定依据:
① 访问频率: 1分钟访问 > N 次 → 热
② 业务性质: 登录态/购物车 → 必须热
③ 预加载: 启动路径数据 → 主动预热
分层落地:
内存层: LruCache / 内存数据库 (如 Redis 思想)
磁盘层: 文件缓存 + 过期清理
远端层: 云端按需拉取
五、问题排查与性能优化
| 问题 | 原因 | 解决 |
|---|---|---|
| 应用反复冷启动 | 页缓存被频繁回收 | 减少内存占用、调整 watermark |
| 写卡顿明显 | 脏页堆积集中写回 | 调低 dirty_ratio、分散写 |
| 明明有内存却卡 | 内存碎片化 | 启用内存压缩/zram |
| 缓存命中率低 | LRU 判定不合理 | 业务层二次缓存 |
| 后台被杀 | 常驻内存过大 | 冷数据下沉到磁盘 |
| 内存泄漏缓慢增长 | 页面未释放 | 监测 shrink 效果 |
5.1 冷热数据优化清单
1. 按业务分缓存: 热数据内存缓存(短TTL),冷数据磁盘缓存(长TTL)
2. 主动预热: 应用启动时预加载首页/核心页数据到页缓存
3. 主动回写: 批量写操作集中 flush,减少脏页堆积
4. 监听内存: onMemoryLevel 回调 → 降级释放冷缓存
5. 监控指标: 缓存命中率、脏页比例、回收压力水位
六、高阶总结与最佳实践
- 缓存是性能之源:Page Cache 让第二次访问快 100 倍,善用缓存层是性能优化的第一课。
- LRU 管冷暖:活跃/非活跃双链表精细区分冷热,回收永远从最冷开始。
- 分级回收保稳定:watermark 三级警戒线让内存压力平滑释放,避免系统雪崩。
- 脏页先回写:任何回收前保证数据落盘,这是数据可靠性的底线。
- 应用层配合:业务层冷热分层 + 主动预热,与内核 Page Cache 形成双层加速。
一句话记住:冷热分层的本质是用"时间换空间、空间换时间"——热数据留在内存(LRU 管冷暖)、脏页及时回写(数据可靠)、内存压力分级回收(watermark 保稳定),应用层配合缓存分层与主动预热,才能让系统又快又稳。
更多推荐




所有评论(0)