真的,搞过Elasticsearch的人都知道,处理地理位置数据这事儿,看着简单,水深得能淹死人。我前几天刚帮一个朋友排查了一个线上Bug,折腾了整整两天,最后发现居然是因为es geo数据写入的时候格式搞错了。这玩意儿要是没整明白,你的搜索功能基本就是摆设,不仅查不准,还容易把集群搞崩。
咱们先说个最基础的误区。很多人觉得,经纬度不就是两个数字吗?直接存成double类型不就行了?我当初也是这么想的,直到项目上线后,用户反馈说“附近的餐厅”根本搜不到,或者搜出来的距离离谱得吓人。这时候你再去查日志,会发现很多报错,或者干脆静默失败。其实,ES对地理位置有专门的字段类型,叫geo_point。你要是把它当成普通数值存进去,后面想做地理围栏、半径搜索,那就全歇菜了。
那怎么正确地进行es geo数据写入呢?我有几种常用的姿势,大家可以根据实际情况选。
第一种,也是最推荐的,用对象格式。比如这样:{"location": {"lat": 39.9, "lon": 116.4}}。这种写法语义最清晰,ES解析起来也最快。我在做物流轨迹回放的时候,就是用这种方式,数据量一大,查询效率依然很稳。不过要注意,lat和lon的顺序千万别写反了,一个是纬度,一个是经度,虽然有时候看着差不多,但在高精度场景下,差之毫厘谬以千里。
第二种,用字符串格式。比如"39.9,116.4"。这种写法比较省事,代码里拼个字符串就完事了。但是!这里有个大坑,逗号后面千万别加空格。如果你写成"39.9, 116.4",ES可能就不认了,或者解析出错。我有一次就是加了个空格,结果数据全丢了,查了半天才发现是这个低级错误。这种格式适合那些对性能要求没那么极致,或者数据源本身就很杂乱的情况。
第三种,用地理哈希(geohash)。比如"wx4g0b..."。这种方式的优点是存储空间小,而且支持前缀查询,适合做那种“附近的人”或者“附近的店”这种模糊搜索。但是它的精度是有限的,哈希越长精度越高,但存储成本也越高。如果你不需要特别高的精度,用geohash确实能省不少空间。
除了格式,还有一个关键点经常被忽略,那就是索引映射(Mapping)。在创建索引之前,一定要先定义好geo_point类型。如果你已经建了索引,字段类型不对,想改都改不了,只能重建索引。这一步很麻烦,尤其是数据量大的时候,重建索引意味着停机或者双写,成本很高。所以,前期规划一定要做好。
另外,我在实际开发中还发现,批量写入的时候,如果数据量特别大,比如每秒几万条,直接单条插入肯定不行,得用_bulk接口。但是用_bulk的时候,要注意JSON格式的规范性,每一行数据都要独立成行,最后以换行符结尾。有一次我因为少了一个换行符,导致整批数据写入失败,查错查得头都大了。这种细节问题,往往是最让人抓狂的。
还有一点,关于性能。地理位置查询,尤其是半径搜索,底层是用KD-Tree或者GeoHash实现的。如果你的数据分布很不均匀,比如某些区域数据特别密集,某些区域特别稀疏,可能会导致查询热点。这时候,可能需要考虑分片策略,或者使用更高级的查询优化手段。不过这些属于进阶内容了,对于大多数场景,把基础的es geo数据写入搞对,就已经能解决80%的问题了。
最后想说,技术这东西,真的不能想当然。看着简单的经纬度,背后藏着不少坑。希望大家在遇到es geo数据写入相关的问题时,多看看官方文档,多测试几种格式,别等出了问题再慌忙补救。毕竟,线上故障可不是闹着玩的。
本文关键词:es geo数据写入