diff --git a/src/main/java/net/lamgc/cgj/bot/cache/CacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/CacheStore.java index 9060b85..db63ff5 100644 --- a/src/main/java/net/lamgc/cgj/bot/cache/CacheStore.java +++ b/src/main/java/net/lamgc/cgj/bot/cache/CacheStore.java @@ -4,6 +4,14 @@ import java.util.Date; public interface CacheStore { + /** + * 更新或添加缓存项 + * @param key 缓存键名 + * @param value 缓存值 + * @param expire 有效期, 单位为ms(毫秒), 如不过期传入0或赋值 + */ + void update(String key, T value, long expire); + /** * 更新或添加缓存项 * @param key 缓存键名 diff --git a/src/main/java/net/lamgc/cgj/bot/cache/HotDataCacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/HotDataCacheStore.java new file mode 100644 index 0000000..1aead4a --- /dev/null +++ b/src/main/java/net/lamgc/cgj/bot/cache/HotDataCacheStore.java @@ -0,0 +1,96 @@ +package net.lamgc.cgj.bot.cache; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.Objects; +import java.util.Random; + +/** + * 具有继承性的热点数据缓存库 + * @param 存储类型 + * @author LamGC + */ +public class HotDataCacheStore implements CacheStore { + + private final CacheStore parent; + private final CacheStore current; + private final long expireTime; + private final int expireFloatRange; + private final Random random = new Random(); + private final Logger log = LoggerFactory.getLogger(HotDataCacheStore.class.getSimpleName() + "@" + Integer.toHexString(this.hashCode())); + + /** + * 构造热点缓存存储对象 + * @param parent 上级缓存存储库 + * @param current 热点缓存存储库, 最好使用本地缓存(例如 {@linkplain LocalHashCacheStore LocalHashCacheStore}) + * @param expireTime 本地缓存库的缓存项过期时间, + * 该时间并不是所有缓存项的最终过期时间, 还需要根据expireFloatRange的设定随机设置, 公式: + * {@code expireTime + new Random().nextInt(expireFloatRange)} + * @param expireFloatRange 过期时间的浮动范围, 用于防止短时间内大量缓存项失效导致的缓存雪崩 + */ + public HotDataCacheStore(CacheStore parent, CacheStore current, long expireTime, int expireFloatRange) { + this.parent = parent; + this.current = current; + this.expireTime = expireTime; + this.expireFloatRange = expireFloatRange; + log.debug("HotDataCacheStore初始化完成. (Parent: {}, Current: {}, expireTime: {}, expireFloatRange: {})", + parent, current, expireTime, expireFloatRange); + } + + @Override + public void update(String key, T value, long expire) { + update(key, value, expire <= 0 ? null : new Date(System.currentTimeMillis() + expire)); + } + + @Override + public void update(String key, T value, Date expire) { + parent.update(key, value, expire); + current.update(key, value, expire); + } + + @Override + public T getCache(String key) { + if(!exists(key)) { + log.debug("查询缓存键名不存在, 直接返回null."); + return null; + } + T result = current.getCache(key); + if(Objects.isNull(result)) { + log.debug("Current缓存库未命中, 查询Parent缓存库"); + T parentResult = parent.getCache(key); + if(Objects.isNull(parentResult)) { + log.debug("Parent缓存库未命中, 缓存不存在"); + return null; + } + log.debug("Parent缓存命中, 正在更新Current缓存库..."); + current.update(key, parentResult, expireTime + random.nextInt(expireFloatRange)); + log.debug("Current缓存库更新完成."); + result = parentResult; + } else { + log.debug("Current缓存库缓存命中."); + } + return result; + } + + @Override + public boolean exists(String key) { + return current.exists(key) || parent.exists(key); + } + + @Override + public boolean exists(String key, Date date) { + return current.exists(key, date) || parent.exists(key, date); + } + + @Override + public boolean clear() { + return current.clear(); + } + + @Override + public boolean supportedPersistence() { + return current.supportedPersistence() || parent.supportedPersistence(); + } +} diff --git a/src/main/java/net/lamgc/cgj/bot/cache/LocalHashCacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/LocalHashCacheStore.java index 8830a5f..ca8f0de 100644 --- a/src/main/java/net/lamgc/cgj/bot/cache/LocalHashCacheStore.java +++ b/src/main/java/net/lamgc/cgj/bot/cache/LocalHashCacheStore.java @@ -28,6 +28,11 @@ public class LocalHashCacheStore implements CacheStore { } } + @Override + public void update(String key, T value, long expire) { + update(key, value, expire <= 0 ? null : new Date(System.currentTimeMillis() + expire)); + } + @Override public void update(String key, T value, Date expire) { if(cache.containsKey(key)) { diff --git a/src/main/java/net/lamgc/cgj/bot/cache/RedisCacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/RedisCacheStore.java deleted file mode 100644 index 35fdf9b..0000000 --- a/src/main/java/net/lamgc/cgj/bot/cache/RedisCacheStore.java +++ /dev/null @@ -1,100 +0,0 @@ -package net.lamgc.cgj.bot.cache; - -import com.google.common.base.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.Transaction; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.net.URI; -import java.util.Date; - -public abstract class RedisCacheStore implements CacheStore { - - private final Jedis jedis; - private final Logger log; - private final String keyPrefix; - - public RedisCacheStore(URI redisServerUri, String prefix) { - this(redisServerUri, null, prefix); - } - - /** - * 创建一个Redis缓存数据库对象 - * @param redisServerUri 数据库链接 - * @param password 登录密码(如果有) - * @throws JedisConnectionException 当连接失败时抛出 - */ - public RedisCacheStore(URI redisServerUri, String password, String prefix) throws JedisConnectionException { - this.jedis = new Jedis(redisServerUri.getHost(), redisServerUri.getPort() <= 0 ? 6379 : redisServerUri.getPort()); - log = LoggerFactory.getLogger(this.getClass().getSimpleName() + "@" + Integer.toHexString(jedis.hashCode())); - log.info("Redis数据库连接状态: {}", jedis.ping()); - if(password != null) { - this.jedis.auth(password); - } - if(!Strings.isNullOrEmpty(prefix)) { - keyPrefix = prefix.endsWith(".") ? prefix : prefix + "."; - } else { - keyPrefix = ""; - } - } - - public void connect() { - if(!"PONE".equals(jedis.ping())) { - jedis.connect(); - } - } - - @Override - public void update(String key, T value, Date expire) { - Transaction multi = jedis.multi(); - multi.set(keyPrefix + key, parse(value)); - if(expire != null) { - multi.expireAt(keyPrefix + key, expire.getTime()); - log.debug("已设置Key {} 的过期时间(Expire: {})", key, expire.getTime()); - } - multi.exec(); - } - - @Override - public T getCache(String key) { - return analysis(jedis.get(keyPrefix + key)); - } - - @Override - public boolean exists(String key) { - return jedis.exists(keyPrefix + key); - } - - @Override - public boolean exists(String key, Date date) { - return exists(key); - } - - @Override - public boolean clear() { - String result = jedis.flushDB(); - log.info("flushDB返回结果: {}", result); - return true; - } - - /** - * 转换方法 - * @param dataObj 原数据 - * @return 文本型数据 - */ - protected abstract String parse(T dataObj); - - /** - * 将String数据转换成指定类型的对象 - * @param dataStr String数据 - * @return 泛型指定类型的对象 - */ - protected abstract T analysis(String dataStr); - - @Override - public boolean supportedPersistence() { - return true; - } -} diff --git a/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java index 1d5bcf6..906c057 100644 --- a/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java +++ b/src/main/java/net/lamgc/cgj/bot/cache/RedisPoolCacheStore.java @@ -21,15 +21,11 @@ public abstract class RedisPoolCacheStore implements CacheStore { } public RedisPoolCacheStore(URI redisServerUri, JedisPoolConfig config, int timeout, String password, String prefix) { - jedisPool = new JedisPool(config == null ? new GenericObjectPoolConfig() : config, redisServerUri.getHost(), - redisServerUri.getPort() <= 0 ? 6379 : redisServerUri.getPort(), - timeout <= 0 ? Protocol.DEFAULT_TIMEOUT : timeout, password); - log = LoggerFactory.getLogger(this.getClass().getSimpleName() + "@" + Integer.toHexString(jedisPool.hashCode())); - if(prefix != null) { - keyPrefix = prefix.endsWith(".") ? prefix : prefix + "."; - } else { - keyPrefix = ""; - } + this(new JedisPool(config == null ? new GenericObjectPoolConfig() : config, redisServerUri.getHost(), + redisServerUri.getPort() == -1 ? 6379 : redisServerUri.getPort(), + timeout <= 0 ? Protocol.DEFAULT_TIMEOUT : timeout, password), + prefix + ); } public RedisPoolCacheStore(JedisPool pool, String keyPrefix) { @@ -45,16 +41,19 @@ public abstract class RedisPoolCacheStore implements CacheStore { } } + @Override + public void update(String key, T value, long expire) { + update(key, value, expire <= 0 ? null : new Date(System.currentTimeMillis() + expire)); + } + @Override public void update(String key, T value, Date expire) { Jedis jedis = jedisPool.getResource(); - Transaction multi = jedis.multi(); - multi.set(keyPrefix + key, parse(value)); + jedis.set(keyPrefix + key, parse(value)); if(expire != null) { - multi.expireAt(keyPrefix + key, expire.getTime()); + jedis.pexpireAt(keyPrefix + key, expire.getTime()); log.debug("已设置Key {} 的过期时间(Expire: {})", key, expire.getTime()); } - multi.exec(); jedis.close(); } @@ -106,4 +105,14 @@ public abstract class RedisPoolCacheStore implements CacheStore { public boolean supportedPersistence() { return true; } + + /** + * 替换原本的分隔符(.)为(:).
+ * 即将启用 + * @param key 要处理的键名 + * @return 处理后的键名 + */ + public static String replaceKey(String key) { + return key.replaceAll("\\.", ":"); + } } diff --git a/src/main/java/net/lamgc/cgj/bot/cache/StringRedisCacheStore.java b/src/main/java/net/lamgc/cgj/bot/cache/StringRedisCacheStore.java new file mode 100644 index 0000000..33c61ca --- /dev/null +++ b/src/main/java/net/lamgc/cgj/bot/cache/StringRedisCacheStore.java @@ -0,0 +1,30 @@ +package net.lamgc.cgj.bot.cache; + +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +import java.net.URI; + +public class StringRedisCacheStore extends RedisPoolCacheStore { + public StringRedisCacheStore(URI redisServerUri, String prefix) { + super(redisServerUri, prefix); + } + + public StringRedisCacheStore(URI redisServerUri, JedisPoolConfig config, int timeout, String password, String prefix) { + super(redisServerUri, config, timeout, password, prefix); + } + + public StringRedisCacheStore(JedisPool pool, String keyPrefix) { + super(pool, keyPrefix); + } + + @Override + protected String parse(String dataObj) { + return dataObj; + } + + @Override + protected String analysis(String dataStr) { + return dataStr; + } +}