深入Redis(八)GeoHash

GeoHash

Redis 3.2版本后增加了地理位置模块,所以可以通过Redis来实现附近的XX这样的功能了。

用数据库来算附近的人

地图元素的位置数据使用二维经纬度表示,经度范围(-180, 180],纬度范围(-90, 90],纬度正负以赤道为界,北正南负,经度正负以本初子午线为界,东正西负。

当两个元素距离不是很远时,可直接用勾股定理算距离,但要注意的是经纬度坐标的密度不一样,需要按一定系数比加权求勾股定理。

如果要计算附近的人,也就是给定一个元素的坐标,然后计算其周围的元素,并按照距离排序,该如何做呢?

首先,不可能通过遍历来计算所有的元素和目标元素的距离,然后再进行排序,这样计算量太大,性能指标肯定无法满足。

常规做法是通过矩形区域来限定元素的数量,然后对区域内的元素进行全量距离计算再排序,这样可以减少计算量。

如何划分矩形区域呢?指定一个半径r,用一条SQL圈出来,如果对筛选出的结果不满意,增加或减少r。

select id from positions where x0-r < x < x0+r and y0-r < y < y0+r;

为了满足高性能的矩形区域算法,数据表需要再经纬度坐标加上双向复合索引(x,y),这样可以最大优化查询性能,但数据库查询性能毕竟有限,在高并发场合并不是一个很好的方案。

GeoHash算法

业界通用的地理位置距离排序算法是GeoHash算法,Redis也使用该算法。

GeoHash算法将二维的经纬度数据映射到一维整数,这样所有元素都映射到一条线上,距离靠近的二维坐标映射到一维后点之间的距离也会很近,要计算附近的人时,获取目标点附近的点就行了。

那么这个映射算法是怎样的呢?

它将整个地球看成一个二维平面,并划分成一系列的正方形方格,所有地图元素坐标都放在唯一的方格中,方格越小,坐标越精确。然后对这些方格进行整数编码,这个编码方法是base32,越靠近的方格,其整数编码也越接近。

在Redis内,经纬度使用52位的整数进行编码,放进zset中,zset的value是元素的key,score是GeoHash的52位整数。zset的score虽然是浮点数,但可以无损存储52位整数。

在查询时,通过zset的score排序就可以得到坐标附近的其他元素,将score还原成坐标值即可获得原始坐标。

Redis的Geo指令基本使用

Redis提供的Geo指令只有6个,必须注意的一点是,它就是一个普通的zset结构。

增加

geoadd [setname] [经度] [纬度] [名称],后面的三元组可以有多个,返回整数。

删除可通过zrem key member来操作,Geo并没有提供单独的方法。

距离

geodist [setname] [名称1] [名称2] [单位],单位可以是km、m、ml、ft(千米、米、英里、尺),返回字串。

获取元素位置

geopos [setname] [名称],可以获取多个元素位置,返回列表,坐标会有轻微误差。

获取元素的hash值

geohash [setname] [名称],获取元素的经纬度编码字符串,base32编码,可以在http://geohash.org/${hash}中直接定位。

附近的公司

georadiusbymember,该命令参数复杂。

127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 asc    # 20km以内最多3个元素按距离正向排序,不排除自身
1) "ireader"
2) "juejin"
3) "meituan"
127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 desc    # 20km以内最多3个元素按距离反向排序,排除自身
1) "jd"
2) "meituan"
3) "juejin"

另有三个可选参数,withcoord、withdist、withhash,其中withdist用于显示距离,即返回列表中包括距离值。

根据坐标查询附近的元素

georadius,用法同georadiusbymember,将名称更换为经纬度即可。

注意事项

使用Geo算法,他们将被放在一个zset中,在集群环境中会导致单个key数据过大,对集群迁移工作造成较大影响,集群环境中单个key对应数据量不应超过1M,否则会导致迁移卡顿,影响线上服务。

建议Geo的数据使用单独的Redis实例部署,不要使用集群环境,如果数据量太大,可对Geo的数据按国家、省、市、区拆分。

猜你喜欢

转载自www.cnblogs.com/ikct2017/p/9499453.html