# 《高性能Redis组件》落地实现-第03节:高性能Redis组件防缓存击穿、穿透和雪崩的核心设计与实现
作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)
沉淀,成长,突破,帮助他人,成就自我。
- 本章难度:★★☆☆☆
- 本章重点:对高性能Redis组件防缓存击穿、穿透和雪崩问题的功能进行设计和实现,从总体上理解高性能Redis组件防缓存击穿、穿透和雪崩问题的核心设计思想,并从全局视角了解高性能Redis组件的设计和架构思想,并能够将其灵活应用到自身实际项目中。
大家好,我是冰河~~
高性能Redis组件的亮点不仅仅在于其性能非常高,更多的是其在设计和实现上彻底解决了缓存击穿、穿透和雪崩问题。
# 一、前言
经历过高并发、大流量生产场景的小伙伴都知道,缓存作为承接高并发、大流量场景下,读取数据的一种核心技术手段,被广泛应用于各大核心系统中。但是缓存的设计并不是想象中的那么简单,稍有不慎,就会引起缓存击穿、穿透,甚至雪崩的问题。
如何设计和实现一个通用的高性能缓存组件,对使用方屏蔽处理缓存击穿、穿透和雪崩问题的实现细节,业务只需要调用组件对外暴露的简单接口,即可在保证性能的同时,又能彻底解决缓存击穿、穿透和雪崩问题。所以,在此背景下,高性能Redis组件诞生了。
# 二、本节诉求
对高性能Redis组件防缓存击穿、穿透和雪崩问题的功能进行设计和实现,从总体上理解高性能Redis组件防缓存击穿、穿透和雪崩问题的核心设计思想,并从全局视角了解高性能Redis组件的设计和架构思想,并能够将其灵活应用到自身实际项目中。
# 三、核心类设计
注意:本节只给大家展示高性能Redis组件防缓存击穿、穿透和雪崩的的核心类实现关系,其他代码的实现细节,大家可以自行到本节对应的源码分支进行查看,这里不再赘述。
高性能Redis组件分布式锁的核心类设计如图3-1所示。
可以看到,高性能Redis组件中,最核心的实现就是DistributeCacheService接口和RedisDistributeCacheService实现类。
- DistributeCacheService接口:定义了各种缓存操作方法,并封装了获取Key、Value以及结果数据的默认方法。
- RedisDistributeCacheService类:组件最核心的实现,实现了DistributeCacheService接口,并在方法的实现中,彻底解决了缓存击穿、穿透和雪崩问题。
# 四、编码实现
本节只给大家展示高性能Redis组件解的核心类编码实现,其他代码的实现细节,大家可以自行到本节对应的源码分支进行查看,这里不再赘述。
(1)实现DistributeCacheService接口
定义了各种缓存操作方法,并封装了获取Key、Value以及结果数据的默认方法。
源码详见:io.binghe.redis.plugin.cache.DistributeCacheService。
public interface DistributeCacheService {
/**
* 永久缓存
* @param key 缓存key
* @param value 缓存value
*/
void set(String key, Object value);
/**
* 将数据缓存一段时间
* @param key 缓存key
* @param value 缓存value
* @param timeout 物理缓存的时长
* @param unit 物理时间单位
*/
void set(String key, Object value, Long timeout, TimeUnit unit);
/**
* 设置缓存过期
* @param key 缓存key
* @param timeout 过期时长
* @param unit 时间单位
* @return 设置过期时间是否成功
*/
Boolean expire(String key, final long timeout, final TimeUnit unit);
/**
* 保存缓存时设置逻辑过期时间
* @param key 缓存key
* @param value 缓存value
* @param timeout 缓存逻辑过期时长
* @param unit 缓存逻辑时间单位
*/
void setWithLogicalExpire(String key, Object value, Long timeout, TimeUnit unit);
/**
* 获取缓存中的数据
* @param key 缓存key
* @return 缓存value
*/
String get(String key);
/**
* 获取缓存数据
* @param key 缓存的key
* @param targetClass 目标对象Class
* @param <T> 泛型
* @return 返回的数据
*/
<T> T getObject(String key, Class<T> targetClass);
/**
* 根据key列表批量获取value
* @param keys key列表
* @return value集合
*/
List<String> multiGet(Collection<String> keys);
/**
* 根据正则表达式获取所有的key集合
* @param pattern 正则表达式
* @return key的集合
*/
Set<String> keys(String pattern);
/**
* 删除指定的key
* @param key key
* @return 删除是否成功
*/
Boolean delete(String key);
/**
* 带参数查询对象和简单类型数据,防止缓存穿透
* @param keyPrefix 缓存key的前缀
* @param id 缓存的业务标识,
* @param type 缓存的实际对象类型
* @param dbFallback 查询数据库的Function函数
* @param timeout 缓存的时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
* @param <ID> 查询数据库参数泛型,也是参数泛型类型
*/
<R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long timeout, TimeUnit unit);
/**
* 不带参数查询对象和简单类型数据,防止缓存穿透
* @param keyPrefix key的前缀
* @param type 缓存的实际对象类型
* @param dbFallback 无参数查询数据库数据
* @param timeout 缓存的时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
*/
<R> R queryWithPassThroughWithoutArgs(String keyPrefix, Class<R> type, Supplier<R> dbFallback, Long timeout, TimeUnit unit);
/**
* 带参数查询集合数据,防止缓存穿透
* @param keyPrefix 缓存key的前缀
* @param id 缓存的业务标识,
* @param type 缓存的实际对象类型
* @param dbFallback 查询数据库的Function函数
* @param timeout 缓存的时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
* @param <ID> 查询数据库参数泛型,也是参数泛型类型
*/
<R,ID> List<R> queryWithPassThroughList(String keyPrefix, ID id, Class<R> type, Function<ID, List<R>> dbFallback, Long timeout, TimeUnit unit);
/**
* 不带参数查询集合数据,防止缓存穿透
* @param keyPrefix 缓存key的前缀
* @param type 缓存的实际对象类型
* @param dbFallback 无参数查询数据库数据
* @param timeout 缓存的时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
*/
<R> List<R> queryWithPassThroughListWithoutArgs(String keyPrefix, Class<R> type, Supplier<List<R>> dbFallback, Long timeout, TimeUnit unit);
/**
* 带参数查询数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源
* @param keyPrefix 缓存key的前缀
* @param id 缓存业务标识,也是查询数据库的参数
* @param type 缓存的实际对象类型
* @param dbFallback 查询数据库的Function函数
* @param timeout 缓存逻辑过期时长
* @param unit 缓存逻辑过期时间单位
* @return 业务数据
* @param <R> 结果数据泛型类型
* @param <ID> 查询数据库泛型类型,也是参数泛型类型
*/
<R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long timeout, TimeUnit unit);
/**
* 不带参数查询数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源
* @param keyPrefix 缓存key的前缀
* @param type 缓存的实际对象类型
* @param dbFallback 无参数查询数据库数据
* @param timeout 缓存的时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
*/
<R> R queryWithLogicalExpireWithoutArgs(String keyPrefix, Class<R> type, Supplier<R> dbFallback, Long timeout, TimeUnit unit);
/**
* 带参数查询集合数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源
* @param keyPrefix 缓存key的前缀
* @param id 缓存业务标识,也是查询数据库的参数
* @param type 缓存的实际对象类型
* @param dbFallback 查询数据库的Function函数
* @param timeout 缓存逻辑过期时长
* @param unit 缓存逻辑过期时间单位
* @return 业务数据
* @param <R> 结果数据泛型类型
* @param <ID> 查询数据库泛型类型,也是参数泛型类型
*/
<R, ID> List<R> queryWithLogicalExpireList(String keyPrefix, ID id, Class<R> type, Function<ID, List<R>> dbFallback, Long timeout, TimeUnit unit);
/**
* 不带参数查询集合数据,按照逻辑过期时间读取缓存数据,新开线程重建缓存,其他线程直接返回逻辑过期数据,不占用资源
* @param keyPrefix 缓存key的前缀
* @param type 缓存的实际对象类型
* @param dbFallback 无参数查询数据库数据
* @param timeout 缓存的时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
*/
<R> List<R> queryWithLogicalExpireListWithoutArgs(String keyPrefix, Class<R> type, Supplier<List<R>> dbFallback, Long timeout, TimeUnit unit);
/**
* 带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试
* @param keyPrefix 缓存key的前缀
* @param id 缓存业务标识,也是查询数据库的参数
* @param type 缓存的实际对象类型
* @param dbFallback 查询数据库的Function函数
* @param timeout 缓存时长
* @param unit 时间单位
* @return 业务数据
* @param <R> 结果数据泛型类型
* @param <ID> 查询数据库泛型类型,也是参数泛型类型
*/
<R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long timeout, TimeUnit unit);
/**
* 不带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试
* @param keyPrefix 缓存key的前缀
* @param type 缓存的实际对象类型
* @param dbFallback 无参数查询数据库数据
* @param timeout 缓存时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
*/
<R> R queryWithMutexWithoutArgs(String keyPrefix, Class<R> type, Supplier<R> dbFallback, Long timeout, TimeUnit unit);
/**
* 带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试
* @param keyPrefix 缓存key的前缀
* @param id 缓存业务标识,也是查询数据库的参数
* @param type 缓存的实际对象类型
* @param dbFallback 查询数据库的Function函数
* @param timeout 缓存时长
* @param unit 时间单位
* @return 业务数据
* @param <R> 结果数据泛型类型
* @param <ID> 查询数据库泛型类型,也是参数泛型类型
*/
<R, ID> List<R> queryWithMutexList(String keyPrefix, ID id, Class<R> type, Function<ID, List<R>> dbFallback, Long timeout, TimeUnit unit);
/**
* 不带参数查询数据,按照互斥锁方式获取缓存数据,同一时刻只有一个线程访问数据库,其他线程访问不到数据重试
* @param keyPrefix 缓存key的前缀
* @param type 缓存的实际对象类型
* @param dbFallback 无参数查询数据库数据
* @param timeout 缓存时长
* @param unit 时间单位
* @return 返回业务数据
* @param <R> 结果泛型
*/
<R> List<R> queryWithMutexListWithoutArgs(String keyPrefix, Class<R> type, Supplier<List<R>> dbFallback, Long timeout, TimeUnit unit);
/**
* 将对象类型的json字符串转换成泛型类型
* @param obj 未知类型对象
* @param type 泛型Class类型
* @return 泛型对象
* @param <R> 泛型
*/
default <R> R getResult(Object obj, Class<R> type){
if (obj == null){
return null;
}
//简单类型
if (TypeConversion.isSimpleType(obj)){
return Convert.convert(type, obj);
}
return JSONUtil.toBean(JSONUtil.toJsonStr(obj), type);
}
/**
* 将对象类型的json字符串转换成泛型类型的List集合
* @param str json字符串
* @param type 泛型Class类型
* @return 泛型List集合
* @param <R> 泛型
*/
default <R> List<R> getResultList(String str, Class<R> type){
if (StrUtil.isEmpty(str)){
return null;
}
return JSONUtil.toList(JSONUtil.parseArray(str), type);
}
/**
* 获取简单的key
* @param key key
* @return 返回key
*/
default String getKey(String key){
return getKey(key, null);
}
/**
* 不确定参数类型的情况下,使用MD5计算参数的拼接到Redis中的唯一Key
* @param keyPrefix 缓存key的前缀
* @param id 泛型参数
* @return 拼接好的缓存key
* @param <ID> 参数泛型类型
*/
default <ID> String getKey(String keyPrefix, ID id){
if (id == null){
return keyPrefix;
}
String key = "";
//简单数据类型与简单字符串
if (TypeConversion.isSimpleType(id)){
key = StrUtil.toString(id);
}else {
key = MD5.create().digestHex(JSONUtil.toJsonStr(id));
}
if (StrUtil.isEmpty(key)){
key = "";
}
return keyPrefix.concat(key);
}
/**
* 获取要保存到缓存中的value字符串,可能是简单类型,也可能是对象类型,也可能是集合数组等
* @param value 要保存的value值
* @return 处理好的字符串
*/
default String getValue(Object value){
return TypeConversion.isSimpleType(value) ? String.valueOf(value) : JSONUtil.toJsonStr(value);
}
}
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
(2)实现RedisDistributeCacheService类
RedisDistributeCacheService类是组件最核心的实现,实现了DistributeCacheService接口,并在方法的实现中,彻底解决了缓存击穿、穿透和雪崩问题。
源码详见:io.binghe.redis.plugin.cache.redis.RedisDistributeCacheService。
# 查看完整文章
加入冰河技术 (opens new window)知识星球,解锁完整技术文章、小册、视频与完整代码
