You've already forked FrameTour-BE
feat(service): 使用带TTL的缓存Map替换静态Map
- 新增TtlCacheMap类,用于实现带生存时间的缓存 - 在ScenicServiceImpl中使用TtlCacheMap替换原有的ConcurrentHashMap - 为不同类型的适配器创建了对应的缓存Map - 优化了缓存获取逻辑,增加了TTL支持 - 添加了缓存清理和统计功能
This commit is contained in:
@@ -13,6 +13,7 @@ import com.ycwl.basic.storage.StorageFactory;
|
|||||||
import com.ycwl.basic.storage.adapters.IStorageAdapter;
|
import com.ycwl.basic.storage.adapters.IStorageAdapter;
|
||||||
import com.ycwl.basic.storage.exceptions.StorageUnsupportedException;
|
import com.ycwl.basic.storage.exceptions.StorageUnsupportedException;
|
||||||
import com.ycwl.basic.util.ScenicConfigManager;
|
import com.ycwl.basic.util.ScenicConfigManager;
|
||||||
|
import com.ycwl.basic.util.TtlCacheMap;
|
||||||
import com.ycwl.basic.utils.ApiResponse;
|
import com.ycwl.basic.utils.ApiResponse;
|
||||||
import com.ycwl.basic.utils.JacksonUtil;
|
import com.ycwl.basic.utils.JacksonUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -21,7 +22,7 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author:longbinbin
|
* @Author:longbinbin
|
||||||
@@ -33,16 +34,30 @@ public class ScenicServiceImpl implements ScenicService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ScenicRepository scenicRepository;
|
private ScenicRepository scenicRepository;
|
||||||
|
|
||||||
|
// TTL缓存配置,默认10分钟过期
|
||||||
|
private static final long DEFAULT_CACHE_TTL_MINUTES = 10;
|
||||||
|
|
||||||
|
// 使用TTL缓存替代静态Map
|
||||||
|
private static final TtlCacheMap<Long, IStorageAdapter> scenicStorageAdapterCache =
|
||||||
|
new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES));
|
||||||
|
private static final TtlCacheMap<Long, IStorageAdapter> scenicTmpStorageAdapterCache =
|
||||||
|
new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES));
|
||||||
|
private static final TtlCacheMap<Long, IStorageAdapter> scenicLocalStorageAdapterCache =
|
||||||
|
new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES));
|
||||||
|
private static final TtlCacheMap<Long, IFaceBodyAdapter> scenicFaceBodyAdapterCache =
|
||||||
|
new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES));
|
||||||
|
private static final TtlCacheMap<Long, IPayAdapter> scenicPayAdapterCache =
|
||||||
|
new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES));
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public ApiResponse<List<ScenicV2DTO>> list(ScenicReqQuery scenicReqQuery) {
|
public ApiResponse<List<ScenicV2DTO>> list(ScenicReqQuery scenicReqQuery) {
|
||||||
return ApiResponse.success(scenicRepository.list(scenicReqQuery));
|
return ApiResponse.success(scenicRepository.list(scenicReqQuery));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<Long, IStorageAdapter> scenicStorageAdapterMap = new ConcurrentHashMap<>();
|
|
||||||
@Override
|
@Override
|
||||||
public IStorageAdapter getScenicStorageAdapter(Long scenicId) {
|
public IStorageAdapter getScenicStorageAdapter(Long scenicId) {
|
||||||
return scenicStorageAdapterMap.computeIfAbsent(scenicId, (key) -> {
|
return scenicStorageAdapterCache.computeIfAbsent(scenicId, (key) -> {
|
||||||
IStorageAdapter adapter;
|
IStorageAdapter adapter;
|
||||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
||||||
if (scenicConfig.getString("store_type") != null) {
|
if (scenicConfig.getString("store_type") != null) {
|
||||||
@@ -58,10 +73,9 @@ public class ScenicServiceImpl implements ScenicService {
|
|||||||
return adapter;
|
return adapter;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private static final Map<Long, IStorageAdapter> scenicTmpStorageAdapterMap = new ConcurrentHashMap<>();
|
|
||||||
@Override
|
@Override
|
||||||
public IStorageAdapter getScenicTmpStorageAdapter(Long scenicId) {
|
public IStorageAdapter getScenicTmpStorageAdapter(Long scenicId) {
|
||||||
return scenicTmpStorageAdapterMap.computeIfAbsent(scenicId, (key) -> {
|
return scenicTmpStorageAdapterCache.computeIfAbsent(scenicId, (key) -> {
|
||||||
IStorageAdapter adapter;
|
IStorageAdapter adapter;
|
||||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
||||||
if (scenicConfig.getString("tmp_store_type") != null) {
|
if (scenicConfig.getString("tmp_store_type") != null) {
|
||||||
@@ -77,10 +91,9 @@ public class ScenicServiceImpl implements ScenicService {
|
|||||||
return adapter;
|
return adapter;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private static final Map<Long, IStorageAdapter> scenicLocalStorageAdapterMap = new ConcurrentHashMap<>();
|
|
||||||
@Override
|
@Override
|
||||||
public IStorageAdapter getScenicLocalStorageAdapter(Long scenicId) {
|
public IStorageAdapter getScenicLocalStorageAdapter(Long scenicId) {
|
||||||
return scenicLocalStorageAdapterMap.computeIfAbsent(scenicId, (key) -> {
|
return scenicLocalStorageAdapterCache.computeIfAbsent(scenicId, (key) -> {
|
||||||
IStorageAdapter adapter;
|
IStorageAdapter adapter;
|
||||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
||||||
if (scenicConfig.getString("local_store_type") != null) {
|
if (scenicConfig.getString("local_store_type") != null) {
|
||||||
@@ -97,10 +110,9 @@ public class ScenicServiceImpl implements ScenicService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<Long, IFaceBodyAdapter> scenicFaceBodyAdapterMap = new ConcurrentHashMap<>();
|
|
||||||
@Override
|
@Override
|
||||||
public IFaceBodyAdapter getScenicFaceBodyAdapter(Long scenicId) {
|
public IFaceBodyAdapter getScenicFaceBodyAdapter(Long scenicId) {
|
||||||
return scenicFaceBodyAdapterMap.computeIfAbsent(scenicId, (key) -> {
|
return scenicFaceBodyAdapterCache.computeIfAbsent(scenicId, (key) -> {
|
||||||
IFaceBodyAdapter adapter;
|
IFaceBodyAdapter adapter;
|
||||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
||||||
if (scenicConfig.getString("face_type") != null) {
|
if (scenicConfig.getString("face_type") != null) {
|
||||||
@@ -113,10 +125,9 @@ public class ScenicServiceImpl implements ScenicService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<Long, IPayAdapter> scenicPayAdapterMap = new ConcurrentHashMap<>();
|
|
||||||
@Override
|
@Override
|
||||||
public IPayAdapter getScenicPayAdapter(Long scenicId) {
|
public IPayAdapter getScenicPayAdapter(Long scenicId) {
|
||||||
return scenicPayAdapterMap.computeIfAbsent(scenicId, (key) -> {
|
return scenicPayAdapterCache.computeIfAbsent(scenicId, (key) -> {
|
||||||
IPayAdapter adapter;
|
IPayAdapter adapter;
|
||||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
||||||
if (scenicConfig.getString("pay_type") != null) {
|
if (scenicConfig.getString("pay_type") != null) {
|
||||||
@@ -128,4 +139,94 @@ public class ScenicServiceImpl implements ScenicService {
|
|||||||
return adapter;
|
return adapter;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 缓存管理方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除指定景区的所有适配器缓存
|
||||||
|
*
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
*/
|
||||||
|
public void clearScenicAdapterCache(Long scenicId) {
|
||||||
|
log.info("清除景区 {} 的所有适配器缓存", scenicId);
|
||||||
|
scenicStorageAdapterCache.remove(scenicId);
|
||||||
|
scenicTmpStorageAdapterCache.remove(scenicId);
|
||||||
|
scenicLocalStorageAdapterCache.remove(scenicId);
|
||||||
|
scenicFaceBodyAdapterCache.remove(scenicId);
|
||||||
|
scenicPayAdapterCache.remove(scenicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有适配器缓存
|
||||||
|
*/
|
||||||
|
public void clearAllAdapterCache() {
|
||||||
|
log.info("清除所有适配器缓存");
|
||||||
|
scenicStorageAdapterCache.clear();
|
||||||
|
scenicTmpStorageAdapterCache.clear();
|
||||||
|
scenicLocalStorageAdapterCache.clear();
|
||||||
|
scenicFaceBodyAdapterCache.clear();
|
||||||
|
scenicPayAdapterCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动触发过期缓存清理
|
||||||
|
*
|
||||||
|
* @return 清理的过期缓存项总数
|
||||||
|
*/
|
||||||
|
public int cleanupExpiredCache() {
|
||||||
|
log.info("手动触发过期缓存清理");
|
||||||
|
int totalCleaned = 0;
|
||||||
|
totalCleaned += scenicStorageAdapterCache.cleanupExpired();
|
||||||
|
totalCleaned += scenicTmpStorageAdapterCache.cleanupExpired();
|
||||||
|
totalCleaned += scenicLocalStorageAdapterCache.cleanupExpired();
|
||||||
|
totalCleaned += scenicFaceBodyAdapterCache.cleanupExpired();
|
||||||
|
totalCleaned += scenicPayAdapterCache.cleanupExpired();
|
||||||
|
log.info("清理了 {} 个过期缓存项", totalCleaned);
|
||||||
|
return totalCleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存统计信息
|
||||||
|
*
|
||||||
|
* @return 缓存统计信息
|
||||||
|
*/
|
||||||
|
public String getCacheStats() {
|
||||||
|
StringBuilder stats = new StringBuilder();
|
||||||
|
stats.append("=== ScenicServiceImpl 缓存统计信息 ===\n");
|
||||||
|
stats.append("Storage Adapter Cache: ").append(scenicStorageAdapterCache.getStats()).append("\n");
|
||||||
|
stats.append("Tmp Storage Adapter Cache: ").append(scenicTmpStorageAdapterCache.getStats()).append("\n");
|
||||||
|
stats.append("Local Storage Adapter Cache: ").append(scenicLocalStorageAdapterCache.getStats()).append("\n");
|
||||||
|
stats.append("FaceBody Adapter Cache: ").append(scenicFaceBodyAdapterCache.getStats()).append("\n");
|
||||||
|
stats.append("Pay Adapter Cache: ").append(scenicPayAdapterCache.getStats()).append("\n");
|
||||||
|
return stats.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置所有缓存统计信息
|
||||||
|
*/
|
||||||
|
public void resetCacheStats() {
|
||||||
|
log.info("重置所有缓存统计信息");
|
||||||
|
scenicStorageAdapterCache.resetStats();
|
||||||
|
scenicTmpStorageAdapterCache.resetStats();
|
||||||
|
scenicLocalStorageAdapterCache.resetStats();
|
||||||
|
scenicFaceBodyAdapterCache.resetStats();
|
||||||
|
scenicPayAdapterCache.resetStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定景区缓存的剩余TTL时间
|
||||||
|
*
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 各类型适配器缓存的剩余TTL时间(毫秒)
|
||||||
|
*/
|
||||||
|
public String getScenicCacheTtl(Long scenicId) {
|
||||||
|
StringBuilder ttlInfo = new StringBuilder();
|
||||||
|
ttlInfo.append("景区 ").append(scenicId).append(" 缓存TTL信息:\n");
|
||||||
|
ttlInfo.append("Storage: ").append(scenicStorageAdapterCache.getRemainTtl(scenicId)).append("ms\n");
|
||||||
|
ttlInfo.append("TmpStorage: ").append(scenicTmpStorageAdapterCache.getRemainTtl(scenicId)).append("ms\n");
|
||||||
|
ttlInfo.append("LocalStorage: ").append(scenicLocalStorageAdapterCache.getRemainTtl(scenicId)).append("ms\n");
|
||||||
|
ttlInfo.append("FaceBody: ").append(scenicFaceBodyAdapterCache.getRemainTtl(scenicId)).append("ms\n");
|
||||||
|
ttlInfo.append("Pay: ").append(scenicPayAdapterCache.getRemainTtl(scenicId)).append("ms\n");
|
||||||
|
return ttlInfo.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
340
src/main/java/com/ycwl/basic/util/TtlCacheMap.java
Normal file
340
src/main/java/com/ycwl/basic/util/TtlCacheMap.java
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
package com.ycwl.basic.util;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带TTL(生存时间)的缓存Map工具类
|
||||||
|
*
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TtlCacheMap<K, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存项包装类
|
||||||
|
*/
|
||||||
|
private static class CacheItem<V> {
|
||||||
|
private final V value;
|
||||||
|
private final long expireTime;
|
||||||
|
|
||||||
|
public CacheItem(V value, long ttlMillis) {
|
||||||
|
this.value = value;
|
||||||
|
this.expireTime = System.currentTimeMillis() + ttlMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
return System.currentTimeMillis() > expireTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRemainTtl() {
|
||||||
|
return Math.max(0, expireTime - System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<K, CacheItem<V>> cache;
|
||||||
|
private final long defaultTtlMillis;
|
||||||
|
private final ReentrantReadWriteLock lock;
|
||||||
|
private final ScheduledExecutorService cleanupExecutor;
|
||||||
|
|
||||||
|
// 统计信息
|
||||||
|
private volatile long hitCount = 0;
|
||||||
|
private volatile long missCount = 0;
|
||||||
|
private volatile long expiredCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*
|
||||||
|
* @param defaultTtlMillis 默认TTL时间(毫秒)
|
||||||
|
*/
|
||||||
|
public TtlCacheMap(long defaultTtlMillis) {
|
||||||
|
this.cache = new ConcurrentHashMap<>();
|
||||||
|
this.defaultTtlMillis = defaultTtlMillis;
|
||||||
|
this.lock = new ReentrantReadWriteLock();
|
||||||
|
this.cleanupExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "TtlCacheMap-Cleanup");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动定期清理任务,每分钟清理一次过期条目
|
||||||
|
this.cleanupExecutor.scheduleWithFixedDelay(this::cleanupExpired, 60, 60, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数,使用默认TTL为10分钟
|
||||||
|
*/
|
||||||
|
public TtlCacheMap() {
|
||||||
|
this(TimeUnit.MINUTES.toMillis(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存值,如果不存在或过期则通过supplier创建
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @param valueSupplier 值提供器
|
||||||
|
* @return 缓存值
|
||||||
|
*/
|
||||||
|
public V computeIfAbsent(K key, Function<K, V> valueSupplier) {
|
||||||
|
return computeIfAbsent(key, valueSupplier, defaultTtlMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存值,如果不存在或过期则通过supplier创建
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @param valueSupplier 值提供器
|
||||||
|
* @param ttlMillis TTL时间(毫秒)
|
||||||
|
* @return 缓存值
|
||||||
|
*/
|
||||||
|
public V computeIfAbsent(K key, Function<K, V> valueSupplier, long ttlMillis) {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
CacheItem<V> item = cache.get(key);
|
||||||
|
if (item != null && !item.isExpired()) {
|
||||||
|
hitCount++;
|
||||||
|
return item.getValue();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存不存在或已过期,需要重新创建
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
// 双重检查,防止重复创建
|
||||||
|
CacheItem<V> item = cache.get(key);
|
||||||
|
if (item != null && !item.isExpired()) {
|
||||||
|
hitCount++;
|
||||||
|
return item.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item != null && item.isExpired()) {
|
||||||
|
expiredCount++;
|
||||||
|
cache.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新值
|
||||||
|
missCount++;
|
||||||
|
V value = valueSupplier.apply(key);
|
||||||
|
if (value != null) {
|
||||||
|
cache.put(key, new CacheItem<>(value, ttlMillis));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接放入缓存
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @param value 缓存值
|
||||||
|
*/
|
||||||
|
public void put(K key, V value) {
|
||||||
|
put(key, value, defaultTtlMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接放入缓存
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @param value 缓存值
|
||||||
|
* @param ttlMillis TTL时间(毫秒)
|
||||||
|
*/
|
||||||
|
public void put(K key, V value, long ttlMillis) {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
cache.put(key, new CacheItem<>(value, ttlMillis));
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存值
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @return 缓存值,如果不存在或过期返回null
|
||||||
|
*/
|
||||||
|
public V get(K key) {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
CacheItem<V> item = cache.get(key);
|
||||||
|
if (item != null) {
|
||||||
|
if (!item.isExpired()) {
|
||||||
|
hitCount++;
|
||||||
|
return item.getValue();
|
||||||
|
} else {
|
||||||
|
// 异步清理过期项
|
||||||
|
cleanupExecutor.execute(() -> {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
CacheItem<V> expiredItem = cache.get(key);
|
||||||
|
if (expiredItem != null && expiredItem.isExpired()) {
|
||||||
|
cache.remove(key);
|
||||||
|
expiredCount++;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
missCount++;
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除缓存项
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @return 被移除的值,如果不存在返回null
|
||||||
|
*/
|
||||||
|
public V remove(K key) {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
CacheItem<V> item = cache.remove(key);
|
||||||
|
return item != null ? item.getValue() : null;
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有缓存
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
cache.clear();
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查缓存键是否存在且未过期
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @return true如果存在且未过期
|
||||||
|
*/
|
||||||
|
public boolean containsKey(K key) {
|
||||||
|
return get(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存大小(包含过期项)
|
||||||
|
*
|
||||||
|
* @return 缓存大小
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return cache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查缓存是否为空
|
||||||
|
*
|
||||||
|
* @return true如果为空
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return cache.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动触发过期清理
|
||||||
|
*
|
||||||
|
* @return 清理的过期项数量
|
||||||
|
*/
|
||||||
|
public int cleanupExpired() {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
int cleanedCount = 0;
|
||||||
|
var iterator = cache.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
var entry = iterator.next();
|
||||||
|
if (entry.getValue().isExpired()) {
|
||||||
|
iterator.remove();
|
||||||
|
cleanedCount++;
|
||||||
|
expiredCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cleanedCount > 0) {
|
||||||
|
log.debug("清理了 {} 个过期缓存项", cleanedCount);
|
||||||
|
}
|
||||||
|
return cleanedCount;
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存统计信息
|
||||||
|
*
|
||||||
|
* @return 统计信息字符串
|
||||||
|
*/
|
||||||
|
public String getStats() {
|
||||||
|
long total = hitCount + missCount;
|
||||||
|
double hitRate = total > 0 ? (double) hitCount / total * 100 : 0;
|
||||||
|
|
||||||
|
return String.format(
|
||||||
|
"TtlCacheMap Stats: size=%d, hits=%d, misses=%d, expired=%d, hitRate=%.2f%%",
|
||||||
|
cache.size(), hitCount, missCount, expiredCount, hitRate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置统计信息
|
||||||
|
*/
|
||||||
|
public void resetStats() {
|
||||||
|
hitCount = 0;
|
||||||
|
missCount = 0;
|
||||||
|
expiredCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取剩余TTL时间
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @return 剩余TTL毫秒数,如果不存在或已过期返回0
|
||||||
|
*/
|
||||||
|
public long getRemainTtl(K key) {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
CacheItem<V> item = cache.get(key);
|
||||||
|
return item != null ? item.getRemainTtl() : 0;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭清理线程池,释放资源
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
cleanupExecutor.shutdown();
|
||||||
|
try {
|
||||||
|
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
cleanupExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
cleanupExecutor.shutdownNow();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user