延时双删

一、什么是 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>
     *
     */
    @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、延时双删依然无法保证一致,只能减轻出现脏数据的情况,所以对一致性要求较高的数据尽量不要放入缓存