赞
踩
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的Key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
目录
需求:修改根据id查询商铺的业务,基于互斥锁方式来解决缓存击穿问题
操作锁的代码
核心思路就是利用redis的setnx方法来表示获取锁,如果redis没有这个key,则插入成功,返回1,如果已经存在这个key,则插入失败,返回0。在StringRedisTemplate中返回true/false,我们可以根据返回值来判断是否有线程成功获取到了锁
ShopServiceImpl
- public Shop queryWithMutex(Long id) {
- //先从Redis中查,这里的常量值是固定的前缀 + 店铺id
- String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
- //如果不为空(查询到了),则转为Shop类型直接返回
- if (StrUtil.isNotBlank(shopJson)) {
- Shop shop = JSONUtil.toBean(shopJson, Shop.class);
- return shop;
- }
- if (shopJson != null) {
- return null;
- }
- Shop shop = null;
- try {
- //否则去数据库中查
- boolean flag = tryLock(LOCK_SHOP_KEY + id);
- if (!flag) {
- Thread.sleep(50);
- return queryWithMutex(id);
- }
- //查不到,则将空值写入Redis
- shop = getById(id);
- if (shop == null) {
- stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
- return null;
- }
- //查到了则转为json字符串
- String jsonStr = JSONUtil.toJsonStr(shop);
- //并存入redis,设置TTL
- stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr, CACHE_SHOP_TTL, TimeUnit.MINUTES);
- //最终把查询到的商户信息返回给前端
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } finally {
- unLock(LOCK_SHOP_KEY + id);
- }
- return shop;
- }

- // 尝试获取锁
- private boolean tryLock(String key){
- Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
- return BooleanUtil.isTrue(flag);
- }
-
- // 释放锁
- private void unLock(String key){
- stringRedisTemplate.delete(key);
- }
直接在redis中设计一个永久的热点key,当发现逻辑时间已过期则采用类似互斥锁方式解决缓存击穿
封装数据:因为现在redis中存储的数据的value需要带上过期时间,此时要么你去修改原来的实体类,要么新建一个类包含原有的数据和过期时间
步骤一
- @Data
- public class RedisData<T> {
- private LocalDateTime expireTime;
- private T data;
- }
步骤二
- public void saveShop2Redis(Long id, Long expirSeconds) {
- Shop shop = getById(id);
- RedisData redisData = new RedisData();
- redisData.setData(shop);
- redisData.setExpireTime(LocalDateTime.now().plusSeconds(expirSeconds));
- stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
- }
步骤三
:正式代码- //这里需要声明一个线程池,因为下面我们需要新建一个现成来完成重构缓存
- private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
-
- @Override
- public Shop queryWithLogicalExpire(Long id) {
- //1. 从redis中查询商铺缓存
- String json = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
- //2. 如果未命中,则返回空
- if (StrUtil.isBlank(json)) {
- return null;
- }
- //3. 命中,将json反序列化为对象
- RedisData redisData = JSONUtil.toBean(json, RedisData.class);
- //3.1 将data转为Shop对象
- JSONObject shopJson = (JSONObject) redisData.getData();
- Shop shop = JSONUtil.toBean(shopJson, Shop.class);
- //3.2 获取过期时间
- LocalDateTime expireTime = redisData.getExpireTime();
- //4. 判断是否过期
- if (LocalDateTime.now().isBefore(expireTime)) {
- //5. 未过期,直接返回商铺信息
- return shop;
- }
- //6. 过期,尝试获取互斥锁
- boolean flag = tryLock(LOCK_SHOP_KEY + id);
- //7. 获取到了锁
- if (flag) {
- //8. 开启独立线程
- CACHE_REBUILD_EXECUTOR.submit(() -> {
- try {
- this.saveShop2Redis(id, LOCK_SHOP_TTL);
- } catch (Exception e) {
- throw new RuntimeException(e);
- } finally {
- unlock(LOCK_SHOP_KEY + id);
- }
- });
- //9. 直接返回商铺信息
- return shop;
- }
- //10. 未获取到锁,直接返回商铺信息
- return shop;
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。