真正可用的方案是避免逐个调用geodist,改用georadius或georadiusbymember预筛选再计算;因geodist单次两点计算、不支持批量、网络开销大、易超时,且丧失geohash索引优势。

直接用 GeoOperations 批量算距离不现实,性能差、易超时,真正可用的方案是避免逐个调用 geodist,改用 georadius 或 georadiusbymember 预筛选再计算。
为什么不能对每个目标反复调用 geodist
Redis 的 geodist 是单次两点计算,底层用 haversine 公式,看似简单,但每调一次都触发一次独立命令往返。你有 1000 个目标,就要发 1000 次请求——网络开销、序列化、Redis 线程排队全叠加,实测在中等负载下很容易触发连接池耗尽或响应超时。
-
geodist不支持批量,RedisTemplate.opsForGeo().distance()每次只接受两个成员名 - 即使本地缓存了所有坐标再自己算球面距离,也失去 Redis GEO 的 geohash 索引加速能力
- 纬度超过 ±85.05112878 会直接报错,而
geodist不校验输入,错误常在运行时暴露
georadius 是更合理的起点
如果你已知用户当前经纬度(比如 App 上报的 GPS),优先走 georadius:它利用 geohash 分区做范围剪枝,只查物理上可能靠近的点,不是全量扫描。
- 命令形如:
GEORADIUS shop:pos 116.48 39.92 5 km WITHDIST WITHCOORD - Java 中对应
redisTemplate.opsForGeo().radius(),返回的是GeoResult<geolocation>></geolocation>列表,含距离、坐标、成员名 - 注意单位参数必须显式传
RedisGeoCommands.DistanceUnit.KILOMETERS,漏掉会默认按米返回,数值错一个数量级 - 如果要限制返回数量,用
count+sort(ASC表示由近到远),别依赖客户端排序
用 georadiusbymember 替代“用户到目标”场景
当你的用户 ID 已作为成员存在某个 GEO key 中(比如 user:pos),且目标集合也是 GEO key,这时不要手动拼 user:1 和 shop:123 去调 geodist,直接用 georadiusbymember:
- 命令:
GEORADIUSBYMEMBER user:pos user:123 5 km WITHDIST,表示“以 user:123 的位置为中心,查 user:pos 里 5km 内的其他用户” - Java 调用:
redisTemplate.opsForGeo().radiusByMember(key, member, distance, unit) - 关键点:member 必须已存在于该 key 中;否则报
(error) ERR could not fetch geohash for member - 这个命令天然规避了经纬度越界检查——Redis 内部已校验过该 member 的坐标有效性
临时 key + delete 的陷阱
知识库里那个先 add 用户点、再循环 add 目标点、最后逐个 distance 的做法,问题不在逻辑,而在生命周期管理:
-
redisTemplate.delete(geoKey)放在finally里看似稳妥,但如果 JVM crash 或 Redis 连接中断,key 就残留,占用内存且污染后续请求 - 更好的方式是用带过期时间的 key:
String geoKey = "tmp:" + IdUtil.fastSimpleUUID(); redisTemplate.expire(geoKey, 30, TimeUnit.SECONDS); - 或者彻底放弃临时 key,改用已有的业务 key(如
shop:geo)+georadius,省去写入开销
真正难处理的从来不是怎么算距离,而是怎么让 Redis 不成为瓶颈——geohash 索引只在你让它起作用的时候才起作用,硬切回逐点计算,等于把引擎拆了用手摇。


















