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

全部评论

相关推荐

强度拉满了,连着干了2+h,累死了,而且和笔试连着,面完问答脑子一团浆糊,笔试就a了0.几个,估计是gg解释HTTP和HTTPS的区别具体说一下TLS握手的过程证书验证过程中,CA的作用是什么,CA本身被攻破会造成什么影响解释Java中的类加载机制,包括双亲委派模型和类加载器的种类双亲委派模型可以避免核心类被篡改,怎么设计一个核心类加载器,具体实现思路是什么双亲委派模型可以避免核心类被篡改,怎么设计一个类加载器,可以仿照tomcat,具体实现思路是什么在实现自定义类加载器的过程中,findclass和loadclass如何配合讲述数据库中常见的索引类型在高并发情况,既包含范围查询,又包含等值查询,如何选择查询方式如果选择了B+树索引,使用时发现查询性能不如预期,怎么诊断和优化如何设计一个用户注册和登陆系统,包括验证码步骤,描述设计和实现按步骤密码加密存储,具体选用哪种加密算法,为什么系统需要支持高并发场景,如何使用BCrypt以减轻对系统响应速度的影响描述一次你在过往项目中遇到问题的经历,描述问题、目标、结果面对这个问题中,你是怎么学习新技术的,比如消息队列、限流等,这些是你本来就会的还是在项目中临时学习的设计解决方案中,有多种因素和选择,在此过程中,你是怎么得知关键影响因素的介绍你印象最深的团队合作项目,你的职责是什么,你采取了哪些行为来应对如何协调团队成员之间的分歧,推进项目
查看18道真题和解析
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

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