最近我使用Redis.Eval改进了一些代码,效果很好。事实上,它的效果太好了,但我不理解这是如何实现的。
TL;DR: 改进了之前多次使用Redis.zcard的Redis代码,改为一次使用Redis.eval。测试环境下代码速度提高了100+倍,在真实项目中则提高了1000+倍。我不知道原因,能否有人解释一下?
代码功能: 其任务非常简单。它接受一个字符串数组作为参数,这些字符串是存储在Redis中的ZSET键,并将相应ZSET的大小总和求和,返回一个整数值(即总和)。
测试环境设置: 为了尽可能消除外部变量,我建立了一个简单的测试环境,如下所示。
然后,我使用以下一行代码测量了执行上述代码所需的时间。
这给出了
混淆之处在于,一个
当我在我的项目上首次发现这个问题时,我认为网络延迟的减少和等待队列时间的减少起了作用。然而,当我在本地redis上进行测试时,我对自己的理论产生了怀疑。根本没有网络延迟,也没有其他任务正在使用Redis。
我的理论如下:
TL;DR: 改进了之前多次使用Redis.zcard的Redis代码,改为一次使用Redis.eval。测试环境下代码速度提高了100+倍,在真实项目中则提高了1000+倍。我不知道原因,能否有人解释一下?
代码功能: 其任务非常简单。它接受一个字符串数组作为参数,这些字符串是存储在Redis中的ZSET键,并将相应ZSET的大小总和求和,返回一个整数值(即总和)。
测试环境设置: 为了尽可能消除外部变量,我建立了一个简单的测试环境,如下所示。
redis = Redis.new(host: '127.0.0.1', db: 1)
KEYS = 500.times.collect do |i| "KEY#{i}" end
KEYS.each do |key|
redis.zadd(key, 0, "DATA")
end
改进之前
在我修改代码之前,它的工作方式如下。
sum = 0
KEYS.each do |key|
sum += redis.zcard(key)
end
我用以下一行代码测试了此代码的速度。
t = Time.now; sum=0; KEYS.each do |key| sum += redis.zcard(key) end; puts(Time.now - t)
结果打印出 0.202秒(202毫秒)
(请注意,我是根据测试环境和上面编写的代码计算时间,而不是真实环境)
改进后
我使用 Lua 脚本和 EVAL 改变了代码,现在它的工作方式如下。
script = "
local sum = 0
for index, key in pairs(KEYS) do
sum = sum + redis.call('zcard', key);
end
return sum"
sum = redis.eval(script, KEYS)
然后,我使用以下一行代码测量了执行上述代码所需的时间。
t = Time.now; redis.eval(script, KEYS); puts(Time.now - t)
这给出了
0.001519秒(1.5毫秒)
。这比“改进之前”的代码快了134倍。混淆之处在于,一个
redis.zcard(KEYS[0])
需要约0.000542秒(0.542毫秒)
。因此,用于在redis中求和500个ZCARD的redis.eval代码的计算时间与ruby中计算3个Redis.ZCARD的时间大致相同。当我在我的项目上首次发现这个问题时,我认为网络延迟的减少和等待队列时间的减少起了作用。然而,当我在本地redis上进行测试时,我对自己的理论产生了怀疑。根本没有网络延迟,也没有其他任务正在使用Redis。
我的理论如下:
- Ruby求和(
sum += redis.zcard(key)
)占用了大部分时间。 - 即使我使用本地主机,redis和ruby之间仍存在某种延迟。
- 在处理多个查询时,redis内部存在延迟。(不太可能)