Redis缓存数据和表数据一致性之延时双删策略

文章介绍了Redis延时双删策略,用于解决分布式系统中数据库和缓存数据一致性问题。该策略在更新数据时,先删除缓存,再更新数据库,然后延迟一段时间再次删除缓存,以减少脏数据的可能性。尽管此方法不能确保完全一致性,但能有效缓解一致性问题。示例代码展示了如何在Java中实现这一策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是 Redis 延时双删?

1、延迟双删策略是分布式系统中数据库存储和缓存数据保持一致性的常用策略,但它不是强一致。不管哪种方案,都无法绝对避免Redis存在脏数据的问题,只能减轻这个问题

2、因为双删策略执行的结果是把redis中保存的那条数据删除了,以后的查询就都会去查询数据库。经常修改的数据表不适合使用redis缓存

3、Redis适用的是读频率远远大于改频率的数据表,不适合改频率大于读频率的数据

二、为什么要使用延时双删?

先看下面两种脏数据情况:

1、情况一:先删除缓存数据,再update更新数据表

当请求1执行删除缓存数据后,还未来得及更新数据表或更新动作还未完成,此时请求2查询到数据表中仍然是更新之前的数据、并把脏数据写入了Redis缓存

2、情况二:先update更新数据表,再删除缓存数据

当请求1执行update更新数据表后,还未来得及删除缓存数据或删除缓存动作还未完成,此时请求2查询到Redis缓存中仍然是旧数据、并返回给前端

为了避免上述的两种情况数据不一致问题,就需要用到我们介绍的延时双闪策略:先删除缓存数据 》再执行update更新数据表 》最后(延时N秒)执行删除缓存

三、实现延时双删,示例:

1、有实现注释,代码如下

更新操作代码:

@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
 
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private ScheduledExecutorService scheduledExecutorService;
 
 
    /**
     * <p>保存用户信息</p>
     *
     * @author hkl
     * @date 2022/11/22
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveUserInfo(UserInfo userInfo) {
        if(ObjectUtil.isNull(userInfo)){
            return;
        }
 
        if(ObjectUtil.isNull(userInfo.getId())){
            this.save(userInfo);
            //Hash方式 start
            redisUtil.putHash(RedisConstants.USER_INFO_HASH, String.valueOf(userInfo.getId()), userInfo);
            //Hash方式 end
        } else {
            //测试Redis延时双删,Hash方式 start
            //1、删除缓存数据
            UserInfo getUserInfoResFirst = this.getById(userInfo.getId());
            Optional.ofNullable(getUserInfoResFirst).ifPresent(obj -> {
                redisUtil.deleteHash(RedisConstants.USER_INFO_HASH, String.valueOf(obj.getId()));
            });
            
            //2、更新数据表
            this.updateById(userInfo);
            
            //3、延时2秒后,再删除缓存数据
            scheduledExecutorService.schedule(() -> {
                Optional.ofNullable(getUserInfoResFirst).ifPresent(obj -> {
                    redisUtil.deleteHash(RedisConstants.USER_INFO_HASH, String.valueOf(obj.getId()));
                });
            }, 2, TimeUnit.SECONDS);
            //测试Redis延时双删,Hash方式 end
        }
    }
 
}

查询操作代码:

  @ApiOperation(value = "根据用户Id查询用户详情信息")
    @ApiImplicitParam(value = "用户Id", name = "userId", required = true)
    @GetMapping("/getUserInfoById")
    public CommonResult<UserInfo> getUserInfoById(Integer userId) {
        //Hash方式 start
        Object userInfoObj = redisUtil.getHash(RedisConstants.USER_INFO_HASH, String.valueOf(userId));
        if(ObjectUtil.isNotNull(userInfoObj)){
            return success(userInfoObj);
        }
        UserInfo userInfo = userInfoService.getById(userId);
        Optional.ofNullable(userInfo).ifPresent(obj -> {
            redisUtil.putHash(RedisConstants.USER_INFO_HASH, String.valueOf(userId), obj);
        });
        //Hash方式 end
        return success(userInfo);
    }

说明:

以上就是实现缓存和数据表一致的延时双删策略Demo,经过测试可以保持数据一致

四、小结

1、使用延时双删策略是为了保持Redis缓存与数据表一致

2、第一次删除不再赘述,为了清除缓存中的旧数据

3、主要是第二次删除,第二次删除为什么要延时呢?延时1是为了等待更新数据表动作完成、2是为了等待更新数据表之前查询查到的旧数据写入缓存动作完成,最后再把写入缓存的旧数据删除

4、延时双删依然无法保证一致,只能减轻出现脏数据的情况,所以对一致性要求较高的数据尽量不要放入缓存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值