在 Redis 的 Lettuce(4.x) 中,如何减少往返行程并使用一个命令的输出作为另一个命令的输入,尤其是 Georadius

Posted

技术标签:

【中文标题】在 Redis 的 Lettuce(4.x) 中,如何减少往返行程并使用一个命令的输出作为另一个命令的输入,尤其是 Georadius【英文标题】:In Lettuce(4.x) for Redis how to reduce round trips and use output of one command as input for another command, especially for Georadius 【发布时间】:2015-12-12 13:26:10 【问题描述】:

我看过这个pass results to another command in redis 并通过命令行使用此命令效果很好:

src/redis-cli keys '*' | xargs src/redis-cli mget

但是我们怎样才能通过 Lettuce 达到同样的效果(我开始尝试 4.0.2.Final)

在以下情况下,解决方案也特别重要:

假设我们正在使用地理定位功能,我们添加了一组“my-location-category”位置 使用 GEOADD

GEOADD "category-1" 8.6638775 49.5282537 "location-id:1" 8.3796281 48.9978127 "location-id:2" 8.665351 49.553302 "location-id:3"

接下来,假设我们执行 GeoRadius 以获取“类别 1”的 8.6582361 49.5285495 10 公里半径范围内的位置

现在当我们得到“location-id:1”和“location-id:3”时

鉴于我已经为上述键“location-id:1”和“location-id:3”设置了值

我想通过管道命令执行 GEORADIUS 以及对所有匹配结果执行 mget。

Redis 是否提供这样做的功能?

和/或我们如何通过 Lettuce 客户端库来实现这一点,而无需先手动迭代 GEORADIUS 的结果,然后再进行手动管理。

对于使用它的程序来说,这将是更有效的性能。

有谁知道我们如何做到这一点?

更新 这是我上面讨论的场景的管道命令:

src/redis-cli GEORADIUS "category-1" 8.6582361 49.5285495 10 km | xargs src/redis-cli mget

现在我们需要知道如何通过生菜来做到这一点

【问题讨论】:

永远不要使用KEYS,总是使用SCAN 感谢您让我了解 SCAN。 现在我已经更正了问题的标题,以真正代表我的意思。 :=) 【参考方案1】:

重要提示:切勿使用KEYS,如果必须,请始终使用SCAN

这不是关于生菜或 Java 的真正问题,所以我实际上可以回答:)

您要做的是使用读取操作 (GEORADIUS) 的结果作为另一个读取操作 (MGET) 的输入(键名)。这种类型的流程不能流水线化,好吧,正因为如此 - 流水线化意味着您不需要立即获得操作的答案,但在您的情况下,您确实需要。

但是。

由于您正在使用 MGET 读取字符串键,因此您不妨将所有内容非规范化(记住,我们是 NoSQL)并将这些键的内容存储在排序集的成员中,例如:

GEOADD "category-1" 8.6638775 49.5282537 "location-id:1:moredata:evenmoredata:maybe a JSON document here:orperhapsmsgpack"

这将允许您通过一个GEORADIUS 调用获取位置及其“数据”。当然,location:1 数据的任何更新都需要在所有类别中完成。

关于 Lua 脚本的说明:虽然 Lua 脚本在这种情况下肯定可以来回保存,但任何此类脚本都将违反最佳实践/不是集群安全的。

【讨论】:

我计划使用它的方式是 Webapp 使用键“location-id:1”和 JSON data 进行 CRUD 只有那些需要 GEO 位置的人才能转到 GEOADD“category-1 " "location-id:1" 同样使用更多,所以下次更新使用键 "location-id:1",id 值是恒定的。 , 经纬度不是。如果 lang-long 更改,我将没有较早的值来从该类别的地理列表中删除较早的值并添加一个新值。因此,我觉得一些标准化会有所帮助。另外考虑到我们现在的 RAM 有限 :) 我愿意接受想法,在这种情况下你会建议什么?【参考方案2】:

在四处挖掘和研究 Lua 脚本之后,我的结论是,按照 Itamar Haber 的建议,只能通过 Lua 脚本来以这种方式删除往返。

我最终创建了一个 lua 脚本文件 (myscript.lua),如下所示

local locationKeys = redis.call('GEORADIUS', 'category-1', '8.6582361', '49.5285495', '10', 'km' ) 
if unpack(locationKeys) == nil then
    return nil
else
    return redis.call('MGET', unpack(locationKeys))
end

** 当然我们应该向它发送参数...这只是一个 poc :)

现在你可以通过命令执行它了

src/redis-cli EVAL "$(cat myscript.lua)" 0

然后,为了减少将整个脚本发送到 Redis 执行的网络开销,我们可以选择将脚本注册到 Redis。

Redis 将为我们提供一个 sha1 消化的代码,以供该脚本将来参考,可用于该脚本的下一次调用。

这可以通过以下方式完成:

src/redis-cli SCRIPT LOAD "$(cat myscript.lua)"

这应该返回一个类似这样的sha1代码:49730aa2ed3034ee48f818e486tpbdf1b500b19e

可以使用此代码完成下一次调用 例如

src/redis-cli evalsha 49730aa2ed3034ee48f818e486b2bdf1b500b19e 0

然而这里可悲的是,只有当 redis 实例正在运行时,才会记住 sha1 摘要。如果重新启动,则 sha1 摘要丢失。然后再次执行 SCRIPT LOAD。如果脚本中没有任何变化,那么 sha1-digest 代码将是相同的。

理想情况下,通过客户端 api 使用时,我们应该首先尝试 evalsha,如果返回“无匹配脚本”错误,然后作为后备执行脚本加载,并再次获取 sha1 代码,并创建其内部映射并使用该 sha1 代码进行进一步调用。

这可以通过生菜很好地完成。我可以找到那些方法。希望这可以很好地了解问题的解决方案。

【讨论】:

读完这个***.com/questions/16692233/… 现在我知道为什么Redis 不记得脚本了。但可能的一种解决方案是创建一个脚本文件来启动 redis 服务器,然后开始加载所有必需的脚本。这将模拟相同的效果。但是我还没有尝试过......我觉得这应该可以工作。

以上是关于在 Redis 的 Lettuce(4.x) 中,如何减少往返行程并使用一个命令的输出作为另一个命令的输入,尤其是 Georadius的主要内容,如果未能解决你的问题,请参考以下文章

springboot2.x版本整合redis(单机/集群)(使用lettuce)

Redis连接池Lettuce踩坑记录

lettuce之springboot整合redis

Redis高级客户端Lettuce详解

spring boot 集成 redis lettuce(jedis)

redis网络波动,Jedis/Lettuce是阻塞还是失败?