xiatian 5 سال پیش
والد
کامیت
fc14500de6

+ 58 - 0
src/main/java/cn/com/qmth/examcloud/web/cache/CacheCloudServiceProvider.java

@@ -35,6 +35,8 @@ public class CacheCloudServiceProvider implements CloudService {
 			.getLog(CacheCloudServiceProvider.class);
 
 	private static Map<String, ObjectCache<?>> map = Maps.newConcurrentMap();
+	
+	private static Map<String, HashCache<?>> hashCacheMap = Maps.newConcurrentMap();
 
 	@PostMapping("refresh")
 	public String refresh(@RequestBody RefreshCacheReq req) {
@@ -74,5 +76,61 @@ public class CacheCloudServiceProvider implements CloudService {
 		Object object = objectCache.get(expectedKeys);
 		return JsonUtil.toJson(object);
 	}
+	
+	@PostMapping("refreshHash")
+    public String refreshHash(@RequestBody RefreshHashCacheReq req) {
+
+        String className = req.getClassName();
+        String[] keys = req.getKeys();
+        String[] subkeys = req.getSubKeys();
+        BasicDataType[] typeArray = req.getTypeArray();
+        BasicDataType[] subTypeArray = req.getSubTypeArray();
+
+        Object[] expectedKeys = new Object[keys.length];
+
+        for (int i = 0; i < keys.length; i++) {
+            String key = keys[i];
+            BasicDataType type = typeArray[i];
+            if (type.equals(BasicDataType.LONG)) {
+                expectedKeys[i] = Long.parseLong(key);
+            } else if (type.equals(BasicDataType.STRING)) {
+                expectedKeys[i] = key;
+            } else if (type.equals(BasicDataType.INTEGER)) {
+                expectedKeys[i] = Integer.parseInt(key);
+            } else {
+                throw new ExamCloudRuntimeException("key type is not supported");
+            }
+        }
+        
+        Object[] expectedSubKeys = new Object[subkeys.length];
+        for (int i = 0; i < subkeys.length; i++) {
+            String key = subkeys[i];
+            BasicDataType type = subTypeArray[i];
+            if (type.equals(BasicDataType.LONG)) {
+                expectedSubKeys[i] = Long.parseLong(key);
+            } else if (type.equals(BasicDataType.STRING)) {
+                expectedSubKeys[i] = key;
+            } else if (type.equals(BasicDataType.INTEGER)) {
+                expectedSubKeys[i] = Integer.parseInt(key);
+            } else {
+                throw new ExamCloudRuntimeException("subkey type is not supported");
+            }
+        }
+
+        HashCache<?> hashCache = hashCacheMap.get(className);
+        if (null == hashCache) {
+
+            try {
+                Class<?> c = Class.forName(className);
+                hashCache = (HashCache<?>) SpringContextHolder.getBean(c);
+                hashCacheMap.put(className, hashCache);
+            } catch (ClassNotFoundException e) {
+                throw new StatusException("008002", "class not found", e);
+            }
+        }
+        hashCache.refresh(expectedKeys,expectedSubKeys);
+        Object object = hashCache.get(expectedKeys,expectedSubKeys);
+        return JsonUtil.toJson(object);
+    }
 
 }

+ 41 - 0
src/main/java/cn/com/qmth/examcloud/web/cache/HashCache.java

@@ -0,0 +1,41 @@
+package cn.com.qmth.examcloud.web.cache;
+
+/**
+ * Hash缓存
+ *
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ * @param <T>
+ */
+public interface HashCache<T> {
+
+	/**
+	 * 从缓存中获取
+	 *
+	 * @param keys
+	 * @return
+	 */
+	T get(Object[] keys,Object[] subkeys);
+
+	/**
+	 * 删除缓存
+	 *
+	 * @param keys 大key
+	 */
+	void remove(Object... keys);
+
+	/**
+	 * 刷新缓存
+	 *
+	 * @param keys
+	 */
+	void refresh(Object[] keys,Object[] subkeys);
+
+	/**
+	 * 从数据源(数据库,配置文件等)加载单个缓存项
+	 *
+	 * @param keys
+	 * @return
+	 */
+	T loadFromResource(Object[] keys,Object[] subkeys);
+
+}

+ 89 - 0
src/main/java/cn/com/qmth/examcloud/web/cache/HashRedisCache.java

@@ -0,0 +1,89 @@
+package cn.com.qmth.examcloud.web.cache;
+
+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.exception.StatusException;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+/**
+ * Hash redis缓存
+ *
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ * @param <T>
+ */
+public abstract class HashRedisCache<T extends RandomCacheBean> implements HashCache<T> {
+
+    private RedisClient redisClient;
+
+    private RedisClient getRedisClient() {
+        if (null == redisClient) {
+            redisClient = SpringContextHolder.getBean(RedisClient.class);
+        }
+        return redisClient;
+    }
+
+    protected abstract String getKeyPrefix();
+
+    protected abstract int getTimeout();
+
+    protected String buildKey(Object... keys) {
+        String key = getKeyPrefix() + StringUtils.join(Arrays.asList(keys), '_');
+        return key;
+    }
+    
+    protected String buildSubKey(Object... keys) {
+        String key = StringUtils.join(Arrays.asList(keys), '_');
+        return key;
+    }
+
+    private T getFromCache(String key, String subkey) {
+        Object object = getRedisClient().get(key,subkey, Object.class);
+        @SuppressWarnings("unchecked")
+        T t = (T) object;
+        return t;
+    }
+    @Override
+    public T get(Object[] keys,Object[] subkeys) {
+        String key = buildKey(keys);
+        String subkey = buildSubKey(subkeys);
+        T t = getFromCache(key,subkey);
+
+        if (null == t) {
+            refresh(keys,subkeys);
+        }
+        t = getFromCache(key,subkey);
+        return t;
+    }
+
+    @Override
+    public void remove(Object... keys) {
+        String key = buildKey(keys);
+        getRedisClient().delete(key);
+    }
+
+    @Override
+    public void refresh(Object[] keys,Object[] subkeys) {
+        String key = buildKey(keys);
+        String subkey = buildSubKey(subkeys);
+        T t = null;
+        try {
+            t = loadFromResource(keys,subkeys);
+        } catch (StatusException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ExamCloudRuntimeException("fail to load data. key=" + key+" subkey="+subkey, e);
+        }
+        if (null != t) {
+            int timeout = getTimeout();
+            if (timeout < 60) {
+                timeout = 60;
+            }
+            getRedisClient().set(key,subkey, t, timeout);
+        } else {
+            getRedisClient().delete(key,subkey);
+        }
+    }
+}

+ 134 - 0
src/main/java/cn/com/qmth/examcloud/web/cache/HashRedisCacheProcessor.java

@@ -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;
+	}
+
+}

+ 109 - 0
src/main/java/cn/com/qmth/examcloud/web/cache/HashRedisCacheTrigger.java

@@ -0,0 +1,109 @@
+package cn.com.qmth.examcloud.web.cache;
+
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Maps;
+
+import cn.com.qmth.examcloud.api.commons.enums.BasicDataType;
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.web.cloud.CloudClientSupport;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+/**
+ * redis缓存触发器
+ *
+ * @author 
+ * @date 2019年5月9日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component
+public class HashRedisCacheTrigger extends CloudClientSupport {
+
+	private static Map<String, Boolean> hasClassMap = Maps.newConcurrentMap();
+
+	private static Map<String, HashCache<?>> hashCacheMap = Maps.newConcurrentMap();
+
+	/**
+	 * 开火
+	 *
+	 * @author 
+	 * @param appName
+	 * @param keys
+	 * @return
+	 */
+	public void fire(String appName, String className, Object[] keys,Object[] subkeys) {
+
+		Boolean has = hasClassMap.get(className);
+		HashCache<?> hashCache = null;
+
+		if (null != has) {
+			if (has) {
+				hashCache = hashCacheMap.get(className);
+				hashCache.refresh(keys,subkeys);
+				return;
+			}
+		} else {
+			try {
+				Class<?> c = Class.forName(className);
+				hashCache = (HashCache<?>) SpringContextHolder.getBean(c);
+				hashCache.refresh(keys,subkeys);
+				hashCacheMap.put(className, hashCache);
+				hasClassMap.put(className, true);
+				return;
+			} catch (ClassNotFoundException e) {
+				hasClassMap.put(className, false);
+			}
+		}
+
+		String[] keyArray = new String[keys.length];
+		BasicDataType[] typeArray = new BasicDataType[keys.length];
+
+		for (int i = 0; i < keys.length; i++) {
+			keyArray[i] = String.valueOf(keys[i]);
+			Class<? extends Object> c = keys[i].getClass();
+			if (c.equals(Long.class)) {
+				typeArray[i] = BasicDataType.LONG;
+			} else if (c.equals(String.class)) {
+				typeArray[i] = BasicDataType.STRING;
+			} else if (c.equals(Integer.class)) {
+				typeArray[i] = BasicDataType.INTEGER;
+			} else {
+				throw new ExamCloudRuntimeException("key type is not supported");
+			}
+		}
+		
+		String[] subKeyArray = new String[subkeys.length];
+        BasicDataType[] subTypeArray = new BasicDataType[subkeys.length];
+
+        for (int i = 0; i < subkeys.length; i++) {
+            subKeyArray[i] = String.valueOf(subkeys[i]);
+            Class<? extends Object> c = subkeys[i].getClass();
+            if (c.equals(Long.class)) {
+                subTypeArray[i] = BasicDataType.LONG;
+            } else if (c.equals(String.class)) {
+                subTypeArray[i] = BasicDataType.STRING;
+            } else if (c.equals(Integer.class)) {
+                subTypeArray[i] = BasicDataType.INTEGER;
+            } else {
+                throw new ExamCloudRuntimeException("subkey type is not supported");
+            }
+        }
+
+		RefreshHashCacheReq req = new RefreshHashCacheReq();
+		req.setClassName(className);
+		req.setKeys(keyArray);
+		req.setTypeArray(typeArray);
+		req.setSubKeys(subKeyArray);
+		req.setSubTypeArray(subTypeArray);
+		post(appName, "refreshHash", req);
+
+	}
+
+	@Override
+	protected String getRequestMappingPrefix() {
+		return "cache";
+	}
+
+}

+ 76 - 0
src/main/java/cn/com/qmth/examcloud/web/cache/RefreshHashCacheReq.java

@@ -0,0 +1,76 @@
+package cn.com.qmth.examcloud.web.cache;
+
+import cn.com.qmth.examcloud.api.commons.enums.BasicDataType;
+import cn.com.qmth.examcloud.api.commons.exchange.BaseRequest;
+
+/**
+ * 刷新Hash缓存请求
+ *
+ * @author 
+ * @date 2019年5月9日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class RefreshHashCacheReq extends BaseRequest {
+
+
+	/**
+     * 
+     */
+    private static final long serialVersionUID = 3251806982238101789L;
+
+    private String className;
+
+	private String[] keys;
+
+	private BasicDataType[] typeArray;
+	
+	private String[] subKeys;
+
+    private BasicDataType[] subTypeArray;
+
+	public String getClassName() {
+		return className;
+	}
+
+	public void setClassName(String className) {
+		this.className = className;
+	}
+
+	public String[] getKeys() {
+		return keys;
+	}
+
+	public void setKeys(String[] keys) {
+		this.keys = keys;
+	}
+
+	public BasicDataType[] getTypeArray() {
+		return typeArray;
+	}
+
+	public void setTypeArray(BasicDataType[] typeArray) {
+		this.typeArray = typeArray;
+	}
+
+    
+    public String[] getSubKeys() {
+        return subKeys;
+    }
+
+    
+    public void setSubKeys(String[] subKeys) {
+        this.subKeys = subKeys;
+    }
+
+    
+    public BasicDataType[] getSubTypeArray() {
+        return subTypeArray;
+    }
+
+    
+    public void setSubTypeArray(BasicDataType[] subTypeArray) {
+        this.subTypeArray = subTypeArray;
+    }
+
+
+}

+ 26 - 0
src/main/java/cn/com/qmth/examcloud/web/redis/RedisClient.java

@@ -35,6 +35,13 @@ public interface RedisClient {
 	 * @param value
 	 */
 	public void set(String key, Object value);
+	
+	/**
+	 * @param key
+	 * @param subkey
+	 * @param value
+	 */
+	public void set(String key,String subkey, Object value);
 
 	/**
 	 * 方法注释
@@ -45,6 +52,14 @@ public interface RedisClient {
 	 * @param timeout
 	 */
 	public void set(String key, Object value, int timeout);
+	
+    /**
+     * @param key
+     * @param subkey
+     * @param value
+     * @param timeout
+     */
+    public void set(String key,String subkey, Object value, int timeout);
 
 	/**
 	 * 方法注释
@@ -85,6 +100,15 @@ public interface RedisClient {
 	 * @return
 	 */
 	public <T> T get(String key, Class<T> c);
+	
+	/** hash get
+	 * @param <T>
+	 * @param key
+	 * @param subkey
+	 * @param c
+	 * @return
+	 */
+	public <T> T get(String key, String subkey, Class<T> c);
 
 	/**
 	 * 方法注释
@@ -93,6 +117,8 @@ public interface RedisClient {
 	 * @param key
 	 */
 	public void delete(String key);
+	
+	public void delete(String key,String subkey);
 
 	/**
 	 * 方法注释

+ 38 - 1
src/main/java/cn/com/qmth/examcloud/web/redis/SimpleRedisClient.java

@@ -104,7 +104,18 @@ public final class SimpleRedisClient implements RedisClient {
 		afterMethod("get(String key, Class<T> c)", s);
 		return t;
 	}
-
+	
+	
+    @Override
+    public <T> T get(String key,String hashKey, Class<T> c) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        Object object = redisTemplate.opsForHash().get(key,hashKey);
+        @SuppressWarnings("unchecked")
+        T t = (T) object;
+        afterMethod("get(String key, Class<T> c)", s);
+        return t;
+    }
 	@Override
 	public void delete(String key) {
 		long s = System.currentTimeMillis();
@@ -146,4 +157,30 @@ public final class SimpleRedisClient implements RedisClient {
 		return redisTemplate.getExpire(key, timeUnit);
 	}
 
+    @Override
+    public void set(String key, String subkey, Object value, int timeout) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        set(key,subkey, value);
+        expire(key, timeout);
+        afterMethod("set(String key,String subkey,Object value, int timeout)", s);
+        
+    }
+
+    @Override
+    public void delete(String key, String subkey) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.opsForHash().delete(subkey, subkey);
+        afterMethod("delete(String key,String subkey)", s);
+    }
+
+    @Override
+    public void set(String key, String subkey, Object value) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.opsForHash().put(key,subkey, value);
+        afterMethod("set(String key,String subkey, Object value)", s);
+    }
+
 }