feat(cache): 添加店铺服务的 Redis 缓存功能
- 新增 RedisIdWorker 工具类,用于生成分布式 ID - 在 ShopServiceImpl 中基于互斥锁解决缓存击穿问题 - 添加测试用例验证 ID 生成和缓存功能
This commit is contained in:
parent
5c962b28e6
commit
bb80b6da7a
@ -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<ShopMapper, Shop> 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<ShopMapper, Shop> 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
|
||||
|
||||
31
src/main/java/com/hmdp/utils/RedisIdWorker.java
Normal file
31
src/main/java/com/hmdp/utils/RedisIdWorker.java
Normal file
@ -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)); //获取这个时间的秒数
|
||||
// }
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user