|
@@ -0,0 +1,134 @@
|
|
|
+package cn.com.qmth.examcloud.web.cache;
|
|
|
+
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.assertj.core.util.Arrays;
|
|
|
+
|
|
|
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
|
|
|
+import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
|
|
|
+import cn.com.qmth.examcloud.commons.util.Util;
|
|
|
+import cn.com.qmth.examcloud.web.redis.RedisClient;
|
|
|
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
|
|
|
+
|
|
|
+/**
|
|
|
+ * redis Hash缓存处理器
|
|
|
+ *
|
|
|
+ * @date 2019年5月10日
|
|
|
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
|
|
|
+ */
|
|
|
+public class HashRedisCacheProcessor {
|
|
|
+
|
|
|
+ private static RedisClient redisClient;
|
|
|
+
|
|
|
+ private static HashRedisCacheTrigger hashRedisCacheTrigger;
|
|
|
+
|
|
|
+ private static RedisClient getRedisClient() {
|
|
|
+ if (null == redisClient) {
|
|
|
+ redisClient = SpringContextHolder.getBean(RedisClient.class);
|
|
|
+ }
|
|
|
+ return redisClient;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static HashRedisCacheTrigger getHashRedisCacheTrigger() {
|
|
|
+ if (null == hashRedisCacheTrigger) {
|
|
|
+ hashRedisCacheTrigger = SpringContextHolder.getBean(HashRedisCacheTrigger.class);
|
|
|
+ }
|
|
|
+ return hashRedisCacheTrigger;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取缓存对象
|
|
|
+ *
|
|
|
+ * @author
|
|
|
+ * @param keyPrefix
|
|
|
+ * @param propKeys
|
|
|
+ * @param c
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public static <T> T get(String keyPrefix, Object[] propKeys,Object[] subpropKeys, Class<T> c) {
|
|
|
+ String key = keyPrefix + StringUtils.join(Arrays.asList(propKeys), '_');
|
|
|
+ String hashKey=StringUtils.join(Arrays.asList(subpropKeys), '_');
|
|
|
+ T t = getRedisClient().get(key,hashKey, c);
|
|
|
+ return t;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取缓存对象(不存在时远程或本地加载)<br>
|
|
|
+ * 缓存失效时,只允许一个线程加载缓存,防止缓存击穿<br>
|
|
|
+ * 缓存加载时长不得超过10秒,否则所有取缓存线程无等待抛出异常,只到缓存被正确加载<br>
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * @author
|
|
|
+ * @param keyPrefix
|
|
|
+ * @param propKeys
|
|
|
+ * @param c
|
|
|
+ * @param appName
|
|
|
+ * @param className
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public static <T> T get(String keyPrefix, Object[] propKeys,Object[] subpropKeys, Class<? extends RandomCacheBean> c,
|
|
|
+ String appName, String className) {
|
|
|
+
|
|
|
+ String key = keyPrefix + StringUtils.join(Arrays.asList(propKeys), '_');
|
|
|
+ String subKey=StringUtils.join(Arrays.asList(subpropKeys), '_');
|
|
|
+
|
|
|
+ RandomCacheBean t = getRedisClient().get(key,subKey, c);
|
|
|
+
|
|
|
+ if (null == t) {
|
|
|
+
|
|
|
+ int count = 0;
|
|
|
+
|
|
|
+ String cacheLock = "$_CACHE_LOCK:" + key+"_"+subKey;
|
|
|
+ String cacheException = "$_CACHE_EXCEPTION:" + key+"_"+subKey;
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ count++;
|
|
|
+
|
|
|
+ t = getRedisClient().get(key,subKey, c);
|
|
|
+
|
|
|
+ if (null != t) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ Boolean locked = getRedisClient().setIfAbsent(cacheLock,
|
|
|
+ ThreadLocalUtil.getTraceId(), 30);
|
|
|
+
|
|
|
+ if (locked) {
|
|
|
+ try {
|
|
|
+ getHashRedisCacheTrigger().fire(appName, className, propKeys,subpropKeys);
|
|
|
+ getRedisClient().delete(cacheException);
|
|
|
+ t = getRedisClient().get(key,subKey, c);
|
|
|
+ break;
|
|
|
+ } catch (Exception e) {
|
|
|
+ getRedisClient().set(cacheException, true, 60);
|
|
|
+ throw e;
|
|
|
+ } finally {
|
|
|
+ getRedisClient().delete(cacheLock);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+
|
|
|
+ if (null != getRedisClient().get(cacheException, Boolean.class)) {
|
|
|
+ throw new ExamCloudRuntimeException(
|
|
|
+ "exception happened when loading cache. key=" + key+" subKey="+subKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 10秒内未加载完缓存,抛出异常
|
|
|
+ if (200 < count) {
|
|
|
+ throw new ExamCloudRuntimeException(
|
|
|
+ "fail to load cache in 10 seconds. key=" + key+" subKey="+subKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ Util.sleep(TimeUnit.MILLISECONDS, 50);
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ T ret = (T) t;
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|