在互联网产品中,“附近的人”“附近的店”“配送范围”这类功能越来越常见。以前做这种功能可能需要依赖MySQL的经纬度计算,或者上专业的GIS数据库(比如PostGIS),但Redis 3.2版本后推出的Geo(地理信息)模块,用极简的API和高效的性能,完美解决了这类问题。今天咱们就来深入聊聊Redis Geo的底层原理、常用命令和实战场景。
先想个场景:你要做一个“附近3公里的餐厅”功能。传统做法是把餐厅的经纬度存MySQL,查询时用WHERE
语句结合经纬度计算距离(比如Haversine公式),但这样做有两个问题:
而Redis Geo基于ZSET(有序集合)和Geohash编码优化,能以O(logN)的时间复杂度完成位置存储和范围查询,轻松支持百万级数据的“附近搜索”“距离计算”等操作,堪称“地理信息场景的瑞士军刀”。
要理解Redis Geo,必须先搞懂两个关键:Geohash编码和ZSET的巧妙利用。
Geohash是一种将经纬度(二维坐标)转换为一维字符串的编码算法,核心思想是“二进制交替切割”。举个例子:
1110001.011001...
,纬度39.905是100111.111010...
);比如北京天安门(116.405285, 39.904989)的Geohash是wx4g0s8q3jf9
(长度12)。Geohash有个特性:两个位置越近,Geohash的前缀越长(就像手机号前几位相同代表同一地区)。这为后续的范围查询提供了基础。
Redis Geo并没有单独的数据结构,而是直接复用了ZSET(有序集合)。具体来说:
member
(比如“天安门”)作为ZSET的成员;score
字段存储的是该位置的Geohash转换后的52位整数(双精度浮点数精度足够,不会丢数据)。这样一来,ZSET的天然排序能力(按score排序)就能直接用来做地理范围的排序和查询,而Geohash的前缀特性又能快速筛选出附近的点。
小提示:用
ZSCORE key member
命令可以看到某个位置的Geohash整数,比如ZSCORE shops 天安门
会返回一个很大的整数,这就是它的Geohash编码值。
Redis Geo提供了7个核心命令,覆盖了“增删改查”和各种地理计算需求。咱们结合示例逐个看。
GEOADD
:添加/更新位置(最常用)语法:
GEOADD key longitude latitude member [longitude latitude member ...]
作用:向key
中添加一个或多个地理位置(经度、纬度、成员名)。如果成员已存在,会覆盖旧坐标。
示例:
# 添加天安门、故宫、景山的位置
GEOADD shops 116.405285 39.904989 "天安门" 116.415 39.915 "故宫" 116.425 39.925 "景山"
注意:经纬度顺序是longitude
(经度)在前,latitude
(纬度)在后!写反了会导致位置偏差(比如把北京写成南半球)。
GEODIST
:计算两个位置的距离语法:
GEODIST key member1 member2 [unit]
作用:计算member1
和member2
之间的距离,unit
支持米(m)、千米(km)、英里(mi)、英尺(ft),默认是米。
示例:
# 计算天安门到故宫的距离(单位米)
GEODIST shops 天安门 故宫 m # 输出约1420米(实际测试值)
如果其中一个成员不存在,返回nil
。
GEORADIUS
/GEORADIUSBYMEMBER
:附近搜索(重点中的重点)这两个命令是最常用的“附近的人/店”功能实现方式,区别在于:
GEORADIUS
:以指定的经纬度为中心搜索;GEORADIUSBYMEMBER
:以已有成员的坐标为中心搜索(更方便)。语法(以GEORADIUS为例):
GEORADIUS key longitude latitude radius unit
[WITHCOORD] # 返回结果的经纬度坐标
[WITHDIST] # 返回结果到中心的距离
[WITHHASH] # 返回结果的Geohash整数
[COUNT n] # 限制返回数量(近似值)
[ASC|DESC] # 按距离升序/降序排序
示例1:以坐标为中心搜索附近的店
# 搜索天安门附近2公里内的店铺,按距离升序返回前5家,并显示距离和坐标
GEORADIUS shops 116.405 39.905 2 km WITHDIST WITHCOORD ASC COUNT 5
输出示例:
1) 1) "天安门" # 成员名
2) "0.0000" # 到中心的距离(米)
3) 1) "116.40528503417969" # 经度
2) "39.90498910404111" # 纬度
2) 1) "故宫"
2) "1420.0000"
3) 1) "116.41500091552734"
2) "39.91500106535918"
...
示例2:以已有成员为中心搜索(更常用)
假设“故宫”的坐标已知,想找它附近1公里的店:
GEORADIUSBYMEMBER shops 故宫 1 km WITHCOORD DESC
GEOPOS
:获取位置的经纬度语法:
GEOPOS key member [member ...]
作用:返回一个或多个成员的经纬度坐标(如果成员不存在,返回nil
)。
示例:
GEOPOS shops 天安门 # 输出 [116.40528503417969, 39.90498910404111]
GEOHASH
:获取位置的Geohash字符串语法:
GEOHASH key member [member ...]
作用:返回一个或多个成员的Geohash字符串(默认长度11,对应约0.5米精度)。
示例:
GEOHASH shops 天安门 # 输出 "wx4g0s8q3jf9"
GEOSEARCH
(Redis 6.2+):更灵活的范围查询GEOSEARCH
是Redis 6.2新增的命令,支持矩形范围(BYBOX
)和圆形范围(BYRADIUS
),语法类似GEORADIUS
但更强大。
示例:搜索某个矩形范围内的店铺
# 搜索经度116.4~116.5、纬度39.9~39.92的店铺
GEOSEARCH shops FROMLONLAT 116.4 39.9 BYBOX 0.1 0.02 ASC
用户打开APP,定位当前坐标,调用GEORADIUS
搜索3公里内的餐厅,按距离排序,同时用WITHCOORD
返回餐厅坐标,前端渲染地图标记。这是最常见的场景。
用户刷新“附近的人”时,后端用当前用户的经纬度,调用GEORADIUS
搜索1公里内的其他用户,按距离排序,返回头像和昵称。
骑手接单时,计算订单地址与骑手当前位置的距离(GEODIST
),如果超过配送范围(比如5公里),直接拒绝或提示加钱。
后台需要统计某个区域(比如商圈)内的可用单车数量,可以用GEORADIUS
圈选该区域,结合COUNT
参数快速统计。
Redis Geo的距离计算基于Haversine公式(考虑地球曲率),误差约0.5%,日常业务足够用。但如果需要厘米级精度(如测绘),得用专业GIS工具(如PostGIS)。
Redis集群中,ZSET是单键存储,所有Geo数据会被分配到同一个节点。如果数据量超过单节点内存(比如1000万条),会导致内存不足。建议单节点Geo数据量控制在500万以内,或用分片策略(比如按城市分key)。
Geo只能处理“点与圆”的查询(附近搜索),不支持“点与多边形”(如“某园区内所有点”)或“线段相交”等复杂操作。这种需求得用MongoDB(地理索引)或Elasticsearch(geo_point类型)。
GEOADD
和GEORADIUS
的参数顺序是longitude, latitude
(经度在前,纬度在后),写反了会导致位置偏差(比如把北京写成澳大利亚)。
Redis Geo凭借“Geohash+ZSET”的组合,用极低的成本解决了地理位置存储和查询的问题,是中小型项目中“附近功能”的首选方案。掌握GEOADD
、GEODIST
、GEORADIUS
这几个核心命令,就能快速实现大部分需求。
如果是复杂场景(比如多边形查询、高精度计算),可以结合其他工具(如PostGIS),但Redis Geo在性能和易用性上的优势,足以让它成为地理信息处理的“轻量级神器”。
下次做“附近的人”功能时,不妨试试Redis Geo,你会发现原来这么简单!
点赞、收藏+关注,再来不迷路~