【Redis篇05】redis缓存-双写一致性

前提

redis作为缓存数据,如何与DB进行同步,就是双写一致性的问题,回答问题的前提是结合业务背景

按照两种业务类型去讲
  • 一致性要求高的业务
  • 允许延迟一致的业务
数据强一致

简历上xxx功能,因为需要让数据库和redis保持高度一致,时效性也比较高,当时我采用的是读写锁保证的强一致性。

具体是:使用Redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我更新数据时,添加排他锁,它是读写、读读都互斥的。这样保证了在写数据的同时是不会让其他线程读数据的,避免了脏数据。(这里需要注意的是读方法和写方法使用的是同一把锁就行)

  • 那这个排他锁是如何保证读写、读读互斥的呢?

因为排他锁底层使用的也是LUA脚本获取读写锁,保证了只有一个线程操作锁住的方法。

数据最终一致

简历上xxx功能,因为需要让数据库和redis保持一致,可以有一定的时延,当时我采用的是canal组件实现数据同步的。

代码侵入性低,部署一个canal服务,canal服务将自己伪装成mysql的一个从节点,当mysql有数据更新后,canal会读取binlog数据,然后在通过canal客户端获取数据,更新redis缓存。

数据最终一致:延迟双删不采用原因:技术上延时时间不好控制,延迟过程中可能会造成脏数据

延迟双删:如果写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延迟删除缓存中的数据,其中的延时多久不太好确定,再延迟过程中可能会出现脏数据,并不能保证强一致性

读写锁代码案例

引入依赖

js 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.8</version> 
</dependency>

由于 RLock 实现了 AutoCloseable 接口,我们可以使用 try-with-resources 语句来自动管理锁的生命周期。这意味着当 try 代码块执行完毕后(无论是正常结束还是因为异常提前退出),锁都会被自动释放。

js 复制代码
import org.redisson.Redisson;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class UserProfileService {

    private final RedissonClient redissonClient;
    private final RReadWriteLock profileLock;

    public UserProfileService() {
        // 配置 Redisson
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        redissonClient = Redisson.create(config);

        // 获取读写锁实例
        profileLock = redissonClient.getReadWriteLock("userProfileLock");
    }

    public void viewUserProfile(String userId) {
        // 使用读锁
        try (RLock readLock = profileLock.readLock()) {
            readLock.lock();  // 获取读锁
            System.out.println("User " + userId + " is viewing the profile.");
            // 查看用户资料的逻辑
            Thread.sleep(1000);  // 模拟耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    public void editUserProfile(String userId) {
        // 使用写锁
        try (RLock writeLock = profileLock.writeLock()) {
            writeLock.lock();  // 获取写锁
            System.out.println("User " + userId + " is editing the profile.");
            // 编辑用户资料的逻辑
            Thread.sleep(2000);  // 模拟耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        UserProfileService service = new UserProfileService();

        // 模拟多个线程同时访问
        Runnable viewTask = () -> service.viewUserProfile("user-1");
        Runnable editTask = () -> service.editUserProfile("user-2");

        new Thread(viewTask).start();
        new Thread(editTask).start();
        new Thread(viewTask).start();
    }
}
相关推荐
蒹葭玉树4 小时前
【C++上岸】C++常见面试题目--算法篇(第二十期)
c++·算法·面试
CoderYanger7 小时前
MySQL数据库——3.2.1 表的增删查改-查询部分(全列+指定列+去重)
java·开发语言·数据库·mysql·面试·职场和发展
雲墨款哥7 小时前
一个前端开发者的救赎之路-JS基础回顾(五)-数组
前端·javascript·面试
我是哪吒8 小时前
分布式微服务系统架构第170集:Kafka消费者并发-多节点消费-可扩展性
后端·面试·github
一只叫煤球的猫10 小时前
2025年基于Java21的的秒杀系统要怎么设计?来点干货
后端·面试·性能优化
yanlele11 小时前
前端面试第 78 期 - 2025.09.07 更新 Nginx 专题面试总结(12 道题)
前端·javascript·面试
yh云想12 小时前
《Java线程池面试全解析:从原理到实践的高频问题汇总》
jvm·面试·职场和发展
橙序员小站12 小时前
搞定系统面试题:如何实现分布式Session管理
java·后端·面试
元闰子14 小时前
怎么用CXL加速数据库?· SIGMOD'25
数据库·后端·面试
小高00715 小时前
🔥🔥🔥Vue部署踩坑全记录:publicPath和base到底啥区别?99%的前端都搞错过!
前端·vue.js·面试