2. 单体架构下的缓存模式:本地缓存与数据库直连

在单体架构(Monolithic Architecture)阶段,所有业务逻辑、数据访问和缓存管理都运行在同一个进程内。这一阶段的缓存策略以“简单、直接”为主要特征,但同时也埋下了数据一致性的隐患。本章将剖析三种核心模式:本地缓存(HashMap/Guava)、缓存与数据库直连,以及它们带来的一致性挑战。

2.1 本地缓存实现:从 HashMap 到 Guava Cache

本地缓存是指将数据存储在应用进程的内存中,所有请求共享同一份缓存数据。在单体应用中,最常见的实现方式有两种:

实现方式 核心机制 典型代码 适用场景
HashMap 缓存 使用 ConcurrentHashMap 手动管理键值对,无过期策略 private static final Map<String, Object> cache = new ConcurrentHashMap<>(); 极简原型、配置项缓存、临时数据
Guava Cache 基于 CacheBuilder 构建,支持过期、最大容量、弱引用 Cache<String, Object> cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).maximumSize(1000).build(); 高频读取、可容忍短暂不一致、本地会话
关键区别: HashMap 缓存需要开发者手动处理并发、过期和淘汰逻辑;Guava Cache 内置了 LRU 淘汰、定时过期、统计监控等能力,是单体架构下本地缓存的推荐选型。

2.2 缓存与数据库直连模式

在单体架构中,缓存与数据库的交互通常采用“直连模式”(Direct-Access Pattern),即应用层直接调用缓存 API 和数据库 DAO,没有中间代理层。其典型流程如下:

  1. 读操作: 先查询本地缓存(Cache-Aside 模式),命中则直接返回;未命中则查询数据库,将结果写入缓存后返回。
  2. 写操作: 先更新数据库,再删除或更新缓存(通常选择删除缓存,避免并发写导致的数据不一致)。
  3. 失效策略: 依赖 TTL(Time-To-Live)或手动调用 invalidate() 方法。
实践建议: 单体架构下推荐使用“先更新数据库,再删除缓存”的策略(Cache-Aside + Delete),而非“先更新缓存,再更新数据库”,后者更容易出现脏数据。
单体架构缓存与数据库直连模式流程图 单体架构:缓存与数据库直连模式 应用层(单体进程) 读请求:查本地缓存 未命中:查数据库 回写缓存 + 返回 写请求:更新数据库 删除/失效缓存 返回写成功 读流程:Cache-Aside;写流程:先更新DB再删缓存

2.3 单体架构的缓存一致性挑战

尽管本地缓存 + 直连模式实现简单,但在单体架构中,缓存与数据库之间的数据一致性面临以下三大核心挑战:

  • 并发写冲突: 两个线程同时执行“更新数据库 + 删除缓存”操作,可能因时序交错导致缓存中残留旧数据。例如:线程A更新DB为v2,线程B更新DB为v3,线程A删除缓存后,线程B又写入v2(旧值)。
  • 读扩散与缓存穿透: 热点数据失效瞬间,大量请求同时穿透到数据库,导致数据库压力骤增,甚至引发雪崩。
  • 本地缓存无法跨进程共享: 单体架构虽然只有一个进程,但若部署多实例(如多容器),每个实例的本地缓存相互独立,导致同一用户在不同实例上看到不同数据。
典型陷阱: 很多团队在单体架构中使用“先更新缓存,再异步更新数据库”的策略,认为这样可以提升写性能。但一旦缓存更新成功而数据库更新失败,系统将长期提供错误数据,且难以自动恢复。这种模式在单体架构中应严格避免。
单体架构缓存一致性挑战示意图 单体架构缓存一致性三大挑战 挑战1:并发写冲突 线程A: 更新DB→v2 线程B: 更新DB→v3 缓存残留v2(旧值) 挑战2:读扩散/穿透 缓存失效瞬间 N个请求同时穿透 数据库压力飙升 挑战3:多实例不一致 实例1缓存: 数据A 实例2缓存: 数据B 用户请求漂移导致不一致 单体架构下,本地缓存无法保证强一致性,只能做到最终一致性

2.4 单体架构下的缓存一致性策略对比

针对上述挑战,单体架构中常用的缓存一致性策略及其适用性如下表所示:

策略名称 实现方式 一致性级别 适用场景 风险点
Cache-Aside + 删除 读时回写,写时先更新DB再删缓存 最终一致性 读多写少,可容忍短暂不一致 并发写可能残留旧值
Cache-Aside + 更新 写时同时更新DB和缓存 弱一致性 写频率低,缓存数据可覆盖 并发写导致缓存与DB数据错位
Read-Through + TTL 缓存层自动加载,TTL到期失效 最终一致性(延迟取决于TTL) 数据变化不频繁,可接受延迟 TTL期间数据不一致
Write-Through 写操作同时写入缓存和DB(同步) 强一致性(单机) 对一致性要求极高,写性能可牺牲 写延迟增加,缓存与DB耦合
单体架构最佳实践: 推荐优先采用 Cache-Aside + 删除 策略,配合合理的 TTL(如 5~10 分钟)作为兜底。对于关键数据(如库存、余额),可考虑使用 Write-Through 模式,但需评估写性能影响。

2.5 本章小结