1. 问题背景:我们为什么需要关心数据一致性?
想象这样一个场景:你刚在电商平台抢到限量球鞋,付款成功后页面却显示库存未减少。这种"显示有货实际无货"的尴尬,就是典型的缓存与数据库数据不一致问题。在互联网高并发场景中,当Redis作为缓存层和MySQL等持久化数据库配合使用时,如何保证两者数据同步,成为系统设计的核心挑战。
2. 经典解决方案全景图
2.1 Cache Aside Pattern(旁路缓存模式)
// 技术栈:Spring Boot + Redis + MySQL
// 查询操作示例
public Product getProductById(Long id) {
// 1. 先查缓存
String cacheKey = "product:" + id;
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product; // 缓存命中直接返回
}
// 2. 缓存未命中则查数据库
product = productMapper.selectById(id);
if (product != null) {
// 3. 数据库查询结果写入缓存(设置合理过期时间)
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
}
return product;
}
// 更新操作示例
@Transactional
public void updateProduct(Product product) {
// 1. 先更新数据库
productMapper.updateById(product);
// 2. 再删除缓存
String cacheKey = "product:" + product.getId();
redisTemplate.delete(cacheKey);
}
实现要点:
- 读流程遵循"缓存存在直接取,不存在查库回填"原则
- 写操作严格遵循"先更新数据库,再删除缓存"顺序
- 设置合理的缓存过期时间作为兜底策略
2.2 Read/Write Through Pattern(穿透读写模式)
class CacheThrough:
def __init__(self, cache, db):
self.cache = cache # Redis客户端实例
self.db = db # 数据库连接
def read_through(self, key):
# 自动处理缓存未命中情况
value = self.cache.get(key)
if not value:
value = self.db.query("SELECT * FROM data WHERE key=%s", (key,))
if value:
self.cache.setex(key, 3600, value) # 设置1小时过期
return value
def write_through(self, key, value):
# 原子化更新操作
with self.db.transaction():
self.db.execute("UPDATE data SET value=%s WHERE key=%s", (value, key))
self.cache.setex(key, 3600, value)
模式特点:
- 抽象出统一的缓存访问层
- 对业务代码透明化处理缓存逻辑
- 需要维护复杂的状态管理
2.3 延迟双删策略
// 技术栈:Spring Boot + Redisson
@Autowired
private RedissonClient redissonClient;
public void updateWithDoubleDelete(Long productId, Product newData) {
// 第一次删除缓存
String cacheKey = "product:" + productId;
redisTemplate.delete(cacheKey);
// 更新数据库
productMapper.updateById(newData);
// 提交事务后异步二次删除
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
// 延迟500毫秒二次删除
redissonClient.getLock(cacheKey + "_lock").lock();
try {
Thread.sleep(500);
redisTemplate.delete(cacheKey);
} finally {
redissonClient.getLock(cacheKey + "_lock").unlock();
}
}
});
}
适用场景:
- 高并发写操作环境
- 需要处理极端情况下的脏读问题
- 配合分布式锁防止并发修改
3. 进阶解决方案
3.1 基于消息队列的最终一致性
// 技术栈:Spring Cloud Stream + RabbitMQ
@Autowired
private CacheUpdateSender cacheUpdateSender;
public void updateOrderStatus(Long orderId, String status) {
// 1. 数据库更新
orderMapper.updateStatus(orderId, status);
// 2. 发送缓存更新事件
CacheUpdateEvent event = new CacheUpdateEvent(
"order",
orderId.toString(),
"status"
);
cacheUpdateSender.send(event);
}
// 消息消费者
@StreamListener("cacheUpdateInput")
public void handleCacheUpdate(CacheUpdateEvent event) {
String cacheKey = event.getEntityType() + ":" + event.getEntityId();
if ("delete".equals(event.getOperationType())) {
redisTemplate.delete(cacheKey);
} else {
Object data = fetchFromDatabase(event);
redisTemplate.opsForValue().set(cacheKey, data);
}
}
架构优势:
- 解耦数据库操作与缓存更新
- 通过消息持久化保证操作可达
- 支持批量处理提升效率
3.2 版本号控制策略
# Redis命令示例
> SET product:123 "{'name':'球鞋','version':5}" EX 3600
> WATCH product:123
> MULTI
> SET product:123 "{'name':'跑鞋','version':6}"
> EXEC
实现原理:
- 在缓存数据中嵌入版本标识
- 更新时校验版本号连续性
- 结合WATCH命令实现乐观锁控制
3.3 异步补偿机制
// 技术栈:Quartz调度框架
@Scheduled(fixedDelay = 60000) // 每分钟执行
public void cacheConsistencyCheck() {
List<Long> hotProductIds = getHotProductsFromDB();
hotProductIds.parallelStream().forEach(id -> {
String cacheKey = "product:" + id;
Product cacheProduct = redisTemplate.opsForValue().get(cacheKey);
Product dbProduct = productMapper.selectById(id);
if (cacheProduct != null && !cacheProduct.equals(dbProduct)) {
log.warn("发现数据不一致,产品ID:{}", id);
redisTemplate.delete(cacheKey);
}
});
}
注意事项:
- 需要识别业务关键数据
- 控制扫描频率避免性能损耗
- 记录差异日志用于后续分析
4. 技术选型指南
应用场景矩阵:
方案名称 | 适用场景 | QPS支持 | 数据敏感性 |
---|---|---|---|
Cache Aside | 读多写少 | 10万+ | 最终一致 |
Write Through | 写密集型业务 | 5万+ | 强一致 |
延迟双删 | 秒杀类场景 | 1万+ | 最终一致 |
消息队列方案 | 分布式系统 | 横向扩展 | 最终一致 |
技术对比表:
维度 | 实现复杂度 | 一致性强度 | 性能影响 | 适用阶段 |
---|---|---|---|---|
手动维护策略 | ★★☆☆☆ | ★★☆☆☆ | 低 | 快速验证期 |
中间件方案 | ★★★★☆ | ★★★★☆ | 中 | 成熟系统 |
全自动解决方案 | ★★★★★ | ★★★★★ | 高 | 金融级系统 |
5. 实战注意事项
- 缓存穿透防护:对空值设置短TTL,避免频繁查询不存在的数据
- 雪崩预防:采用随机过期时间,避免大量缓存同时失效
- 热点Key处理:使用本地缓存+Redis的多级缓存架构
- 失败重试机制:对缓存删除操作增加重试队列
- 监控告警:实时监控缓存命中率、延迟等核心指标
6. 总结与展望
通过六大核心策略的灵活组合,我们能够构建出适应不同业务场景的缓存一致性解决方案。随着Redis6.0推出的Client-side caching功能,以及新一代分布式数据库的HTAP特性,未来缓存与数据库的边界将越来越模糊。但无论技术如何演进,理解数据流动的本质、选择合适的同步策略,仍然是保证系统可靠性的关键。