做搜索引擎这行整整9年了,我见过太多团队因为一个小小的地理位置查询需求,把整个架构搞得乱七八糟。以前我也觉得,搞个经纬度搜索能有多难?直到上个月,有个老哥们儿哭着喊着找我救火,说他们搞了个本地生活类APP,用户一搜“附近的美食”,服务器直接瘫痪,响应时间飙到5秒以上。我一看日志,好家伙,全表扫描,连个索引都没建对。
今天我就把这事儿掰开了揉碎了讲,希望能帮正在踩坑的兄弟们省点头发。咱们聊聊es的geo功能,这东西用对了是神器,用错了就是定时炸弹。
先说个场景。你有个餐厅数据,里面有经纬度。很多新手上来就搞两个字段,一个lat,一个lon,然后想查“5公里内的店”。这时候如果不用geo_point类型,而是用普通的double类型,那你基本就废了。因为es没法用空间索引去算距离,只能硬算,数据量一大,CPU直接起飞。
这就是es的geo功能最核心的地方:它不是简单的数字存储,而是把地球当成了一个球体(或者扁平化的网格),专门优化了空间计算。
我在处理这个老哥的问题时,第一步就是让他把lat和lon合并成一个geo_point字段。别嫌麻烦,这一步能省掉后面80%的麻烦。设置好字段类型后,你会发现查询语句变得特别优雅。比如你想查某个坐标附近的点,直接用geo_distance查询,es底层会自动调用GeoHash或者Binary Quadtree算法,速度比你自己写算法快几个数量级。
但是,这里有个坑,我得狠狠吐槽一下。很多兄弟喜欢用geo_shape类型来搞复杂的多边形,比如查“某个行政区内的所有店铺”。geo_shape确实强大,支持多边形、线、点各种形状。但它的写入性能比geo_point慢不少,而且查询复杂度也高。如果你的业务只是简单的“附近的人”、“附近的店”,千万别手贱去用geo_shape,老老实实用geo_point。我见过有人为了追求“高级感”,非要把所有数据都搞成geo_shape,结果写入延迟高得让人想砸键盘。
再说说聚合。有时候用户不仅想知道有哪些店,还想知道这些店的平均评分或者总价区间。这时候可以用geo_distance聚合,它能把结果按距离分段,比如0-1km,1-5km,5-10km,然后分别统计。这个功能在数据分析场景下简直不要太好用。不过要注意,聚合的时候最好配合filter使用,先过滤掉无效数据,再聚合,不然计算量会大得吓人。
还有个细节,关于精度。es默认的经纬度精度是100米,这对于大多数本地生活场景够了。但如果你是做物流轨迹或者高精度定位,可能需要调整precision参数。别默认设置就不管了,有时候精度太高会导致索引文件过大,内存吃不消。
我真心觉得,技术选型没有最好的,只有最合适的。es的geo功能很强大,但它不是万能的。如果你的数据量只有几万条,随便怎么查都行;但如果到了百万、千万级,就必须得讲究策略。比如,你可以结合分片策略,把地理位置相近的数据尽量放在同一个分片上,这样查询的时候能减少跨分片通信,速度提升明显。
最后说点心里话。做技术这行,最怕的就是盲目跟风。看到别人用啥高大上的功能,自己也跟着上,结果连原理都没搞懂。es的geo功能虽然好用,但也需要你对底层逻辑有清晰的认知。别等到线上出问题了,才想起来去翻文档,那时候黄花菜都凉了。
如果你还在为地理位置查询头疼,或者不确定自己的索引结构是否合理,欢迎随时来聊聊。别自己在那瞎琢磨,有时候旁观者清,一眼就能看出你的架构漏洞。毕竟,头发掉光了,问题还在那,多不划算啊。
本文关键词:es的geo功能