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圣经