[Add] RedisConnectionPool 添加对 Lua 脚本的支持;

[Add] LuaScript 添加 Lua 脚本声明定义类;
[Add] RedisConnectionPool 添加对 Lua 脚本加载管理和执行的功能;
This commit is contained in:
LamGC 2021-01-11 16:37:50 +08:00
parent f1e76092a0
commit 752cf907d6
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
2 changed files with 133 additions and 1 deletions

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
/**
* @author LamGC
*/
public enum LuaScript {
/**
* [List] 检查元素是否存在.
*/
LIST_CHECK_ELEMENT_CONTAINS("CheckElementContains"),
/**
* [List] 删除指定索引的元素.
*/
LIST_REMOVE_ELEMENT_BY_INDEX("RemoveElementByIndex"),
/**
* [All] 删除所有前缀为指定字符串的键.
*/
STORE_REMOVE_KEYS_BY_PREFIX("RemoveKeysByPrefix")
;
public final static String PACKAGE_PATH = "lua/";
private final String scriptName;
LuaScript(String scriptName) {
this.scriptName = scriptName;
}
public String getScriptName() {
return scriptName;
}
}

View File

@ -23,7 +23,13 @@ import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
@ -38,6 +44,8 @@ class RedisConnectionPool {
private final AtomicReference<JedisPool> POOL = new AtomicReference<>();
private final AtomicReference<URL> CONNECTION_URL = new AtomicReference<>();
private final Map<LuaScript, String> scriptMap = new HashMap<>();
public synchronized void setConnectionUrl(URL connectionUrl) {
if(CONNECTION_URL.get() != null) {
CONNECTION_URL.set(connectionUrl);
@ -60,6 +68,7 @@ class RedisConnectionPool {
connectionUrl.getPath().toLowerCase().contains("ssl=true"));
}
POOL.set(jedisPool);
loadScript();
}
/**
@ -110,10 +119,83 @@ class RedisConnectionPool {
return "pong".equalsIgnoreCase(jedis.ping());
} catch (Exception e) {
log.error("Redis 连接测试时发生异常", e);
return false;
}
return false;
}
return true;
}
/**
* 获取指定脚本的 Sha.
* @param script 脚本.
* @return 如果存在, 返回 Sha, 否则返回 null.
*/
public String getScriptSha(LuaScript script) {
return scriptMap.get(script);
}
/**
* 加载脚本.
*/
private void loadScript() {
for (LuaScript script : LuaScript.values()) {
InputStream scriptStream = this.getClass().
getResourceAsStream("/" + LuaScript.PACKAGE_PATH + script.getScriptName() + ".lua");
if (scriptStream == null) {
log.warn("脚本 {} 获取失败, 相关操作将无法使用, 请检查缓存组件是否损坏.", script.getScriptName());
continue;
}
String scriptContent;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(scriptStream, StandardCharsets.UTF_8))) {
String line;
StringBuilder builder = new StringBuilder();
while((line = reader.readLine()) != null) {
builder.append(line).append('\n');
}
scriptContent = builder.toString();
} catch (IOException e) {
log.error("读取脚本文件时发生异常.(Script: " + script.getScriptName() + ")", e);
continue;
}
try {
String scriptSha = executeRedis(jedis -> jedis.scriptLoad(scriptContent));
if (scriptSha != null) {
scriptMap.put(script, scriptSha);
log.debug("脚本 {} 已成功加载.(Sha: {})", script, scriptSha);
}
} catch (Exception e) {
log.error("加载脚本时发生异常.(Script: " + script.getScriptName() + ")", e);
}
}
}
/**
* 执行脚本.
* @param script Lua 脚本.
* @param keys 待传入脚本的键列表.
* @param args 待传入脚本的参数列表.
* @return 如果成功, 返回脚本所返回的数据, 需根据脚本实际返回转换对象.
* @throws NullPointerException script {@code null} 时抛出.
*/
public Object executeScript(final LuaScript script, final List<String> keys, final List<String> args) {
String scriptSha = this.getScriptSha(Objects.requireNonNull(script));
if (scriptSha == null) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
log.warn("脚本未加载, 方法 {}() 无法执行(方法存在于Class {}:{}). (所需脚本: {})",
stackTraceElements[2].getMethodName(),
stackTraceElements[2].getClassName(),
stackTraceElements[2].getLineNumber(),
script.getScriptName());
return false;
}
return executeRedis(jedis -> {
List<String> keysList = (keys == null) ? Collections.emptyList() : keys;
List<String> argsList = (args == null) ? Collections.emptyList() : args;
return jedis.evalsha(scriptSha, keysList, argsList);
});
}
}