用geosearch替代georadius,配合统一wgs84坐标系、批量预热商家位置、显式指定单位(如m)、限制count及加asc排序,才能稳定支撑骑手高频上报与并发查距。

直接结论:用 GEOSEARCH 替代 GEORADIUS,配合统一坐标系 + 批量预热 + 单位显式指定,才能扛住骑手位置高频刷新与并发查距的双重压力。
为什么不能只用 GEORADIUS 查骑手距离
很多团队一开始写成 GEORADIUS shop:geo $lng $lat 500 m COUNT 1 去找最近骑手,结果在 QPS 上千时延迟突增、超时频发。根本原因有三个:
-
GEORADIUS每次都以经纬度为中心做球面扫描,Redis 需实时解码所有 GeoHash 并逐个比对,计算开销随半径内点数线性增长 - 骑手位置每 3–5 秒上报一次,若用
GEORADIUS查“离某商家最近的骑手”,本质是遍历全量骑手集合,没利用 zset 的有序性优势 - 单位不显式指定(比如漏写
m)会导致 Redis 默认用m,但业务逻辑按km解析,距离误判率陡升
GEOSEARCH 怎么查才真正高效
核心思路是把“查骑手”变成“查商家”——让骑手作为查询主体,反向检索其覆盖范围内的可接单商家。但在调度场景中,我们仍需知道“某个商家附近有哪些骑手”,此时应改用 FROMMEMBER 模式,让商家作为锚点:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 先确保商家已写入:
geoAdd('shop:active', $lng, $lat, $shopId),注意经度在前、纬度在后 - 骑手位置单独存入另一 key:
geoAdd('rider:live', $lng, $lat, $riderId),保持数据隔离 - 查商家
$shopId周边 1.5km 内骑手:GEOSEARCH rider:live FROMMEMBER $shopId BYRADIUS 1500 m ASC COUNT 3 WITHDIST - 必须加
ASC,否则默认无序;COUNT 3要严格限制,避免返回几十个骑手再由 PHP 二次筛选
坐标系不一致是静默故障最大来源
高德地图 SDK 返回的是 GCJ-02 坐标,而 Redis GEO 内部用 WGS84 球面模型计算 haversine 距离。两者混用会导致 100–500 米偏差,调度系统会频繁选错骑手。
- 前后端必须约定统一坐标系:推荐全部转为 WGS84(可用
gcoord库或高德 API 的convert接口) - 不要在 Redis 里存“转换中”的坐标,例如前端传 GCJ-02,后端一边转一边写 —— 转换失败时写入的就是脏数据
- 上线前用固定坐标对打:取北京望京某商家(WGS84: 116.48105,39.996794),查它周围 500m 骑手,再用高德 JSAPI 同样半径画圆,看结果是否重合
真实压测下容易被忽略的三个细节
即便命令写对、坐标对齐,QPS 过万后仍可能抖动。问题往往出在这些非代码层:
- Redis 实例未开启
io-threads(Tair 或 GaussDB(for Redis) 必须显式配置,开源 Redis 6.0+ 需编译时启用) -
rider:livekey 没设置过期时间,骑手下线后位置残留,GEOSEARCH仍会扫到“幽灵骑手” - PHP 中调用
$redis->geoSearch()时未捕获异常,当 Redis 返回空数组或NULL时,业务层直接报foreach() on null,掩盖了底层 GEO 查询失败
最麻烦的是:这些细节在本地单机测试完全暴露不出来,只有压测到 8k QPS 以上、骑手模拟器并发上报时,才会集中爆发。










