redis+mysql数据一致性+缓存穿透解决方案

ops/2024/10/10 15:02:55/

在分布式事务中我们知道有cap定理,即 我们保证高可用的情况下,必然要牺牲一些一致性,在保证强一致性的情况下,必然会牺牲一些可用性。而我们redis+mysql数据一致性的使用策略就是在我们保证可用性的情况下尽量保证数据的一致性。想要达到强一致性,不加锁,只用 一些缓存策略那必然不是不可能的

1.一般 查询的业务情况

我们常用的 情况有  对一些热点的数据,或者频繁的查询的数据,如果频繁访问数据库,必然会对数据库造成很大的压力,为了减缓这种压力,我们 用redis缓存,数据库做底    我们的查询数据的情况一般如下

这样看似好像并没有什么问题,但是当我们想象这样一个业务场景

现在 我要对 该数据 A进行 修改,那么我们修改数据A之后,必然会要修改缓存 和数据库中的数据

那么问题来了,高并发的情况下,在修改数据的时候,如果来了一个查询操作,在这之间会怎么样呢?我们是先修改缓存数据,还是先修改数据库的数据呢?

我们要么先删除缓存 再修改数据库 ,要么先修改数据库再删除缓存,但是这两种情况都会出现 redis 和数据库 数据不一致的情况 

2.删除 缓存 +修改数据库

3.先修改数据库 再删除缓存

 所以综上所述  我们一般会采用 先修改数据库 然后再删除缓存,这也是 大家 选择的的比较多的方案

4.数据一致性兜底解决方案

先写库再删缓存”方案结合事务控制,能彻底保证缓存和数据库的一致性,但会极大程度损耗性能。而且对于业务操作来说,执行业务逻辑、更新库都没报错,偏偏走到最后删缓存时出错,因此需要将整个事务回滚,这是极不公平的  

 所以我们对删除缓存 进行一系列的 处理   

1. try catch  捕捉到 缓存删除失败,在catch中再次删除缓存,也就是 失败的话 重试多次

2. 延时双删  

1.删除缓存

2. 修改数据库

3.休眠 一段时间

4.再次删除缓存

3.异步处理  

这也是比较推荐的方案 

我们先 修改数据库,修改之后,使用mq 发送消息,然后由mq进行对缓存的删除,这样对业务代码侵入也比较低

5.缓存穿透解决

我们知道什么是缓存穿透,就是 请求 一个不存在的数据,然后 由于数据不存在,就不会加入到缓存,会一直访问数据库,这时候会对数据库造成非常大的压力  解决方案一般有两种

 布隆过滤器   和返回null 值  我们  这里只实现返回null值

 当我们 数据 在数据库查不到的时候,我们缓存一个空数据,为了防止内存浪费,我们给该空数据设置一个过期时间,这样 无论谁 用空数据 过度访问 ,都不会给数据库造成太大压力

我们以根据 商品 id 查询数据为例子,同时也对应我们 标题1 的业务实现

 

  @Overridepublic Result getByShopId(Long id) {//查缓存Map entriesMap = redisTemplate.opsForHash().entries(RedisConstants.CACHE_SHOP_KEY + id);if(StrUtil.isNotBlank((CharSequence) entriesMap.get("nullId"))){log.info(entriesMap.get("nullId").toString());log.info("店铺信息不存在");return Result.fail("店铺信息不存在");}// 有的话返回if(!entriesMap.isEmpty()){Shop shop = BeanUtil.fillBeanWithMap(entriesMap, new Shop(), false);log.info("缓存查询到了");return  Result.ok(shop);}//没有的话查数据库Shop shopById = query().eq("id", id).one();if(BeanUtil.isNotEmpty(shopById)){//数据库有的话 添加缓存并且返回Map<String, Object> stringObjectMap = BeanUtil.beanToMap(shopById);redisTemplate.opsForHash().putAll(RedisConstants.CACHE_SHOP_KEY + id,stringObjectMap);// 设置过期时间 防止内存 占满redisTemplate.expire(RedisConstants.CACHE_SHOP_KEY+id,RedisConstants.CACHE_SHOP_TTL,TimeUnit.MINUTES);return  Result.ok(shopById);}//数据库没有 返回false//解决缓存穿透   访问不存在的数据 缓存为null值 并且设置过期时间redisTemplate.opsForHash().put(RedisConstants.CACHE_SHOP_KEY+id,"nullId","null");redisTemplate.expire(RedisConstants.CACHE_SHOP_KEY+id,RedisConstants.CACHE_NULL_TTL, TimeUnit.SECONDS);return Result.ok("返回成功");}


http://www.ppmy.cn/ops/123544.html

相关文章

【高等代数笔记】线性空间(二十四下半部分-二十六)

3.23 子空间的运算 【推论1】 dim ⁡ ( V 1 V 2 ) dim ⁡ V 1 dim ⁡ V 2 ⇔ V 1 ∩ V 2 0 \dim(\textbf{V}_1\textbf{V}_2 )\dim\textbf{V}_1\dim\textbf{V}_2\Leftrightarrow\textbf{V}_1\cap\textbf{V}_2\textbf{0} dim(V1​V2​)dimV1​dimV2​⇔V1​∩V2​0 3.24 子…

Spring源码-AOP具体源码

1.类ProxyFactory 核心方法&#xff1a;getProxy 1.DefaultAopProxyFactory#createAopProxy 判断使用JDK还是CGLIB动态代理的代码如下&#xff1a; Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {// 如果ProxyFactory的isOp…

【VUE】虚拟DOM真的比真实DOM性能好吗

首次渲染大量DOM时&#xff0c;由于多了一层虚拟DOM的计算&#xff0c;会比innerHTML插入慢。它能保证性能下限&#xff0c;在真实DOM操作的时候进行针对性的优化时&#xff0c;还是更快的。 虚拟DOM&#xff08;Virtual DOM&#xff09;相比真实DOM&#xff08;Real DOM&…

golang包管理

package 在工程化的Go语言开发项目中&#xff0c;Go语言的源码复用是建立在包&#xff08;package&#xff09;基础之上的。本文介绍了Go语言中如何定义包、如何导出包的内容及如何导入其他包。 包与依赖管理 本章学习目标 掌握包的定义和使用掌握init初始化函数的使用掌握…

前端 + Nginx + 后端架构的无感升级方案

一、前端无感升级 构建新的前端包 使用 Webpack、Vite 等工具进行打包&#xff0c;生成带有版本号或哈希值的静态文件名。确保 index.html 引用最新的静态资源文件&#xff08;例如 app.js?versionabc123&#xff09;。 上传静态资源到服务器 将打包后的前端静态资源上传到 Ng…

昇思MindSpore进阶教程--数据处理性能优化(中)

大家好&#xff0c;我是刘明&#xff0c;明志科技创始人&#xff0c;华为昇思MindSpore布道师。 技术上主攻前端开发、鸿蒙开发和AI算法研究。 努力为大家带来持续的技术分享&#xff0c;如果你也喜欢我的文章&#xff0c;就点个关注吧 shuffle性能优化 shuffle操作主要是对有…

Web3与传统互联网的比较:机遇与挑战

随着科技的不断进步&#xff0c;Web3作为新一代互联网的概念逐渐浮出水面&#xff0c;改变了我们对网络的认知。相较于传统互联网&#xff0c;Web3在许多方面展现出不同的特征与潜力。本文将对Web3与传统互联网进行比较&#xff0c;探讨其带来的机遇与挑战。 一、核心概念的差异…

ubuntu下载gitee库源码

在Ubuntu系统中&#xff0c;你可以通过以下步骤从Gitee&#xff08;码云&#xff09;下载源码&#xff1a; 1. 安装Git 首先&#xff0c;确保你的系统上已经安装了Git。你可以使用以下命令安装Git&#xff1a; sudo apt-get update sudo apt-get install git2. 配置Git 在使…