1. 简单的步骤说明
创建一个逻辑缓存数据类型
封装缓冲穿透和缓冲击穿工具类
2. 逻辑缓存数据类型
这里主要是创建一个可以往Redis里边存放的数据类型
RedisData 的Java类型
1
2
3
4
5
6
7
8
9
|
import lombok.Data; import java.time.LocalDateTime; @Data public class RedisData { private LocalDateTime expireTime; private Object data; } |
3. 缓冲工具类的封装
3.1 CacheClient 类的类图结构
3.2 CacheClient 类代码
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
174
175
176
177
178
179
180
181
182
183
|
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.hmdp.entity.Shop; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; import static com.hmdp.utils.RedisConstants.*; @Component @Slf4j public class CacheClient { @Resource private final StringRedisTemplate stringRedisTemplate; // 线程池对象 private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool( 10 ); /** * 注入StringRedisTemplate的构造方法 * @param stringRedisTemplate */ public CacheClient(StringRedisTemplate stringRedisTemplate) { this .stringRedisTemplate = stringRedisTemplate; } /** * 在Redis中实现物理上添加数据和设置有效期 * @param key : Redis 存储的key的值 * @param value : Redis 存储的value的值 * @param time : Redis 存储的逻辑过期时间的值 * @param unit :Redis 存储的逻辑过期时间的单位 */ public void set(String key, Object value, Long time, TimeUnit unit) { stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit); } /** * 解决缓存击穿的Rides的逻辑存储的方法 * @param key : Redis 存储的key的值 * @param value : Redis 存储的value的值 * @param time : Redis 存储的逻辑过期时间的值 * @param unit :Redis 存储的逻辑过期时间的单位 */ 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)); } /** * 解决缓存穿透问题的Reids查询方法 * @param keyPrefix : key的前缀 * @param id : 查询的id * @param type : 查询数据的Class类型 * @param dbFallback : 查询数据库的sql具体语句 * @param time : 设置超时间 * @param unit : 设置超时时间单位 * @return : 返回一个 设置的 Class 类型对象 * @param <R> 返回值类型参数泛型 * @param <T> id类型参数泛型 */ public <R, T> R queryWithPassThrough( String keyPrefix, T id, Class<R> type, Function<T, 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 ) { stringRedisTemplate.opsForValue().set(key, "" , CACHE_NULL_TTL, TimeUnit.MINUTES); return null ; } // 6. 写入redis this .set(key, r, time, unit); // 7. 返回 return r; } /** * 解决缓存击穿问题的Reids查询方法 * @param keyPrefix : key的前缀 * @param id : 查询的id * @param type : 查询数据的Class类型 * @param dbFallback : 查询数据库的sql具体语句 * @param time : 设置超时间 * @param unit : 设置超时时间单位 * @return : 返回一个 设置的 Class 类型对象 * @param <R> 返回值类型参数泛型 * @param <T> id类型参数泛型 */ public <R, T> R queryWithLogicalExpire( String keyPrefix, T id, Class<R> type, Function<T, R> dbFallback, Long time, TimeUnit unit) { String key = keyPrefix + id; // 1. 从 redis 查询缓存 String json = stringRedisTemplate.opsForValue().get(key); // 2. 判断是否存在 if (StrUtil.isBlank(json)) { return null ; } // 4. 命中需要判断过期时间 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 = LOCK_SHOP_KEY + id; // 6.2 判断是否获取锁成功 boolean isLock = tryLock(lockKey); if (isLock) { // 6.3 成功 开启独立线程,实现缓存重建 CACHE_REBUILD_EXECUTOR.submit(() -> { try { // 查询数据库 R r1 = dbFallback.apply(id); // 写入Redis this .setWithLogicalExpire(key, r1, time, unit); } catch (Exception e) { throw new RuntimeException(e); } finally { // 释放锁 unlock(lockKey); } }); } // 6.4 返回店铺信息 return r; } /** * * @param key * @return */ private boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1" , 10 , TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } /** * 释放错 * * @param key */ private void unlock(String key) { stringRedisTemplate.delete(key); } } |
到此这篇关于Redis缓存穿透/击穿工具类的封装的文章就介绍到这了,更多相关Redis缓存穿透 击穿内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://blog.csdn.net/weixin_46213083/article/details/126005177