特定服务器端点上的 Evaluate lua 脚本
Evaluate lua script on particular server endpoint
是否可以使用 StackExchange.Redis 在特定服务器端点上 运行 lua 脚本
server.Execute (ExecuteAsync) 呼叫?
目前正在使用以下代码:
public async Task<bool> ExecuteScriptAsync(string scriptName, object[] keyArgs = null, object[] valueArgs = null)
{
List<byte[]> scriptHashes;
_logger.Log(LogLevel.Information, $"Evaluating script: {scriptName}");
if (!_scriptHashes.TryGetValue(scriptName, out scriptHashes))
{
throw new ArgumentException($"Requested script {scriptName} was not found");
}
RedisValue[] redisValues = valueArgs == null ? null : Array.ConvertAll(valueArgs, RedisValueHelper.ToRedisValue);
RedisKey[] redisKeys = keyArgs == null
? null
: Array.ConvertAll(keyArgs,
source =>
{
if (source == null)
{
return default;
}
return (RedisKey)source.ToString();
});
string script = GetScriptByName(scriptName);
foreach (EndPoint endPoint in _connectionMultiplexer.GetEndPoints())
{
IServer server = _connectionMultiplexer.GetServer(endPoint);
if (server != null && server.IsConnected && !server.IsReplica)
{
_logger.Log(LogLevel.Information, $"Executing script {scriptName} on endpoint: {server.EndPoint}");
scriptTasks.Add(server.ExecuteAsync("EVAL", script, 0, redisKeys, redisValues));
}
}
foreach (RedisResult result in await Task.WhenAll(scriptTasks))
{
_logger.Log(LogLevel.Information, $"Got script eval result:{result}");
}
return true;
}
但它似乎不起作用。它因 TimeoutException 而失败。
StackExchange.Redis.RedisTimeoutException:等待响应超时(出站=0KiB,入站=0KiB,经过 5360 毫秒,超时为 5000 毫秒),命令=未知,下一个:EVAL,inst:0,qu:0,qs : 1, aw: False, rs: ReadAsync, ws: Idle, in: 0, in-pipe: 0, out-pipe: 0, serverEndpoint: :6379, mc: 1/1/0 , mgr: 10 of 10 available, clientName: CLIENT, IOCP: (Busy=0,Free=1000,Min=12,Max=1000), WORKER: (Busy=1,Free=32766,Min=12,Max=32767 ), v: 2.2.88.56325(请查看这篇文章,了解一些可能导致超时的常见客户端问题:https://stackexchange.github.io/StackExchange.Redis/Timeouts)
我想在redis集群的每个主节点上执行脚本。
lua 脚本通过指定的键前缀删除键:
local keys = {};
local done = false;
local cursor = "0";
local numDeleted = 0;
local unlinkKey = function(key)
local result;
local num = redis.pcall("UNLINK", key);
if type(num) == "table" then
result = 0;
else
result = num;
end
return result;
end
repeat
local result = redis.call("SCAN", cursor, "match", KEYS[1], "count", 10000)
cursor = result[1];
keys = result[2];
for i, key in ipairs(keys) do
numDeleted = numDeleted + unlinkKey(key);
end
if cursor == "0" then
done = true;
end
until done
return numDeleted;
db.ScriptEvaluate 在集群的情况下无法正常工作,因为它不会在所有节点上执行它,而是在一些随机节点上执行。
谢谢。
您的直接问题
首先,让我指出这一行:
scriptTasks.Add(server.ExecuteAsync("EVAL", script, 0, redisKeys, redisValues));
由于几个原因似乎是错误的,首先你正在使用一个密钥,因此,密钥计数不是 0,它应该与你传递到脚本中的密钥数组的长度相匹配。
local result = redis.call("SCAN", cursor, "match", KEYS[1], "count", 10000)
第三个参数应该是你的 redis 键数组的长度,而不是 0。所以这只是执行脚本的问题。
其次,您确实应该传入一个参数数组,传入几个 ad-hoc 个参数后跟一个数组并不理想,例如:
这确实挂起(与您看起来正在做的非常相似)
await server.ExecuteAsync("EVAL", "return KEYS[1]", 0, new RedisKey[]{new ("blah")});
而
var arguments = new List<object>();
arguments.Add("return KEYS[1]");
arguments.Add(1);
arguments.Add(new RedisKey("blah"));
var result = muxer.GetDatabase().Execute("EVAL", arguments.ToArray());
执行没有问题
所以更像是:
var arguments = new List<object>();
arguments.Add(script);
arguments.Add(redisKeys.Count());
arguments.AddRange(redisKeys);
arguments.AddRange(redisValues);
scriptTasks.Add(server.ExecuteAsync("EVAL", arguments));
可能会更适合你
另一个抬头
如果我没有向您指出这本身就是一个非常 long-running 的脚本,那我就是失职了。
在 EVAL
中耗尽 SCAN
的游标等同于使用 KEYS
命令,这意味着您的脚本需要扫描碎片上的每个键(它只会 return 匹配,但它仍然需要查看每个键),所以这个脚本将 time-complexity 与你的分片上的键数成线性关系。这会导致真正的 long-running 脚本。 Long-running 脚本在 Redis 中是一个 anti-pattern,以阻塞方式耗尽 SCAN 的游标本身就是一个 anti-pattern。由于 Redis 是 single-threaded,因此在您的脚本执行时没有其他任何东西能够与您的 Redis 分片通信。
您可能需要考虑一种更好的方法来确保 scan/delete 操作的原子性(假设这就是您想要做的)。这成为一个稍微复杂的数据建模问题,但您应该能够构建 sets/locks 的组合,以防止您在进行这些清理时锁定 redis。
是否可以使用 StackExchange.Redis 在特定服务器端点上 运行 lua 脚本 server.Execute (ExecuteAsync) 呼叫?
目前正在使用以下代码:
public async Task<bool> ExecuteScriptAsync(string scriptName, object[] keyArgs = null, object[] valueArgs = null)
{
List<byte[]> scriptHashes;
_logger.Log(LogLevel.Information, $"Evaluating script: {scriptName}");
if (!_scriptHashes.TryGetValue(scriptName, out scriptHashes))
{
throw new ArgumentException($"Requested script {scriptName} was not found");
}
RedisValue[] redisValues = valueArgs == null ? null : Array.ConvertAll(valueArgs, RedisValueHelper.ToRedisValue);
RedisKey[] redisKeys = keyArgs == null
? null
: Array.ConvertAll(keyArgs,
source =>
{
if (source == null)
{
return default;
}
return (RedisKey)source.ToString();
});
string script = GetScriptByName(scriptName);
foreach (EndPoint endPoint in _connectionMultiplexer.GetEndPoints())
{
IServer server = _connectionMultiplexer.GetServer(endPoint);
if (server != null && server.IsConnected && !server.IsReplica)
{
_logger.Log(LogLevel.Information, $"Executing script {scriptName} on endpoint: {server.EndPoint}");
scriptTasks.Add(server.ExecuteAsync("EVAL", script, 0, redisKeys, redisValues));
}
}
foreach (RedisResult result in await Task.WhenAll(scriptTasks))
{
_logger.Log(LogLevel.Information, $"Got script eval result:{result}");
}
return true;
}
但它似乎不起作用。它因 TimeoutException 而失败。
StackExchange.Redis.RedisTimeoutException:等待响应超时(出站=0KiB,入站=0KiB,经过 5360 毫秒,超时为 5000 毫秒),命令=未知,下一个:EVAL,inst:0,qu:0,qs : 1, aw: False, rs: ReadAsync, ws: Idle, in: 0, in-pipe: 0, out-pipe: 0, serverEndpoint:
我想在redis集群的每个主节点上执行脚本。 lua 脚本通过指定的键前缀删除键:
local keys = {};
local done = false;
local cursor = "0";
local numDeleted = 0;
local unlinkKey = function(key)
local result;
local num = redis.pcall("UNLINK", key);
if type(num) == "table" then
result = 0;
else
result = num;
end
return result;
end
repeat
local result = redis.call("SCAN", cursor, "match", KEYS[1], "count", 10000)
cursor = result[1];
keys = result[2];
for i, key in ipairs(keys) do
numDeleted = numDeleted + unlinkKey(key);
end
if cursor == "0" then
done = true;
end
until done
return numDeleted;
db.ScriptEvaluate 在集群的情况下无法正常工作,因为它不会在所有节点上执行它,而是在一些随机节点上执行。
谢谢。
您的直接问题
首先,让我指出这一行:
scriptTasks.Add(server.ExecuteAsync("EVAL", script, 0, redisKeys, redisValues));
由于几个原因似乎是错误的,首先你正在使用一个密钥,因此,密钥计数不是 0,它应该与你传递到脚本中的密钥数组的长度相匹配。
local result = redis.call("SCAN", cursor, "match", KEYS[1], "count", 10000)
第三个参数应该是你的 redis 键数组的长度,而不是 0。所以这只是执行脚本的问题。
其次,您确实应该传入一个参数数组,传入几个 ad-hoc 个参数后跟一个数组并不理想,例如:
这确实挂起(与您看起来正在做的非常相似)
await server.ExecuteAsync("EVAL", "return KEYS[1]", 0, new RedisKey[]{new ("blah")});
而
var arguments = new List<object>();
arguments.Add("return KEYS[1]");
arguments.Add(1);
arguments.Add(new RedisKey("blah"));
var result = muxer.GetDatabase().Execute("EVAL", arguments.ToArray());
执行没有问题
所以更像是:
var arguments = new List<object>();
arguments.Add(script);
arguments.Add(redisKeys.Count());
arguments.AddRange(redisKeys);
arguments.AddRange(redisValues);
scriptTasks.Add(server.ExecuteAsync("EVAL", arguments));
可能会更适合你
另一个抬头
如果我没有向您指出这本身就是一个非常 long-running 的脚本,那我就是失职了。
在 EVAL
中耗尽 SCAN
的游标等同于使用 KEYS
命令,这意味着您的脚本需要扫描碎片上的每个键(它只会 return 匹配,但它仍然需要查看每个键),所以这个脚本将 time-complexity 与你的分片上的键数成线性关系。这会导致真正的 long-running 脚本。 Long-running 脚本在 Redis 中是一个 anti-pattern,以阻塞方式耗尽 SCAN 的游标本身就是一个 anti-pattern。由于 Redis 是 single-threaded,因此在您的脚本执行时没有其他任何东西能够与您的 Redis 分片通信。
您可能需要考虑一种更好的方法来确保 scan/delete 操作的原子性(假设这就是您想要做的)。这成为一个稍微复杂的数据建模问题,但您应该能够构建 sets/locks 的组合,以防止您在进行这些清理时锁定 redis。