Redis缓存中遇到的各种问题
Redis缓存
一、关于缓存
缓存的定义:
缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码
举个例子:越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样;
同样,实际开发中,系统也需要"避震器",防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪;
这在实际开发中对企业讲,对产品口碑,用户评价都是致命的;所以企业非常重视缓存技术;
缓存的分类:
浏览器缓存:主要是存在于浏览器端的缓存
应用层缓存:可以分为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) {
String key = "cache:shop:" + id;
//从redis查询商铺缓存,并判断缓存是否命中
String shopJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(shopJson)) {
//命中就返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class); //json格式转换为对象
return Result.ok(shop);
}
//未命中就根据id查询数据库
Shop shop = getById(id);
// 判断商铺是否存在 存在就将商铺数据写入Redis
if (shop == null) {
return Result.fail("店铺不存在!");
}
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);// 否则就返回404
return Result.ok(shop);
二缓存更新策略:
缓存更新是redis为了节约内存而设计出来的,为保证redis中的数据都是热点数据,而对数据进行的淘汰
淘汰的方式有三种:
- 内存淘汰:
redis自动进行,当redis内存达到咱们设定的max-memery的时候,会自动触发淘汰机制,淘汰掉一些不重要的数据(可以自己设置策略方式) - 超时剔除:
当我们给redis设置了过期时间ttl之后,redis会将超时的数据进行删除,方便咱们继续使用缓存
主动更新: - 我们可以手动调用方法把缓存删掉,通常用于解决缓存和数据库不一致问题
数据库缓存不一致解决方案(即双写不一致问题)
产生的原因:
由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有一致性问题存在
解决的方案:
大致上有三种方案:
-
Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案
-
Read/Write Through Pattern : 由系统本身完成,数据库与缓存的问题交由系统本身去处理
-
Write Behind Caching Pattern :调用者只操作缓存,其他线程去异步处理数据库,实现最终一致
最终我们选择第一种方案
但是在调用第一种有几个注意事项:
-
- 删除缓存还是更新缓存?
- 更新缓存:每次更新数据库都更新缓存,无效写操作较多
- 删除缓存:更新数据库时让缓存失效,查询时再更新缓存
- 如何保证缓存与数据库的操作的同时成功或失败?
- 单体系统,将缓存与数据库操作放在一个事务
- 分布式系统,利用TCC等分布式事务方案
方案一大致上又分为四种
1、先写缓存在写数据库
2、先写数据库在写缓存
3、先删缓存在写数据库
4、先写数据库再删缓存
最终解决方案
延迟双删
该方案有个非常关键的地方是:第二次删除缓存,并非立马就删,而是要在一定的时间间隔之后。
sleep的时间要对业务读写缓存的时间做出评估,sleep时间大于读写缓存的时间即可。
那么,为什么一定要间隔一段时间之后,才能删除缓存呢?
请求d卡顿结束,把新值写入数据库后,请求c将数据库中的旧值,更新到缓存中。此时,如果请求d删除太快,在请求c将数据库中的旧值更新到缓存之前,就已经把缓存删除了,这次删除就没任何意义。必须要在请求c更新缓存之后,再删除缓存,才能把旧值及时删除了。
注意:如果接口对性能的要求不是很高则只需设置redis过期时间即可
三、缓存穿透
定义:客户端请求的数据在缓存和数据库中都不存在,这样的缓存永远不会生效,会直接请求到数据库
解决方案:
- 缓存空对象
但是会有额外的内存消耗,可以设置TTl过期时间
会存在短期的不一致性 - 布隆过滤器
是一种算法,在客户端与redis中添加一个布隆过滤器,不存在则拒绝反之则放行
问题:布隆过滤器怎么知道数据是否存在数据库中?
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(它不同于map、list、set),特点是高效地插入和查询。根据查询结果可以用来告诉你 某样东西一定不存在或者可能存在 这句话是该算法的核心。而且数据只能插入不能删除
且空间占用量小
布隆过滤器的存储过程
布隆过滤器是由一个很长的bit数组和一系列哈希函数组成的。
- 布隆过滤器还拥有k个哈希函数,当一个元素加入布隆过滤器时,会使用k个哈希函数对其进行k次计算,得到k个哈希值,并且根据得到的哈希值,在维数组中把对应下标的值置位1。
- 判断某个数是否在布隆过滤器中,就对该元素进行k次哈希计算,得到的值在位数组中判断每个元素是否都为1,如果每个元素都为1,就说明这个值在布隆过滤器中。
布隆过滤器为什么会有误判
当插入的元素越来越多时,当一个不在布隆过滤器中的元素,经过同样规则的哈希计算之后,得到的值在位数组中查询,有可能这些位置因为其他的元素先被置1了。
所以布隆过滤器存在误判的情况,但是如果布隆过滤器判断某个元素不在布隆过滤器中,那么这个值就一定不在。
具体编码解决方案:
四、缓存雪崩
定义:
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
产生的原因:
大量的key在同一个时间内同时到期
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了