Redis
Redis
SpringDataRedis




缓存穿透
主要针对恶意请求,数据库不存在的请求

缓存击穿
主要针对热点数据,redis过期时,在redis重建过程中,大量访问冲击数据库

缓存穿透与缓存击穿的三种解决方法
// 解决内存穿透,将非法请求存储redis为空
Shop shop = redisClient.getObjectOrEmpty(RedisConstants.CACHE_SHOP_KEY,
id,
Shop.class,
id2->getById(id2),
RedisConstants.CACHE_SHOP_TTL,
TimeUnit.MINUTES);
// 基于互斥锁解决缓存击穿
Shop shop = redisClient.getObjectAndLock(
RedisConstants.CACHE_SHOP_KEY,
id,
Shop.class,
id2 -> getById(id2),
RedisConstants.LOCK_SHOP_TTL,
TimeUnit.MINUTES
);
// 基于逻辑过期,解决缓存击穿
Shop shop = redisClient.getWithLogicalExpire(
RedisConstants.CACHE_SHOP_KEY,
id,
Shop.class,
id2 -> getById(id2),
RedisConstants.LOCK_SHOP_TTL,
TimeUnit.MINUTES
);
redisClient工具类
@Component
@Slf4j
@RequiredArgsConstructor
public class RedisClient {
private final StringRedisTemplate stringRedisTemplate;
private final RedisLock redisLock;
private final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
// 基于逻辑过期,默认命中(会提前在redis缓存热点内容)
public <ID, R> R getWithLogicalExpire(String keyPre, ID id, Class<R> objectClass,Function<ID, R> selectObject,Long time, TimeUnit timeUnit){
// 查询redis
String key = keyPre+id;
R object = null;
String string = stringRedisTemplate.opsForValue().get(key);
// 命中为空
if (StrUtil.isBlank(string)){
return null;
}
// 命中不为空,判断是否过期
RedisData bean = JSONUtil.toBean(string, RedisData.class);
JSONObject data = (JSONObject) bean.getData();
object = JSONUtil.toBean(data, objectClass);
LocalDateTime expireTime = bean.getExpireTime();
// 未过期
if (expireTime.isAfter(LocalDateTime.now())){
return object;
}
// 过期,重建缓存
// 获取互斥锁,开启新的线程重建
boolean lock = redisLock.tryLock(RedisConstants.LOCK_SHOP_KEY+id, RedisConstants.LOCK_SHOP_TTL, TimeUnit.MINUTES);
if (!lock){
return object;
}
CACHE_REBUILD_EXECUTOR.submit(()->{
// 获取到互斥锁,请求数据
RedisData redisData = new RedisData();
R newObject = selectObject.apply(id);
redisData.setData(newObject);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(RedisConstants.LOCK_SHOP_TTL));
// 查询到放入redis缓存
this.set(key,redisData,time,timeUnit);
// 释放锁
redisLock.delLock(RedisConstants.LOCK_SHOP_KEY + id);
});
return object;
}
public <ID, R> R getObjectAndLock(String keyPre, ID id, Class<R> objectClass,Function<ID, R> selectObject,Long time, TimeUnit timeUnit){
// 查询redis
String key = keyPre+id;
R object = null;
try {
String string = stringRedisTemplate.opsForValue().get(key);
// 命中不为空
if (StrUtil.isNotBlank(string)){
return JSONUtil.toBean(string, objectClass);
}
// 命中为空
if (string!=null){
return null;
}
// 未命中,获取互斥锁
boolean getLock = redisLock.tryLock(RedisConstants.LOCK_SHOP_KEY + id, RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);
if (!getLock){
Thread.sleep(50);
return getObjectAndLock(keyPre,id,objectClass,selectObject,time,timeUnit);
}
// 获取锁成功,再次检查redis是否存在,做DoubleCheck
string = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(string)){
redisLock.delLock(RedisConstants.LOCK_SHOP_KEY + id);
return JSONUtil.toBean(string, objectClass);
}
object = selectObject.apply(id);
Thread.sleep(200);
// 未查询到,非法访问
if (object==null){
this.set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
// 查询到放入redis缓存
this.set(key,object,time,timeUnit);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 释放锁
redisLock.delLock(RedisConstants.LOCK_SHOP_KEY + id);
}
return object;
}
public <ID, R> R getObjectOrEmpty(String keyPre, ID id, Class<R> objectClass,Function<ID, R> selectObject,Long time, TimeUnit timeUnit){
// 查询redis
String key = keyPre+id;
String string = stringRedisTemplate.opsForValue().get(key);
// 命中不为空
if (StrUtil.isNotBlank(string)){
return JSONUtil.toBean(string, objectClass);
}
// 命中为空
if (string!=null){
return null;
}
// 未命中,查询数据库
R object = selectObject.apply(id);
// 查询到
if (object!=null){
this.set(key,object,time,timeUnit);
return object;
}
// 非法访问
this.set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
public void set(String key, Object value, Long time, TimeUnit timeUnit){
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(value),time,timeUnit);
}
}
基于reids的setnx实现互斥锁
@Component
public class RedisLock {
@Resource
private StringRedisTemplate stringRedisTemplate;
public boolean tryLock(String key, Long time, TimeUnit timeUnit){
Boolean f = stringRedisTemplate.opsForValue().setIfAbsent(key,"true",time,timeUnit);
return BooleanUtil.isTrue(f);
}
public void delLock(String keyPrefix){
stringRedisTemplate.delete(keyPrefix+"lock");
}
}
秒杀问题
全局唯一id生成策略

@Component
@RequiredArgsConstructor
public class RedisWorker {
private static final long BEGIN_TIMESTAMP = 1640995200;
private static final long COUNT_BITS = 32;
private final StringRedisTemplate stringRedisTemplate;
public long nextId(String keyPrefix){
// 生成时间戳
LocalDateTime now = LocalDateTime.now();
long temp = now.toEpochSecond(ZoneOffset.UTC) - BEGIN_TIMESTAMP;
String format = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// redis 自增长
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + format);
return temp << COUNT_BITS | count;
}
}
测试
private ExecutorService es = Executors.newFixedThreadPool(500);
@Test
void testIdWorker() throws InterruptedException {
//计数器
CountDownLatch latch = new CountDownLatch(300);
// 线程类,不能直接运行
Runnable task = ()->{
for (int i = 0; i < 100; i++) {
long id = redisWorker.nextId("order");
System.out.println("id= "+id);
}
//计数器减一
latch.countDown();
};
long begin = System.currentTimeMillis();
for (int i = 0; i < 300; i++) {
//启动线程
es.submit(task);
}
// 计数器不为0,阻塞
latch.await();
long end = System.currentTimeMillis();
System.out.println("time: "+(end-begin));
}
乐观锁与悲观锁

boolean success = killVocherService.update()
.setSql("Stock = stock-1")
.eq("voucher_id", voucherId)
// 库存大于0,而不是大于查询到的数量
// 防止在高并发下,线程1、2、3、4同时拿到同一查询数量,线程1执行结束,更新值,其他线程比较与查询数量不同全部失败
.gt("stock",0)
.update();
Redison
分布式服务



浙公网安备 33010602011771号