Redis实战篇(四)基于GEO实现查找附近的人功能
如果现在要开发一个功能:
要为一款交友App实现查找附近的人,并按距离进行排序。
让你来开发这个功能,你会如何实现?
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 集合的大小。