【附近的人】实现方案
【附近的人】实现方案
方案一:Redis Commands: Geography Edition
自Redis 3.2开始,Redis基于geohash和有序集合提供了地理位置相关功能。Redis Geo模块包含了以下6个命令:
-
GEOADD: 将给定的位置对象(纬度、经度、名字)添加到指定的key;
-
GEOPOS: 从key里面返回所有给定位置对象的位置(经度和纬度);
-
GEODIST: 返回两个给定位置之间的距离;
-
GEOHASH: 返回一个或多个位置对象的Geohash表示;
-
GEORADIUS: 以给定的经纬度为中心,返回目标集合中与中心的距离不超过给定最大距离的所有位置对象;
-
GEORADIUSBYMEMBER: 以给定的位置对象为中心,返回与其距离不超过给定最大距离的所有位置对象。
-
GEOSEARCH: 此命令代替现在6.2已弃用的GEORADIUSand GEORADIUSBYMEMBER。
其中,组合使用GEOADD和GEORADIUS可实现“附近的人”中“增”和“查”的基本功能。要实现微信中“附近的人”功能,可直接使用GEORADIUSBYMEMBER命令。其中“给定的位置对象”即为用户本人,搜索的对象为其他用户。不过本质上,GEORADIUSBYMEMBER = GEOPOS + GEORADIUS,即先查找用户位置再通过该位置搜索附近满足位置相互距离条件的其他用户对象。
优点: 效率高,API丰富。
缺点: 维护需要对API有一定的熟悉度,数据流程的可理解性相对数据库差。如果需要查询其他业务数据需要做 in 操作。
方案二:Elasticsearch GEO
ES提供了很多地理位置的搜索方式 :
geo_bounding_box: 找出落在指定矩形框中的点。
geo_distance: 找出与指定位置在给定距离内的点。
geo_distance_range: 找出与指定点距离在给定最小距离和最大距离之间的点。
优点: 天然支持,性能很优。
缺点:需要投入时间研究一下特性,以及存储,对后续维护人员有一定要求。如果需要查询其他业务数据需要做 in 操作。
方案三:Mysql :Sql 直接进行计算
SELECT *, 6378.138 * 2 * ASIN( SQRT( POW( SIN( ( 40.0497810000 * PI() / 180 - lat * PI() / 180 ) / 2 ), 2 ) + COS(40.0497810000 * PI() / 180) * COS(lat * PI() / 180) * POW( SIN( ( 116.3424590000 * PI() / 180 - lon * PI() / 180 ) / 2 ), 2 ) ) ) AS juli FROM customer ORDER BY juli ASC
优点: 开发简单,维护容易。
缺点:不走索引,当数据量达到一定程度,需要进行优化。
方案四:Mysql :GeoHash 函数(需要升级到mysql 5.7)
优点: 函数直接调用,生成目标hash、根据hash获取经纬度。可以直接连表查询业务数据。
缺点:不支持范围查询函数,需要自行处理周边8点的问题,需要补充geo的算法。
题外话:GEO 算法 (详见:https://www.jianshu.com/p/2fd0cf12e5ba)
GeoHash是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串
-
将经纬度转换为01的数字;
例如,我们将东经西经[-180,180]平均划分为左右两块,那么(39.923201, 116.390705)这么个点只看经度的话是在[0,180],然后我们定义点在[-180,0]为0(西边/左边),[0,180]为1(东边/右边)。以此类推,继续将点在[0,180]细分,点在[0,90]为0,点在[0,45]为0...那么会获得经度的二进制数:
11010010110001000100
同样的,将点在纬度上细分...会得到纬度的二进制数:
10111000110001111001
那么怎么现在需要将经度和纬度的数据组合,那么就可以将单数表示为经度,双数表示为纬度进行组合得到新的二进制数:
11100 11101 00100 01111 00000 01101 01011 00001
GeoHash算法可以简单理解为x步:组合得到新的二进制数:
11100 11101 00100 01111 00000 01101 01011 00001
-
按照Base32进行编码;
Base32编码表的其中一种如下,是用0-9、b-z(去掉a, i, l, o)这32个字母进行编码。具体操作是先将上一步得到的合并后二进制转换为10进制数据,然后对应生成Base32码。需要注意的是,将5个二进制位转换成一个base32码。上例最终得到的值为wx4g0ec1
Geohash比直接用经纬度的高效很多,而且使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。
GeoHash用一个字符串表示经度和纬度两个坐标。在数据库中可以实现在一列上应用索引(某些情况下无法在两列上同时应用索引)
GeoHash表示的并不是一个点,而是一个矩形区域。
GeoHash编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。
编码越长,表示的范围越小,位置也越精确。因此我们就可以通过比较GeoHash匹配的位数来判断两个点之间的大概距离。
为什么不用base64?
首先,是可以使用base64的,但是可能是考虑性能和空间的平衡。base32相比base64数据量会增加一点,长度也会更长一点。
采用方案:Redis
目前,由于用户不是很多所以还好。但是性能测试是发现了问题的,就是如果Redis保存的数据太多的话(如几十万数据),那么搜索是非常消耗性能的,而Redis执行命令的线程又是单线程的,那么很容易会阻塞其他项目的redis操作。所以,要考虑提前对用户进行分类存储,例如使用cloudflare的国家记录,进行提前分类。那么就可以进一步区分用户进行搜索。
本文作者:rongbu2
本文链接:https://www.cnblogs.com/gronbu1/p/16823494.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步