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

全部评论

相关推荐

大名鼎鼎楚雨荨:我寻思这不才刚二面?
秋招的第一个offer,...
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务