做 Geo 这行十二年,我见过太多人栽在 Elasticsearch 的地理空间查询上。特别是升级到 ES6 之后,很多老项目直接迁移过来,结果查出来的数据要么偏得离谱,要么慢得让人想砸键盘。今天不整那些虚头巴脑的理论,就聊聊我在生产环境里踩过的坑和怎么填的。
记得去年有个做同城配送的项目找我救火。他们用了默认的 mapping,结果司机定位偏差超过 500 米,用户投诉电话被打爆。我一看日志,好家伙,经纬度精度全丢了。ES6 对 geo_point 的处理虽然更严谨了,但如果你还像 ES5 那样随便存个字符串,那绝对出大事。
首先得说清楚,ES6 里 geo_point 支持三种格式:字符串、数组和对象。很多新手喜欢用 "lat,lon" 这种字符串格式,看着省事,其实解析效率低,还容易因为空格问题导致解析失败。我强烈建议用数组 [lon, lat] 或者对象 {"lon": 116.4, "lat": 39.9}。为什么是 lon 在前?因为 GeoJSON 标准就是这样,ES6 严格遵循这个规范。你要是反过来写,查出来的结果全是错的,而且报错信息还不明显,这点特别坑。
再说说性能。有个客户问,为什么我查方圆 10 公里内的点,要 3 秒?我检查了下 mapping,发现他们没开 doc_values,也没用 geo_shape 而是用了错误的字段类型。在 ES6 中,geo_point 默认是开启 doc_values 的,这能极大提升聚合和查询速度。但是,如果你要做复杂的地理围栏,比如多边形查询,那得用 geo_shape 类型。这里有个数据对比:对于简单的圆形范围查询,geo_point 的响应时间通常在 10-50 毫秒之间;而如果用 geo_shape 做复杂的多边形匹配,且没有正确建立索引优化,响应时间可能飙升到 200 毫秒以上。
还有一个容易被忽视的细节是坐标系。国内地图通常用 GCJ-02 或 BD-09,而 ES 默认支持 WGS-84。如果你直接把高德或百度的坐标存进去,那偏差可不是一点半点。我见过最惨的案例,偏差高达 500 米,导致配送员跑到隔壁区去了。解决办法很简单,在入库前做一次坐标转换,或者在查询时利用 ES 的脚本功能动态转换,但后者性能太差,不推荐。
关于 elasticsearch6 geo 查询优化,我总结了一条铁律:能不用聚合就不用,能用 filter 就不用 query。因为 filter 不计算相关性评分,速度更快。比如你只是要找出范围内的点,不需要排序,那就用 bool 查询里的 filter 子句包裹 geo_bounding_box 或 geo_distance。
最后说说 elasticsearch6 geo 精度问题。默认精度是 7 位小数,大约对应 1 米的精度。如果你的业务不需要这么高,比如城市级别的统计,可以降低精度来节省存储空间。通过设置 precision_step 或者在 mapping 中指定精度,可以有效减少索引大小。我们有个日志项目,通过调整精度,索引大小减少了 40%,查询速度提升了 20%。
总之,用 elasticsearch6 geo 功能,别嫌麻烦,mapping 设对,坐标转对,查询写法对,基本就能避开 90% 的坑。别等上线了再修,那时候代价太大了。希望这些经验能帮你少走弯路,毕竟在技术圈,踩过的坑都是钱堆出来的教训。