redis实现缓存查询(商铺信息缓存为例)

什么是缓存,缓存的作用

缓存就是数据交换的缓冲区,是存贮数据的临时地方,一般读写性能较高。

对于一个web应用来说,如果我们的查询操作都交给数据库服务器来处理,很可能会导致数据库服务器处理不过来,导致延迟处理甚至是数据库服务器宕机。其中一个解决方案是在web应用与服务器中间简历缓存,把一些常用的数据存储在缓存中,当有查询操作时,首先到缓存中查询,如果缓存中没有数据,再去服务器中查询。这样以来,不仅降低了数据库的压力,同时也提高了查询效率(因为缓存一般在内存中,查询这部分数据不需要与磁盘进行交互)。

缓存虽好,但是维护缓存也需要一定的成本。比如如何保持数据库与缓存数据一致性的问题,当数据库中数据被修改时,如何保证缓存中的数据也同步修改;再比如缓存中的数据保存在内存中,内存的容量是有限的,当内存被这些数据占满时,我们应该如何淘汰部分缓存;同时我们还要维护缓存的高可用性,避免缓存服务器宕机,避免缓存穿透,缓存击穿,缓存雪崩的问题等等。

一图明白redis缓存的作用

没有缓存时:

查询结果缓存redis redis 查询缓存_数据

添加缓存时

查询结果缓存redis redis 查询缓存_数据库_02

代码执行思路(查询商铺为例)

首先根据商铺id在redis缓存中查找是否有对应的商铺信息,如果缓存中存在相关信息,直接返回程序结束。如果缓存中不存在该商铺id对应的商铺,则去数据库中查询。如果数据库中有该商铺信息,首先将其保存到redis中,再返回给客户端(前端),如果数据库中也没有该商铺信息,则返回404。

流程图

查询结果缓存redis redis 查询缓存_redis_03

代码

@Override
    public Result queryById(Long id) {
        // 从redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);

        // 判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 存在则直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        // redis中没有则取数据库中查询
        Shop shop = getById(id);

        // 数据库中没有该商铺信息,返回404
        if (shop == null) {
            return Result.fail("此店铺不存在");
        }
        // 数据库中存在,则把它放到redis
        stringRedisTemplate.opsForValue().set("cache:shop:" + id,JSONUtil.toJsonStr(shop));
        // 返回数据
        return Result.ok(shop);
    }

上述代码存在问题,试想一下,当在数据库中查询出的结果放入缓存以后,如果内存不发生泄露,这部分数据永远都会存在缓存中,而且不发生改变。那么如果此时数据库中的数据发生了变化,而缓存中的数据不做更新,就会产生数据不一致的问题。解决数据不一致问题一般有三种策略:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AHf94GyW-1657517716584)(C:\Users\24879\Desktop\笔记图片\微信图片_20220711111726.png)]

主动更新一般需要我们程序员自己编写代码,这个过程中我们还需要考虑三个问题:

  1. 更新缓存还是删除缓存:当数据库中数据发生变化时,如果每次都更新缓存,会产生很多无效的写操作,因为更新后的数据在查询时才需要用到,所以我们考虑删除缓存,一旦数据库中数据发生改变,我们就把对应的缓存中的数据删除,只有再次查询时,我们才从数据库中查找并把它放进缓存。
  2. 如何保证数据库与缓存的操作同时成功或者失败
    单体系统考虑把缓存与数据库操作放在一个事务
    分布式系统利用TCC等分布式事务方案
  3. 当一条更新语句执行时,先删除缓存再操作数据库好,还是先操作数据库,再操作缓存好(线程安全问题)?
    这两种情况都可以却也都存在一些问题

先删除缓存再操作数据库:正常情况当线程a删除缓存并更新数据库后,线程b访问数据库并将数据更新到缓存。异常情况是线程a删除缓存后数据库还未来得及更新,线程b就查询,此时缓存为空则查询数据库,查询完成后将数据保存再redis。之后线程a更新数据库。就会导致数据库中的数据与缓存中的数据不一致问题。

先操作数据库再删除缓存:正常情况线程c先更新数据库数据,再把缓存数据删除,线程d访问查询时发现缓存无数据就到数据库中查找并将其放入缓存。异常情况是线程c查询时缓存恰好失效,此时到数据库中查询并将结果返回,再重建缓存之前,线程d更新数据库并删除缓存,线程d执行完之后线程c才开始重建数据库。此时就又会导致数据库与缓存数据不一致问题。

由于先操作数据库再删除缓存出现数据不一致的情况几率比较小。我们就选择后者。

给查询商铺的缓存添加超时剔除和主动更新策略

  1. 根据id查询店铺信息,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间
  2. 根据id修改店铺信息,先修改数据库再删除缓存
stringRedisTemplate.opsForValue().set("cache:shop:" + id,JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
@Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺不能为空");
        }
        //更新数据库
        updateById(shop);
        //删除缓存
        stringRedisTemplate.delete("cache:shop:"+shop.getId());
        return Result.ok();
    }