与 redis-cli 客户端上的本机命令 运行 相比,为什么 EVALSHA 命令的性能成本如此之高?

Why does the EVALSHA command come at such a performance cost when compared to native commands run on the redis-cli client?

以下是我针对 redis-benchmark 工具进行的一些测试和结果 运行。

C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 JSON.SET fooz . [9999]
JSON.SET fooz . [9999]: 93049.23 requests per second

C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 evalsha 8d2d42f1e3a5ce869b50a2b65a8bfaafe8eff57a 1 fooz [5555]
evalsha 8d2d42f1e3a5ce869b50a2b65a8bfaafe8eff57a 1 fooz [5555]: 61132.17 requests per second

C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 eval "return redis.call('JSON.SET', KEYS[1], '.', ARGV[1])" 1 fooz [5555]
eval return redis.call('JSON.SET', KEYS[1], '.', ARGV[1]) 1 fooz [5555]: 57423.41 requests per second

对于脚本 运行 宁服务器端相对于客户端 运行 宁脚本客户端具有性能优势的东西来说,这是性能的显着下降。

从客户端到 EVALSHA = 34% 性能损失

EVALSHAEVAL = 6% 性能损失

非JSON 插入 set 命令的结果相似

C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 set fooz 3333
set fooz 3333: 116414.43 requests per second

C02YLCE2LVCF:Downloads xxxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 evalsha e32aba8d03c97f4418a8593ed4166640651e18da 1 fooz [2222]
evalsha e32aba8d03c97f4418a8593ed4166640651e18da 1 fooz [2222]: 78520.67 requests per second

我第一次注意到这一点是在我执行 info commandstat 并观察到 ​​EVALSHA 命令

的较差性能时
# Commandstats
cmdstat_ping:calls=331,usec=189,usec_per_call=0.57
cmdstat_eval:calls=65,usec=4868,usec_per_call=74.89
cmdstat_del:calls=2,usec=21,usec_per_call=10.50
cmdstat_ttl:calls=78,usec=131,usec_per_call=1.68
cmdstat_psync:calls=51,usec=2515,usec_per_call=49.31
cmdstat_command:calls=5,usec=3976,usec_per_call=795.20
cmdstat_scan:calls=172,usec=1280,usec_per_call=7.44
cmdstat_replconf:calls=185947,usec=217446,usec_per_call=1.17
****cmdstat_json.set:calls=1056,usec=26635,usec_per_call=25.22**
****cmdstat_evalsha:calls=1966,usec=68867,usec_per_call=35.03**
cmdstat_expire:calls=1073,usec=1118,usec_per_call=1.04
cmdstat_flushall:calls=9,usec=694,usec_per_call=77.11
cmdstat_monitor:calls=1,usec=1,usec_per_call=1.00
cmdstat_get:calls=17,usec=21,usec_per_call=1.24
cmdstat_cluster:calls=102761,usec=23379827,usec_per_call=227.52
cmdstat_client:calls=100551,usec=122382,usec_per_call=1.22
cmdstat_json.del:calls=247,usec=2487,usec_per_call=10.07
cmdstat_script:calls=207,usec=10834,usec_per_call=52.34
cmdstat_info:calls=4532,usec=229808,usec_per_call=50.71
cmdstat_json.get:calls=1615,usec=11923,usec_per_call=7.38
cmdstat_type:calls=78,usec=115,usec_per_call=1.47

JSON.SETEVALSHA 有大约 30% 的性能降低,这是我在直接测试中观察到的。

问题是,为什么?而且,这有什么值得关注的,或者这种观察是否在合理的预期之内?

对于上下文,我使用 EVALSHA 而不是直接 JSON.SET 命令的原因有两个。

  1. IORedis 客户端库不直接支持使用 RedisJson。

  2. 由于之前的事实,我将不得不使用 send_command() 然后将直接命令发送到服务器但在使用时无法使用流水线打字稿。所以我将不得不单独执行所有其他命令并放弃流水线。

  3. 我以为这应该是更好的表现?

****** 更新:

所以最后,根据下面的答案,我重构了我的代码,只包含 1 个 EVALSHA 用于写入,因为它使用 2 个命令,即 set 和 expire 命令。同样,我不能将其单独放入 RedisJson,这就是原因。

这是供某人参考的代码:显示 evalsha 和回退

await this.client.evalsha(this.luaWriteCommand, '1', documentChange.id, JSON.stringify(documentChange), expirationSeconds)
   .catch((error) => {
        console.error(error);
        evalSHAFail = true;
    });
if (evalSHAFail) {
   console.error('EVALSHA for write not processed, using EVAL');
   await this.client.eval("return redis.pcall('JSON.SET', KEYS[1], '.', ARGV[1]), redis.pcall('expire', KEYS[1], ARGV[2]);", '1', documentChange.id, JSON.stringify(documentChange), expirationSeconds);
   console.log('SRANS FRUNDER');
   this.luaWriteCommand = undefined;                 

Why Lua script is slower in your case?

因为 EVALSHA 需要比单个 JSON.SETSET 命令做更多的工作。当运行宁EVALSHA时,Redis需要将参数压入Lua栈,运行Lua脚本,从[=26中弹出return值=] 堆栈。它应该比 JSON.SETSET.

的 c 函数调用慢

So When does server side script has a performance advantage?

首先,您必须运行在脚本中使用多个命令,否则,将没有我上面提到的任何性能优势。

其次,服务器端脚本运行s比一个一个地向Redis发送serval命令更快,从Redis获取结果,并在客户端进行计算工作。因为,Lua 脚本节省了大量往返时间。

第三,如果您需要在 Lua 脚本中进行非常复杂的计算工作。这可能不是一个好主意。因为Redis运行是单线程的脚本,如果脚本耗时过多,会阻塞其他客户端。相反,在客户端,您可以利用多核的优势来进行复杂的计算。