diff --git a/src/main/java/com/hmdp/service/impl/ShopServiceImpl.java b/src/main/java/com/hmdp/service/impl/ShopServiceImpl.java index 31d0f47..3d17787 100644 --- a/src/main/java/com/hmdp/service/impl/ShopServiceImpl.java +++ b/src/main/java/com/hmdp/service/impl/ShopServiceImpl.java @@ -1,5 +1,7 @@ package com.hmdp.service.impl; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; @@ -32,6 +34,14 @@ public class ShopServiceImpl extends ServiceImpl implements IS private StringRedisTemplate stringRedisTemplate; @Override public Result queryById(Long id) { + Shop shop = queryByIdWithMutex(id); + if (shop == null){ + return Result.fail("店铺不存在"); + } + return Result.ok(shop); + } + + public Shop queryByIdWithRedis(Long id) { // 使用redis缓存 // 查询缓存 String cacheShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id); @@ -40,16 +50,73 @@ public class ShopServiceImpl extends ServiceImpl implements IS if(StrUtil.isNotBlank(cacheShop)){ // 存在直接返回 shop = JSONUtil.toBean(cacheShop, Shop.class); - return Result.ok(shop); + return shop; } // 不存在,查询数据库 shop = getById(id); if(shop == null){ // 数据库不存在,返回错误 - return Result.fail("店铺不存在"); + return null; + // 解决缓存穿透的话,就在这往redis存一个null } stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES); - return Result.ok(shop); + return shop; + } + + public Shop queryByIdWithMutex(Long id) { + String key = CACHE_SHOP_KEY + id; + // 使用redis缓存 + // 查询缓存 + String cacheShop = stringRedisTemplate.opsForValue().get(key); + // 判断是否存在 + Shop shop = new Shop(); + if(StrUtil.isNotBlank(cacheShop)){ + // 存在直接返回 + shop = JSONUtil.toBean(cacheShop, Shop.class); + return shop; + } + + boolean isLock = false; + try { + isLock =tryLock(id); + // 不存在,尝试获取互斥锁 + if(isLock){ + // 获取到互斥锁,查询数据库 + shop = getById(id); + Thread.sleep(200); //模拟重建时的延迟 + if(shop == null){ + // 解决缓存穿透的话,就在这往redis存一个null + stringRedisTemplate.opsForValue().set(key, "",CACHE_SHOP_TTL, TimeUnit.MINUTES); + // 数据库不存在,返回错误 + return null; + } + // 存入缓存 + stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES); + } + // 没有获取到互斥锁 + // 休眠后重新执行 + Thread.sleep(50); + return queryByIdWithMutex (id); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + // 释放互斥锁 + if (isLock) { + delectLock(id); + } + + } + } + + private boolean tryLock(Long id) { + // 本质就是设置一个redis缓存,key任意 + // 设置10秒过期时间防止死锁,10秒就够,因为这些请求一半都很快 + Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock:shop:"+id, "1",10, TimeUnit.SECONDS); + return BooleanUtil.isTrue(flag); + } + private void delectLock(Long id) { + // 本质就是删除上面key对应的缓存 + stringRedisTemplate.delete("lock:shop:"+id); } @Override diff --git a/src/main/java/com/hmdp/utils/RedisIdWorker.java b/src/main/java/com/hmdp/utils/RedisIdWorker.java new file mode 100644 index 0000000..7a4ce0a --- /dev/null +++ b/src/main/java/com/hmdp/utils/RedisIdWorker.java @@ -0,0 +1,31 @@ +package com.hmdp.utils; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + +@Component +public class RedisIdWorker { + private static final long BEGIN_TIMESTAMP = 1640995200L; + private static final long COUNT_BITS = 32; + @Resource + private StringRedisTemplate stringRedisTemplate; + // 拼接一个时间戳,再加一个根据输入的字符串自增长的32位id + public long nextId(String prefix) { + LocalDateTime now = LocalDateTime.now(); + long GapSecond = now.toEpochSecond(ZoneOffset.UTC)-BEGIN_TIMESTAMP; + String date = now.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + long count = stringRedisTemplate.opsForValue().increment("icr:" + prefix+date); + return GapSecond <<32 | count; + } + + +// public static void main(String[] args) { +// LocalDateTime localDateTime = LocalDateTime.of(2022, 1, 1, 0, 0, 0); +// System.out.println(localDateTime.toEpochSecond(ZoneOffset.UTC)); //获取这个时间的秒数 +// } +} diff --git a/src/test/java/com/hmdp/HmDianPingApplicationTests.java b/src/test/java/com/hmdp/HmDianPingApplicationTests.java index bf82f64..5f99c6f 100644 --- a/src/test/java/com/hmdp/HmDianPingApplicationTests.java +++ b/src/test/java/com/hmdp/HmDianPingApplicationTests.java @@ -1,9 +1,27 @@ package com.hmdp; +import com.hmdp.utils.RedisIdWorker; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -@SpringBootTest -class HmDianPingApplicationTests { +import javax.annotation.Resource; +@SpringBootTest() +@RunWith(SpringJUnit4ClassRunner.class) +public class HmDianPingApplicationTests { + @Autowired + RedisIdWorker redisIdWorker; + + @Test + public void testIdWorker() { + for (int i = 0; i < 100; i++) { + long id = redisIdWorker.nextId("order"); + System.out.println("id = " + id); + } + } + }