文章目录
-
- [**Redis GEO 底层实现(结合源码分析)**](#Redis GEO 底层实现(结合源码分析))
-
- [**(1) GEO 命令的底层调用**](#(1) GEO 命令的底层调用)
- [**(2) Geohash 编码**](#(2) Geohash 编码)
- [**(3) 存储结构**](#(3) 存储结构)
- [**2. 为什么纬度限制在 85°?**](#2. 为什么纬度限制在 85°?)
-
- [**(1) Geohash 的边界问题**](#(1) Geohash 的边界问题)
- [**(2) 地球的投影变形**](#(2) 地球的投影变形)
- [**(3) 源码中的限制**](#(3) 源码中的限制)
- [**3. 总结**](#3. 总结)
Redis GEO 底层实现(结合源码分析)
(1) GEO 命令的底层调用
Redis 的 GEO 相关命令(如 GEOADD
、GEODIST
、GEORADIUS
)最终都会调用 zset
的操作。例如:
GEOADD
实际上是调用了zadd
,但会先对经纬度进行编码。GEORADIUS
会先计算目标区域的 Geohash 范围,然后查询 Sorted Set 中符合条件的成员。
(2) Geohash 编码
Redis 使用 Geohash 算法将经纬度编码成一个 52 位整数(score
),具体逻辑在 geohash.c
中:
plain
// redis/src/geohash.c
uint64_t geohashEncodeWGS84(double longitude, double latitude, uint8_t step) {
return geohashEncode(/* bounds */ -180, 180, -90, 90, longitude, latitude, step);
}
其中:
longitude
范围是[-180, 180]
latitude
范围是[-90, 90]
step
表示 Geohash 的精度(Redis 默认使用 26 位 表示经度,26 位 表示纬度,共 52 位)
(3) 存储结构
GEO 数据在 Redis 中的存储形式:
plain
ZSET:
Key: "cities:geo"
Member: "Beijing"
Score: 942457884123456789 (Geohash 编码后的 52 位整数)
2. 为什么纬度限制在 85°?
在 Redis 的 GEO 实现中,纬度范围被限制在 [-85°, 85°] ,而不是理论上的 [-90°, 90°]
。原因如下:
(1) Geohash 的边界问题
Geohash 使用 Z 阶曲线(Z-order curve) 对二维坐标进行编码。在极地附近(纬度接近 ±90°),经度的变化会导致 Geohash 值剧烈波动,导致:
- 相邻地理位置的 Geohash 值差异巨大,破坏局部性(locality)。
- 范围查询(GEORADIUS)效率降低,因为 Geohash 无法有效表示极地区域。
(2) 地球的投影变形
- 在高纬度地区(如北极/南极附近),墨卡托投影(Mercator Projection) 会导致严重的形变,使得距离计算不准确。
- Redis 使用 Haversine 公式 计算球面距离,但高纬度地区的计算误差会显著增加。
(3) 源码中的限制
在 Redis 的 geo.c
中,可以看到对纬度的限制:
plain
// redis/src/geo.c
int decodeGeohash(double bits, double *xy) {
// ...
if (xy[1] > 85.05112878 || xy[1] < -85.05112878) {
return 0; // 纬度超出范围
}
// ...
}
这里的 85.05112878°
是 Web Mercator 投影 的最大有效纬度(由 arctan(sinh(π))
计算得出)。
3. 总结
关键点 | 说明 |
---|---|
底层数据结构 | 使用 Sorted Set(zset)存储,Geohash 编码为 score |
Geohash 编码 | 52 位整数(26 位经度 + 26 位纬度) |
纬度限制 | 限制在 [-85°, 85°] ,避免极地 Geohash 计算问题 |
原因 | Geohash 局部性破坏、投影变形、计算误差 |
因此,Redis GEO 的实现既利用了 Sorted Set 的高效查询特性,又通过 Geohash 和纬度限制保证了地理计算的准确性和性能。