功能02-商鋪查詢快取03
3.功能02-商鋪查詢快取
3.6封裝redis工具類
3.6.1需求說明
基于StringRedisTemplate封裝一個工具列,滿足下列需求:
方法1:將任意Java物件序列化為json,并存盤在string型別的key中,并且可以設定TTL過期時間
方法2:將任意Java物件序列化為json,并存盤在string型別的key中,并且可以設定邏輯過期時間,用戶處理快取擊穿問題(針對熱點key)
方法3:根據指定的key查詢快取,并反序列化為指定型別,利用快取空值的方式解決快取穿透問題
方法4:根據指定的key查詢快取,并反序列化為指定型別,需要利用邏輯過期解決快取擊穿問題(針對熱點key)
3.6.2代碼實作
(1)創建redis工具類,封裝上述方法
package com.hmdp.utils;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static com.hmdp.utils.RedisConstants.*;
/**
* @author 李
* @version 1.0
* 封裝redis工具類
*/
@Component
@Slf4j
public class CacheClient {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 將任意Java物件序列化為json,并存盤在string型別的key中,并且可以設定TTL過期時間
*
* @param key 快取的key值
* @param value 快取的value值
* @param time 過期時間值
* @param unit 過期的時間單位
*/
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue()
.set(key, JSONUtil.toJsonStr(value), time, unit);
}
/**
* 將任意Java物件序列化為json,并存盤在string型別的key中,
* 并且可以設定邏輯過期時間,用戶處理快取擊穿問題(針對熱點key)
*
* @param key 快取的key值
* @param value 快取的value值
* @param time 過期時間值
* @param unit 過期的時間單位
*/
public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
//設定邏輯過期時間
RedisData redisData = https://www.cnblogs.com/liyuelian/archive/2023/04/21/new RedisData();
redisData.setData(value);
//邏輯過期時間=當前時間+指定的時間
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
/**
* 根據指定的key查詢快取,并反序列化為指定型別,利用快取空值的方式解決快取穿透問題
*
* @param keyPrefix 查詢的key值的前綴
* @param id 查詢的key值的后綴
* @param type 要轉換的Class型別
* @param dbFallback 傳入的函式
* @param time 過期時間值
* @param unit 時間單位
* @param 泛型
* @param 泛型
* @return 回傳指定的型別物件
*/
public R queryWithPassThrough(
String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit) {
String key = keyPrefix + id;
//redis查詢快取
String json = stringRedisTemplate.opsForValue().get(key);
//判斷json是否存在
if (StrUtil.isNotBlank(json)) {
//存在,轉為java物件并回傳
return JSONUtil.toBean(json, type);
}
//判斷是否為"",如果是,說明該key是為了解決快取穿透設定的空值
if ("".equals(json)) {
//回傳錯誤資訊
return null;
}
//不存在,根據id查詢資料庫——使用函式式編程
R r = dbFallback.apply(id);
if (r == null) {//說明資料庫中沒有該資料
//快取空值,應對快取穿透
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
//回傳錯誤資訊
return null;
}
//r存在,則將其寫入redis
this.set(key, r, time, unit);
return r;
}
private static final ExecutorService CACHE_REBUILD_EXECUTOR =
Executors.newFixedThreadPool(10);
/**
* 根據指定的key查詢快取,并反序列化為指定型別,需要利用邏輯過期解決快取擊穿問題(針對熱點key)
* @param keyPrefix 查詢的key值的前綴
* @param id 查詢的key值的后綴
* @param type 要轉換的Class型別
* @param dbFallback 傳入的函式
* @param time 過期時間值
* @param unit 時間單位
* @param <R> 泛型
* @param <ID> 泛型
* @return 回傳指定的型別物件
*/
public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback, Long time,
TimeUnit unit) {
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
//這里不再考慮快取穿透問題,因為key永不過期
if (StrUtil.isBlank(json)) {
//如果未命中,說明不是熱點key,直接回傳null
return null;
}
//如果命中
//先把json反序列化為物件
RedisData redisData = https://www.cnblogs.com/liyuelian/archive/2023/04/21/JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
LocalDateTime expireTime = redisData.getExpireTime();
//判斷是否邏輯過期
if (expireTime.isAfter(LocalDateTime.now())) {
//未過期,直接回傳資訊
return r;
}
//過期,獲取互斥鎖
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
if (isLock) {//成功獲取互斥鎖
//開啟獨立執行緒
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
//重建快取
//先查詢資料庫
R apply = dbFallback.apply(id);
//再存入reids快取
this.setWithLogicalExpire(key, apply, time, unit);
} catch (Exception e) {
throw new RuntimeException(e);
}
//釋放互斥鎖
unLock(lockKey);
});
}
//如果未獲取互斥鎖,直接回傳舊資料
return r;
}
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);
}
}
(2)修改ShopServiceImpl,呼叫封裝好的方法,簡化代碼
package com.hmdp.service.impl;
import ...
/**
* 服務實作類
*
* @author 李
* @version 1.0
*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop>
implements IShopService {
@Resource
StringRedisTemplate stringRedisTemplate;
@Resource
private CacheClient cacheClient;
@Override
public Result queryById(Long id) {
//快取穿透
//Shop shop =
// cacheClient.queryWithPassThrough
// (CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
//快取擊穿方案(邏輯過期)
Shop shop = cacheClient.queryWithLogicalExpire
(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.MINUTES);
if (shop == null) {
return Result.fail("店鋪不存在!");
}
return Result.ok(shop);
}
@Override
@Transactional
public Result update(Shop shop) {
...
}
}
3.7快取總結
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/550828.html
標籤:其他
上一篇:即時通訊系統為什么選擇GaussDB(for Redis)?
下一篇:返回列表
