20240716学习redis
- 一、缓存穿透
- 1. isNotBlank()方法和isNotEmpty()方法的区别
- 2. 缓存穿透
- 3. 缓存雪崩
- 4. 缓存击穿的解决
- 5 ctrl+alt+T,可以快速生成包围方式:
- 6 //模拟重建延时
- 7. 代码 (使用互斥锁)
一、缓存穿透
1. isNotBlank()方法和isNotEmpty()方法的区别
在常见的 Java 编程语言中,isNotBlank() 和 isNotEmpty() 方法通常用于判断字符串是否为空。它们通常用于 Apache Commons StringUtils 库中。
isNotBlank() 方法用于判断一个字符串是否有值并且不是全由空格组成。
如果字符串为 null,或者为空字符串 “”,或者只包含空格字符,则 isNotBlank() 方法会返回 false。
如果字符串不为 null 且长度大于 0,并且至少包含一个非空格字符,则 isNotBlank() 方法会返回 true。
isNotEmpty() 方法用于判断一个字符串是否不为空。
如果字符串为 null,或者为空字符串 “”,则 isNotEmpty() 方法会返回 false。
如果字符串不为 null 且长度大于 0,则 isNotEmpty() 方法会返回 true。
因此,两者的区别在于对空格字符的处理。isNotBlank() 方法会额外检查字符串中是否有非空格字符,而 isNotEmpty() 方法仅判断字符串是否为空或为 null。
2. 缓存穿透
3. 缓存雪崩
4. 缓存击穿的解决
可以通过和setnx一样的思路
5 ctrl+alt+T,可以快速生成包围方式:
6 //模拟重建延时
Thread.sleep(200);这是 Java Thread 类中的一个静态方法。它使当前线程暂停(或休眠)指定的时间。
下面就是我们的互斥锁的使用和定义了。
7. 代码 (使用互斥锁)
package com.hmdp.service.impl;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2021-12-22
*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryById(Long id) {//首先,根据id在redis中查询店铺缓存
//缓存穿透
// Shop shop = queryWithPassThrough(id);
//用互斥锁解决缓存击穿
Shop shop = queryWithMutex(id);
if (shop == null) {
return Result.fail("店铺不存在");
}
//7.然后返回。
return Result.ok(shop);
}
public Shop queryWithMutex(Long id){//互斥锁
String key =CACHE_SHOP_KEY + id; //定义一个key,包装id和一个字段名
//1.从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);//存的是一个对象可以用哈希,也可以用String,这里用hash演示
//2.判断是否存在
if (StrUtil.isNotBlank(shopJson)) {//isNotBlank只有里面有字符的时候才是true,null和空或者/n都为空false
//3.存在,直接返回
//把JSON对象转化为shop对象
return JSONUtil.toBean(shopJson, Shop.class);
}
//判断命中的是否是空值
if (shopJson != null) {
//返回一个错误信息
return null;
}
//4.开始实现缓存重建
//4.1 获取互斥锁
String lockKey = "lock:shop:" + id; //定义一个key,包装id和一个字段名
Shop shop = null;
try {
boolean isLock = tryLock(lockKey);
//4.2 判断是否获取成功
if(isLock){
//4.3 失败,则休眠并重试
Thread.sleep(50);
return queryWithMutex(id);
}
//4.3 失败,则休眠并重试
//4.4不存在,根据id查询数据库
shop = getById(id);
//模拟重建延时
Thread.sleep(200);
//5.不存在,返回错误
if (shop == null) {
//将空值,写入redis
stringRedisTemplate.opsForValue().set(key,"null",2L, TimeUnit.MINUTES);
//返回错误信息
return null;
}
//6.存在,把数据写入redis,
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);//
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//7.释放互斥锁
unlock(lockKey);
}
//8.然后返回。
return shop;
}
public Shop queryWithPassThrough(Long id){
String key =CACHE_SHOP_KEY + id; //定义一个key,包装id和一个字段名
//1.从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);//存的是一个对象可以用哈希,也可以用String,这里用hash演示
//2.判断是否存在
if (StrUtil.isNotBlank(shopJson)) {//isNotBlank只有里面有字符的时候才是true,null和空或者/n都为空false
//3.存在,直接返回
//把JSON对象转化为shop对象
return JSONUtil.toBean(shopJson, Shop.class);
}
//判断命中的是否是空值
if (shopJson != null) {
//返回一个错误信息
return null;
}
//4.不存在,根据id查询数据库
Shop shop = getById(id);
//5.不存在,返回错误
if (shop == null) {
//将空值,写入redis
stringRedisTemplate.opsForValue().set(key,"null",2L, TimeUnit.MINUTES);
//返回错误信息
return null;
}
//6.存在,把数据写入redis,
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);//
//7.然后返回。
return shop;
}
//拿到锁
private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//相当于setnx
return BooleanUtil.isTrue(flag);//判断是否成功,因为直接返回可能会导致拆箱
}
//释放锁
private void unlock(String key){
stringRedisTemplate.delete(key);
}
@Override
@Transactional
public Result update(Shop shop) {
Long id = shop.getId();
if (id == null) {
return Result.fail("店铺id不能为空");
}
//1.更新数据库
updateById(shop);
//2.删除缓存
stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
//3.返回结果
return Result.ok();
}
}