1.什么是延时双删?
延时双删是为了保证缓存和数据库一致性的一种技术方案,即
1.先删除缓存
2.然后更新数据库
3.再删除缓存
2.延时双删解决的问题
在并发量不高的场景下,先删除缓存,后更新数据库就可以基本保证缓存一致性。
但在并发量比较高的场景下,可能存在这样的情况
读写并发时序如下
写进程 | 读进程 |
---|---|
读缓存,缓存中没有值 | |
读数据库,数据库中得到的结果为11 | |
写数据库,更新为21 | |
写缓存,更新为11(数据不一致) |
3.代码
/**
* 冻结用户
*
* @param userId
* @return
*/
@Transactional(rollbackFor = Exception.class)
public UserOperatorResponse freeze(Long userId) {
UserOperatorResponse userOperatorResponse = new UserOperatorResponse();
User user = userMapper.findById(userId);
Assert.notNull(user, () -> new UserException(USER_NOT_EXIST));
Assert.isTrue(user.getState() == UserStateEnum.ACTIVE, () -> new UserException(USER_STATUS_IS_NOT_ACTIVE));
//第一次删除缓存
idUserCache.remove(user.getId().toString());
if (user.getState() == UserStateEnum.FROZEN) {
userOperatorResponse.setSuccess(true);
return userOperatorResponse;
}
user.setState(UserStateEnum.FROZEN);
// 更新数据库
boolean updateResult = updateById(user);
Assert.isTrue(updateResult, () -> new BizException(RepoErrorCode.UPDATE_FAILED));
//加入流水
long result = userOperateStreamService.insertStream(user, UserOperateTypeEnum.FREEZE);
Assert.notNull(result, () -> new BizException(RepoErrorCode.UPDATE_FAILED));
//第二次删除缓存
userCacheDelayDeleteService.delayedCacheDelete(idUserCache, user);
userOperatorResponse.setSuccess(true);
return userOperatorResponse;
}
/**
* 用户缓存延迟删除服务
*
*/
@Service
@Slf4j
public class UserCacheDelayDeleteService {
private static ThreadFactory userCacheDelayProcessFactory = new ThreadFactoryBuilder()
.setNameFormat("user-cache-delay-delete-pool-%d").build();
private ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(10, userCacheDelayProcessFactory);
/**
* 两秒后执行定时任务,删除缓存
* @param idUserCache
* @param user
*/
public void delayedCacheDelete(Cache idUserCache, User user) {
scheduler.schedule(() -> {
boolean idDeleteResult = idUserCache.remove(user.getId().toString());
log.info("idUserCache removed, key = {} , result = {}", user.getId(), idDeleteResult);
}, 2, TimeUnit.SECONDS);
}
}
4.使用延时双删一定没有问题吗
只能降低不一致的概率,并不是一定没有问题,在写进程操作的间隙,读进程拿到脏数据写到缓存,同时延时双删失败的概率特别低。