十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

黑马点评之解决缓存击穿问题

黑马点评之解决缓存击穿问题 1.基于互斥锁方式解决缓存击穿问题需求:修改根据id查询商铺的业务基于互斥锁方式来解决缓存击穿问题。1.1创建互斥锁和释放互斥锁private boolean tryLockString key{ Boolean flag stringRedisTemplate.opsForValue.setIfAbsent(key,value1,timeout:10,TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } private void unlock (String key){ stringRedisTemplate.delete(key); }用返回值类型用boolean来代表成功与否我们知道redis创建互斥锁的命令是setnxsetnx是string操作所以用opsForValue然后用里面的setIfAbsent(key, value:1, timeout:10, TimeUnit.SECONDS),这样获取锁的操作就完成了但是不能像return flag这样直接返回flag因为它会作拆箱的在拆箱的过程中是有可能出现空指针的所以直接用BooleanUtil这个工具类。1.2具体业务代码public Result queryById(Long id) { //互斥锁解决缓存击穿 //Shop shop queryWithMutex(id);基于互斥锁来解决缓存击穿问题 // public Shop queryWithMutex(Long id){ // String key CACHE_SHOP_KEY id; // //1.从redis查缓存 // String shopJson stringRedisTemplate.opsForValue().get(key);//以店铺id做为key,这样就得到店铺的json字符串 // //2.判断是否存在 // if (StrUtil.isNotBlank(shopJson)) { // //3.存在直接返回 // return JSONUtil.toBean(shopJson, Shop.class); // } // //判断命中的是否是空值 // if(shopJson !null){ // //返回一个错误信息 // return null; // } // //4.实现缓存重建 // //4.1获取互斥锁 // String lockKey lock:shop:id; // Shop shop null; // try { // boolean isLock tryLock(lockKey); // //4.2判断是否获取成功 // if (!isLock){ // //4.3失败则休眠并重试 // Thread.sleep(50); // queryWithMutex(id);//递归 // //注意获取锁成功应该再次检测redis缓存是否存在做DoubleCheck。如果存在则无需重建缓存。 // } // //4.3失败则休眠并重试 // //4.4成功根据id查询数据库 // //4.不存在根据id查询数据库 // shop getById(id); // //5.不存在返回错误 // if (shopnull) { // //将空值写入redis // stringRedisTemplate.opsForValue().set(key,,CACHE_NULL_TTL,TimeUnit.MINUTES); // //返回错误信息 // return null; // } // // //6.存在写入redis // stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES); // //因为用的是opsForValue所以是value是string类型的所以后面的shop还得转为string // // // } catch (InterruptedException e) { // throw new RuntimeException(e); // }finally { // //7.释放互斥锁 // unLock(lockKey); // } // //8.返回 // return shop;2.基于逻辑过期方式解决缓存击穿问题需求:修改根据id查询商铺的业务基于逻辑过期方式来解决缓存击穿问题。2.1怎么添加逻辑过期时间在shop实体类里面本身是没有逻辑过期的字段的那怎么添加逻辑过期时间呢当然我们可以在shop实体类直接加一个逻辑过期时间的字段当然这样是可以的但是这样要对原来的代码和业务逻辑作一个修改所以是不太推荐的。所以我们这边定义一个RedisData的对象。其实也可以让shop继承继承RedisData但是还是要去修改shop所以我们选择在RedisData里面添加一个Object属性。可以用这个data来存shop以及之后的其他数据。public class RedisData{ //过期时间 private LocateDateTime expireTime; private Object data; }2.2具体业务代码public void saveShop2Redis(Long id,Long expireSeconds) throws InterruptedException { //1.查询店铺数据 Shop shop getById(id); Thread.sleep(200); //2.封装逻辑过期时间 RedisData redisData new RedisData(); redisData.setData(shop); redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds)); //3.写入Redis stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEYid,JSONUtil.toJsonStr(redisData)); }LocalDateTime.now()代表的是当时时间plusSeconds代表在原有时间的基础上加,seconds代表单位是秒。这边的value是RedisData因为里面包含了shop和过期时间。利用JSONUtil.toJsonStr()将对象序列化JSON格式的字符串。它的核心作用是将任意 Java 对象如普通Java对象、Map、List、数组等序列化成 JSON 格式的字符串。private static final ExecutorService CACHE_REBUILD_EXECUTOR Executors.newFixedThreadPool(10); //创建一个线程池 // public Shop queryWithLogicalExpire(Long id){ // String key CACHE_SHOP_KEY id;//把这个转为字符串方便使用前面标明cache shop 方便区分id // //1.从redis查缓存 // String shopJson stringRedisTemplate.opsForValue().get(key);//以店铺id做为key,这样就得到店铺的json字符串 // //2.判断是否存在 // if (StrUtil.isBlank(shopJson)) { // //3.存在直接返回 // return null; // } // //4.命中需要先把json反序列化为对象 // RedisData redisData JSONUtil.toBean(shopJson, RedisData.class); // Shop shop JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class); // LocalDateTime expireTime redisData.getExpireTime(); // //5.判断是否过期 // if (expireTime.isAfter(LocalDateTime.now())){ // //5.1.未过期直接返回店铺信息 // return shop; // } // // //5.2.已过期需要缓存重建 // // //6.缓存重建 // //6.1获取互斥锁 // String lockKey LOCK_SHOP_KEY id; // boolean isLock tryLock(lockKey); // //6.2判断是否获取锁成功 // if (isLock){ // //TODO 6.3成功开启独立线程实现缓存重建 // //注意获取锁成功应该再次检测redis缓存是否过期做DoubleCheck。如果存在则无需重建缓存 // CACHE_REBUILD_EXECUTOR.submit(() -{ // try { // //重建缓存 // this.saveShop2Redis(id,20L); // } catch (Exception e) { // throw new RuntimeException(e); // }finally { // //释放锁 // unLock(lockKey); // } // // }); // // } // //6.4.失败返回过期商铺信息 // return shop; // // }RedisData redisData JSONUtil.toBean(shopJson, RedisData.class);Shop shop JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);用JSONUtil.toBean()来做反序列化虽然给的是shopJson但是转过来并不是商铺的信息我们存取的是RedisData所以类型应该是RedisData类型。店铺信息在RedisData里面的data里但是data类型是Object类型并没有具体的类型说是店铺这边我们没有写死是店铺。万一以后是其他数据呢没有写成店铺意味着它在反序列化的时候不知道你的类型是不是店铺。它的本质其实是一个JsonObject这里做一个强转。Shop shop JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);这段代码可以写成JsonObject Data JSONObject redisData.getData; Shop shop JSONUtil.toBean(data,shop.class);if (expireTime.isAfter(LocalDateTime.now())) 判断过期时间是不是在当前时间之后如果是之后说明还没有过期。开启独立线程去做重建建议这边是用一个线程池去做private static final ExecutorService CACHE_REBUILD_EXECUTOR Executors.newFixedThreadPool(10);
返回列表