
Cache-Aside 模式不是靠“一套固定代码模板”实现的,而是由明确读写逻辑、时序约束和错误应对组成的工程实践。标准编写的关键在于守住三个原则:读操作不写库、写操作不更新缓存、所有缓存失效由应用主动触发且与数据库变更强绑定。
读操作的标准流程
必须严格遵循“缓存优先 → 未命中查库 → 回填缓存 → 返回结果”顺序,不能跳过缓存直连数据库,也不能在缓存命中时再去查库校验。
- 先调用 cache.get(key),判断返回是否为 null 或空值(注意区分缓存穿透)
- 若未命中,执行数据库查询(如 select * from user where id = ?)
- 查到数据后,立即执行 cache.put(key, data, expireSeconds),再返回;查不到则按业务规则决定是否缓存空对象(防穿透)
- 整个过程避免在缓存命中的路径里做任何数据库访问
写操作的标准流程
必须坚持“先成功写库,再删除缓存”,且删除失败要有兜底处理,绝不能改成“更新缓存”或“先删缓存”。
- 执行 update / insert / delete 等数据库语句,确认事务提交成功(比如 Spring 中 @Transactional 生效且无异常)
- 紧接着调用 cache.evict(key) 或 cache.delete(key),确保旧缓存条目被清除
- 如果删除缓存失败(如 Redis 连接超时),需记录告警并触发异步重试(例如落库一张 delay_cache_invalidate 表,由定时任务补偿)
- 禁止在写操作中执行 cache.put(key, newData),这是 Cache-Aside 的核心禁忌
关键边界与容错设计
真实系统中,网络延迟、Redis 暂不可用、数据库主从延迟都会打破理想流程,标准编写必须覆盖这些情况。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 缓存 key 命名需与数据库主键/查询条件严格对齐,避免因 key 不一致导致“删了 A 却读了 B”
- 对高并发更新同一数据的场景,可加分布式锁(如 Redis SETNX + TTL)保护写路径,防止重复删除或误删
- 设置合理的缓存过期时间(TTL)作为最终一致性兜底,但不能依赖它替代主动删除
- 日志中必须记录每次 cache.evict 的 key、数据库操作类型、时间戳,便于问题回溯
Spring Boot 下典型伪代码结构
以下体现的是职责分离和流程可控性,而非语法细节:
@Service
public class UserService {
@Autowired private UserMapper userMapper;
@Autowired private CacheService cacheService; // 封装了 get/put/evict
<pre class="brush:php;toolbar:false;">public User getUserById(Long id) {
String key = "user:" + id;
User cached = cacheService.get(key, User.class);
if (cached != null) return cached;
User dbUser = userMapper.selectById(id);
if (dbUser != null) {
cacheService.put(key, dbUser, 30 * 60); // 30分钟
}
return dbUser;
}
@Transactional
public void updateUser(User user) {
userMapper.updateById(user);
String key = "user:" + user.getId();
boolean deleted = cacheService.evict(key);
if (!deleted) {
// 触发异步补偿,如发 MQ 或写延迟表
invalidateCacheLater(key);
}
}}










