3. 缓存架构模式:旁路缓存、读写穿透、异步回写
在集成测试中验证 PostgreSQL 缓存数据一致性,首先需要理解主流的缓存架构模式。不同的模式决定了缓存与数据库之间的交互时序、一致性边界以及测试关注点。本节聚焦三种核心模式:旁路缓存 (Cache-Aside)、读写穿透 (Read/Write-Through) 和 异步回写 (Write-Behind),并分析每种模式在一致性验证中的关键特征。
3.1 旁路缓存 (Cache-Aside)
旁路缓存是应用最广泛的模式,应用代码直接同时管理缓存和数据库。读取时优先查缓存,未命中则从数据库加载并回填缓存;写入时先更新数据库,再删除或更新缓存。
核心特征:应用层承担缓存与数据库的双写/双删逻辑;缓存与数据库之间无自动同步机制。
旁路缓存验证要点
| 验证场景 | 测试方法 | 预期结果 |
|---|---|---|
| 缓存未命中回填 | 首次读取某 key | 从 PostgreSQL 加载并写入缓存,后续读取命中 |
| 写后删缓存 | 更新数据库后立即删除缓存 | 下次读取触发回填,数据一致 |
| 删缓存失败 | 模拟 Redis 异常或网络超时 | 缓存保留旧数据,需补偿或过期机制 |
| 并发读写竞争 | 线程 A 写库删缓存,线程 B 同时读 | 可能读到旧缓存,需加锁或延迟双删 |
测试建议:旁路缓存模式下,集成测试应重点构造“写后立即读”的并发场景,并验证缓存过期或删除失败后的数据收敛能力。
3.2 读写穿透 (Read/Write-Through)
读写穿透模式将缓存作为数据访问的唯一入口。应用只与缓存交互,缓存组件内部负责同步数据库。读取时缓存未命中由缓存层加载数据库;写入时缓存先更新自身,再同步写入数据库。
核心特征:缓存层承担同步代理职责;应用对数据库无感知;缓存与数据库之间由缓存中间件保证同步。
读写穿透验证要点
- 缓存未命中回源:验证缓存代理层在 key 不存在时,正确从 PostgreSQL 加载并回填缓存。
- 同步写库失败:模拟数据库连接超时或写入冲突,验证代理层是否抛出异常或进入降级模式。
- 缓存与数据库原子性:验证写操作在缓存更新后、数据库写入前发生崩溃时,数据是否回滚。
- 并发穿透:高并发读取同一未命中 key 时,验证是否只触发一次数据库查询(防雪崩)。
注意:读写穿透模式对缓存中间件要求较高,集成测试需覆盖代理层本身的故障场景,而非仅关注应用逻辑。
3.3 异步回写 (Write-Behind)
异步回写模式中,写入操作只更新缓存,立即返回成功;缓存组件在后台异步批量将数据写入数据库。该模式追求极致写性能,但牺牲了强一致性。
核心特征:写操作先落缓存,异步批量刷库;读操作始终从缓存获取;数据最终一致。
异步回写验证要点
| 验证场景 | 测试方法 | 预期结果 |
|---|---|---|
| 写后立即读 | 写入缓存后立即查询数据库 | 数据库尚未更新,数据不一致(最终一致) |
| 刷库延迟 | 监控异步队列处理时间 | 在配置的延迟窗口内完成刷库 |
| 缓存崩溃 | 模拟 Redis 宕机后恢复 | 未刷库的写入丢失,需持久化或 WAL 保护 |
| 重复刷库 | 模拟队列重试机制 | 数据库操作需幂等,避免重复写入 |
测试建议:异步回写模式应重点验证“缓存持久化”和“崩溃恢复”场景,确保异步队列在故障后不丢失关键数据。
3.4 三种模式对比总结
| 维度 | 旁路缓存 | 读写穿透 | 异步回写 |
|---|---|---|---|
| 一致性模型 | 最终一致(需补偿) | 强一致(同步写库) | 最终一致(时间窗口) |
| 写性能 | 中等(双写开销) | 中等(同步等待) | 高(异步批量) |
| 实现复杂度 | 低(应用自管理) | 中(需代理层) | 高(队列+幂等) |
| 数据丢失风险 | 低(数据库为主) | 低(同步写入) | 高(缓存崩溃) |
| 集成测试重点 | 并发竞争、删缓存失败 | 代理层故障、回源穿透 | 刷库延迟、崩溃恢复 |
选型建议:对一致性要求高的业务(如订单、支付)优先使用读写穿透或旁路缓存+延迟双删;对写性能要求极高且可接受短暂不一致的业务(如点赞、日志)可选用异步回写。