昨晚折腾到凌晨两点,头发都要掉光了,就为了搞定那个该死的elasticsearch6 geo查询。说实话,刚开始接触这块的时候,我觉得挺简单的,不就是存个经纬度,然后查个距离嘛。结果呢?现实给了我一记响亮的耳光。数据死活查不出来,或者查出来的结果跟预期完全对不上,那种感觉真的想砸键盘。今天把这一路踩过的坑整理出来,希望能帮到正在同样泥潭里挣扎的你。
首先,你得明白,在es6里处理地理位置,核心就是那个lat_lon类型。别整那些花里胡哨的,就用这个。很多新手喜欢用object或者nested去存坐标,然后自己算距离,那是自找苦吃。es内置的geo_point或者lat_lon就是为了这个存在的。我之前的错误做法是,直接往字段里扔字符串,比如"40.7128, -74.0060",看着没问题,但查询的时候就是报错或者返回空。记住,一定要用数字类型,而且顺序是lat, lon,别搞反了,经纬度反了,你查出来的地方可能就在地球另一端了。
第一步,建mapping的时候,别偷懒。一定要显式地声明字段类型为lat_lon。别指望es能自动识别,虽然它有时候挺智能,但在生产环境,显式声明最稳妥。比如:
"location": {
"type": "lat_lon"
}
就这么简单。别加什么text或者keyword,加了就废了。我有一次因为复制了之前的mapping模板,里面混入了text类型,结果导致整个索引重建花了半小时,心都在滴血。
第二步,插入数据。这里有个细节,很多人喜欢用bulk批量导入,速度快啊。但是,如果你用的是json格式,确保你的经纬度是数字,不是字符串。我在测试的时候,不小心把经纬度写成了字符串,虽然es没报错,但查询geo_distance的时候,它直接忽略了这些文档。查了半天日志,才发现是数据类型的问题。这坑太深了,建议你在插入前,写个小脚本校验一下数据格式。
第三步,查询。这是最让人头大的部分。geo_distance查询,你得指定中心点,还有距离。比如,查我附近5公里内的咖啡店。代码长这样:
{
"query": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 39.9042,
"lon": 116.4074
}
}
}
}
注意,这里的location字段名必须和你mapping里定义的一致。我之前就是在这里栽了跟头,mapping里叫loc,查询里写成了location,结果查不到任何数据。调试的时候,我一度怀疑是es的bug,后来才发现是字段名拼写错误。这种低级错误,真的让人想扇自己。
还有,geo_distance有个默认的距离单位是米,你可以改成km,但别搞混了。我有一次把5km写成了5m,结果查出来一堆根本不在附近的点,因为5米范围内可能只有几棵树。这种数据偏差,在业务上可是要出大问题的。
另外,别忘了geo_shape,如果你要查的是多边形区域,比如某个行政区的边界,那就得用geo_shape。这个比geo_distance复杂多了,得先导入shape数据。我上次查北京市五环内的数据,就是用的这个。导入shape的时候,格式要是GeoJSON,别用WKT,es6对GeoJSON支持更好。导入的时候,记得检查拓扑错误,不然查询结果会乱套。
最后,性能优化。geo查询挺耗资源的,特别是数据量大的时候。建议开启doc_values,默认是开的,但别动它。还有,别在geo字段上做聚合,除非你确定数据量不大。我有一次在geo字段上做terms聚合,直接导致节点OOM,服务挂了半小时。那种焦虑感,真的不想再体验第二次。
总之,elasticsearch6 geo这东西,看着简单,水很深。多测试,多验证,别信网上的某些过时教程。现在的es版本更新快,很多老方法都不适用了。希望我的这些血泪教训,能帮你少走点弯路。要是你还遇到什么问题,欢迎在评论区留言,大家一起讨论。毕竟,独乐乐不如众乐乐,大家一起踩坑,才能一起爬出来。