做LBS(基于位置的服务)项目,谁没被Elasticsearch的地理位置查询折磨过?前阵子帮朋友重构一个“附近的人”功能,他急得满头大汗,说查询慢得像蜗牛,数据还经常不准。我一看他的mapping,差点没背过气去。很多人以为elasticsearch geo 搜索就是简单的经纬度匹配,其实里面水深得吓人。
咱们先说最基础的mapping类型。别再用text去存经纬度了,那是找死。必须用geo_point。但是,geo_point有两种存法:一种是字符串格式,比如"lat,lon";另一种是对象格式,即{"lat": 39.9, "lon": 116.4}。朋友用的就是字符串,结果查询时解析效率极低,而且容易因为空格问题导致查询失败。我让他改成对象格式,瞬间响应时间从2秒降到了200毫秒以内。这里有个细节,对象格式更利于后续扩展,比如你以后可能要存海拔信息,或者做更复杂的地理围栏判断,字符串格式根本搞不定。
再来说说精度问题。很多开发者为了省事,经纬度只保留两位小数,比如39.90, 116.40。这在地图上看着差不多,但在实际业务中,误差可能达到几公里。对于外卖配送、网约车这种对位置敏感的场景,这是致命的。我推荐至少保留6位小数,这样精度能控制在1米左右。当然,这也意味着存储量会稍微增加,但对于现在的硬件来说,这点成本完全可以忽略不计。记住,elasticsearch geo 搜索对精度的敏感度很高,模糊一点,结果就飘了。
还有一个大坑,就是geo_shape和geo_point的选择。如果你只是查“附近的人”,用geo_point就够了。但如果你要查“在这个商圈范围内”,那就得用geo_shape。很多新手分不清这两者的区别,导致查询语句写得极其复杂,甚至直接查崩集群。geo_shape支持点、线、面等多种几何图形,适合做复杂的地理围栏。比如,你要圈定一个不规则的公园范围,只有geo_shape能搞定。
再聊聊性能优化。当数据量达到千万级时,简单的geo_distance查询可能会变慢。这时候,你可以考虑使用geohash。geohash将经纬度编码成一个字符串,相同前缀的字符串表示地理位置相近。虽然ES底层已经做了优化,但理解geohash的原理有助于你更好地设计索引。比如,你可以先通过geohash前缀进行初步过滤,再精确计算距离,这样能大幅减少计算量。
最后,分享一个真实的避坑经验。有一次,我们上线了一个新功能,要求用户只能看到自己所在城市的商家。一开始,我们直接在查询条件里加了city字段过滤,结果发现查询速度极慢。后来发现,是因为city字段没有分词,导致精确匹配失败,进而触发了全表扫描。改成keyword类型后,问题迎刃而解。所以,在做elasticsearch geo 搜索时,不要只盯着经纬度,周边的业务字段同样重要,它们的类型选择直接影响查询性能。
总之,elasticsearch geo 搜索不是简单的API调用,它涉及到数据结构设计、精度控制、性能优化等多个方面。只有深入理解其底层逻辑,才能真正发挥ES的优势。别等线上出问题了再后悔,现在就把这些细节处理好,让你的项目跑得更快、更稳。