Appearance
移动端与服务端数据库、Redis 缓存体系总览
回到总览:后台 / API / 数据层
相关模块:缓存更新策略与常见坑:穿透、击穿、雪崩与一致性 · 状态持久化、内存恢复与分层恢复策略
一句话定义
数据库与缓存体系是指在移动端(SQLite / Room / Hive / MMKV)与服务端(MySQL / PostgreSQL / Redis / MongoDB)架构中,根据数据的结构化程度、读写吞吐要求以及持久化诉求,选择最适存储引擎并进行多级缓存设计的技术体系。
代码索引
对应 Lab:database-cache-simulation
labs/shared/backend-data/database-cache-demo/— Room 数据库 WAL 模式优化与 MMKV 键值对性能测试
为什么需要
- 为什么移动端本地存储大批量数据时,使用 SQLite (Room) 远比将 JSON 序列化后存入 SharedPreferences / 文件好得多?
- 一句话答:SharedPreferences 每次修改都需要将整个 XML 全量读写磁盘;而 SQLite 拥有 B+ 树索引与 WAL(预写日志)机制,支持局部增删改查与事务操作,数据量越大性能差距越呈数量级放大。
- 为什么 10 年 Android 工程师必须理解后端 Redis 缓存与移动端本地缓存的异同?
- 一句话答:两者本质都是“以空间换时间”的缓存层;服务端 Redis 解决的是高并发 QPS 压垮关系型数据库的问题,移动端本地缓存解决的是弱网无网下的离线可读与首帧渲染体验。
底层机制
1. 移动端与服务端存储选型全景矩阵
text
移动端/客户端 (Client)
┌───────────────────────────┬───────────────────────────┐
│ 键值/小配置 (KV/Prefs) │ 结构化数据 (Relational) │
│ - MMKV (mmap 内存映射) │ - SQLite / Room (Android)│
│ - SharedPreferences (旧) │ - Hive / Isar (Flutter) │
└───────────────────────────┴───────────────────────────┘
│
▼ (HTTP / API 网络交互)
服务端/后台 (Server)
┌───────────────────────────┬───────────────────────────┐
│ 高速内存缓存 (In-Memory) │ 持久化数据库 (DB) │
│ - Redis (单线程/IO多路复用)│ - MySQL / PostgreSQL │
│ - Memcached │ - MongoDB (文档型 NoSQL) │
└───────────────────────────┴───────────────────────────┘典型存储引擎对比表
| 存储引擎 | 存储类型 | 数据位置 | 核心优势 | 缺点/局限 |
|---|---|---|---|---|
| MMKV | Key-Value | 内存映射 (mmap) | 极其高速,崩溃数据不丢失,多进程安全 | 不适合复杂 SQL 条件查询 |
| Room (SQLite) | 关系型 (RDBMS) | 磁盘文件 (.db) | 支持 SQL 复杂查询、事务、ORM 映射 | 大批量并发写入需要开启 WAL |
| Redis | Key-Value (多数据结构) | 内存 + 磁盘 RDB/AOF | QPS 达十万级,支持 List/Hash/ZSet | 内存昂贵,数据非 100% 强一致 |
| MySQL | 关系型 (RDBMS) | 磁盘 (B+ 树) | 强 ACID 事务支持,数据高度结构化 | 无法承受数十万级高并发直读 |
2. SQLite WAL (Write-Ahead Logging) 预写日志原理
对应 Lab:database-cache-simulation
传统 SQLite 写入会锁住整个数据库(读写互斥);开启 WAL 模式后:
- 写操作:不直接修改原数据库文件,而是顺序追加写入到
.db-wal日志文件中。 - 读操作:先查 WAL 日志,再查原数据库文件。
- 读写并发:读写不再互相阻塞,大幅提升了移动端数据库的并发性能。
Android / Flutter / Web / Backend 对照
| 维度 | Android 原生 | Flutter 框架 | 服务端 (Java/Node) |
|---|---|---|---|
| 键值存储 | MMKV / DataStore | shared_preferences / hive | Redis |
| 数据库 | Room (SQLite) | sqflite / isar | Spring Data JPA / MyBatis (MySQL) |
| 缓存淘汰 | LruCache (内存) | ImageCache (内存) | Redis allkeys-lru / Caffeine |
常见场景与代码优化
1. Room 数据库开启 WAL 模式与事务批量写入
java
// 开启 WAL 模式提升并发读写性能
Room.databaseBuilder(context, AppDatabase.class, "app_db")
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
.build();
// 批量插入必须使用事务 (@Transaction),性能提升百倍!
@Transaction
public void insertUserList(List<User> users) {
userDao.insertAll(users);
}常见误配、事故后果与排障
1. 事故:在主线程直接调用 Room 数据库读写引发 ANR
- 误配原因:未在 Room 构造时禁止主线程读写,直接在 UI 线程执行
userDao.getAllUsers()。 - 后果:SQLite 磁盘 I/O 阻塞主线程,抛出
Cannot access database on the main thread或触发 ANR。 - 排障与修法:Room 配合 Kotlin 协程
suspend函数,自动将底层 I/O 派发到Dispatchers.IO执行。
与相近概念对比
| 数据结构 | Redis 对应类型 | 典型应用场景 |
|---|---|---|
| String | 字符串 / 字节流 | 缓存 JSON 对象、Token 令牌、计数器 |
| Hash | 哈希表 (Map) | 存储用户信息结构(可单独修改某个 Field) |
| ZSet | 有序集合 (Sorted Set) | 实时排行榜(根据 Score 自动排序) |
对应实验
| Lab | 说明 | 源码 |
|---|---|---|
| database-cache-simulation | SQLite WAL 读写并发 + 内存/磁盘/远端三级缓存 | DatabaseCacheSimulation.java |
labs/shared/backend-data/database-cache-demo/— Room 数据库 WAL 模式优化与 MMKV 键值对性能测试
复习检查题
为什么 Android 移动端使用 MMKV 替代 SharedPreferences 性能会有质的飞跃?
答:SharedPreferences 写入时采用全量 XML 序列化并写入磁盘,频繁修改开销极大;而 MMKV 基于 mmap(内存映射) 机制,将文件直接映射到进程的虚拟内存空间,读写内存就等于读写磁盘。同时 MMKV 使用 Protocol Buffers 增量序列化,避免了全量重新写入,即使应用发生 Crash 崩溃,操作系统内核也会保证内存映射的数据安全落盘。
SQLite 开启 WAL (Write-Ahead Logging) 模式有什么好处?
答:传统 SQLite 在写入时会锁定整个数据库文件,导致读操作和写操作互相阻塞。开启 WAL 模式后,所有的写操作都会改为顺序追加写入独立的
.db-wal日志文件中,原数据库文件保持不变。这实现了**“读不阻塞写,写也不阻塞读”**,极大地提升了并发读写性能。
速记
- KV 首选 MMKV:基于 mmap 内存映射,崩溃不丢数据,读写性能远超 SP。
- SQLite 开 WAL:开启 WAL 预写日志,读写互不阻塞,大批量插入必加事务。
- 后端 Redis 缓存:QPS 十万级高并发,内存存储 + 异步持久化。