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

news2024/12/28 11:49:23

缓存的定义

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

使用缓存的目的

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

如何使用缓存

实际开发中,会构筑多级缓存来使系统运行速度进一步提升,例如:本地缓存与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);
}

缓存模型和思路

标准的操作方式就是查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入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));
        //返回
        return Result.ok(shop);

缓存更新策略

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

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

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

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

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

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

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

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

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();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/903321.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于蜉蝣算法优化的BP神经网络(预测应用) - 附代码

基于蜉蝣算法优化的BP神经网络(预测应用) - 附代码 文章目录 基于蜉蝣算法优化的BP神经网络(预测应用) - 附代码1.数据介绍2.蜉蝣优化BP神经网络2.1 BP神经网络参数设置2.2 蜉蝣算法应用 4.测试结果:5.Matlab代码 摘要…

MySQL8.0.26-Linux版安装

MySQL8.0.26-Linux版安装 1. 准备一台Linux服务器 云服务器或者虚拟机都可以; Linux的版本为 CentOS7; 2. 下载Linux版MySQL安装包 MySQL :: Download MySQL Community Server (Archived Versions) 3. 上传MySQL安装包 4. 创建目录,并解压 mkdir mysql ​ tar -xvf mysql-8…

网站老域名跳转到新域名有哪些方法?内网穿透内网主机让外网访问

在网站服务器变更及本地主机搭建时,我们经常会遇到老域名地址跳转到新URL的配置,一些朋友还会面对无公网IP让外网访问的问题。今天我们来了解下网站老域名跳转到新域名有哪些方法,以及如何通过内网穿透实现内网主机让外网访问。 网站老域名跳…

【Unity小技巧】Unity2D TileMap的探究(最简单,最全面的TileMap使用介绍)

文章目录 前言介绍一、TileMap简单的使用1、创建Unity工程2、Tilemap的使用2.1、导入素材图片2.2、切割图片2.3、创建画板2.4、创建瓦片2.5、创建网格2.6、在网格上刷瓦片2.7、解决瓦片没有占满格子的问题2.8、解决瓦片之间有缝隙的问题2.9、擦除瓦片2.10、区域瓦片绘制2.11、瓦…

适合上班族做的4个低门槛的副业兼职

对于大多数职场中人来说,如果没有在30岁之后获得晋升,获得更好的发展平台,可能就会感到工作缺乏足够的吸引力了。当我们只有一份工作的时候,就好比把鸡蛋放在一个篮子里,把自己的青春放在一家公司里。这也就好比单一的…

华盛顿大学Baker实验室率先设计出双稳态结构蛋白质

在蛋白质世界,“结构决定功能”是一条基本原则。因此,很多人可能认为,一个蛋白质就应该有一个唯一确定的结构,使得它能够去执行确定的生物学功能。其实,在真实的世界中,蛋白质大多都是处于一种不断起伏的动…

MongDB【CRUD练习-条件查询-文档关系】

练习1-CRUD // 进入test数据库 use test; // 查询文档内容 db.students.find(); // 显示当前数据库中所有集合 show collections; // 向数据库的user集合中插入一个文档 db.users.insertOne({username: "lyh"} ); // 查看当前数据库中所有的集合 发现users集合被创建…

信号处理--基于EEG脑电信号的眼睛状态的分析

本实验为生物信息学专题设计小项目。项目目的是通过提供的14导联EEG 脑电信号,实现对于人体睁眼和闭眼两个状态的数据分类分析。每个脑电信号的时长大约为117秒。 目录 加载相关的库函数 读取脑电信号数据并查看数据的属性 绘制脑电多通道连接矩阵 绘制两类数据…

《强化学习:原理与Python实战》——可曾听闻RLHF

前言: RLHF(Reinforcement Learning with Human Feedback,人类反馈强化学习)是一种基于强化学习的算法,通过结合人类专家的知识和经验来优化智能体的学习效果。它不仅考虑智能体的行为奖励,还融合了人类专家…

LeetCode算法递归类—二叉树的右视图

目录 199. 二叉树的右视图 题解: 目标: 思路: 过程: 代码: 运行结果: 给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所…

使用渲染纹理(RenderTexture)来实现3D视觉

如上图的效果,使用2D场景,通过摄像头的改动,使其看起来像是3D效果一样。 Nintendo Switch上刚推出的《超级马里奥》中,有一些关卡混合了2D和3D的画面, 一般来说,摄像机会直接渲染到电脑屏幕;…

【C语言练习】数组OJ题

目录 一.消失的数字思路1:思路2: 二.移除元素三.轮转数组四.删除有序数组中的重复项五.合并两个有序数组 一.消失的数字 题目: 思路1: 数组是从0加到N,所以把0到N的数加起来减去数组中的值,结果就是消失…

阿里云ECS服务器和轻量应用服务器区别?怎么选择?

阿里云轻量应用服务器和云服务器ECS有什么区别?ECS是专业级云服务器,轻量应用服务器是轻量级服务器,轻量服务器使用门槛更低,适合个人开发者或中小企业新手使用,可视化运维,云服务器ECS适合集群类、高可用、…

Cadence 仿真

电路的学习离不开仿真和实践,主流的仿真软件有很多,有用功pretues和mutisim,这次主要是工作中使用cadence,而且带有比较强大的仿真工具Pspice,所以选择了它 ,我使用的版本是cadence 17.4自带的。官方提供了…

ViT模型架构和CNN区别

目录 Vision Transformer如何工作 ViT模型架构 ViT工作原理解析 步骤1:将图片转换成patches序列 步骤2:将patches铺平 步骤3:添加Position embedding 步骤4:添加class token 步骤5:输入Transformer Encoder 步…

leetcode 213. 打家劫舍 II

2023.8.20 本题是 打家劫舍 的进阶版,房屋之间形成一个环了,也就是第一个房屋和最后一个房屋不能一起偷了。那么能偷的情况分为下列三种: 不考虑偷首房间。不考虑偷尾房间。不考虑偷首尾房间。 第三种情况包含于第一和第二种情况了&#xff…

CSerialPort教程4.3.x (1) - CSerialPort项目简介

CSerialPort教程4.3.x (1) - CSerialPort项目简介 前言 CSerialPort项目是一个基于C/C的轻量级开源跨平台串口类库,可以轻松实现跨平台多操作系统的串口读写,同时还支持C#, Java, Python, Node.js等。 CSerialPort项目的开源协议自 V3.0.0.171216 版本…

分组与引用:用正则实现更复杂的查找和替换操作

括号在正则中的功能就是用于分组。简单来理解就是,由多个元字符组成某个部分,应该被看成一个整体的时候,可以用括号括起来表示一个整体,这是括号的一个重要功能。其实用括号括起来还有另外一个作用,那就是“复用”。 …

Linux 线程同步——信号量

一、线程同步的概念 这里的同步就是对程序的执行进行控制,因为如果不进行控制就会出现错误的问题,这里的控制是为了保证程序的正确性。 线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作&#xff0…

json文件读取数据报错 AttributeError: ‘str‘ object has no attribute ‘items‘

trans_width_table表如下: {frozenset({2}): {3: 250, 2.5: 100, 1.5: 25, 2: 50, 1.8: 50, 2.75: 200, 5: 350, 4: 350, 2.3: 100, 4.5: 350, 3.5: 300}, frozenset({1, 3, 4, 5}): {2.5: 75, 2.75: 100, 1.5: 25, 4: 300, 3.5: 200, 4.5: 300, 3: 100, 5: 300, 2…