Redis GEO 完全攻略
“地理位置神器” —— 附近的人、门店查询、打车路径… 10 米级精度,毫秒级响应!
一、GEO 核心特点
| 特性 | 说明 |
|---|---|
| 底层结构 | Sorted Set + Geohash 编码 |
| 精度 | 约 0.1 米 ~ 10 米(取决于 Geohash 长度) |
| 最大成员数 | 2^32 – 1(约 42 亿) |
| 核心命令 | GEOADD、GEODIST、GEORADIUS |
| 性能 | O(log N + M),N=总成员,M=返回数量 |
| 支持单位 | m、km、mi(英里)、ft(英尺) |
GEO = Sorted Set 的高级封装
二、GEO 命令全表(共 7 个)
| 命令 | 说明 | 示例 |
|---|---|---|
GEOADD key longitude latitude member | 添加位置 | GEOADD cities 116.40 39.90 "北京" |
GEOPOS key member [member ...] | 获取坐标 | GEOPOS cities 北京 |
GEODIST key m1 m2 [unit] | 两点距离 | GEODIST cities 北京 上海 km |
GEOHASH key member [member ...] | Geohash 编码 | GEOHASH cities 北京 |
GEORADIUS key lon lat radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT n] [ASC|DESC] [STORE key] [STOREDIST key] | 圆形范围查询 | GEORADIUS cities 116.40 39.90 100 km |
GEORADIUSBYMEMBER key member radius unit ... | 以成员为中心 | GEORADIUSBYMEMBER cities 北京 200 km |
GEOSEARCH key [FROMMEMBER m] [FROMLONLAT lon lat] [BYRADIUS r unit] [BYBOX w h unit] [ASC|DESC] [COUNT n] [WITHCOORD] [WITHDIST] [WITHHASH] [STORE key] [STOREDIST key] | Redis 6.2+ 高级查询 | GEOSEARCH cities FROMLONLAT 116.40 39.90 BYRADIUS 100 km |
三、核心实战场景
1. 附近的人(社交 App)
# 添加用户位置
GEOADD users:online 116.40 39.90 "user:1001"
GEOADD users:online 116.41 39.91 "user:1002"
# 查询 5km 内的人
GEORADIUS users:online 116.40 39.90 5 km WITHDIST WITHCOORD COUNT 10 ASC
返回示例:
1) 1) "user:1001"
2) "0.0000" # 距离(km)
3) 1) "116.400000000000006"
2) "39.900000000000006"
2) 1) "user:1002"
2) "1.4142"
3) ...
2. 附近门店查询
# 添加门店
GEOADD stores 116.40 39.90 "朝阳店"
GEOADD stores 116.38 39.88 "海淀店"
# 查询最近 3 家门店
GEORADIUSBYMEMBER stores 朝阳店 10 km WITHDIST COUNT 3 ASC
3. 打车路径优化(司机匹配)
# 司机位置
GEOADD drivers:online 116.39 39.89 "driver:001"
# 乘客下单
GEORADIUS drivers:online 116.40 39.90 3 km COUNT 1 ASC
# 返回最近司机
4. 矩形区域查询(Redis 6.2+)
# 查询北京五环内(矩形)
GEOSEARCH cities \
FROMLONLAT 116.10 39.70 \
BYBOX 60 40 km \
WITHCOORD WITHDIST
四、GEO vs 数据库 对比
| 对比项 | Redis GEO | MySQL GEOMETRY |
|---|---|---|
| 查询速度 | 毫秒级 | 秒级 |
| 内存占用 | 极低 | 高 |
| 实时更新 | 支持 | 支持 |
| 复杂查询 | 圆形/矩形 | 支持任意 |
| 推荐场景 | 高并发、简单范围 | 复杂 GIS |
结论:附近的人/门店 → GEO,复杂地图 → PostGIS
五、Geohash 编码原理
北京: 116.40, 39.90 → Geohash: wx4g0s
上海: 121.47, 31.23 → Geohash: wtw3sj
精度对照表
| Geohash 长度 | 误差 | 应用 |
|---|---|---|
| 5 | ±2.5 km | 城市级 |
| 6 | ±0.6 km | 区域级 |
| 7 | ±0.15 km | 街道级 |
| 8 | ±40 m | 建筑级 |
| 9 | ±10 m | 推荐 |
Redis 内部使用 52 位 Geohash,精度 ≈ 0.6 米
六、性能与内存优化建议
| 建议 | 说明 |
|---|---|
| 控制成员数 < 100万 | 避免 ZSet 退化 |
| 用 ID 替代长名称 | user:1001 而非 "张三" |
| 设置 TTL | EXPIRE users:online 300 |
| 分片存储 | users:online:beijing、users:online:shanghai |
用 COUNT 限制返回 | COUNT 50 |
# 分片 + TTL
GEOADD users:online:beijing 116.40 39.90 "u1"
EXPIRE users:online:beijing 300
七、大 key 排查与拆分
# 排查大 GEO
redis-cli --bigkeys
# Biggest zset found "users:online" with 5000000 members
拆分方案:
# 按城市分片
users:online:beijing
users:online:shanghai
users:online:guangzhou
八、一键速查表
# 添加位置
GEOADD loc 116.40 39.90 "北京" 121.47 31.23 "上海"
# 距离
GEODIST loc 北京 上海 km
# 附近查询
GEORADIUS loc 116.40 39.90 100 km WITHDIST WITHCOORD COUNT 10
# 以成员为中心
GEORADIUSBYMEMBER loc 北京 200 km
# 高级查询(Redis 6.2+)
GEOSEARCH loc FROMLONLAT 116.40 39.90 BYRADIUS 50 km COUNT 20
九、客户端代码示例
Python (redis-py)
import redis
r = redis.Redis()
# 添加用户位置
r.geoadd('users:online', (116.40, 39.90, 'user:1001'))
r.geoadd('users:online', (116.41, 39.91, 'user:1002'))
# 查询附近 5km 内用户
nearby = r.georadius('users:online', 116.40, 39.90, 5, unit='km', withdist=True, withcoord=True, count=10, sort='ASC')
for user, dist, coord in nearby:
print(f"用户: {user.decode()}, 距离: {dist}m, 坐标: {coord}")
Go (go-redis)
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
rdb.GeoAdd(ctx, "cities", &redis.GeoLocation{Longitude: 116.40, Latitude: 39.90, Name: "北京"})
dist, _ := rdb.GeoDist(ctx, "cities", "北京", "上海", "km").Result()
fmt.Printf("距离: %.2f km\n", dist)
十、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 精度不够 | Geohash 误差 | 用 WITHCOORD 后端二次计算 |
| 查询慢 | 成员太多 | 分片 + COUNT |
| 内存暴涨 | 无限添加 | 加 TTL + 分片 |
| 坐标错误 | 经纬度反了 | 经度在前,纬度在后 |
十一、GEO 在高并发中的妙用
1. 实时位置更新 + 附近推送
-- Lua 脚本:更新位置 + 查询附近
EVAL "
redis.call('geoadd', KEYS[1], ARGV[1], ARGV[2], ARGV[3])
return redis.call('georadius', KEYS[1], ARGV[1], ARGV[2], 1, 'km', 'WITHCOORD', 'COUNT', 10)
" 1 users:online 116.40 39.90 user:1001
2. LBS 反作弊(位置跳跃检测)
# 记录上次位置
GEOPOS users:last user:1001
# 计算与当前距离 > 100km/h → 异常
完成!你已精通 Redis GEO!
# 一行命令体验 GEO 全功能
redis-cli <<EOF
GEOADD loc 116.40 39.90 "北京" 121.47 31.23 "上海"
GEODIST loc 北京 上海 km
GEORADIUS loc 116.40 39.90 1000 km WITHDIST WITHCOORD COUNT 2
GEOHASH loc 北京
EOF
下一步推荐:
需要我送你:
- “附近的人完整系统(GEO + 在线状态 + 推送)”?
- “门店推荐引擎(GEO + 评分排序)”?
- “实时轨迹回放(GEO + Stream)”?
回复:附近的人 | 门店推荐 | 轨迹回放 即可!