首页 > 解决方案 > 与在 redis-cli 客户端上运行的本机命令相比,为什么 EVALSHA 命令会带来如此高的性能成本?

问题描述

以下是我针对 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() ,然后它将直接命令发送到服务器,但在使用 TypeScript 时不能使用流水线。所以我不得不单独执行所有其他命令并放弃流水线。

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

****** 更新:

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

这是供某人参考的代码:Shows evalsha and fallback

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;                 

标签: redisredisjson

解决方案


为什么 Lua 脚本在您的情况下较慢?

因为EVALSHA需要比单个JSON.SETSET命令做更多的工作。运行时EVALSHA,Redis 需要将参数推送到 Lua 堆栈,运行 Lua 脚本,并从 Lua 堆栈中弹出返回值。它应该比对JSON.SETor的 ac 函数调用慢SET

那么服务器端脚本什么时候有性能优势呢?

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

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

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


推荐阅读