首页 > 数据库 >redis缓存雪崩

redis缓存雪崩

时间:2022-11-13 15:45:05浏览次数:47  
标签:缓存 return redis param 雪崩 key time id unit

 

 

 

 

 

 

 

 

 

 代码实现:互斥锁

package com.hmdp.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;

import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL;
import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;

@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);
}

/**
* 过期时间
* @param key
* @param value
* @param time
* @param 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));
}

/**
* 缓存穿透
* @param keyPrefix
* @param id
* @param type
* @param dbFallback
* @param time
* @param unit
* @param <R>
* @param <ID>
* @return
*/
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, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
// 返回错误信息
return null;
}
// 6.存在,写入redis
this.set(key, r, time, unit);
return r;
}

/**
* 缓存雪崩
* @param keyPrefix
* @param id
* @param type
* @param dbFallback
* @param time
* @param unit
* @param <R>
* @param <ID>
* @return
*/
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 = LOCK_SHOP_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;
}

/**
* 缓存击穿
* @param keyPrefix
* @param id
* @param type
* @param dbFallback
* @param time
* @param unit
* @param <R>
* @param <ID>
* @return
*/
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 shopJson = stringRedisTemplate.opsForValue().get(key);
// 2.判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
// 3.存在,直接返回
return JSONUtil.toBean(shopJson, type);
}
// 判断命中的是否是空值
if (shopJson != null) {
// 返回一个错误信息
return null;
}

// 4.实现缓存重建
// 4.1.获取互斥锁
String lockKey = LOCK_SHOP_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, "", 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;
}

/**
* 加锁
* @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);
}
}

 

标签:缓存,return,redis,param,雪崩,key,time,id,unit
From: https://www.cnblogs.com/ymsblog/p/16886058.html

相关文章

  • redis中缓存穿透
         代码实现 //Stringkey=CACHE_SHOP_KEY+id;////1从缓存中查询上铺缓存//StringshopJson=stringRedisTemplate.opsForVa......
  • 小白教程 懒人听书音频文件乱码,缓存音频文件名解码mp3教程
    通过懒人听书app下载下来的音频文件是乱码,直接通过播放器打开文件是播放不了的,那么想将音频文件分享给朋友随时随地听,怎么办? 如果能够直接下载MP3格式的音频文件,那......
  • redis的数据类型和python操作redis
    一redis的五种数据类型类型string(字符串类型)hash(哈希类型)list(列表类型)set(无序集合)zset(有序集合)说明是Redis中最为基础的数据存储类型,它在Redis中是二进......
  • Redis几种数据结构的存储方式
    一、使用stringRedisTemplate向redis中存储List数据取出privateStringRedisTemplatestringRedisTemplate;这里的RedisConstants.CACHE_SHOP_TYPE是"cache:shop-ty......
  • Redis列表(List)
    单键多值Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。1.1.1.   常用命令lpush/rpush<key><value1><value......
  • Redis集合(Set)
    简介Redisset对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提......
  • Redis有序集合Zset(sorted set)
    Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高......
  • Redis哈希(Hash)
      简介Redishash是一个键值对集合。Redishash是一个string类型的field和value的映射表,hash特别适合用于存储对象。类似Java里面的Map<String,Object>常用命令hs......
  • Redis配置文件介绍
     Redis配置文件介绍自定义目录:/myredis/redis.conf1.1.  ###Units单位###配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit大小写不敏感 1.2.......
  • 后台启动Redis
    1、 备份redis.conf拷贝一份redis.conf到其他目录cp /opt/redis-3.2.5/redis.conf /myredis2、后台启动设置daemonizeno改成yes修改redis.conf(128行)文件将里面......