Redis缓存与数据库一致性深度解析
一、缓存一致性核心问题
1. 数据一致性流程图
2. 典型方案对比
方案 | 一致性强度 | 实现复杂度 | 适用场景 |
---|---|---|---|
缓存删除模式 | 最终一致 | 简单 | 读多写少 |
双写模式 | 较强 | 中等 | 写多读少 |
延迟双删 | 较强 | 较高 | 高一致性要求 |
订阅Binlog | 强一致 | 复杂 | 金融级系统 |
二、生产环境解决方案(电商订单系统案例)
1. 延迟双删策略实现
时序图:
代码实现:
public class OrderService {
private final RedisTemplate<String, Object> redisTemplate;
private final RabbitTemplate rabbitTemplate;
@Transactional
public void updateOrder(Order order) {
// 第一次删除
redisTemplate.delete("order:" + order.getId());
// 更新数据库
orderDao.update(order);
// 发送延迟消息
rabbitTemplate.convertAndSend(
"cache.delete.delay.exchange",
"cache.delete",
order.getId(),
message -> {
message.getMessageProperties()
.setDelay(500); // 500ms延迟
return message;
});
}
@RabbitListener(queues = "cache.delete.queue")
public void handleDelayMessage(Long orderId) {
// 第二次删除
redisTemplate.delete("order:" + orderId);
}
}
2. Binlog监听方案
架构图:
关键配置:
public class BinlogConsumer {
@KafkaListener(topics = "canal_topic")
public void process(ChangeEvent event) {
if (event.getTable().equals("orders")) {
String cacheKey = "order:" + event.getPrimaryKey();
switch (event.getType()) {
case INSERT:
case UPDATE:
Order order = queryFromDB(event.getPrimaryKey());
redisTemplate.opsForValue().set(
cacheKey, order, 30, TimeUnit.MINUTES);
break;
case DELETE:
redisTemplate.delete(cacheKey);
break;
}
}
}
}
三、大厂面试深度追问
追问1:如何解决高并发下的缓存与DB一致性问题?
解决方案(库存系统案例):
- 版本号控制机制:
public class VersionedCache {
private final RedisTemplate<String, Object> redisTemplate;
public void updateWithVersion(String key, Object value, long version) {
while (true) {
Long currentVersion = (Long) redisTemplate.opsForValue()
.get(key + ":version");
if (currentVersion == null || version > currentVersion) {
// 乐观锁控制
redisTemplate.watch(key + ":version");
redisTemplate.multi();
redisTemplate.opsForValue().set(key, value);
redisTemplate.opsForValue().set(key + ":version", version);
if (redisTemplate.exec() != null) {
break;
}
} else {
// 版本冲突处理
refreshFromDB(key);
break;
}
}
}
}
- 分布式事务方案:
public class XACacheService {
@Transactional
public void updateWithXA(String key, Object value) {
// 1. 记录事务日志
transactionLogDao.insert(new TransactionLog(key));
// 2. 更新数据库
dataDao.update(value);
// 3. 提交Redis事务
redisTemplate.execute(new SessionCallback<>() {
@Override
public Object execute(RedisOperations operations) {
operations.multi();
operations.opsForValue().set(key, value);
operations.exec();
return null;
}
});
}
@Scheduled(fixedRate = 10000)
public void compensate() {
// 定时检查未完成事务
List<TransactionLog> logs = transactionLogDao.findUnfinished();
logs.forEach(log -> {
Object value = dataDao.getById(log.getKey());
redisTemplate.opsForValue().set(log.getKey(), value);
transactionLogDao.updateStatus(log.getId(), "COMPLETED");
});
}
}
追问2:如何处理缓存与DB更新失败的不同步问题?
解决方案(支付系统案例):
- 事务状态追踪表:
CREATE TABLE cache_sync_status (
id BIGINT PRIMARY KEY,
business_key VARCHAR(64) UNIQUE,
cache_status TINYINT COMMENT '0-未同步 1-已同步',
retry_count INT DEFAULT 0,
create_time DATETIME,
update_time DATETIME
);
- 异步修复服务:
public class CacheRepairService {
private final ScheduledExecutorService executor;
public void init() {
executor.scheduleAtFixedRate(this::repair, 5, 5, TimeUnit.SECONDS);
}
private void repair() {
List<CacheSyncStatus> failedItems = dao.selectFailedItems();
failedItems.forEach(item -> {
try {
Object data = dbQuery(item.getBusinessKey());
redisTemplate.opsForValue().set(
item.getBusinessKey(),
data);
dao.updateStatus(item.getId(), 1);
} catch (Exception e) {
dao.incrementRetry(item.getId());
if (item.getRetryCount() > 5) {
alertAdmin(item);
}
}
});
}
}
- 多级修复策略:
四、架构防御最佳实践
- 多级一致性保障:
保障级别 | 技术方案 | 一致性强度 | 性能影响 |
---|---|---|---|
基础级 | 删除缓存 | 最终一致 | <5% |
进阶级 | 延迟双删 | 较强一致 | 10-15% |
企业级 | Binlog监听 | 强一致 | 20-30% |
金融级 | 分布式事务 | 严格一致 | 50%+ |
- Redis配置建议:
# 开启AOF持久化
appendonly yes
appendfsync everysec
# 集群配置
cluster-enabled yes
cluster-node-timeout 5000
- 监控指标:
- 缓存不一致率:
(缓存未命中且DB存在)/总请求
- 修复延迟:
数据更新到缓存更新耗时
- 修复成功率:
成功修复数/总修复数
- 未来演进方向:
- 混合一致性协议:结合RAFT和最终一致性
- AI预测修复:基于历史模式预测需要修复的数据
- 持久内存应用:使用PMEM减少同步延迟
- Serverless架构:自动扩缩容的修复服务