朝花夕拾

A Development Engineer, a Life Liver, a Hope Holder

实际场景

假如我们开发了一款APP,用户数量很大,APP定时会采集上报海量的轨迹点。这时,如果我们需要查询给定经纬度POI指定范围的用户信息列表。这就可以用到本文的范围查找了。

技术难点

1. 轨迹点是海量数据
2. 用户的轨迹点实时更新

解决方案

方案一:

1. 使用geohash算法将地球切分为若干块(当然,对于国内大部分APP场景来说,只对中国进行切分就可以了,这样可以减少切割出来HASH块的数量)
2. 我们在内存(或者第三方内存存储引擎)中维护一个以geohash为KEY,用户轨迹点信息列表为VAL的的数据结构。
3. 当查询过来时,我们首先使用查询经纬度和查询半径构建一个查询矩形。判断这个查询矩形与geohash块的空间关系。获取到所有与查询矩形存在相交关系的所有geohash列表。
4. 从内存缓存列表中查询最新的用户轨迹列表,对于被查询区域完全覆盖的geohash块,返回所有的用户列表,对于局部覆盖(相交)的区域,要进行点点距离过滤。只将那些满足条件的用户点数据范围即可。

例如: 查询我附近1km内的微信好友(这里只是举例,微信LBS的具体实现与本文无关)。那么首先判断以我为中心,1km的圆与那些geohash相交。当然了,为了方便判断,我们往往选择圆或者多边形的最小外包矩形作为查询区域。如果被查询区域覆盖的geohash有wk3a, wk3b,与查询区域相交的区域为wk3c, wk3d。这时

wk3a: 张三(lon1, lat1)  李四(lon2, lat2)
wk3b:王五(lon3, lat3)
wk3c:赵六(lon4, lat4)
wk3d:王麻子(lon5, lat5)

那么,根据上面的算法逻辑,我们首先全部返回wk3a, wk3b中的用户,张三,李四,王五。之后呢,我们会分别判断赵六,王麻子与查询轨迹的距离是否超出1km。入股赵六具我1.2km, 王麻子据我0.9km。那么最后的返回结果是:张三,李四,王五,王麻子。

上面计算方案的挑战
  1. 用户位置实时更新,这一刻用户在geohashA,下一刻就可能在geohashB了。所以,这时,就需要我们把用户在geohashA的数据删除,在geohashB中加入。说白了,我们需要维护用户的实时位置。
  2. geohash级别的选择。对于5级编码,误差应该在1km左右,4级误差应该是20km左右。中间存在比较大的跨度,这时,就需要考虑到底怎么切分了。切分小了,一次查询就可能覆盖和相交更多的geohash块,这样会增加用户位置是否满足条件的计算量。切分大了,一个hash块内就可能存入了大量的数据。比如像微信这种高频C段APP,20KM内可能有非常多的用户。一个块存这么多数据,尤其是当这些数据存储在第三方引擎的时候,一次的网络交互IO式一个非常大的挑战。

方案二:

思路方案一类似,主要是改变的地图编码的实现方式。方案一使用geohash算法,正如上面提到的,geohash不同编码登记存在很大的范围落差。比如5级编码范围是1km,4级编码就是20km了。中间存在很大的落差。所以,我们需要实现更精细力度的切分。目前我采用了1km网格的切分规则。具体的实施方案是,从中国最西北角开始切分,向东,向南每一公里切除一个小格子。通过这种方式实现了对网格大小的控制,实现对方案1的优化