@Slf4j
@Service
@RequiredArgsConstructor
public class DefaultRefreshCacheProcessImpl implements RefreshCacheProcess<RefreshCacheInfo<Object, Object>> {
private final RedisTemplate<String, Object> redisTemplate; // Spring Data Redis
private final ExecutorService executorService = Executors.newFixedThreadPool(10); // 使用執行緒池
private static final Integer GAT_TIME = 10;
private Map<String, RefreshCacheInfo> refreshCacheInfoMap = new HashMap<>();
private String CACHE_LOCK_PREFIX = "cache:refresh:lock:";
private static final String CACHE_KEY_PREFIX = "cache:data:";
private static final String LAST_UPDATE_TIME_KEY_PREFIX = "cache:last_update_time:";
@PostConstruct
public void init() {
// 初始化已完成的 cache 結構
log.info("Initializing cache structure...");
// 緩存預熱
for (RefreshCacheInfo info : refreshCacheInfoMap.values()) {
refresh(info);
}
}
public void addRefreshCache(RefreshCacheInfo info) {
refreshCacheInfoMap.put(info.getCacheName(), info);
}
@Scheduled(fixedRate = 3000) // 每分鐘執行
public void refreshCacheMain() {
executorService.submit(() -> {
try {
for (RefreshCacheInfo info : refreshCacheInfoMap.values()) {
if (!existed(info) || isCacheOutdated(info)) {
if (acquireLock(info.getCacheName())) { // 取得分布式鎖
if (!existed(info) || isCacheOutdated(info)) {
refresh(info);
releaseLock(info.getCacheName()); // 釋放分布式鎖
log.info("Cache {} refreshed", info.getCacheName());
} else {
log.info("Cache {} Not close to expiration.", info.getCacheName());
}
} else {
log.info("Cache {} no longer needs refresh (likely refreshed by another instance).", info.getCacheName());
}
} else {
log.info("Cache {} Not close to expiration.", info.getCacheName());
}
}
} catch (Exception e) {
log.error("Error refreshing cache: {}", e.getMessage());
e.printStackTrace();
}
});
}
public <T> T getCacheData(String cacheName) {
return (T) redisTemplate.opsForValue().get(CACHE_KEY_PREFIX + cacheName);
}
public <Q, T> Boolean existed(RefreshCacheInfo<Q, T> info) {
return Optional.ofNullable(getCacheData(info.getCacheName())).isPresent();
}
private <Q, T> void refresh(RefreshCacheInfo<Q, T> info) {
Object data = info.getGetDataFunction().apply(info.getQueryData());
redisTemplate.opsForValue().set(CACHE_KEY_PREFIX + info.getCacheName(), data, info.getCacheExpireTime() + GAT_TIME, TimeUnit.SECONDS);
redisTemplate.opsForValue().set(LAST_UPDATE_TIME_KEY_PREFIX + info.getCacheName(), System.currentTimeMillis(), info.getCacheExpireTime() + GAT_TIME, TimeUnit.SECONDS);
}
private long getLastUpdateTime(String cacheName) {
Long time = (Long) redisTemplate.opsForValue().get(LAST_UPDATE_TIME_KEY_PREFIX + cacheName);
log.info("Fetching last update time for cache: {}, time:{}", cacheName, time);
if (time == null) {
return 0; // 預設值
}
return time;
}
private <Q, T> boolean isCacheOutdated(RefreshCacheInfo<Q, T> info) {
// 判斷緩存是否過期,這裡可以加入一些容錯機制
// 例如,如果 lastUpdateTime 過於久遠,也強制刷新
long cacheLastUpdateTime = getLastUpdateTime(info.getCacheName());
long currentTime = System.currentTimeMillis();
long cacheAgeThreshold = info.getCacheExpireTime() * 1000; // 1 分鐘 (毫秒)
if (currentTime - cacheLastUpdateTime > cacheAgeThreshold)
return true;
if (Objects.nonNull(info.getIsCacheOutdatedFunction()))
return info.getIsCacheOutdatedFunction().apply(getCacheData(info.getCacheName()));
else
return false;
}
private boolean acquireLock(String cacheName) {
// 取得分布式鎖 (簡化)
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CACHE_LOCK_PREFIX + cacheName, "1", 5, TimeUnit.SECONDS));
}
private void releaseLock(String cacheName) {
// 釋放分布式鎖 (簡化)
redisTemplate.delete(CACHE_LOCK_PREFIX + cacheName);
}
public Boolean isCacheRangeHit(String cacheName, Long primaryKey) {
RefreshCacheInfo info = refreshCacheInfoMap.get(cacheName);
if (Objects.nonNull(info)) {
Optional<Object> limitListOpt = Optional.ofNullable(redisTemplate.opsForValue().get(info.getCacheName()));
if (limitListOpt.isPresent()) {
return limitListOpt.map(o -> {
Set<Long> ids = (Set<Long>) o;
return ids.contains(primaryKey);
}).orElse(false);
} else {
executorService.submit(() -> {
// 如果沒有緩存限制數據,則重新生成
redisTemplate.opsForValue().set(info.getCacheName(), info.getGetDataFunction().apply(null), info.getCacheExpireTime(), TimeUnit.MINUTES);
});
return false;
}
} else {
executorService.submit(() -> {
// 如果沒有緩存限制數據,則重新生成
redisTemplate.opsForValue().set(info.getCacheName(), info.getGetDataFunction().apply(null), info.getCacheExpireTime(), TimeUnit.MINUTES);
});
return false;
}
}
}