做LBS(基于位置的服务)开发的朋友,大概都经历过被elasticsearch geo查询折磨的深夜。起初觉得挺简单,建个索引,丢个经纬度,搜一下“附近的人”,完事。结果一上线,数据量稍微大点,查询慢得像老牛拉车,或者更惨,直接超时报错。我见过太多团队在这里踩坑,甚至有人为了省事,直接把经纬度转成字符串存起来,搞个范围查询凑合用,那简直是自废武功。
咱们得说点实在的。很多人不知道,es里的geo_point字段,底层其实是把经纬度做了一种特殊的编码处理,目的是利用倒排索引来加速空间检索。如果你只是随便查查,可能感觉不到差别,但一旦涉及到“半径内排序”或者“多边形过滤”,性能差距能拉开十倍不止。
先说个真实案例。有个做同城配送的兄弟,刚开始用es查“3公里内的骑手”,QPS也就几百,响应时间200毫秒左右,看着还行。后来业务扩张,并发上去了,查询延迟飙升到2秒以上。排查发现,他用的查询语句里,没有指定distance_type,默认用的是plane(平面计算),在大数据量下,这个算法不仅慢,而且精度在远距离时误差巨大。后来改成arc(球面计算),虽然计算稍微复杂一丢丢,但配合正确的索引结构,整体性能反而稳了。
那具体该怎么优化?别整那些虚的,直接上步骤。
第一步,索引mapping要写对。这是地基。别偷懒,geo_point字段必须明确指定geohash_precision。这个参数决定了地理哈希的精度,值越大,精度越高,但索引体积也会变大。一般建议设在10-12之间,既能保证精度,又不会让索引膨胀太厉害。如果你的业务对精度要求极高,比如外卖骑手定位,那得斟酌一下,别盲目追求高精度,否则磁盘IO会成为瓶颈。
第二步,查询语句别乱写。很多新手喜欢用geo_distance查询,这没问题,但要注意,如果你只需要判断是否在范围内,不需要排序,用geo_bounding_box或者geo_polygon可能更快。因为geo_distance涉及到距离计算,开销相对大一些。还有,记得加boost参数,对距离近的文档给予更高的权重,这样排序结果更符合用户直觉。
第三步,缓存机制不能少。geo查询的结果,尤其是热门区域的查询,变化频率没那么高。你可以引入Redis做一层缓存,key可以是“经纬度+半径”,value是查询结果。这样能挡住大部分重复查询,减轻es的压力。当然,缓存失效策略得设计好,别让用户看到半小时前的骑手位置。
这里插一句,关于elasticsearch geo查询的长尾词搜索,其实很多开发者只关注基础用法,忽略了性能调优。比如elasticsearch距离排序,很多人不知道sort里的_geo_distance字段可以自定义单位,米、公里、英里都能设,这细节挺重要。还有geo_point查询优化,除了上述几点,分片策略也很关键。别把所有地理位置数据都堆在一个分片里,那样查询就是串行执行,毫无性能可言。
再说说elasticsearch附近的人这种场景。这不仅仅是查数据,还涉及到实时性。如果你的用户数据更新频繁,记得设置合理的刷新间隔(refresh_interval),默认是1秒,对于高频写入场景,可以适当调大,比如5秒,以减少IO压力,牺牲一点实时性换取吞吐量。
最后,别迷信官方文档里的默认配置。每个业务场景都不一样,你的数据分布、查询模式、硬件资源,都决定了最佳实践。我见过有人为了追求极致性能,把geo_point拆分成两个double字段,自己写脚本计算距离,虽然麻烦,但在特定场景下确实有效。但这属于高级玩法,不建议新手尝试。
总之,elasticsearch geo查询不是银弹,它需要你对底层原理有清晰的认识,并结合实际业务场景进行调优。别指望一套配置走天下,多监控,多分析,找到适合你的平衡点。
如果你还在为geo查询的性能头疼,或者不确定自己的mapping设计是否合理,欢迎来聊聊。我们可以一起看看你的查询日志,找找优化空间。毕竟,代码是写给人看的,也是写给机器跑的,跑得顺,才是真的好。