SpringBoot整合Redis实现常用功能
1 登陆功能
1.1 基于Session实现登录流程
功能流程:
1.1.1 session共享问题
核心思路分析:
但是这种方案具有两个大问题
所以咱们后来采用的方案都是基于redis来完成,我们把session换成redis,redis数据本身就是共享的,就可以避免session共享的问题了
1.2 Redis替代Session
1.2.1、设计key的结构
1.2.2、设计key的具体细节
1.2.3、整体访问流程
2 缓存功能
2.1 什么是缓存?
例1:Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 本地用于高并发
例2:static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 用于redis等缓存
例3:Static final Map<K,V> map = new HashMap(); 本地缓存
2.1.1 为什么要使用缓存
2.1.2 如何使用缓存
2.2.使用缓存
2.2.1 、缓存模型和思路
2.3 缓存更新策略
内存淘汰:redis自动进行,当redis内存达到咱们设定的max-memery的时候,会自动触发淘汰机制
,淘汰掉一些不重要的数据(可以自己设置策略方式)
超时剔除:当我们给redis设置了过期时间ttl之后,redis会将超时的数据进行删除,方便咱们继续使用缓存
主动更新:我们可以手动调用方法把缓存删掉,通常用于解决缓存和数据库不一致问题
2.3.1 、数据库缓存不一致解决方案:
2.3.2 、数据库和缓存不一致采用什么方案
2.4 缓存穿透问题的解决思路
小总结:
3 工具类
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
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.CACHE_NULL_TTL;
/**
* @author : look-word
* 2022-08-19 17:02
**/
@Component
public class CacheClient {
@Resource
private StringRedisTemplate stringRedisTemplate;
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}
/**
* 设置逻辑过期时间
*/
public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
// .封装逻辑时间
RedisData redisData = new RedisData();
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
redisData.setData(value);
String redisDataJson = JSONUtil.toJsonStr(redisData);
// 写入Redis
stringRedisTemplate.opsForValue().set(key, redisDataJson);
}
/**
* 解决缓存穿透 对未存在的数据 设置为null
*/
public <R, ID> R queryWithPassThrough
(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long cacheTime, TimeUnit cacheUnit) {
// 缓存key
String key = keyPrefix + id;
// 1 查询缓存中是否命中
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {
R r = JSONUtil.toBean(json, type);
return r;
}
// 解决缓存穿透 数据库不存在的数据 缓存 也不存在 恶意请求
if (json != null) {
return null;
}
// 2 查询数据库 存在 存入缓存 返回给前端
R r = dbFallback.apply(id);
if (r == null) {
// 解决缓存穿透
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
// 2.1 转换成json 存入缓存中
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(r), cacheTime, cacheUnit);
return r;
}
// 线程池
public static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
/**
* 解决缓存击穿 逻辑过期时间方式
*/
public <R, ID> R queryWithLogicalExpire
(String keyPrefix, ID id, Class<R> type, String lockKeyPrefix, Function<ID, R> dbFallback, Long expiredTime, TimeUnit expiredUnit) {
// 缓存key
String key = keyPrefix + id;
// 1 查询缓存中是否命中
String redisDataJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isBlank(redisDataJson)) {
return null;
}
// 2.命中 查看是否过期,
// 2.1 未过期 直接返回旧数据
// 2.2 过期 获取锁 查询数据写入Redis设置新的过期时间
// 2.3 过期 未获取锁 返回 旧数据
RedisData redisData = JSONUtil.toBean(redisDataJson, RedisData.class);
LocalDateTime expireTime = redisData.getExpireTime();
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
if (LocalDateTime.now().isBefore(expireTime)) {
return r;
}
String lockKey = lockKeyPrefix + id;
// 获取锁
boolean isLock = tryLock(lockKey);
if (isLock) {
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
// 查询数据库
R r1 = dbFallback.apply(id);
// 存储Redis 设置逻辑过期 过期时间
setWithLogicalExpire(key, r1, expiredTime, expiredUnit);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 释放锁
unlock(lockKey);
}
});
}
// 未获取到锁
return r;
}
/**
* 获取锁
*/
public boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 100, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
/**
* 释放锁
*/
public void unlock(String key) {
stringRedisTemplate.delete(key);
}
}