特定服务器端点上的 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。