Redis GEO 详细教程:经纬度存储与附近搜索实战

· Redis笔记 Redis redis-cli 数据结构 入门笔记 NoSQL · 1 阅读 · 约 8 分钟

Redis GEO 是 Redis 3.2 引入的地理位置功能,可以把经纬度坐标存进 Redis,并计算两点距离、按半径搜索附近的成员。它底层其实是 zset,通过 GeoHash 把经纬度编码成一个 52 位的整数作为 score,所以很多 zset 命令也能用在 GEO 上。

一、GEO 相关命令一览

命令 作用
GEOADD 添加经纬度坐标
GEOPOS 获取某成员的经纬度
GEODIST 计算两个成员之间的距离
GEOHASH 返回成员的 GeoHash 字符串
GEOSEARCH 按半径或矩形范围搜索(Redis 6.2+,推荐)
GEOSEARCHSTORE 把搜索结果存到另一个 key
GEORADIUS 以某个坐标为中心搜索(已标记过时)

二、GEOADD:添加地理位置

GEOADD key longitude latitude member [longitude latitude member ...]

注意顺序是经度在前、纬度在后,别写反了。添加几个城市的位置:

GEOADD cities 116.405285 39.904989 beijing
GEOADD cities 121.472644 31.231706 shanghai
GEOADD cities 113.280637 23.125178 guangzhou
GEOADD cities 114.085947 22.547 shenzhen

有效范围:经度 -180 ~ 180,纬度 -85.05112878 ~ 85.05112878,超出会报错。

三、GEOPOS:查询成员坐标

GEOPOS cities beijing shanghai

返回:

1) 1) "116.40528291463851929"
   2) "39.90498842259668252"
2) 1) "121.47264629602432251"
   2) "31.23170559937162228"

返回值和写入值会有微小误差,这是 GeoHash 编码精度导致的,正常现象。

四、GEODIST:计算两点距离

GEODIST cities beijing shanghai km

返回约 "1067.5983"(千米)。可选单位:

  • m:米(默认)
  • km:千米
  • mi:英里
  • ft:英尺

如果要查的成员不存在,返回 nil。

五、GEOSEARCH:范围搜索(推荐)

GEOSEARCH 是 Redis 6.2 引入的,功能覆盖了老的 GEORADIUS 和 GEORADIUSBYMEMBER,官方建议新代码都用它。

1. 以成员为中心搜索

查找距离上海 1100 公里以内的城市,由近到远排序:

GEOSEARCH cities FROMMEMBER shanghai BYRADIUS 1100 km ASC

2. 以坐标为中心搜索

查找坐标 114.05 22.55(深圳附近)300 公里内的城市:

GEOSEARCH cities FROMLONLAT 114.05 22.55 BYRADIUS 300 km ASC

3. 带距离和坐标返回

GEOSEARCH cities FROMLONLAT 114.05 22.55 BYRADIUS 500 km ASC WITHDIST WITHCOORD COUNT 3
  • WITHDIST:返回距离
  • WITHCOORD:返回经纬度
  • WITHHASH:返回 GeoHash 整数
  • COUNT n:限制返回条数
  • ASC / DESC:升序或降序排列

输出示例:

1) 1) "shenzhen"
   2) "1.2838"
   3) 1) "114.08594697713851929"
      2) "22.54699993761843399"
2) 1) "guangzhou"
   2) "105.1973"
   ...

六、GEOSEARCHSTORE:把结果存起来

GEOSEARCHSTORE nearby:cities cities FROMLONLAT 114.05 22.55 BYRADIUS 500 km ASC

结果会以 zset 形式写入 nearby:cities,score 是距离(米),可以直接用 ZRANGE、ZINCRBY 等 zset 命令二次加工。加 STOREDIST 参数时 score 存的就是距离值。

七、老命令 GEORADIUS 的用法

Redis 6.2 之前只能用 GEORADIUS,语法基本一致:

GEORADIUS cities 114.05 22.55 500 km ASC COUNT 5 WITHDIST

成员为中心的版本是 GEORADIUSBYMEMBER:

GEORADIUSBYMEMBER cities guangzhou 500 km ASC

这两条命令现在仍可用,但已被标记为 deprecated,新项目建议直接用 GEOSEARCH。

八、Java 代码示例

用 Jedis 操作 GEO:

try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
    // 添加位置
    jedis.geoadd("stores", 114.085947, 22.547, "store:1");
    jedis.geoadd("stores", 114.169361, 22.319303, "store:2");

    // 搜索某坐标 50 公里内的门店,附带距离
    List<GeoRadiusResponse> list = jedis.geosearch(
            "stores",
            new GeoCoordinate(114.05, 22.55),
            50,
            GeoUnit.KM
    );
    for (GeoRadiusResponse r : list) {
        System.out.println(r.getMemberByString() + " 距离: " + r.getDistance() + " km");
    }
}

Spring Data Redis 的写法:

redisTemplate.opsForGeo().add("stores", new Point(114.085947, 22.547), "store:1");

GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().search(
        "stores",
        GeoReference.fromCoordinate(114.05, 22.55),
        new Distance(50, Metrics.KILOMETERS),
        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
                .includeDistance()
                .sortAscending()
);

九、常见应用场景

  • 附近的人 / 附近门店:把用户或门店坐标写进 GEO,用 GEOSEARCH 按半径查,配合 ASC 和 COUNT 拿最近的 N 个
  • 打车 / 外卖匹配:按城市分 key,如 geo:driver:beijing,避免单个 key 过大
  • 骑手轨迹:定时更新骑手坐标,直接 GEOADD 覆盖旧值即可

十、注意事项

  1. GEO 底层是 zset,ZREM cities shanghai 就能删除一个成员,没有专门的 GEOREMOVE 命令
  2. 无法修改经纬度精度,GeoHash 编码固定为 52 位整数,距离越远误差越大,超长距离(如几千公里)的结果仅供参考
  3. key 太大时建议按城市、商圈等维度拆分成多个 key
  4. GEOPOS 返回值与写入值有轻微偏差,业务上做模糊匹配即可,不要用 == 精确比较

GEO 的本质就是给 zset 换了个"经纬度视角",理解了这一点,剩下的就是记住几个命令参数的排列组合。实际项目里最常用的组合就是 GEOADD 写入 + GEOSEARCH ... WITHDIST ASC COUNT 查询,掌握了这两条,附近搜索类需求基本都能覆盖。