具体实现:
ShopServiceImpl
package com.hmdp.service.impl;import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
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 com.hmdp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.*;/*** <p>* 服务实现类* </p>** @author ztn* @since 2025-9-12*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {@ResourceStringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 缓存穿透// Shop shop = queryWithPassThrough(id);// 互斥锁解决缓存击穿// Shop shop = queryWithMutex(id);// 逻辑过期解决缓存击穿Shop shop = queryWithLogicalExpire(id);if (shop == null) {return Result.fail("店铺不存在!");}// 7.返回return Result.ok(shop);}private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public Shop queryWithLogicalExpire(Long id){// 1.从redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);// 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){// 6.3 成功,开启独立线程CACHE_REBUILD_EXECUTOR.submit(()->{try {this.saveShop2Redis(id,20L);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unlock(lockKey);}});}// 6.4 返回过期的店铺信息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);}public Shop queryWithMutex(Long id){// 1.从redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);// 2.判断是否存在if(StrUtil.isNotBlank(shopJson)){// 3.存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}// 判断命中的是否是空值if(shopJson != null){return null;}// 4.实现缓存重建// 4.1获取互斥锁String lockKey = LOCK_SHOP_KEY +id;Shop shop = null;try {boolean isLock = tryLock(lockKey);// 4.2判断是否获取成功if(!isLock){// 4.3失败,则休眠并重试Thread.sleep(50);return queryWithMutex(id);}// 4.4成功,根据id查询数据库shop = getById(id);//模拟重建的延时Thread.sleep(200);// 5.不存在,返回错误if(shop == null){//将空值写入reidsstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "",CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 6.存在,写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.返回unlock(lockKey);}// 8.返回return shop;}public Shop queryWithPassThrough(Long id){// 1.从redis中查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);// 2.判断是否存在if(StrUtil.isNotBlank(shopJson)){// 3.存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}// 判断命中的是否是空值if(shopJson != null){return null;}// 4.不存在,根据id查询数据库Shop shop = getById(id);// 5.不存在,返回错误if(shop == null){//将空值写入reidsstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "",CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 6.存在,写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);// 7.返回return shop;}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.写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));}@Override@Transactionalpublic 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);return Result.ok();}
}