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秒时间限制是平衡性能与一致性的关键设计:
- 等待数据库操作完成
- 覆盖线程/网络延迟
- 减少缓存穿透风险
- 错开操作时间差
注意:时间设置需根据实际场景调整(如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秒时间限制是平衡性能与一致性的关键设计:
- 等待数据库操作完成
- 覆盖线程/网络延迟
- 减少缓存穿透风险
- 错开操作时间差
注意:时间设置需根据实际场景调整(如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数据一致性
版权声明:本文标题:mysql和redis数据一致性 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1748263032a2276990.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论