admin管理员组

文章数量:1037775

mysql和redis数据一致性

延迟双删

代码语言:mermaid复制
sequenceDiagram
participant A as 线程A(执行双删)
participant B as 线程B/C(读请求)
participant Cache
participant DB

    A->>DB: 1. 更新数据库
    A->>Cache: 2. 删除缓存
    activate B
    B->>Cache: 3. 读缓存(未命中)
    B->>DB: 4. 查询新数据
    DB-->>B: 返回新数据
    B->>Cache: 5. 写入新数据到缓存
    deactivate B
    A-->>A: 等待延迟时间(例如1秒)
    A->>Cache: 6. 再次删除缓存
    Note over A,Cache: 极端情况:若缓存过期,线程C可能写入旧数据,但会被二次删除覆盖

1.为什么要先更新数据库,再删除缓存?

一、

线程 A 删除缓存(用户信息缓存失效)。

线程 B 在数据库更新前读取缓存(未命中),转而查询数据库旧数据并写入缓存。

线程 A 更新数据库,但此时缓存已被线程 B 写入旧数据,导致数据不一致。

二、

写多读少的业务(如用户信息修改),可减少缓存更新频率。

2.为什么需要5秒时间限制?

1. 数据库操作延迟
  • 数据库更新操作(尤其是高并发场景)可能耗时较长。
  • 5秒限制:确保数据库有足够时间完成更新,避免缓存提前删除。
2. 线程切换与网络延迟
  • 分布式系统中,线程调度、网络传输等因素会导致操作时间不确定。
  • 5秒限制:覆盖这些延迟,避免缓存被误更新。
3. 避免缓存穿透
  • 直接删除缓存可能引发大量数据库查询(缓存穿透)。
  • 5秒限制:在缓存失效期间,其他线程直接读取数据库,减少数据库压力。
4. 错开时间差,避免冲突
  • 核心逻辑:先标记缓存失效更新数据库延时删除缓存
  • 5秒限制:让其他线程有时间从数据库读取新数据并更新缓存。

代码实现示例

1. 用户实体类

代码语言:java复制
@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}

2. 用户服务类

代码语言:java复制
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 获取用户信息(含缓存逻辑)
    public User getUser(Long userId) {
        String cacheKey = "user:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user == null) {
            user = userRepository.findById(userId).orElse(null);
            if (user != null) {
                redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
            }
        }
        return user;
    }

    // 更新用户信息(延时双删实现)
    @Transactional
    public void updateUser(User user) {
        Long userId = user.getId();
        String cacheKey = "user:" + userId;
        
        // 1. 标记缓存失效(设置5秒过期时间)
        redisTemplate.opsForValue().set(cacheKey, null, 5, TimeUnit.SECONDS);
        
        // 2. 更新数据库
        userRepository.save(user);
        
        // 3. 延时删除缓存(利用Redis过期机制)第1步骤已经实现
    }
}

代码解析

1. 标记缓存失效

代码语言:java复制
redisTemplate.opsForValue().set(cacheKey, null, 5, TimeUnit.SECONDS);
  • 作用:将缓存值设为null,并设置5秒过期时间。
  • 原理:其他线程读取时发现缓存失效,直接从数据库获取新数据。

2. 更新数据库

代码语言:java复制
userRepository.save(user);
  • 事务保证:使用@Transactional确保数据库操作的原子性。

3. 延时删除缓存

  • 通过Redis的过期机制实现延时删除,避免直接删除导致的冲突。

小结

延时双删机制的5秒时间限制是平衡性能与一致性的关键设计:

  1. 等待数据库操作完成
  2. 覆盖线程/网络延迟
  3. 减少缓存穿透风险
  4. 错开操作时间差

注意:时间设置需根据实际场景调整(如3秒、10秒),核心是确保数据库更新与缓存操作的时间差合理。

同步方式

原理

优点

缺点

适用场景

应用层同步(双写)

先查询Redis缓存,如果没有数据,则从mysql查询并将结果写入redis。更新数据时,先更新mysql,redis等缓存失效的时候,再从mysql查询,接着写入redis

逻辑清晰,可控性强

代码侵入性大,可能出现数据不一致

适用于高并发读写场景

Binlog 监听同步

解析 MySQL Binlog 并同步到 Redis(如 Canal)

自动化同步,降低应用层负担

存在延迟,数据一致性需优化

高吞吐量业务,如订单、库存

定时同步(批量同步)

定时查询 MySQL 并批量刷新 Redis

适合低频变更,避免实时写入开销

数据延迟,可能影响实时性

适用于统计类数据(如报表、排行榜)

触发器同步

通过 MySQL 触发器在变更时更新 Redis

实时同步,应用层无侵入

影响 MySQL 性能,不适用于分布式

适用于小规模数据变更

消息队列(MQ)同步

MySQL 写入后,发送 MQ 消息,消费端更新 Redis

高可靠性,可保证最终一致性

依赖 MQ,增加架构复杂度

适用于金融、电商等高一致性场景

延迟双删

先删 Redis → 更新 MySQL → 等待片刻再删 Redis

解决缓存穿插问题,优化一致性

延迟时间难确定,并非完全可靠

适用于更新频率适中但一致性要求高的业务

mysql和redis数据一致性

延迟双删

代码语言:mermaid复制
sequenceDiagram
participant A as 线程A(执行双删)
participant B as 线程B/C(读请求)
participant Cache
participant DB

    A->>DB: 1. 更新数据库
    A->>Cache: 2. 删除缓存
    activate B
    B->>Cache: 3. 读缓存(未命中)
    B->>DB: 4. 查询新数据
    DB-->>B: 返回新数据
    B->>Cache: 5. 写入新数据到缓存
    deactivate B
    A-->>A: 等待延迟时间(例如1秒)
    A->>Cache: 6. 再次删除缓存
    Note over A,Cache: 极端情况:若缓存过期,线程C可能写入旧数据,但会被二次删除覆盖

1.为什么要先更新数据库,再删除缓存?

一、

线程 A 删除缓存(用户信息缓存失效)。

线程 B 在数据库更新前读取缓存(未命中),转而查询数据库旧数据并写入缓存。

线程 A 更新数据库,但此时缓存已被线程 B 写入旧数据,导致数据不一致。

二、

写多读少的业务(如用户信息修改),可减少缓存更新频率。

2.为什么需要5秒时间限制?

1. 数据库操作延迟
  • 数据库更新操作(尤其是高并发场景)可能耗时较长。
  • 5秒限制:确保数据库有足够时间完成更新,避免缓存提前删除。
2. 线程切换与网络延迟
  • 分布式系统中,线程调度、网络传输等因素会导致操作时间不确定。
  • 5秒限制:覆盖这些延迟,避免缓存被误更新。
3. 避免缓存穿透
  • 直接删除缓存可能引发大量数据库查询(缓存穿透)。
  • 5秒限制:在缓存失效期间,其他线程直接读取数据库,减少数据库压力。
4. 错开时间差,避免冲突
  • 核心逻辑:先标记缓存失效更新数据库延时删除缓存
  • 5秒限制:让其他线程有时间从数据库读取新数据并更新缓存。

代码实现示例

1. 用户实体类

代码语言:java复制
@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}

2. 用户服务类

代码语言:java复制
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 获取用户信息(含缓存逻辑)
    public User getUser(Long userId) {
        String cacheKey = "user:" + userId;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user == null) {
            user = userRepository.findById(userId).orElse(null);
            if (user != null) {
                redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
            }
        }
        return user;
    }

    // 更新用户信息(延时双删实现)
    @Transactional
    public void updateUser(User user) {
        Long userId = user.getId();
        String cacheKey = "user:" + userId;
        
        // 1. 标记缓存失效(设置5秒过期时间)
        redisTemplate.opsForValue().set(cacheKey, null, 5, TimeUnit.SECONDS);
        
        // 2. 更新数据库
        userRepository.save(user);
        
        // 3. 延时删除缓存(利用Redis过期机制)第1步骤已经实现
    }
}

代码解析

1. 标记缓存失效

代码语言:java复制
redisTemplate.opsForValue().set(cacheKey, null, 5, TimeUnit.SECONDS);
  • 作用:将缓存值设为null,并设置5秒过期时间。
  • 原理:其他线程读取时发现缓存失效,直接从数据库获取新数据。

2. 更新数据库

代码语言:java复制
userRepository.save(user);
  • 事务保证:使用@Transactional确保数据库操作的原子性。

3. 延时删除缓存

  • 通过Redis的过期机制实现延时删除,避免直接删除导致的冲突。

小结

延时双删机制的5秒时间限制是平衡性能与一致性的关键设计:

  1. 等待数据库操作完成
  2. 覆盖线程/网络延迟
  3. 减少缓存穿透风险
  4. 错开操作时间差

注意:时间设置需根据实际场景调整(如3秒、10秒),核心是确保数据库更新与缓存操作的时间差合理。

同步方式

原理

优点

缺点

适用场景

应用层同步(双写)

先查询Redis缓存,如果没有数据,则从mysql查询并将结果写入redis。更新数据时,先更新mysql,redis等缓存失效的时候,再从mysql查询,接着写入redis

逻辑清晰,可控性强

代码侵入性大,可能出现数据不一致

适用于高并发读写场景

Binlog 监听同步

解析 MySQL Binlog 并同步到 Redis(如 Canal)

自动化同步,降低应用层负担

存在延迟,数据一致性需优化

高吞吐量业务,如订单、库存

定时同步(批量同步)

定时查询 MySQL 并批量刷新 Redis

适合低频变更,避免实时写入开销

数据延迟,可能影响实时性

适用于统计类数据(如报表、排行榜)

触发器同步

通过 MySQL 触发器在变更时更新 Redis

实时同步,应用层无侵入

影响 MySQL 性能,不适用于分布式

适用于小规模数据变更

消息队列(MQ)同步

MySQL 写入后,发送 MQ 消息,消费端更新 Redis

高可靠性,可保证最终一致性

依赖 MQ,增加架构复杂度

适用于金融、电商等高一致性场景

延迟双删

先删 Redis → 更新 MySQL → 等待片刻再删 Redis

解决缓存穿插问题,优化一致性

延迟时间难确定,并非完全可靠

适用于更新频率适中但一致性要求高的业务

本文标签: mysql和redis数据一致性