redis缓存一致性
redis缓存一致性
redis是目前使用最广泛的分布式缓存系统,几乎每家公司都在用。它使用简单,吞吐量高,单机 qps 可以达到 10 万每秒,但在使用redis缓存时存在一个问题,即如何保证缓存数据和数据库中数据的一致性。本文就一致性问题提出常用的解决方案。
一致性问题
读取流程
首先,读缓存;
如果缓存里没有值,那就读取数据库的值;
同时把这个值写进缓存中。
双更模式
先更新缓存,再更新数据库
public void putValue(key, value){
putToRedis(key, value);
putToDB(key, value);//异常回滚
}
比如更新一个值,首先刷了缓存,然后把数据库也更新了。但过程中,更新数据库可能会失败,发生了回滚。所以,最后“缓存里的数据”和“数据库的数据”就不一样了,也就是出现了数据一致性问题。
先更新数据库,再更新缓存
public void putValue(key, value){
// 先更新库
putToDB(key, value);
// 再更新缓存
putToRedis(key, value);
}
问题:操作 A 更新 a 的值为 1,操作 B 更新 a 的值为 2。由于数据库和 Redis 的操作,并不是原子的,它们的执行时长也不是可控制的。当两个请求的时序发生了错乱,就会发生缓存不一致的情况。
双更模式下,数据不一致的概率较大,一般不建议使用双更模式。
删除模式
删除模式即更新数据时,删除缓存,查询时重新从数据库中加载数据。先删除缓存还是后删除缓存?
先删除缓存
public void putValue(key, value){
deleteFromRedis(key);
putToDB(key,value);
}
问题:请求A删除了某个 key 的值,这时候有另外一个请求B 到来,那么它就会击穿到数据库,读取到旧的值。无论操作A更新数据库的操作持续多长时间,都会产生不一致的情况。
后删除缓存(Cache-Aside Pattern)
后删除缓存不会出现上述问题。一般情况下这种方式可以解决大部分问题,也是最常用的解决方案。
但是在高并发的情况下,仍有可能出现不一致的情况。场景如下:
public void proccess(key, value){
N:putToDB(key, 1);
N:deleteFromRedis(key);
// A B线程同时操作同一组数据
A:getFromRedis(key);
A:getFromDB(key)=1;
B:putToDB(key, 2);
B:deleteFromRedis(key);
// 特殊情况下导致A更新redis慢于B,在B删除redis之后A才完成更新
A:putToRedis(key, 1);
//DB=2,Redis=1
}
有一系列的高并发操作,一直执行着更新、删除的动作。某个时刻,它更新数据库的值为 1,然后删除了缓存。
正在这时,有两个请求发生了:
- 一个是读操作,读到的当然是数据库的旧值 1,我们记作操作 A;
- 同时,另外一个请求发起了更新操作,把数据库记录更新为 2,我们记作操作 B。
一般情况下,读取操作都是比写入操作快的,但我们要考虑两种极端情况:
- 一种是这个读取操作 A,发生在更新操作 B 的尾部;
- 一种是操作 A 的这个 Redis 的操作时长,耗费了非常多的时间。比如,这个节点正好发生了 STW。(条件比较苛刻)
那么很容易地,读操作 A 的结束时间就超过了操作 B 删除的动作。
实际上,你也无法控制它们的执行顺序。只要发生这种情况,大概率数据库和Redis的值会不一致。
此种场景下如何解决?
延迟双删
如果有一种机制,能够确保删除动作一定被执行,那就可以解决问题,至少能缩小数据不一致的时间。常用的方法就是延时双删,依然是先更新再删除,唯一不同的是:我们把这个删除动作,在不久之后再执行一次,比如 5 秒之后。
public void putValue(key, value){
putToDB(key, value);
deleteFromRedis(key);
// 5秒之后再次进行删除
deleteFromRedisDelay(key, 5second);
}
延迟删除动作也有多种实现方式:
- 如果放在DelayQueue中,会有随着 JVM 进程的死亡,丢失更新的风险;
- 如果放在 MQ 中,会增加编码的复杂性。
实现方案要根据实际情况进行选择,没有完美的方案,只要能满足业务需求即可。
设置较小的缓存时间
俗称闪电缓存,即把缓存的失效时间设置非常短,比如 5秒。一旦失效,就会再次去数据库读取最新数据到缓存,即数据不一致只会在短时间内不一致。但这种方式,在非常高的并发下,同一时间对某个 key 的请求击穿到 DB,产生缓存击穿问题。
缓存击穿
缓存击穿,指的是缓存中没有数据但数据库中有,由于同一时刻请求量特别大,但是没有读到缓存数据,就会一股脑涌入到数据库中读取,导致数据库因压力过大不可用。
解决方案:
- 读操作互斥,使用锁或者分布式锁来控制;
- 更新集中,采用定时或者 binlog 的方式同步更新。
getValue(key){
res = getFromRedis(key);
// 未命中
if(null == res){
lock.lock(...);
// 再次读取缓存为null
res = getFromRedis(key);
if(res == null){
res = getFromDB(key);
if(null != res){
putToRedis(key,res);
}
}
lock.unlock();
}
return res;
}
redis缓存一致性的更多相关文章
- Mybatis一级缓存和二级缓存 Redis缓存
一级缓存 Mybatis的一级缓存存放在SqlSession的生命周期,在同一个SqlSession中查询时,Mybatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对 ...
- redis,缓存雪崩,粗粒度锁,缓存一致性
1, redis单线程为什么快 io多路复用技术 单线程避免多线程的频繁切换问题 memcache缺点 kv形式数据 没有持久化mongodb 海量数据的访问效率 mr的计算模型文档就是类似json的 ...
- Redis缓存如何保证一致性
为什么使用Redis做缓存 MySQL缺点 单机连接数目有限 对数据进行写速度慢 Redis优点 内存操作数据速度快 IO复用,速度快 单线程模型,避免线程切换带来的开销,速度快 一致性问题 读数据的 ...
- 分布式-技术专区-Redis和MySQL缓存一致性问题
1.Redis 缓存和 MySQL 数据如何实现一致性 需求起因 缓存和数据库一致性解决方案 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操 ...
- Redis缓存穿透、击穿、雪崩,数据库与缓存一致性
Redis作为高性能非关系型(NoSQL)的键值对数据库,受到了广大用户的喜爱和使用,大家在项目中都用到了Redis来做数据缓存,但有些问题我们在使用中不得不考虑,其中典型的问题就是:缓存穿透.缓存雪 ...
- Docker安装canal、mysql进行简单测试与实现redis和mysql缓存一致性
一.简介 canal [kə'næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费. 早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求 ...
- Redis - 读写模式 - 缓存一致性
Cache Aside Pattern(旁路缓存模式) 读:从cache中读取数据,若读取到则直接返回:cache中不存在则去database中读取,然后更新到cache. 写:先更新database ...
- redis缓存技术
初学redis缓存技术,如果文章写得不好还请谅解 应用环境:win7 实现环境:cmd,eclipse redis缓存技术的特点就在于高效,因为目前涉及的数据量逐渐增多,在对于数据的存储上面和sql以 ...
- Redis 缓存 + Spring 的集成示例(转)
<整合 spring 4(包括mvc.context.orm) + mybatis 3 示例>一文简要介绍了最新版本的 Spring MVC.IOC.MyBatis ORM 三者的整合以及 ...
- 使用redis缓存数据需要注意的问题以及个人的一些思考和理解
之前我有博客也尝试过使用redis,在实际的项目中确实作用挺大的.至少对于数据的频繁读取来说都起着至关重要的作用. 但是随着技术的学习,慢慢的业务要复杂起来,以后也许会用到redis集群,所以在这边查 ...
随机推荐
- wsl 更新到D盘
更换教程 https://blog.csdn.net/qq_39683682/article/details/113801632 安装后需要换个默认用户 https://blog.csdn.net/q ...
- tomcat报错:java.io.IOException: No space left on device
1 简介 今天网站很多页面访问突然就404了,路径分明没有变,是正确的,就很奇怪 排查日志发现报错java.io.IOException: No space left on device 这个错误,是 ...
- 服务器设置导致mongo数据库的链接数受限
记录一次使用 mongoDB 遇到的BUG,就是服务链接mongodb报错 [05-Nov-2022 16:46:05] WARNING: [pool www] child 10231 said in ...
- xshell连接时显示“服务器发送了一个意外的数据包。received:3,expected:20“问题的解决方法
xshell连接时显示"服务器发送了一个意外的数据包.received:3,expected:20"问题的解决方法 解决方法:在/etc/ssh/sshd_config最后增加以下 ...
- vant组件,picker时间选择,自定义时间选择,实现datePacker,时间选择长期,增加长期选项,用于选择身份证到期时间等...
vant组件,picker时间选择,自定义时间选择,实现datePacker,时间选择长期,增加长期选项,用于选择身份证到期时间等... 最近项目中有个需求,datePicker选项,需要实现增加一个 ...
- 有趣的python库-pillow
pillow-图像处理 安装时不再是PIL,是pillow哦! 烟花 pillow + tkinter实现 import tkinter as tk from PIL import Image, Im ...
- 随时代变迁而进化的治疗策略不断提高RA无药缓解机会[EULAR2015_SAT0058]
随时代变迁而进化的治疗策略不断提高RA无药缓解机会 SAT0058 DMARD-FREE SUSTAINED REMISSION IN RHEUMATOID ARTHRITIS: AN OUTCOME ...
- VUE项目Webpack3升级为webpack4总结
前言: 2018年8月25号webpack4正式发布,webpack4引入了零配置的概念(实际配置下来还是要写不少配置),官方宣传能够提升构建速度 webpack3转webpack4官方说明: htt ...
- vue-element-admin 怎么改后端 可以调跳过登录并且发送接口请求
1.找到根目录的 vue.config.js 添加 proxy 内容 注释掉mock 2.清空 .env.development 里的 VUE_APP_BASE_API 路径 3.user.js 方 ...
- LeetCode-393 UTF-8编码验证
来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/utf-8-validation 题目描述 给定一个表示数据的整数数组 data ,返回它是否为有 ...