GEOSEARCH + HGETALL 循环变慢因N+1查询导致高延迟与资源消耗,改用EVAL将逻辑下推至Redis单次执行可显著提升性能。

为什么 GEOSEARCH + HGETALL 循环会变慢
客户端先调用 GEOSEARCH 拿到一批成员 ID 和距离,再对每个 ID 单独发 HGETALL,这就构成了典型的 N+1 查询。假设返回 200 个点,就得发 201 次命令——光 TCP 往返延迟在跨机房场景下就可能吃掉 2–10 秒。更糟的是,所有数据都要从 Redis 搬到客户端再算,内存和 CPU 都被拉满。
用 EVAL 把 GEOSEARCH + 多次 HGETALL 压进一次 RTT
核心思路是:让 Redis 自己查地理点、自己取每个点的哈希字段、自己做加权计算,最后只返回一个结果数字。脚本里必须用 KEYS[1] 传主表名(如 "locations"),用 ARGV 传经纬度、半径等参数,不能硬编码键名。
-
redis.call("GEOSEARCH", KEYS[1], "FROMLONLAT", ARGV[1], ARGV[2], "BYRADIUS", ARGV[3], "m", "WITHDIST")获取原始结果 - 遍历返回数组,对每个成员名(
res[i][0])调用redis.call("HGETALL", res[i][0])——注意:这个成员名本身也得是 key,所以它必须出现在KEYS数组里,或通过redis.call("EXISTS", ...)安全判断 - 用
tonumber()转换距离和哈希字段值,避免字符串拼接出错 - 最终
return weightedSum,客户端只收一个数字
EVALSHA 在集群环境必须预热且路由正确
直接用 EVAL 发大脚本,每次都要传输几百字节;换成 EVALSHA 可以只传 40 字符摘要,但前提是脚本已加载。问题在于:SCRIPT LOAD 必须发到 KEYS[1] 所在 slot 的节点,否则集群会报 (error) NOSCRIPT No matching script。
- 上线前用
redis-cli -c --cluster call {host}:{port} SCRIPT LOAD "..."或代码中显式指定目标节点执行SCRIPT LOAD - Spring Boot + Lettuce 用户要确认启用了
ScriptExecutor,否则每次重启都重新LOAD,反而加重初始化压力 - 脚本内容哪怕多一个空格,SHA1 就不同,
EVALSHA会失败,务必用构建工具固化脚本内容
KEYS 和 ARGV 传参不合规的典型报错
最常踩的坑是把键名拼在 Lua 字符串里,比如 "user:"..ARGV[1]。Redis 集群靠 KEYS 数组提前提取所有涉及的 key 来计算 slot 并路由,你一拼字符串,它就无法识别,直接拒绝执行。
- 错误写法:
redis.call("HGETALL", "member:"..ARGV[1])→ 集群报(error) ERR EvalSha is not supported in cluster mode - 正确写法:把所有可能用到的 key 全部列在
KEYS里,比如EVAL "...HGETALL KEYS[2]..." 2 locations member:123 123 - 如果成员 ID 是动态生成的(比如来自
GEOSEARCH结果),那就不能用KEYS预声明,得改用redis.call("EXISTS", candidateKey)+redis.call("HGETALL", candidateKey)组合,但要注意:这种 key 不参与 slot 路由,只适用于单节点或客户端已知目标节点的场景


















