Commit d37316ac authored by Administrator's avatar Administrator

三级缓存模版方法添加

parent e6400610
package com.dlbridge.jadp.common.cache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* 通用两级缓存模板:L1(Guava本地缓存)→ L2(Redis分布式缓存)→ L3(数据库回调查询)
* <p>
* 使用示例:
* <pre>{@code
* @Resource
* private CacheTemplate cacheTemplate;
*
* public Result getCmpt(@PathVariable Integer cmptId) {
* Cmpt cmpt = cacheTemplate.get(
* "cmpt:info", // 缓存命名空间
* String.valueOf(cmptId), // 缓存键
* Cmpt.class, // 返回值类型
* 2L, TimeUnit.MINUTES, // L1本地TTL
* 5L, TimeUnit.MINUTES, // L2 Redis TTL
* () -> cmptService.queryCmptBycmptId(cmptId) // L3 DB回调
* );
* return Result.success().setContent(cmpt);
* }
* }</pre>
*
* @author generated
*/
@Component
public class CacheTemplate {
@Resource
private ValueOperations<String, Object> valueOperations;
/** 各命名空间对应的本地 Guava 缓存,按需创建 */
private final ConcurrentHashMap<String, Cache<String, Object>> localCaches = new ConcurrentHashMap<>();
/** 默认本地缓存最大容量 */
private static final int DEFAULT_LOCAL_MAX_SIZE = 1000;
/** Redis key 统一前缀 */
private static final String REDIS_KEY_PREFIX = "gk:";
/**
* 两级缓存查询(自动降级:L1 → L2 → L3)
*
* @param namespace 缓存命名空间,如 "cmpt:info",用于区分不同业务
* @param key 缓存键,同一命名空间下唯一
* @param type 返回值类型 Class 对象
* @param localTtl 本地缓存过期时间数值
* @param localTtlUnit 本地缓存过期时间单位
* @param redisTtl Redis 缓存过期时间数值
* @param redisTtlUnit Redis 缓存过期时间单位
* @param dbLoader 数据库加载回调,仅在两级缓存均未命中时调用
* @param <T> 返回值泛型
* @return 缓存命中返回缓存值,否则执行 dbLoader 并回填两级缓存后返回
*/
public <T> T get(String namespace, String key, Class<T> type,
long localTtl, TimeUnit localTtlUnit,
long redisTtl, TimeUnit redisTtlUnit,
Supplier<T> dbLoader) {
// L1: 查本地 Guava 缓存
Cache<String, Object> localCache = getOrCreateLocalCache(namespace, localTtl, localTtlUnit);
Object cached = localCache.getIfPresent(key);
if (cached != null) {
return type.cast(cached);
}
// L2: 查 Redis 分布式缓存
String redisKey = REDIS_KEY_PREFIX + namespace + ":" + key;
Object redisCached = valueOperations.get(redisKey);
if (redisCached != null && type.isInstance(redisCached)) {
T result = type.cast(redisCached);
localCache.put(key, result); // 回填 L1
return result;
}
// L3: 查数据库,同时回填两级缓存
T result = dbLoader.get();
if (result != null) {
localCache.put(key, result);
valueOperations.set(redisKey, result, redisTtl, redisTtlUnit);
}
return result;
}
/**
* 清除指定命名空间下某个 key 的全部缓存(L1 + L2)
*
* @param namespace 缓存命名空间
* @param key 缓存键
*/
public void evict(String namespace, String key) {
Cache<String, Object> localCache = localCaches.get(namespace);
if (localCache != null) {
localCache.invalidate(key);
}
valueOperations.getOperations().delete(REDIS_KEY_PREFIX + namespace + ":" + key);
}
/**
* 清除指定命名空间的全部本地缓存(L1 only)
*/
public void evictLocal(String namespace) {
Cache<String, Object> localCache = localCaches.get(namespace);
if (localCache != null) {
localCache.invalidateAll();
}
}
/**
* 按需创建本地缓存实例
*/
private Cache<String, Object> getOrCreateLocalCache(String namespace, long ttl, TimeUnit unit) {
return localCaches.computeIfAbsent(namespace, name ->
CacheBuilder.newBuilder()
.maximumSize(DEFAULT_LOCAL_MAX_SIZE)
.expireAfterWrite(ttl, unit)
.recordStats()
.build()
);
}
}
...@@ -45,8 +45,7 @@ public class WebConfig implements WebMvcConfigurer { ...@@ -45,8 +45,7 @@ public class WebConfig implements WebMvcConfigurer {
FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter(); FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig(); FastJsonConfig config = new FastJsonConfig();
config.setWriterFeatures( config.setWriterFeatures(
JSONWriter.Feature.WriteMapNullValue, JSONWriter.Feature.WriteMapNullValue
JSONWriter.Feature.WriteDateUseDateFormat
); );
config.setDateFormat("yyyy-MM-dd HH:mm:ss"); config.setDateFormat("yyyy-MM-dd HH:mm:ss");
fastJsonConverter.setFastJsonConfig(config); fastJsonConverter.setFastJsonConfig(config);
......
...@@ -3,6 +3,7 @@ package com.dlbridge.jadp.plugins.cmpt.web; ...@@ -3,6 +3,7 @@ package com.dlbridge.jadp.plugins.cmpt.web;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.dlbridge.feign.NewsService; import com.dlbridge.feign.NewsService;
import com.dlbridge.feign.bean.News; import com.dlbridge.feign.bean.News;
import com.dlbridge.jadp.common.cache.CacheTemplate;
import com.dlbridge.jadp.common.service.JService; import com.dlbridge.jadp.common.service.JService;
import com.dlbridge.jadp.job.CountJob; import com.dlbridge.jadp.job.CountJob;
import com.dlbridge.jadp.plugins.cmpt.bean.Contact; import com.dlbridge.jadp.plugins.cmpt.bean.Contact;
...@@ -48,7 +49,9 @@ import java.util.concurrent.TimeUnit; ...@@ -48,7 +49,9 @@ import java.util.concurrent.TimeUnit;
@ApiSort(1) @ApiSort(1)
public class MicroApiController { public class MicroApiController {
private static final String SIGNING_CODE = "1"; private static final String SIGNING_CODE = "1";
private static final long CMPT_CACHE_EXPIRE_MINUTES = 5L;
@Resource
private CacheTemplate cacheTemplate;
@Resource @Resource
private RestTemplate restTemplate; private RestTemplate restTemplate;
...@@ -414,36 +417,14 @@ public class MicroApiController { ...@@ -414,36 +417,14 @@ public class MicroApiController {
@ApiResponses({@ApiResponse(code = 200, message = "成功"), @ApiResponse(code = -1, message = "失败", response = ObjectUtils.Null.class), @ApiResponse(code = 200, message = "成功", response = Result.class, responseContainer = "List")}) @ApiResponses({@ApiResponse(code = 200, message = "成功"), @ApiResponse(code = -1, message = "失败", response = ObjectUtils.Null.class), @ApiResponse(code = 200, message = "成功", response = Result.class, responseContainer = "List")})
@ApiOperation(value = "赛事", notes = "赛事", tags = {"首页接口"}) @ApiOperation(value = "赛事", notes = "赛事", tags = {"首页接口"})
public Result getCmpt(@PathVariable("cmptId") Integer cmptId) { public Result getCmpt(@PathVariable("cmptId") Integer cmptId) {
long startTime = System.currentTimeMillis(); Cmpt cmpt = cacheTemplate.get(
"cmpt:info", // 缓存命名空间
// 先从Redis缓存中获取 String.valueOf(cmptId), // 缓存键
String cacheKey = "gk:cmpt:info:" + cmptId; Cmpt.class, // 返回值类型
Object cachedObj = valueOperations.get(cacheKey); 2L, TimeUnit.MINUTES, // L1 本地缓存 TTL
if (cachedObj != null && cachedObj instanceof Cmpt) { 5L, TimeUnit.MINUTES, // L2 Redis 缓存 TTL
Cmpt cachedCmpt = (Cmpt) cachedObj; () -> cmptService.queryCmptBycmptId(cmptId) // L3 数据库回调
long costTime = System.currentTimeMillis() - startTime; );
log.info("[缓存命中] 从缓存中获取赛事信息,cmptId: {}, 耗时: {}ms", cmptId, costTime);
return Result.success().setContent(cachedCmpt);
}
log.info("[缓存未命中] 开始查询数据库,cmptId: {}", cmptId);
long dbStartTime = System.currentTimeMillis();
Cmpt cmpt = cmptService.queryCmptBycmptId(cmptId);
long dbCostTime = System.currentTimeMillis() - dbStartTime;
log.info("[数据库查询] 查询完成,cmptId: {}, 耗时: {}ms", cmptId, dbCostTime);
if (cmpt != null) {
long cacheStartTime = System.currentTimeMillis();
valueOperations.set(cacheKey, cmpt, CMPT_CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
long cacheCostTime = System.currentTimeMillis() - cacheStartTime;
log.info("[缓存设置] 赛事信息已缓存,cmptId: {}, 耗时: {}ms", cmptId, cacheCostTime);
}
long totalCostTime = System.currentTimeMillis() - startTime;
log.info("[总耗时] cmptId: {}, 总耗时: {}ms", cmptId, totalCostTime);
return Result.success().setContent(cmpt); return Result.success().setContent(cmpt);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment