我是靠谱客的博主 无语眼神,这篇文章主要介绍浅析Redis缓存穿透、击穿、雪崩(图解超详细),现在分享给大家,希望可以做个参考。

一.缓存是什么?

缓存就是数据交换的缓存区,是存储数据的地方,一般读写性能较高。
在这里插入图片描述

二、缓存的作用和成本

1、缓存的作用:
① 降低后端负载
② 提高读写效率,降低响应时间
2、缓存的成本:
① 数据一致性成本
② 代码维护成本
③ 运维成本

三、缓存作用模型

在这里插入图片描述
1、根据id查询数据缓存流程
在这里插入图片描述

四、缓存更新策略

1、内存淘汰
Redis的内存淘汰机制,当内存不足时自动淘汰部分数据,下次查询时更新缓存。

2、超时剔除
当缓存数据设置TTL时间,到期后自动删除缓存,下次查询时更新缓存。

3、主动更新
编写业务逻辑,在修改数据库的同时,更新缓存。

五、缓存穿透

缓存穿透是指客户端请求的数据在Redis和数据库中都不存在,这样就无法进行缓存,这些请求都会打到数据库。
解决方法:
1、缓存空对象

对不存在的数据也在Redis中建立缓存,值为空,并设置一个较短的TTL时间。

① 优点:实现简单,维护方便;
② 缺点:额外的内存消耗,可能造成短期的数据不一致;
2、布隆过滤器

利用布隆过滤算法,在请求进入Redis之前,先判断是否存在,如果不存在则直接拒绝访问。
① 优点:内存占用小
② 缺点:实现复杂 、存在误判的可能

六、缓存雪崩

缓存雪崩是指同一时间段大量的缓存key同时失效或者Redis服务宕机,导致大量请求打到数据库,带来巨大压力。

解决方式:

  1. 给不同的key的TTL添加随机值;
  2. 利用Redis集群提高服务的可用性;
  3. 给缓存添加降级限流策略;
  4. 给业务添加多级缓存;

七、缓存击穿

缓存击穿也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key失效了,无数的请求访问会在瞬间打到数据库,带来巨大压力。
1、通过互斥锁解决缓存击穿
给缓存重建过程加锁,确保重建过程只有一个线程执行,其它线程等待。

互斥锁的最大问题是,线程等待问题,性能较差。
在这里插入图片描述
2、根据id查询商品信息,基于互斥锁解决缓存击穿问题
在这里插入图片描述
3、通过逻辑过期解决缓存击穿
逻辑过期的优点是性能好,缺点是不保证一致性,有额外的内存消耗,实现复杂。
在这里插入图片描述

在这里插入图片描述

八、Redis工具类

复制代码
1
2
3
4
5
6
7
8
9
10
// 解决缓存穿透 Goods goods = cacheClient.queryWithPassThrough(CACHE_GOODS_KEY, id, Goods.class, this::getById, CACHE_GOODS_TTL, TimeUnit.MINUTES); // 互斥锁解决缓存击穿 Goods goods = cacheClient.queryWithMutex(CACHE_GOODS_KEY, id, Goods.class, this::getById, CACHE_GOODS_TTL, TimeUnit.MINUTES); // 逻辑过期解决缓存击穿 Goods goods = cacheClient.queryWithLogicalExpire(CACHE_GOODS_KEY, id, Goods.class, this::getById, 20L, TimeUnit.SECONDS);
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.guor.utils; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; @Slf4j @Component public class CacheClient { private final StringRedisTemplate stringRedisTemplate; private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10); public CacheClient(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public void set(String key, Object value, Long time, TimeUnit unit) { stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit); } public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) { // 设置逻辑过期 RedisData redisData = new RedisData(); redisData.setData(value); redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time))); // 写入Redis stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); } public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){ String key = keyPrefix + id; // 1.从redis查询商铺缓存 String json = stringRedisTemplate.opsForValue().get(key); // 2.判断是否存在 if (StrUtil.isNotBlank(json)) { // 3.存在,直接返回 return JSONUtil.toBean(json, type); } // 判断命中的是否是空值 if (json != null) { // 返回一个错误信息 return null; } // 4.不存在,根据id查询数据库 R r = dbFallback.apply(id); // 5.不存在,返回错误 if (r == null) { // 将空值写入redis stringRedisTemplate.opsForValue().set(key, "", RedisConfig.CACHE_NULL_TTL, TimeUnit.MINUTES); // 返回错误信息 return null; } // 6.存在,写入redis this.set(key, r, time, unit); return r; } public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) { String key = keyPrefix + id; // 1.从redis查询商铺缓存 String json = stringRedisTemplate.opsForValue().get(key); // 2.判断是否存在 if (StrUtil.isBlank(json)) { // 3.存在,直接返回 return null; } // 4.命中,需要先把json反序列化为对象 RedisData redisData = JSONUtil.toBean(json, RedisData.class); R r = JSONUtil.toBean((JSONObject) redisData.getData(), type); LocalDateTime expireTime = redisData.getExpireTime(); // 5.判断是否过期 if(expireTime.isAfter(LocalDateTime.now())) { // 5.1.未过期,直接返回店铺信息 return r; } // 5.2.已过期,需要缓存重建 // 6.缓存重建 // 6.1.获取互斥锁 String lockKey = RedisConfig.LOCK_GOODS_KEY + id; boolean isLock = tryLock(lockKey); // 6.2.判断是否获取锁成功 if (isLock){ // 6.3.成功,开启独立线程,实现缓存重建 CACHE_REBUILD_EXECUTOR.submit(() -> { try { // 查询数据库 R newR = dbFallback.apply(id); // 重建缓存 this.setWithLogicalExpire(key, newR, time, unit); } catch (Exception e) { throw new RuntimeException(e); }finally { // 释放锁 unlock(lockKey); } }); } // 6.4.返回过期的商铺信息 return r; } public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) { String key = keyPrefix + id; // 1.从redis查询商铺缓存 String json = stringRedisTemplate.opsForValue().get(key); // 2.判断是否存在 if (StrUtil.isNotBlank(json)) { // 3.存在,直接返回 return JSONUtil.toBean(json, type); } // 判断命中的是否是空值 if (json != null) { // 返回一个错误信息 return null; } // 4.实现缓存重建 // 4.1.获取互斥锁 String lockKey = RedisConfig.LOCK_GOODS_KEY + id; R r = null; try { boolean isLock = tryLock(lockKey); // 4.2.判断是否获取成功 if (!isLock) { // 4.3.获取锁失败,休眠并重试 Thread.sleep(50); return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit); } // 4.4.获取锁成功,根据id查询数据库 r = dbFallback.apply(id); // 5.不存在,返回错误 if (r == null) { // 将空值写入redis stringRedisTemplate.opsForValue().set(key, "", RedisConfig.CACHE_NULL_TTL, TimeUnit.MINUTES); // 返回错误信息 return null; } // 6.存在,写入redis this.set(key, r, time, unit); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { // 7.释放锁 unlock(lockKey); } // 8.返回 return r; } private boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } private void unlock(String key) { stringRedisTemplate.delete(key); } }


1.2023最新MySQL安装:
2022最新版 windows 11下的MySQL安装,手把手教学,超详细

2.SSH框架总结:
SSH框架总结,超详细

最后

以上就是无语眼神最近收集整理的关于浅析Redis缓存穿透、击穿、雪崩(图解超详细)的全部内容,更多相关浅析Redis缓存穿透、击穿、雪崩(图解超详细)内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(112)

评论列表共有 0 条评论

立即
投稿
返回
顶部