liang24

如果现在要开发一个功能:

要为一款交友App实现查找附近的人,并按距离进行排序。

image

让你来开发这个功能,你会如何实现?

MySQL 不合适

你可能想到,把用户用户的经纬度坐标使用MySQL等关系数据库(用户id,经度x,纬度y)存储,但是该如何计算距离和排序呢?

不可能通过遍历来计算所有的用户和目标用户的距离,然后再进行排序,因为这个计算量太大了,性能指标肯定无法满足。

GeoHash的编码方法

为了能高效地对经纬度进行比较,Redis 采用了业界广泛使用的 GeoHash 编码方法,这个方法的基本原理是“二分区间,区间编码”。

关于 GeoHash 参考 https://www.cnblogs.com/LBSer/p/3310455.html

简单来说,GeoHash 能够将二维的经纬度转换为字符串,然后位置就能够直接进行比较和范围查询了。

Redis 中 Geo 的使用

命令 说明  可用版本 时间复杂度
GEOADD 添加位置的经纬度 >= 3.2.0 O(logN)
GEOPOS 返回位置的经纬度 >= 3.2.0 O(logN)
GEODIST 返回两个位置的距离 >= 3.2.0 O(logN)
GEORADIUS 返回与指定位置距离距离不大于指定值的位置的经纬度 >= 3.2.0 O(N+logM)
GEORADIUSBYMEMBER 这个命令和 GEORADIUS 命令一样 >= 3.2.0 O(logN+M)
GEOHASH 返回位置的 GeoHash 值 >= 3.2.0 O(logN)

 

 示例

假设用户ID是33,经纬度位置是(116.054579, 39.030452),我们可以用一个 GEO 集合保存所有用户的经纬度,集合 key 是 users:locations。执行下面的这个命令,就可以把ID号为33的用户的当前经纬度位置存入GEO集合中:

GEOADD users:locations 116.034579 39.030452 33

 

当用户想要寻找自己附近的人时,就可以使用 GEORADIUS 命令。

例如,执行下面的命令,Redis 会根据输入的用户的经纬度信息(116.054579, 39.030452),查找以这个经纬度为中心的5公里内的用户信息。

GEORADIUS users:locations 116.054579 39.030452 5 km ASC COUNT 10

 

总结

在一个地图应用中,车的数据、餐馆的数据、人的数据可能会有百万千万条,如果使用 Redis 的 Geo 数据结构,它们将全部放在一个 Sorted Set 集合中。在 Redis 的集群环境中,集合可能会从一个节点迁移到另一个节点,如果单个 key 的数据过大,会对集群的迁移工作造成较大的影响,在集群环境中单个 key 对应的数据量不宜超过 1M,否则会导致集群迁移出现卡顿现象,影响线上服务的正常运行。

所以,这里建议 Geo 的数据使用单独的 Redis 实例部署,不使用集群环境。

如果数据量过亿甚至更大,就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按市拆分,在人口特大城市甚至可以按区拆分。这样就可以显著降低单个 Sorted Set 集合的大小。

参考资料

相关文章: