geohash简单应用-面对面匹配好友

2,826 阅读4分钟

背景

我们最近有个项目需求,实现面对面匹配好友功能。

预想的场景是两个用户面对面的时候可以很方便地通过摇一摇或者其它互动方式快速找到对方来达成好友。

关键点

后端服务基于GPS位置快速匹配附近的人。

方案

1、全量遍历

第一时间想到的方式是直接计算用户的距离,若用户距离在要求的范围之内,则这两个用户达成匹配。

问题

这个方案需要遍历全量用户,计算量很大,匹配的效率很低。

2、GeoHash

geohash的思想是将二维的经纬度转换成一维的字符串,geohash有以下三个特点:

  • 字符串越长,表示的范围越精确。
  • 字符串相似的表示距离相近,利用字符串的前缀匹配,可以查询附近的地理位置。这样就实现了快速查询某个坐标附近的地理位置。
  • geohash计算的字符串,可以反向解码出原来的经纬度。

geohash精度信息如下,字符串越长,表示的范围越精确。

image-20190508175753256

2.1、Redis GeoHash

Redis在3.2版本之后提供了geo地理位置的支持。我们可以很方便地通过Redis维护地理位置,快速查询某个坐标附近特定距离内的地理位置。

问题

在一般场景里面商店的地理位置基本不变,信息也是长期有效。所以通过Redis维护所有的地理位置也比较合适。可是在我们的需求场景,好友匹配是个瞬时场景。用户的匹配请求本身是有时效性的,若直接使用Redis的GeoHash来维护地理位置还需要考虑信息的删除等情况,反而变得复杂。

2.2、Redis Hash+GeoHash

我们可以直接使用Redis的Hash结构来维护所有待匹配的用户。field为用户ID,value为用户匹配相关的信息,其中包含用户坐标对应的GeoHash。然后通过定时任务获取所有待匹配的用户信息,根据geohash字段排序,再计算相邻用户是否在要求的距离之内,若条件符合则可以达成匹配。

关键点

两个用户在彼此附近,那他们的geohash字符串会很相似,在按geohash排序时,这两个用户信息下标也会相邻。

3、R树

R树是用来做空间数据存储的树状数据结构。在外卖场景很多时候会使用R树来维护商家的配送范围,为用户筛选出在其附近的商家。在本次需求R树不适用。

问题

最终我们采用Redis Hash+GeoHash的方案,在实施过程中也有其它问题需要考虑。

1、用户全量集合大

对用户全量集合进行拆分,先按用户城市来做一次分桶。大概率上只有同一个城市的用户才可能出现在附近。另外用户匹配请求有效时间通常比较短,所以经过分桶之后全量集合大小应该可控。

  • 可以根据用户GPS信息或者IP地址来估算用户所在城市;
  • 可能会出现城市边缘两个附近的用户一直没办法匹配上的情况,这种情况概率很低,可以忽略;

2、用户集合的并发处理

在我们进行匹配任务的同时用户可能会取消匹配。在进行匹配操作时,我们会获取全量的用户集,并删除对应的全量集数据(通过pipeline方式实现原子性)。所以此时用户取消匹配会失败,简化逻辑。若最后用户没有匹配上,再将相应的信息批量回写到用户集合即可。

3、坐标系不统一

在测试过程中发现Android端和iOS端会出现坐标系不统一的情况,这种情况下因出现位置偏移导致无法匹配。可以通过坐标系转换的方式统一坐标系。

4、GeoHash的问题

如下图所示,图片来自网络。边缘附近的点,黄色的点要比黑色的点更加靠近红点,但是由于黑点跟红点的GeoHash前缀匹配数目更多,因此得到黑点更加靠近红点的结果 。一般的处理方式是通过筛选周围8个区域内的所有点,然后计算距离得到满足条件结果。

在我们的匹配定时任务里面,GeoHash只是作为距离远近的参考依据,理论上相邻区域的用户也会在相邻下标位置,然后再通过计算距离判断条件是否符合。

image-20190508194943363

小结

我们通过GeoHash来实现面对面匹配功能。GeoHash将二维的经纬度转换成一维的字符串,我们再对一维的字符串进行排序操作,这样可以快速找到相邻的用户信息。

参考资料

GeoHash简介

Geohash精度和原理