咱们最近有个项目需求,实现面对面匹配好友功能。后端
预想的场景是两个用户面对面的时候能够很方便地经过摇一摇或者其它互动方式快速找到对方来达成好友。网络
关键点数据结构
后端服务基于GPS位置快速匹配附近的人。并发
第一时间想到的方式是直接计算用户的距离,若用户距离在要求的范围以内,则这两个用户达成匹配。测试
问题.net
这个方案须要遍历全量用户,计算量很大,匹配的效率很低。3d
geohash的思想是将二维的经纬度转换成一维的字符串,geohash有如下三个特色:cdn
geohash精度信息以下,字符串越长,表示的范围越精确。blog
![]()
Redis在3.2版本以后提供了geo地理位置的支持。咱们能够很方便地经过Redis维护地理位置,快速查询某个坐标附近特定距离内的地理位置。排序
问题
在通常场景里面商店的地理位置基本不变,信息也是长期有效。因此经过Redis维护全部的地理位置也比较合适。但是在咱们的需求场景,好友匹配是个瞬时场景。用户的匹配请求自己是有时效性的,若直接使用Redis的GeoHash来维护地理位置还须要考虑信息的删除等状况,反而变得复杂。
咱们能够直接使用Redis的Hash结构来维护全部待匹配的用户。field为用户ID,value为用户匹配相关的信息,其中包含用户坐标对应的GeoHash。而后经过定时任务获取全部待匹配的用户信息,根据geohash字段排序,再计算相邻用户是否在要求的距离以内,若条件符合则能够达成匹配。
关键点
两个用户在彼此附近,那他们的geohash字符串会很类似,在按geohash排序时,这两个用户信息下标也会相邻。
R树是用来作空间数据存储的树状数据结构。在外卖场景不少时候会使用R树来维护商家的配送范围,为用户筛选出在其附近的商家。在本次需求R树不适用。
最终咱们采用Redis Hash+GeoHash的方案,在实施过程当中也有其它问题须要考虑。
对用户全量集合进行拆分,先按用户城市来作一次分桶。大几率上只有同一个城市的用户才可能出如今附近。另外用户匹配请求有效时间一般比较短,因此通过分桶以后全量集合大小应该可控。
在咱们进行匹配任务的同时用户可能会取消匹配。在进行匹配操做时,咱们会获取全量的用户集,并删除对应的全量集数据(经过pipeline方式实现原子性)。因此此时用户取消匹配会失败,简化逻辑。若最后用户没有匹配上,再将相应的信息批量回写到用户集合便可。
在测试过程当中发现Android端和iOS端会出现坐标系不统一的状况,这种状况下因出现位置偏移致使没法匹配。能够经过坐标系转换的方式统一坐标系。
以下图所示,图片来自网络。边缘附近的点,黄色的点要比黑色的点更加靠近红点,可是因为黑点跟红点的GeoHash前缀匹配数目更多,所以获得黑点更加靠近红点的结果 。通常的处理方式是经过筛选周围8个区域内的全部点,而后计算距离获得知足条件结果。
在咱们的匹配定时任务里面,GeoHash只是做为距离远近的参考依据,理论上相邻区域的用户也会在相邻下标位置,而后再经过计算距离判断条件是否符合。
咱们经过GeoHash来实现面对面匹配功能。GeoHash将二维的经纬度转换成一维的字符串,咱们再对一维的字符串进行排序操做,这样能够快速找到相邻的用户信息。