Redis Cluster 下 eval 的使用约束

在集群模式下,eval 有一个重要限制:Lua 脚本中用到的所有 key 必须位于同一个节点上。

因此,可以借助 slot 机制,将请求发送到目标 key 所在的节点。这要求 redis-client 能够正确处理 key 的路由。

方法一:客户端显式指定 key 路由

通过 client 显式指定 key,将请求发送到对应节点。这种方式下,Lua 代码不参与 Redis 的脚本缓存——每次发送给 Redis 的 Lua 代码都是全新的,因为所有可变内容都被直接嵌进了脚本里。这会导致缓存泄漏问题,虽然 Redis 文档表示这不是大问题。

方法二:合理利用 Redis Lua 缓存机制

方法一与 Redis scripting 的语义相悖。正确的做法是:将可变的值通过 ARGV 参数传递,使每次发送给 Redis 的脚本代码保持不变,从而确保生成的 SHA1 始终相同,充分利用 Redis 的脚本缓存。

下面是一个 C++ 封装示例,带有 1 个 ARGV 参数:

	//带有 1 个argv 版本
	void CommandEval0_1(const string& sKey, const string & script, const string & argv1, const LuaRef& luaReplyCb)
	{
		LuaRef luaReplyCb2 = luaReplyCb;
		auto replyCb = ToFunction<CRedis::ReplyCb>(luaReplyCb2);
		GetRedis().CommandF(sKey, replyCb, "eval %s 0 %s", script.c_str(), argv1.c_str());
	}

对应的 Lua 脚本示例(玩家修改名字):

-- 玩家修改名字
t.player_modify_name = [[
    local key = 'player_name_' .. ARGV[1];
    if redis.call('exists',key) == 0 then
        local ret =  redis.call('set',key,ARGV[2]);
        if ret and ret.ok and ret.ok == "OK" then
            return "ok";
        else
            return "error";
        end
    else
        return "exists";
    end]];

方法二虽然也没有严格按照 Redis scripting 规范将 key 作为参数传入,但由于 redis-client 保证了脚本会被路由到 key 所在的节点,因此这也无伤大雅。