18.7.7 Redis与数据库数据一致性保证
1. 数据一致性问题分析
1.1 一致性问题场景
public class DataConsistencyProblem {
/*
* Redis与数据库一致性问题:
*
* 1. 问题场景
* - 缓存与数据库数据不一致
* - 并发更新导致的数据竞争
* - 缓存更新失败但数据库更新成功
* - 数据库更新失败但缓存更新成功
*
* 2. 一致性级别
* - 强一致性:数据实时同步
* - 弱一致性:允许短暂不一致
* - 最终一致性:保证最终数据一致
*
* 3. 常见策略
* - Cache Aside模式
* - 延时双删
* - 消息队列异步
* - 分布式事务
*/
public void demonstrateConsistencyProblems() {
System.out.println("=== 数据一致性问题演示 ===");
MockDataService dataService = new MockDataService();
demonstrateInconsistencyScenarios(dataService);
demonstrateConcurrencyIssues(dataService);
}
private void demonstrateInconsistencyScenarios(MockDataService dataService) {
System.out.println("--- 数据不一致场景 ---");
System.out.println("1. 场景1:先更新数据库,后更新缓存");
dataService.updateDatabaseFirst("user:1001", "张三-更新");
System.out.println("\n2. 场景2:先更新缓存,后更新数据库");
dataService.updateCacheFirst("user:1002", "李四-更新");
System.out.println("\n3. 场景3:先删除缓存,后更新数据库");
dataService.deleteCacheFirst("user:1003", "王五-更新");
System.out.println("\n4. 场景4:先更新数据库,后删除缓存");
dataService.updateDatabaseDeleteCache("user:1004", "赵六-更新");
}
private void demonstrateConcurrencyIssues(MockDataService dataService) {
System.out.println("\n--- 并发问题演示 ---");
String userId = "user:concurrent";
// 模拟并发读写
System.out.println("1. 并发场景:多线程同时读写");
// 线程1:更新操作
new Thread(() -> {
dataService.updateUser(userId, "并发更新1");
}, "UpdateThread1").start();
// 线程2:读取操作
new Thread(() -> {
String result = dataService.getUser(userId);
System.out.println("并发读取结果: " + result);
}, "ReadThread1").start();
try {
Thread.sleep(1000); // 等待并发操作完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 模拟数据服务
class MockDataService {
private java.util.Map<String, String> database = new java.util.concurrent.ConcurrentHashMap<>();
private java.util.Map<String, String> cache = new java.util.concurrent.ConcurrentHashMap<>();
public void updateDatabaseFirst(String key, String value) {
System.out.println("执行:先更新数据库,后更新缓存");
// 1. 更新数据库
database.put(key, value);
System.out.println(" 数据库更新成功: " + key + " = " + value);
// 2. 更新缓存
cache.put(key, value);
System.out.println(" 缓存更新成功: " + key + " = " + value);
System.out.println(" 风险:并发读可能获取到旧缓存数据");
}
public void updateCacheFirst(String key, String value) {
System.out.println("执行:先更新缓存,后更新数据库");
// 1. 更新缓存
cache.put(key, value);
System.out.println(" 缓存更新成功: " + key + " = " + value);
// 2. 更新数据库
database.put(key, value);
System.out.println(" 数据库更新成功: " + key + " = " + value);
System.out.println(" 风险:数据库更新失败时缓存数据错误");
}
public void deleteCacheFirst(String key, String value) {
System.out.println("执行:先删除缓存,后更新数据库");
// 1. 删除缓存
cache.remove(key);
System.out.println(" 缓存删除成功: " + key);
// 2. 更新数据库
database.put(key, value);
System.out.println(" 数据库更新成功: " + key + " = " + value);
System.out.println(" 风险:并发读可能重新加载旧数据到缓存");
}
public void updateDatabaseDeleteCache(String key, String value) {
System.out.println("执行:先更新数据库,后删除缓存");
// 1. 更新数据库
database.put(key, value);
System.out.println(" 数据库更新成功: " + key + " = " + value);
// 2. 删除缓存
cache.remove(key);
System.out.println(" 缓存删除成功: " + key);
System.out.println(" 优势:相对安全的策略");
}
public void updateUser(String userId, String userData) {
System.out.println(Thread.currentThread().getName() + " 更新用户: " + userId + " = " + userData);
// 模拟数据库更新
database.put(userId, userData);
// 模拟缓存更新
cache.put(userId, userData);
}
public String getUser(String userId) {
// 先查缓存
String cachedData = cache.get(userId);
if (cachedData != null) {
System.out.println(Thread.currentThread().getName() + " 从缓存获取: " + userId + " = " + cachedData);
return cachedData;
}
// 查数据库
String dbData = database.get(userId);
if (dbData != null) {
cache.put(userId, dbData);
System.out.println(Thread.currentThread().getName() + " 从数据库获取: " + userId + " = " + dbData);
return dbData;
}
return null;
}
}
2. Cache Aside模式
2.1 Cache Aside实现
public class CacheAsidePattern {
/*
* Cache Aside模式:
*
* 1. 读取流程
* - 先查缓存
* - 缓存命中直接返回
* - 缓存未命中查数据库
* - 将数据库结果写入缓存
*
* 2. 更新流程
* - 先更新数据库
* - 再删除缓存
* - 让下次读取时重新加载
*
* 3. 优势
* - 应用控制缓存逻辑
* - 灵活性高
* - 容错性好
*/
public void demonstrateCacheAside() {
System.out.println("=== Cache Aside模式演示 ===");
CacheAsideService service = new CacheAsideService();
demonstrateReadFlow(service);
demonstrateUpdateFlow(service);
}
private void demonstrateReadFlow(CacheAsideService service) {
System.out.println("--- 读取流程演示 ---");
String userId = "user:1001";
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经
美的集团公司福利 798人发布