文章目录
- 背景
- GeoHash 算法
- redis中的GeoHash 算法
- 基本使用
- 增加
- 距离
- 获取元素位置
- 获取元素的 hash 值
- 附近的元素
- 注意事项
- 原理
背景
如果我们有需求需要存储地理坐标,为了满足高性能的矩形区域算法,数据表需要在经纬度坐标加上双向复合索引 (x, y),这样可以最大优化查询性能。但是数据库查询性能毕竟有限,如果「附近的人」查询请求非常多,在高并发场合,这可能并不是一个很好的方案。
GeoHash 算法
业界比较通用的地理位置距离排序算法是 GeoHash 算法, Redis 也使用 GeoHash 算
法。 GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。当我们想要计算「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。
那这个映射算法具体是怎样的呢?它将整个地球看成一个二维平面,然后划分成了一系列正方形的方格,就好比围棋棋盘。所有的地图元素坐标都将放置于唯一的方格中。方格越小,坐标越精确。然后对这些方格进行整数编码,越是靠近的方格编码越是接近。通过这种方式可以还原出元素的坐标,整数越长,还原出来的坐标值的损失程度就越小。对于「附近的人」这个功能而言,损失的一点精确度可以忽略不计。
redisGeoHash__11">redis中的GeoHash 算法
GeoHash 算法会继续对这个整数做一次 base32 编码 (0-9,a-z 去掉 a,i,l,o 四个字母) 变成一个字符串。在 Redis 里面,经纬度使用 52 位的整数进行编码,放进了 zset 里面, zset的 value 是元素的 key, score 是 GeoHash 的 52 位整数值。 zset 的 score 虽然是浮点数,但是对于 52 位的整数值,它可以无损存储。
在使用 Redis 进行 Geo 查询时,我们要时刻想到它的内部结构实际上只是一个
zset(skiplist)。通过 zset 的 score 排序就可以得到坐标附近的其它元素 (实际情况要复杂一些,不过这样理解足够了),通过将 score 还原成坐标值就可以得到元素的原始坐标。
基本使用
增加
geoadd 指令携带集合名称以及多个经纬度名称三元组,注意这里可以加入多个三元组
距离
geodist 指令可以用来计算两个元素之间的距离,携带集合名称、 2 个名称和距离单位。
获取元素位置
geopos 指令可以获取集合中任意元素的经纬度坐标,可以一次获取多个。
获取的经纬度坐标和 geoadd 进去的坐标有轻微的误差,原因是 geohash 对
二维坐标进行的一维映射是有损的,通过映射再还原回来的值会出现较小的差别。对于「附近的人」这种功能来说,这点误差根本不是事。
获取元素的 hash 值
geohash 可以获取元素的经纬度编码字符串。
附近的元素
georadiusbymember 指令是最为关键的指令,它可以用来查询指定元素附近的其它元
素,它的参数非常复杂。
除了 georadiusbymember 指令根据元素查询附近的元素, Redis 还提供了根据坐标值来查询附近的元素,这个指令更加有用,它可以根据用户的定位来计算「附近的车」,「附近的餐馆」等。它的参数和 georadiusbymember 基本一致,除了将目标元素改成经纬度坐标值。
注意事项
在一个地图应用中,车的数据、餐馆的数据、人的数据可能会有百万千万条,如果使用Redis 的 Geo 数据结构,它们将全部放在一个 zset 集合中。在 Redis 的集群环境中,集合可能会从一个节点迁移到另一个节点,如果单个 key 的数据过大,会对集群的迁移工作造成较大的影响,在集群环境中单个 key 对应的数据量不宜超过 1M,否则会导致集群迁移出现卡顿现象,影响线上服务的正常运行。
所以,这里建议 Geo 的数据使用单独的 Redis 实例部署,不使用集群环境。
如果数据量过亿甚至更大,就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按
市拆分,在人口特大城市甚至可以按区拆分。这样就可以显著降低单个 zset 集合的大小。
原理
geo内部是基于zset来实现的,并且只使用一个zset。所以使用时要注意他的存储量。