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;
|
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.core.util.StrUtil;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.hmdp.dto.Result;
|
import com.hmdp.dto.Result;
|
||||||
@ -32,6 +34,14 @@ public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IS
|
|||||||
private StringRedisTemplate stringRedisTemplate;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
@Override
|
@Override
|
||||||
public Result queryById(Long id) {
|
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缓存
|
// 使用redis缓存
|
||||||
// 查询缓存
|
// 查询缓存
|
||||||
String cacheShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
|
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)){
|
if(StrUtil.isNotBlank(cacheShop)){
|
||||||
// 存在直接返回
|
// 存在直接返回
|
||||||
shop = JSONUtil.toBean(cacheShop, Shop.class);
|
shop = JSONUtil.toBean(cacheShop, Shop.class);
|
||||||
return Result.ok(shop);
|
return shop;
|
||||||
}
|
}
|
||||||
// 不存在,查询数据库
|
// 不存在,查询数据库
|
||||||
shop = getById(id);
|
shop = getById(id);
|
||||||
if(shop == null){
|
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);
|
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
|
@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;
|
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.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
@SpringBootTest
|
import javax.annotation.Resource;
|
||||||
class HmDianPingApplicationTests {
|
|
||||||
|
|
||||||
|
|
||||||
|
@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