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覆盖旧值即可
十、注意事项
- GEO 底层是
zset,ZREM cities shanghai就能删除一个成员,没有专门的 GEOREMOVE 命令 - 无法修改经纬度精度,GeoHash 编码固定为 52 位整数,距离越远误差越大,超长距离(如几千公里)的结果仅供参考
- key 太大时建议按城市、商圈等维度拆分成多个 key
GEOPOS返回值与写入值有轻微偏差,业务上做模糊匹配即可,不要用==精确比较
GEO 的本质就是给 zset 换了个"经纬度视角",理解了这一点,剩下的就是记住几个命令参数的排列组合。实际项目里最常用的组合就是 GEOADD 写入 + GEOSEARCH ... WITHDIST ASC COUNT 查询,掌握了这两条,附近搜索类需求基本都能覆盖。