基于StringRedisTemplate封裝一個緩存工具類,滿足下列需求:
-
方法1:將任意Java對象序列化為json并存儲在string類型的key中,并且可以設置TTL過期時間
-
方法2:將任意Java對象序列化為json并存儲在string類型的key中,并且可以設置邏輯過期時間,用于處理緩
存擊穿問題
-
方法3:根據指定的key查詢緩存,并反序列化為指定類型,利用緩存空值的方式解決緩存穿透問題
-
方法4:根據指定的key查詢緩存,并反序列化為指定類型,需要利用邏輯過期解決緩存擊穿問題
將邏輯進行封裝
CacheClient.java
@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 設置指定key對應的value值,使用JSON格式化字符串轉換value對象為字符串進行存儲。* @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);}/*** 設置具有邏輯過期時間的值到指定的 key 中。** @param key 要設置的 key* @param value 要設置的值* @param time 過期時間(單位:秒)* @param unit 過期時間的單位*/public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 創建 RedisData 對象,用于存儲數據和過期時間RedisData redisData = new RedisData();redisData.setData(value);// 設置過期時間為當前時間加上傳入的秒數redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 將 RedisData 對象轉換成 JSON 字符串,并設置到指定的 key 中stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}/*** 通過傳遞的參數進行查詢操作,并使用PassThroughExecutor進行緩存查詢和數據庫查詢的 fallback 處理。** @param keyPrefix 鍵的前綴* @param id 對應的ID值* @param type 查詢結果的目標類型* @param dbFallback 數據庫查詢的 fallback 函數* @param time 緩存有效時間* @param unit 時間單位* @return 查詢結果*/public <R, T> R queryWithPassThrough(String keyPrefix, T id, Class<R> type, Function<T, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 從Redis中查詢緩存String json = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(json)) {return JSONUtil.toBean(json, type);}if (json != null) {return null;}R r = dbFallback.apply(id);if (r == null) {stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}this.set(key, r, time, unit);return r;}private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*** 使用邏輯過期時間查詢緩存* @param keyPrefix 鍵的前綴* @param id 對應的id* @param type 緩存對象的類型* @param dbFallback 數據庫回退函數* @param time 過期時間* @param unit 時間單位* @return 查詢結果*/public <R, T> R queryWithLogicalExpire(String keyPrefix, T id, Class<R> type, Function<T, R> dbFallback, Long time, TimeUnit unit) {// 從Redis查詢緩存String key = keyPrefix + id;String json = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isBlank(json)) {return null;}// 判斷過期時間RedisData redisData = 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 locKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(locKey);if (isLock) {// 開啟線程重建緩存CACHE_REBUILD_EXECUTOR.submit(() -> {try {R r1 = dbFallback.apply(id);this.setWithLogicalExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {unLock(locKey);}});}return r;}/*** 嘗試獲取鎖* @param key 鎖的鍵值* @return 如果成功獲取鎖返回true,否則返回false*/private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}/*** 刪除指定key對應的value值** @param key 要刪除的key值*/private void unLock(String key) {Boolean flag = stringRedisTemplate.delete(key);}}