当前位置:网站首页 > 技术博客 > 正文

geohm c



前言

Hello,小伙们好,我是阿沐!一个喜欢通过实际项目实践来分享技术点的程序员!

你们有没有遇到被面试官嘲讽的场景;之前有位刚毕业的小学弟在上海魔都某某某大公司面试,二面主要是问了关于redis的相关知识点,回答的也是磕磕绊绊的,其中一个问题是如何实现搜索附近人加好友功能;想跟小伙伴们一起分享一起探讨下。如果有不正确的地方,欢迎指正批评,共同进步~~~

面试官的主要考点

当你看到面试官想考验你这些知识点的时候;你在面试官问的过程中,就脑海在飞快的转动着,组合一系列的数据场景准备应战。

Geohash概念介绍

geohash就是一种地理位置编码。用来查询附近的POI(POI是“Point of Interest”的缩写,中文可以翻译为“兴趣点”。在地理信息系统中,一个POI可以是一栋房子、一个商铺、一个邮筒、一个公交站等),也是一种算法的思想

通过将地球看成一个二维的平面图,然后将平面递归切分成更小的模块,然后将空间经纬度数据进行编码生成一个二进制的字符串,再通过base32将其转换为一个字符串。最终是通过比较geohash的值的相似程度查询附近目标元素。

Geohash能实现什么功能?

Geohash 算法原理

讲真地,当我要准备讲解原理和算法的时候,也很纠结,毕竟算法不是我的强项且百度一下千篇一律;并且都是大神级人物总结,且不敢妄自菲薄,所以还是站在前人的肩膀上来理解下geohash原理与算法。

附近的人”也就是常说的 LBS (Location Based Services,基于位置服务),它围绕用户当前地理位置数据而展开的服务,为用户提供精准的增值服务。

“附近的人” 核心思想如下:

① 以“自己”为中心,搜索附近的用户

② 以“自己”当前的地理位置为准,计算出别人和 “我” 之间的距离

③ 按“自己”与别人距离的远近排序,筛选出离我最近的用户或者商店等

那么我们按照我们以往的操作方式:我们在搜索附近人时,会将整个站点的用户信息塞到一个list中,然后去遍历所有节点,检查哪一个节点在自己的范围内;时间复杂度就变成了n*m(n搜索次数,m用户数据)这谁顶得住啊,就是全部放redis一旦数据量上来也顶不住,搜索效率极低。

附近人

上面大家应该可以看出来吧,其实就是把自己的坐标作为一个中心;哎,然后我们要找到围绕我们方圆10公里以内的附近小伙伴:

X轴:我们可以看做是纬度,左半边范围是-180°~~ 0°;右半边是0° ~~ 180° Y轴:我们可以看做是经度,上半边范围是0° ~~ 90°;下半边是-90° ~~ 0° 原点:我们就看做是(0,0)位置;就好像是我们自己的位置

举例子

假如我们现在地点在广州字节跳动有限公司(广州市天河区珠江东路6号)经纬度是:113.(经度),23.(纬度)

geohash实质就是将经纬度进行二分法的形式落于相对应的区间中,越分越细一直到趋近于某一个临界值,那么分的层数越多,精确度越准确。

原则是:左区间标注 0;右区间标注 1

例如我们用代码实现上面经纬度二分法生成的二进制:

从上面的脚本实现来看是不是更清晰了呢?那么我在使用lua语言给大家实现展示一下,本身原理基本一致:

我们可以实际手动打一遍执行下,聪明的朋友应该看到一个函数php()和lua中()私有方法,这个是干嘛用的,通过方法注释我们看到大概意思是我们二分层数:

🤡 大家是不是还会有疑问,我的天啊,这个变量哪里来的呀?是不是感觉跟奇怪?下面看下图GeoHash Base32编码长度与精度表格展示:

在纬度相等的情况下:
在经度相等的情况下:

现在是不是一目了然了,规定Geohash长度最大长度是12层,每一层对应位数。哦哦哦!!!原来是这样来的呀,是不是超级简单。我们得到了经纬度的编码之后要干什么?肯定要对其进行组码了:

组合编码:

通过上述计算,纬度产生的编码为,经度产生的编码为。偶数位放经度,奇数位放纬度,把2串编码组合生成新串:。是不是又有点懵了,它是如何组合的呢?下面一张图带你明白它的组合流程:

经纬度编码组合

有木有豁然开朗的赶脚,组合就是这么简单;可能有的小伙伴在之阅读很多文章有点迷惑,奇偶交叉组合怎么组合的会有点一头雾水;但是仔细看来就是这么简单啦!

代码实现编码组合

真的到了这时候,将经纬度转GeoHash字符串的工作已经完成了一半了,是不是赶脚超级无敌很简单~

base32算法用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,首先将转成十进制,对应着十进制对应的编码就是可以组合成字符串了。我相信学习过编程的小伙们肯定都用过base64编码加解密,但是不确定是否去研究看过怎么加解密的。base32和base64的区别就在于:。

编码组合成十进制再转换为字符串

原理:将组合之后的二进制序列每5位一组进行拆分转换;例如:

是不是更加清晰明了了,照着对就可以啦;下面是编码实现获取组合字符串(解码这里就不写了,文末会有代码地址):

注意将经纬度转换成二进制序列的过程中,转换的次数越多,所表示的精度越细,标识的范围越小

Geohash 实战系列

基于mysql实现附近人查询

创建一个用户地理位置上报的表用来存放的经、纬度属性:

① 第一种方案:查询附近符合要求的附近人,然后再算距离(实习时用过)

② 第二种方案:直接通过复杂的sql语句计算结果(实习时用过)

③ 第三方案:mysql的四个内置函数

注意:单单基于 mysql 实现 “附近的人”;优点:简单,一张表存储经纬度即可;缺点:数据量比较小时可以使用,同时可以配合redis缓存查询结果集,效果也是ok的;但是数据量比较大的时候,我们可以看到需要大量的计算两个点之间的距离,对性能有很大的影响。(不推荐使用了)

基于mysql + GeoHash实现附近人查询

① 设计思路

在原本存储用户经纬度的表中:入库时计算经纬度对应的geohash字符串存储到表中;那么存储时需要我们明确字符串的长度。

那么我们查询的时候就不需要用经纬度查询,可以这样:进行模糊查询,查询到结果集在通过经纬度计算距离;然后筛选指定的距离例如1000m以内,则是附近人。

② 代码实现

③ 问题分析

小伙们都知道geohash算法是将地图划分为多个矩形块,然后在对矩形块编码得到geohash字符串。那是不是会出现这种情况,明明这个人离我很近,但是我们又不在同一个矩形块里,那是不是我搜索的时候就搜不到这个人,那不是血亏(万一是一个漂亮的妹子呢)

④ 解决方案

我们在搜索时,可以根据当前的编码计算出附近的8个区域块的geohash码,然后全部拿到,再一个个的筛选比较;这样那个妹子不就被我搜到加好友了嘛!

⑤ 再次实现

然后查询的结果集进行距离计算,过滤掉大于指定距离的附近人。

基于redis + GeoHash实现附近人查询

① 设计思路

1、查找select指令操作:

2、添加insert指令操作:

大家是不是感觉到有点奇怪,怎么这次的redis命令的时间复杂度都是O(log(n)),这是个啥意思呢?那我们么来一起科普一下:

② 优缺点

③ 代码实现

那么是不是有小伙伴们问:我要分页可咋办?其实在上面已经给出了答案,使用命令中的 将排好序的数据存入一个zset集合中,以后分页查直接从zset集合中取数据即可:

不过也有点不足的地方,就是我们不能根据筛选条件来直接查询,而是要查询到之后手动过滤;比如我们要查询18岁的美少女附近好友;

① 要么按照搜索条件细化分存储一份数据

② 要么就查询之后过滤

基于mongoDB实现附近人查询

① 设计思路

目前阿沐有一个类似直播项目首页推荐就有一个附近好友的功能;它就是基于MongoDB来实现附近好友功能。主要是通过它的两种地理空间索引和,这两种索引底层还是基于geohash来构建的。

索引支持:球星表面点、线、面、多点、多线、多面和几何集合;创建2dsphere索引语法:

索引支持平面几何形状和一些球形查询;支持球面查询但是不太友好,更适合平面查询;创建2d索引语法:

② 代码实现

1、创建数据库,插入几条数据;collection为起名 user(相当于mysql里面的表名)。三个字段user_id用户id,user_name名称,location 为经、纬度数据。

2、因为我们以二维平面上点的方式存储的数据,想要进行LBS查询,那么还是选择设置2d索引:

3、根据自己当前的坐标经纬度查询

4、查看结果集中是否有符合条件的数据,若有数据则会多出刚刚设置的距离字段名distance,表示两点间的距离:

那么到这里是不是对mongo存储经纬度,然后查询附近人获取距离是不是有点了解了;其实性能还是很好地,只不过压力大的时候会出现mongo连接超时,我们可以针对mongo做一个集群;基本上算是比较稳定的了。

基于es搜索引擎实现附近人查询

① 设计思路

其实用es/sphinx/solr等等(后面也会具体聊聊搜索引擎)这类搜索引擎大都能支持查询附近人,因为效率高,查询速度快,而且结果集比较精准;所以还是比较推荐使用这个。

大致流程就是这样:① 用户请求查询附近好友 ② 服务端收到请求,然后通过api(http或者rpc)请求上游搜索引擎组的数据 ③ 搜索组拿到请求参数解析查询对应关系链 ④ 高效率返回给调用者

支持分页查询以及更多条件的查询方案;性能优越、可分页;尤其在大数据量的情况下,其性能更友好。阿沐之前公司就是这样处理,类似个性化推荐;通过用户喜好从几百万商品中检索,整个流程也就是服务端请求搜索组接口。搜索组基本上都是Java开发者,由sorl搜索过度elasticcsearch引擎,再加上使用k8s部署es集群;挺好的!!!

仓库代码地址:

哇哇哇,能有幸看到这里的小伙伴,我很服气你们了,我花了三天的时间去想去画去构思写好的文章;实话实说,写这篇文章压力挺大的;首先底层的算法原理,再者需要实践验证。网络上都是复制来复制去,我就想着走不一样的路线;既然大家都已经看过那么多跟geohash相关的文章,原理算法应该都ok的;假如我再去纠结讲着写,那岂不是有点自取其辱(大牛们都已经讲的很好了);所以决定通过不同的角度去思考一个问题;以及引发的思考。

本文主要还是通过实践动手结合实际的项目以及过往经验;展示geohash的不同场景下的使用,量级大和量极小怎么选择等等?也通过整理文章时,去解析小伙们会遇到的疑问,这些疑问估计很多文章都没有的,因为都是千篇一律。阿沐要做的就是复杂简单化,能动手实践的绝不只看不练!

座右铭:不管做什么,只要坚持下去就会看到不一样

最后,欢迎关注我的个人公众号「我是阿沐」,会不定期的更新后端知识点和学习笔记。也欢迎直接公众号私信或者邮箱联系我,我们可以一起学习,一起进步。

好了,我是阿沐,一个不想30岁就被淘汰的打工人 ⛽️ ⛽️ ⛽️ 。创作不易觉得「阿沐」写的有点料话:👍 关注一下,💖 分享一下,我们下期再见。

  • 上一篇: 生成openid
  • 下一篇: 分配网络流量的软件
  • 版权声明


    相关文章:

  • 生成openid2024-11-07 21:30:05
  • java中的单例设计模式2024-11-07 21:30:05
  • php8 opcache2024-11-07 21:30:05
  • 三态与非门的三种状态2024-11-07 21:30:05
  • win10打开本地策略编辑器2024-11-07 21:30:05
  • 分配网络流量的软件2024-11-07 21:30:05
  • mysql 左连接 右连接2024-11-07 21:30:05
  • python菜鸟教程32024-11-07 21:30:05
  • 引用数据类型 java2024-11-07 21:30:05
  • win10怎么自动检测修复系统2024-11-07 21:30:05