es geo 计算距离 实战避坑指南:从原理到性能优化

es geo 计算距离 实战避坑指南:从原理到性能优化

本文关键词:es geo 计算距离

说实话,刚接触 Elasticsearch 的时候,我对 geo 模块的理解挺片面的。

总觉得就是个简单的经纬度存储,搜一下附近的人,或者画个圈找点数据。

直到上个月,我们那个“同城配送”的项目上线前夕,出了个大问题。

系统反应慢得像蜗牛,用户稍微扩大一点搜索半径,服务器直接报警。

那时候我才意识到,es geo 计算距离 这事儿,水挺深。

今天我就把自己踩过的坑,还有最后怎么救回来的经验,掏心窝子跟大家聊聊。

先说个最基础的误区。

很多人以为 geo_point 就是存两个数字,lat 和 lon。

没错,它是存了数字,但 ES 在底层做了很多优化。

它把经纬度转换成了特殊的格式,存进 inverted index 里。

这样做的目的,是为了让查询快。

但是,如果你直接拿原始数据去算距离,那简直是灾难。

我们之前有个同事,没搞懂 geo_distance 查询的机制。

他直接在查询条件里加了个很大的范围,比如 50 公里。

结果呢?ES 需要扫描大量的文档,然后一个个算距离。

这种操作,在数据量小的表里还行。

一旦数据到了百万级,CPU 直接飙到 100%。

这就是为什么我强调,es geo 计算距离 不能硬算。

得靠索引结构。

ES 用的是 Geohash 或者 QuadTree 算法。

简单说,就是把地球表面切成很多小块。

每个小块有个编码。

离得近的地方,编码长得像。

查询的时候,ES 先根据编码过滤掉大部分无关数据。

剩下的少量数据,再进行精确的距离计算。

这个过程,就是所谓的“粗筛”加“精算”。

我们当时的案例,大概是这样。

有个用户,想查周围 10 公里内的所有骑手。

数据量大概 200 万条。

第一次优化,我们加了个 filter context。

把距离判断放在 filter 里,而不是 query 里。

因为 filter 不需要算相关性评分,可以缓存。

这一改,响应时间从 2 秒降到了 200 毫秒。

但这还不够。

老板嫌慢,要求 100 毫秒内返回。

这时候,我们就得用到 es geo 计算距离 的高级技巧了。

我们调整了 mapping 里的 precision_step。

默认是 12,我们改成了 10。

这意味着,Geohash 的精度降低了,树的结构变浅了。

查询的时候,能更快定位到大的区域块。

虽然牺牲了一点点精度,但对于 10 公里的范围来说,这点误差用户根本感知不到。

改完之后,响应时间稳定在 80 毫秒左右。

完美解决。

这里有个小细节,大家容易忽略。

就是 geo_distance 查询的时候,单位一定要写对。

km, m, mi, yd。

别偷懒不写,ES 默认是米。

如果你传个 10,它以为是 10 米。

那你搜出来的结果,可能就在你脚下。

这就很尴尬了。

还有个坑,就是浮点数精度问题。

经纬度存成 double 没问题。

但如果你用 float,精度不够,算出来的距离会有偏差。

特别是在高纬度地区,偏差会更明显。

我们测试的时候,发现北京和上海的距离,用 float 存,算出来差了大概 50 米。

虽然对于大范围的配送影响不大,但对于精准定位,比如共享单车停放点,这就不能忍了。

所以,强烈建议用 double。

另外,关于排序。

很多人喜欢按距离排序。

es geo 计算距离 后,直接 sort by _score 或者 field。

但是,如果数据量很大,排序本身也很消耗资源。

我们最后加了个限制,只返回前 100 条。

并且,在应用层做了二次过滤。

ES 只负责快速筛选出 500 条候选数据。

然后应用层再精确计算,取最近的 100 个。

这样既保证了速度,又保证了结果的准确性。

其实,es geo 计算距离 的核心,不在于算得有多快。

而在于怎么减少需要计算的数据量。

这就好比在人群中找熟人。

你不会挨个问每个人是不是他。

你会先问:“穿红衣服的站过来。”

然后再在红衣服里找。

这就是索引的作用。

最后,再啰嗦一句。

做地理空间查询,一定要测压。

一定要测压。

一定要测压。

别信文档上的理论数据,生产环境的数据分布千奇百怪。

有的地方数据密集,有的地方稀疏。

不均匀的数据分布,会让你的查询性能波动很大。

我们那次事故,就是因为夏天晚上,某个商圈数据突然激增。

导致热点数据集中,索引效率下降。

所以,监控起来,看看哪些区域是热点。

针对性地优化。

好了,今天就聊到这。

希望这些经验能帮到你。

毕竟,踩过的坑,才是真本事。

如果有其他问题,欢迎留言交流。

咱们下期见。