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可能写入旧数据,但会被二次删除覆盖一、
线程 A 删除缓存(用户信息缓存失效)。
线程 B 在数据库更新前读取缓存(未命中),转而查询数据库旧数据并写入缓存。
线程 A 更新数据库,但此时缓存已被线程 B 写入旧数据,导致数据不一致。
二、
写多读少的业务(如用户信息修改),可减少缓存更新频率。
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}@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步骤已经实现
}
}redisTemplate.opsForValue().set(cacheKey, null, 5, TimeUnit.SECONDS);null,并设置5秒过期时间。userRepository.save(user);@Transactional确保数据库操作的原子性。延时双删机制的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 | 解决缓存穿插问题,优化一致性 | 延迟时间难确定,并非完全可靠 | 适用于更新频率适中但一致性要求高的业务 |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。