博客 redis实战-缓存数据&解决缓存与数据库数据一致性

redis实战-缓存数据&解决缓存与数据库数据一致性

   数栈君   发表于 2023-08-21 10:03  188  0

缓存的定义

缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码。防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪,这在实际开发中对企业讲,对产品口碑,用户评价都是致命的;所以企业非常重视缓存技术,redis作为最常用的缓存中间件,也是面试的高频考点。

使用缓存的目的

缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力。实际开发过程中,企业的数据量,少则几十万,多则几千万,这么大数据量,如果没有缓存,系统是几乎撑不住的,所以企业会大量运用到缓存技术。

http://dtstack-static.oss-cn-hangzhou.aliyuncs.com/2021bbs/files_user1/article/60ee6a64f2dc606d13d71abacc8d1467..png
  

如何使用缓存

实际开发中,会构筑多级缓存来使系统运行速度进一步提升,例如:本地缓存与redis中的缓存并发使用

浏览器缓存:主要是存在于浏览器端的缓存

应用层缓存:可以分为tomcat本地缓存,比如之前提到的map,或者是使用redis作为缓存

数据库缓存:在数据库中有一片空间是 buffer pool,增改查数据都会先加载到mysql的缓存中

CPU缓存:当代计算机最大的问题是 cpu性能提升了,但内存读写速度没有跟上,所以为了适应当下的情况,增加了cpu的L1,L2,L3级的缓存

缓存商铺信息

商铺信息接口具有很高的并发量,查询数据不能每次都从数据库查询,要将商铺数据缓存到redis中,来应对高并发

@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
    //这里是直接查询数据库
    return shopService.queryById(id);
}
http://dtstack-static.oss-cn-hangzhou.aliyuncs.com/2021bbs/files_user1/article/9b8846acb804bba635c065113027f28a..png
  


缓存模型和思路

标准的操作方式就是查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入redis。

http://dtstack-static.oss-cn-hangzhou.aliyuncs.com/2021bbs/files_user1/article/4d1a451ddc9a47973db4b00abf800810..png
  

代码实现

注意此时缓存数据不设置过期时间,为了减轻数据库压力,缓存应该常驻在内存中,但也会带来一个那就是缓存数据与数据库数据不一致的问题,这就引出了缓冲更新策略这一问题

@Override
    public Result queryById(Long id) {
        //根据业务代码组装key
        String key = CACHE_SHOP_KEY + id;
        //从redis中获取商铺信息
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)) {
            //将json转化为shop对象直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);

        }
        Shop shop = getById(id);
        if (shop == null) {
        return Result.fail("店铺不存在");
        }
        //将数据库查询的数据写入缓存
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
        //返回
        return Result.ok(shop);

缓存更新策略

更新策略主要根据业务来选择,在本项目中采用的是主动更新+超时剔除的更新策略,超时剔除主要是作为保底的更新策略,保证缓存在没有触发主动更新的情况下,每隔一段时间就会清理缓存

缓存更新是redis为了节约内存而设计出来的一个东西,主要是因为内存数据宝贵,当我们向redis插入太多数据,此时就可能会导致缓存中的数据过多,所以redis会对部分数据进行更新,或者把他叫为淘汰更合适。

内存淘汰:redis自动进行,当redis内存达到咱们设定的max-memery的时候,会自动触发淘汰机制,淘汰掉一些不重要的数据(可以自己设置策略方式)

超时剔除:当我们给redis设置了过期时间ttl之后,redis会将超时的数据进行删除,方便咱们继续使用缓存

主动更新:我们可以手动调用方法把缓存删掉,通常用于解决缓存和数据库不一致问题

http://dtstack-static.oss-cn-hangzhou.aliyuncs.com/2021bbs/files_user1/article/29e45c293813995d6c8d91d597c3ecba..png
  

数据库缓存数据不一致解决方案

由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在,其后果是:

用户使用缓存中的过时数据,就会产生类似多线程数据安全问题,从而影响业务,产品口碑等;怎么解决呢?有如下几种方案

Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案
Read/Write Through Pattern : 由系统本身完成,数据库与缓存的问题交由系统本身去处理
Write Behind Caching Pattern :调用者只操作缓存,其他线程去异步处理数据库,实现最终一致

使用方案二增加了系统复杂度,不利于调用者排查有关问题,方案三会有一系列线程安全,造成数据库缓存不一致的情况,经过综合考虑选用人工编码的方式较为稳妥

人工编码步骤

    1.删除缓存:更新数据库时让缓存失效,查询时再更新缓存,(如果是更新数据库的同时,更新缓存,会有太多更新动作,无法保证性能)

    2.在单体系统中,将缓存与数据库操作放在一个事务,保证更新数据库成功时,缓存也要添加成功,即保证两个操作同时成功或失败

    3.先操作数据库,再删除缓存,在多线程的情况下,操作数据库的时间要比操作redis缓存的时间多得多,出现数据库写完,缓存失效的可能性较小

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

●根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间

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

添加缓存时设置redis缓存时添加过期时间

@Override
    public Result queryById(Long id) {
        //根据业务代码组装key
        String key = CACHE_SHOP_KEY + id;
        //从redis中获取商铺信息
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)) {
            //将json转化为shop对象直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);

        }
        Shop shop = getById(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        //将数据库查询的数据写入缓存,并设置过期时间
    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L,TimeUnit.MINUTES);
        //返回
return Result.ok(shop);
    }
我们确定了采用删除策略,来解决双写问题,当我们修改了数据之后,然后把缓存中的数据进行删除,查询时发现缓存中没有数据,则会从mysql中加载最新的数据,从而避免数据库和缓存不一致的问题,此方法需要加@Transactional注解来声明事务

@Transactional
@Override
public Result update(Shop shop) {
    Long id = shop.getId();
    //判断id是否为空,因为可以绕过前端直接发送请求,此步必须判断
    if (id == null) {
            return Result.fail("店铺id不能为空");
        }
        //更新数据库
        updateById(shop);
        //删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        return Result.ok();
}        



免责申明:


本文系转载,版权归原作者所有,如若侵权请联系我们进行删除!

《数据治理行业实践白皮书》下载地址:https://fs80.cn/4w2atu

《数栈V6.0产品白皮书》下载地址:
https://fs80.cn/cw0iw1

想了解或咨询更多有关袋鼠云大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:
https://www.dtstack.com/?src=bbs

同时,欢迎对大数据开源项目有兴趣的同学加入「袋鼠云开源框架钉钉技术群」,交流最新开源技术信息,群号码:30537511,项目地址:
https://github.com/DTStack

0条评论
下一篇:
社区公告
  • 大数据领域最专业的产品&技术交流社区,专注于探讨与分享大数据领域有趣又火热的信息,专业又专注的数据人园地

最新活动更多
微信扫码获取数字化转型资料
钉钉扫码加入技术交流群