2.4 缓存穿透

Redis04 穿透、雪崩、击穿 - 图1

Redis04 穿透、雪崩、击穿 - 图2

2.5 缓存雪崩

Redis04 穿透、雪崩、击穿 - 图3

2.6 缓存击穿

Redis04 穿透、雪崩、击穿 - 图4

Redis04 穿透、雪崩、击穿 - 图5

Redis04 穿透、雪崩、击穿 - 图6

Redis04 穿透、雪崩、击穿 - 图7

代码
  1. @Override
  2. public Result queryById(Long id) throws JsonProcessingException {
  3. String key = CacheConst.SHOP_FIELD + id;
  4. String shopCache = stringRedisTemplate.opsForValue().get(key);
  5. if (StrUtil.isNotBlank(shopCache)) {
  6. Shop shop = JSONUtil.toBean(shopCache, Shop.class);
  7. log.debug("走缓存 {}", shop);
  8. return Result.ok(shop);
  9. }
  10. String lockKey = CacheConst.CACHE_LOCK_SHOP+ id;
  11. System.out.println(lockKey);
  12. int count = 10;
  13. try {
  14. // 获取锁失败接着查询缓存看看其他进程有没有更新
  15. while ( !this.tryLock(lockKey)) {
  16. if (count == 0) {break;}
  17. shopCache = stringRedisTemplate.opsForValue().get(key);
  18. log.info("获取锁失败 {}",shopCache);
  19. if (StrUtil.isNotBlank(shopCache)) {
  20. Shop shop = JSONUtil.toBean(shopCache, Shop.class);
  21. log.debug("走缓存 {}", shop);
  22. return Result.ok(shop);
  23. }
  24. TimeUnit.MILLISECONDS.sleep(500L);
  25. count--;
  26. }
  27. log.info("获取锁成功");
  28. Shop shop = getById(id);
  29. if (shop == null) {
  30. Shop obj = new Shop();
  31. obj.setId(id);
  32. stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(obj),CacheConst.NULL_TTL,CacheConst.NULL_UNIT);
  33. return Result.fail("店铺不存在");
  34. }
  35. stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
  36. stringRedisTemplate.expire(key, CacheConst.CACHE_TTL, CacheConst.CACHE_TTL_UNIT);
  37. return Result.ok(shop);
  38. } catch (InterruptedException e) {
  39. throw new RuntimeException(e);
  40. } finally {
  41. this.unlock(lockKey);
  42. }
  43. }
  44. private boolean tryLock(String lockKey){
  45. boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
  46. return lock;
  47. }
  48. private boolean unlock(String lockKey){
  49. boolean unlock = stringRedisTemplate.delete(lockKey);
  50. return unlock;
  51. }

Redis04 穿透、雪崩、击穿 - 图8

  1. String key = CacheConst.SHOP_FIELD + id;
  2. String shopCache = stringRedisTemplate.opsForValue().get(key);
  3. //todo 未命中直接返回
  4. if (StrUtil.isBlank(shopCache)) {
  5. log.debug("未命中==> {}", shopCache);
  6. return Result.fail("店铺不存在");
  7. }
  8. RedisData shopData = JSONUtil.toBean(shopCache, RedisData.class);
  9. JSONObject data = (JSONObject) shopData.getData();
  10. Shop shop = JSONUtil.toBean(data, Shop.class);
  11. // todo 命中判断是否过期
  12. if (shopData.getExpire() .isAfter(LocalDateTime.now())) {
  13. // todo 未过期
  14. log.debug("未过期 {}", shopData);
  15. return Result.ok(shop);
  16. }
  17. // todo 过期
  18. String lockKey = CacheConst.CACHE_LOCK_SHOP + id;
  19. if (this.tryLock(lockKey)) {
  20. // todo 开启独立线程更新缓存刷新过期时间
  21. System.err.println("刷新");
  22. config.refreshRedis(lockKey,1000,getById(id),s->{
  23. this.unlock(lockKey);
  24. System.err.println("解锁");
  25. });
  26. }
  27. System.err.println("过期返回");
  28. //todo 未获取锁 返回过期
  29. return Result.ok(data);

2.7 缓存封装工具

  1. @Slf4j
  2. @Component
  3. public class CacheClient {
  4. private final StringRedisTemplate redisTemplate;
  5. public CacheClient(StringRedisTemplate redisTemplate) {
  6. this.redisTemplate = redisTemplate;
  7. }
  8. public void set(String key, Object value, long expireTime, TimeUnit unit) {
  9. String json = JSONUtil.toJsonStr(value);
  10. redisTemplate.opsForValue().set(key, json, expireTime, unit);
  11. }
  12. // 逻辑过期时间
  13. public void setWithLogicalExpire(String key, Object value, long expireTime, TimeUnit unit) {
  14. RedisData redisData = new RedisData();
  15. redisData.setData(value);
  16. redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(expireTime)));
  17. redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
  18. }
  19. /**
  20. * 防止穿透
  21. */
  22. public <R, ID> R getWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> callback,
  23. long cacheTime, TimeUnit unit
  24. ) {
  25. String key = keyPrefix + id;
  26. String json = redisTemplate.opsForValue().get(key);
  27. // todo redis获取缓存
  28. if (StrUtil.isNotBlank(json)) {
  29. R bean = JSONUtil.toBean(json, type);
  30. return bean;
  31. }
  32. // todo 判断是否为空值 "" null
  33. // 理解为序列化不了的 “”
  34. if (json != null) return null;
  35. // todo 查询数据库
  36. R r = callback.apply(id);
  37. // todo 防止击穿
  38. if (r == null) {
  39. redisTemplate.opsForValue().set(key, "", cacheTime, unit);
  40. return null;
  41. }
  42. // todo 存入redis
  43. this.set(key, r, cacheTime, unit);
  44. return r;
  45. }
  46. /**
  47. * 防止击穿逻辑锁
  48. */
  49. public <R, ID> R getWithLogicalExpire(String keyPrefix, ID id, Class<R> type, String lockKeyPrefix, Function<ID, R> dbcallback) {
  50. String key = keyPrefix + id;
  51. String typeCache = redisTemplate.opsForValue().get(key);
  52. //todo 未命中直接返回
  53. if (StrUtil.isBlank(typeCache)) {
  54. log.debug("未命中==> {}", typeCache);
  55. return null;
  56. }
  57. RedisData redisData = JSONUtil.toBean(typeCache, RedisData.class);
  58. JSONObject data = (JSONObject) redisData.getData();
  59. R r = JSONUtil.toBean(data, type);
  60. // todo 命中判断是否过期
  61. if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
  62. // todo 未过期
  63. log.debug("未过期 {}", redisData);
  64. return r;
  65. }
  66. // todo 过期
  67. String lockKey = lockKeyPrefix + id;
  68. // todo 尝试获取锁
  69. if (this.tryLock(lockKey)) {
  70. // todo 开启独立线程更新缓存刷新过期时间
  71. System.err.println("刷新");
  72. config.refreshRedis(lockKey, 10, dbcallback.apply(id), s -> {
  73. this.unlock(lockKey);
  74. System.err.println("解锁");
  75. });
  76. }
  77. System.err.println("过期返回");
  78. return r;
  79. }
  80. /**
  81. * 缓存击穿互斥锁
  82. */
  83. public <R, ID> R getMutuallyExclusive(String keyPrefix, ID id, Class<R> type, String lockKeyPrefix,
  84. Function<ID, R> function,
  85. long cacheTime, TimeUnit cacheUnit
  86. ) {
  87. String key = keyPrefix + id;
  88. String cacheJson = redisTemplate.opsForValue().get(key);
  89. if (StrUtil.isNotBlank(cacheJson)) {
  90. R r = JSONUtil.toBean(cacheJson, type);
  91. log.debug("走缓存 {}", r);
  92. return r;
  93. }
  94. if (cacheJson != null) return null;
  95. String lockKey = lockKeyPrefix + id;
  96. int count = 10;
  97. try {
  98. // 获取锁失败接着查询缓存看看其他进程有没有更新
  99. while (!this.tryLock(lockKey)) {
  100. if (count == 0) {
  101. return null;
  102. }
  103. cacheJson = redisTemplate.opsForValue().get(key);
  104. log.info("获取锁失败 {}", cacheJson);
  105. if (StrUtil.isNotBlank(cacheJson)) {
  106. R r = JSONUtil.toBean(cacheJson, type);
  107. log.debug("走缓存 {}", r);
  108. return r;
  109. }
  110. TimeUnit.MILLISECONDS.sleep(500);
  111. count--;
  112. }
  113. log.info("获取锁成功");
  114. R r = function.apply(id);
  115. if (r == null) {
  116. redisTemplate.opsForValue().set(key, "", cacheTime, cacheUnit);
  117. return null;
  118. }
  119. redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(r), cacheTime, cacheUnit);
  120. return r;
  121. } catch (InterruptedException e) {
  122. throw new RuntimeException(e);
  123. } finally {
  124. log.debug("释放锁");
  125. this.unlock(lockKey);
  126. }
  127. }
  128. private boolean tryLock(String lockKey) {
  129. boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
  130. return lock;
  131. }
  132. private boolean unlock(String lockKey) {
  133. boolean unlock = redisTemplate.delete(lockKey);
  134. return unlock;
  135. }
  136. @Autowired
  137. private MybatisConfig config;
  138. }