【Redis】实现商铺和缓存与数据库双写一致

在上节我们已经分析了缓存更新策略的最佳实践,因此这节课我们就动手来试试,给查询商铺的缓存添加超时剔除和主动更新的策略

修改ShopController中的业务逻辑,满足下面的需求,需求分为两步,也就是我们查询和更新时要干的事:

① 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间,这样将来就可以做到超时剔除了

② 根据id修改店铺时,先修改数据库,再删除缓存


一、查询

RedisConstants.java

public static final Long CACHE_SHOP_TTL = 30L;

ShopServiceImpl.java

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        // 4.不存在,根据id查询数据库
        Shop shop = getById(id);
        // 5.查询数据库不存在,直接返回错误
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        // 6.存在,写入redis
        //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
        // 7.返回
        return Result.ok(shop);
    }
}

二、更新

ShopController.java

之前的更新是直接使用Mybatis-Plus的 updateById(),因此没有我们什么业务,现在我们要将它改一下。

image-20240525204927800

我们会在service层添加缓存逻辑

/**
 * 更新商铺信息
 * @param shop 商铺数据
 * @return 无
 */
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
    // 写入数据库
    return shopService.update(shop);
}

IShopService.java

image-20240525210205650
Result update(Shop shop);

ShopServiceImpl.java

更新我们分析过,第一步就是更新数据库,第二步就是删除缓存,最后返回结果即可。

其中删除的key就是之前存储时用到的key

image-20240525211236755
// 将来删除缓存的时候如果抛异常,那么更新数据库的也应该回滚,因此整个方法应该有一个统一的事物
@Transactional
@Override
public Result update(Shop shop) {
    Long id = shop.getId();
    if (id == null) {
        return Result.fail("店铺id不能为空");
    }
    // 1.更新数据库
    updateById(shop);
    // 2.删除缓存
    stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + id);

    return Result.ok();
}

此时我们就实现了缓存的同步更新,因为我们现在是一个单体项目,因此数据库操作和缓存操作都在一个方法里,可以通过事物去控制他们的原子性。

但如果你是一个分布式系统,那么这块可能就麻烦一点了,有可能你删除完数据库,删除缓存这块的动作可能不是由你来做的,而是另外一个系统,有可能是通过MQ去异步的通知对方,然后对方去完成这个缓存的处理。当然如果你想要保证两边的这样的一个一致性,就必须借助于我们之前给大家讲的TCC这样的方案才能保证这种一致性。


三、测试

重启服务,清除控制台数据,然后到前端页面随便查询一个店铺,查询的时候记得清除之前查询店铺时留下的缓存

image-20240525212929603

此时redis中已经没有数据了,我们重新访问店铺,刷新,此时第一次访问,因为缓存未命中,因此是会到数据库中查询店铺信息的

image-20240525213917892

接下来验证写入redis有没有加过期时间,TTL:1645S,证明它已经有过期时间了。

证明定时清理这块已经触发了。

image-20240525214058353

接下来就是测试更新,看看更新后缓冲是否也会跟着去更新。

因为更新的动作一般是由管理端去做的,现在我们浏览器看到的页面是面向用户的端,而不是商家的后台管理,因此我们并没有像面向客户的端的页面操作,此时我们只能借助于像Postman这样的工具去实现。

103茶餐厅改为了102茶餐厅可以发现请求成功

image-20240525214921492

回到控制台来查看结果,可以发现更新操作确实是执行了

image-20240525215036045

执行了后我们去数据库中做一个校验:可以发现名字确实变成102茶餐厅了。

image-20240525215226710

回到redis刷新查看,可以发现原来的缓存已经删掉了。我们当初的策略就是更新的时候移除缓存,因此此时如果有用户在浏览器中再次刷新,它看到的一定是 102茶餐厅

image-20240525215542933

并且此时应该会重建缓存,值应该也为 102茶餐厅,所以我们现在已经实现了数据库

image-20240525215907827

因此现在我们已经实现了数据库与缓存的数据同步了,每当数据库发生更新,缓存会移除,这样一来用户查询就能查到最新的数据了。

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值