【Redis】封装Redis缓存工具解决缓存穿透与缓存击穿问题

谁借莪1个温暖的怀抱¢ 2023-09-27 19:30 194阅读 0赞

基于StringRedisTemplate封装一个缓存工具,主要有一下几个方法

方法1:将任意Java对象序列化为json并存储在String的指定key中且设置TTL

方法2:将任意Java对象序列化为json并存储在String的指定key中,并可以设置逻辑过期时间,用户处理缓存击穿问题

方法3:根据指定的key进行查询缓存,并反序列化为指定类型,利用缓存空值的办法解决缓存穿透问题

方法4:根据指定的key进行查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

此处使用到的互斥锁加锁解锁方法,在前面的文章有提及【Redis】Java通过redis实现简单的互斥锁_1373i的博客-CSDN博客icon-default.png_t_N3I4https://blog.csdn.net/qq\_61903414/article/details/130509874?spm=1001.2014.3001.5501

  1. import cn.hutool.core.util.StrUtil;
  2. import cn.hutool.json.JSONObject;
  3. import cn.hutool.json.JSONUtil;
  4. import com.fasterxml.jackson.core.JsonProcessingException;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import lombok.Data;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.data.redis.core.StringRedisTemplate;
  10. import org.springframework.stereotype.Component;
  11. import java.time.LocalDateTime;
  12. import java.util.concurrent.TimeUnit;
  13. import java.util.function.Function;
  14. @Data
  15. class RedisData {
  16. private LocalDateTime expireTime;
  17. private Object data;
  18. public RedisData(LocalDateTime expireTime, Object data) {
  19. this.expireTime = expireTime;
  20. this.data = data;
  21. }
  22. }
  23. /**
  24. * 封装解决redis 缓存击穿、缓存雪崩、缓存穿透等问题的方法
  25. */
  26. @Component
  27. @Slf4j
  28. public class RedisUtil {
  29. @Autowired
  30. private StringRedisTemplate stringRedisTemplate;
  31. @Autowired
  32. private ObjectMapper objectMapper;
  33. /**
  34. * 存入时设置过期时间
  35. * @param key
  36. * @param value
  37. * @param time
  38. * @param unit
  39. */
  40. public void set(String key, Object value, Long time, TimeUnit unit) throws JsonProcessingException {
  41. stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(value),time,unit);
  42. }
  43. /**
  44. * 存入时设置逻辑过期
  45. * @param key
  46. * @param value
  47. * @param time
  48. * @param unit
  49. */
  50. public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) throws JsonProcessingException {
  51. RedisData data = new RedisData(LocalDateTime.now().plusSeconds(unit.toSeconds(time)),value);
  52. stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(data));
  53. }
  54. /**
  55. * 查询缓存,缓存穿透时缓存null解决
  56. * @param id 要查询的id
  57. * @param keyPrefix redis key的前缀
  58. * @param type 要返回的类型
  59. * @param dbFallback 查询数据库的方法的结果
  60. * @param <R>
  61. * @param <I>
  62. * @return
  63. * @throws JsonProcessingException
  64. */
  65. public <R,I> R getWithPassThrough(I id, String keyPrefix, Class<R> type, Function<I,R> dbFallback,Long time,TimeUnit unit) throws JsonProcessingException {
  66. // 生成redis中的key
  67. String key = keyPrefix + id;
  68. // 查询redis
  69. String json = stringRedisTemplate.opsForValue().get(key);
  70. if (StrUtil.isNotBlank(json)) {
  71. // 存在缓存,则返回
  72. return objectMapper.readValue(json,type);
  73. }
  74. // 不存在时,判断是否是空值
  75. if (json != null) {
  76. // 返回错误信息
  77. return null;
  78. }
  79. // 查询数据库
  80. R result = dbFallback.apply(id);
  81. // 判空
  82. if (result == null) {
  83. // 发送缓存穿透,缓存null
  84. stringRedisTemplate.opsForValue().set(key,"",time,unit);
  85. return null;
  86. }
  87. // 存在则重建缓存
  88. stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(result),time,unit);
  89. return result;
  90. }
  91. /**
  92. * 查询缓存,逻辑过期后异步重建缓存
  93. * @param id
  94. * @param keyPrefix
  95. * @param type
  96. * @param dbFallback
  97. * @param time
  98. * @param unit
  99. * @param <R>
  100. * @param <I>
  101. * @return
  102. * @throws JsonProcessingException
  103. */
  104. public <R,I> R get(I id, String keyPrefix,Class<R> type,Function<I,R> dbFallback,Long time,TimeUnit unit) throws JsonProcessingException {
  105. // 构建查询redis的key
  106. String key = keyPrefix + id;
  107. // 查询redis
  108. String json = stringRedisTemplate.opsForValue().get(key);
  109. // 反序列化转为RedisData对象
  110. RedisData data = objectMapper.readValue(json,RedisData.class);
  111. // 获取数据
  112. R r = JSONUtil.toBean((JSONObject) data.getData(),type);
  113. // 获取逻辑时间
  114. LocalDateTime expireTime = data.getExpireTime();
  115. // 判断是否过期
  116. if (expireTime.isAfter(LocalDateTime.now())) {
  117. // 没有过期
  118. return r;
  119. }
  120. // 到达此处已过期,重建缓存:加互斥锁--》查询数据库--》重建--》释放互斥锁
  121. // 1.获取互斥锁
  122. String lockKey = id + "_lock";
  123. boolean isLock = LockByRedis.tryLock(lockKey);
  124. if (isLock) {
  125. // 2.加锁成功,开启线程重建缓存
  126. new Thread(){
  127. @Override
  128. public void run() {
  129. try {
  130. // 3.查询数据库
  131. R db = dbFallback.apply(id);
  132. // 4.重建
  133. stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(db),time,unit);
  134. } catch (JsonProcessingException e) {
  135. e.printStackTrace();
  136. } finally {
  137. // 5.释放互斥锁
  138. LockByRedis.unlock(lockKey);
  139. }
  140. }
  141. }.start();
  142. }
  143. // 返回逻辑过期的数据
  144. return r;
  145. }
  146. }

难点:在重建缓存时,我们需要去查询数据库,而查询数据库不同的reids缓存重建所需要查询的数据库表可能不同,其方法也可能不同。

解决:通过Function<参数类型,返回值>来把该查询数据库的方法交给方法的调用者进行传入

发表评论

表情:
评论列表 (有 0 条评论,194人围观)

还没有评论,来说两句吧...

相关阅读