昨天半夜两点,我盯着屏幕上的日志报错,头发都快掉光了。就在刚才,我们那个号称“智能推荐”的功能,居然把住在朝阳区的用户推荐到了海淀区的一个完全不相干的店。这哪是智能,这简直是人工智障。
很多人觉得,搞个地理位置搜索(Location-based Service)很难吗?在数据库里加个经纬度字段,查个距离,两行代码搞定。要是你也是这么想的,那我劝你赶紧停手,因为坑就在后面等着你呢。咱们今天不聊那些高大上的架构理论,就聊聊我在实战里踩过的坑,特别是关于es的geo那些让人头秃的细节。
先说个真实的案例。我们有个本地生活项目,初期为了省事,直接用了传统的lat/lon字段,存的是双精度浮点数。查询的时候,用range或者简单的距离计算。刚开始数据量小,几千条记录,跑起来飞快,老板还挺满意。结果呢?随着用户量上来,数据到了百万级,查询延迟直接从几十毫秒飙升到了几百毫秒,甚至有时候直接超时。为什么?因为传统坐标系在地球表面并不是均匀的,经纬度的跨度在赤道和两极完全不同,简单的欧几里得距离计算在长距离或者高精度要求下,误差大得离谱。
这时候,你就得认真考虑es的geo类型了。别一听“geo”就觉得复杂,其实它就是为了帮你解决这种“地球是圆的”这个问题而生的。
第一个大坑,是精度问题。很多新手在定义mapping的时候,随便设个精度,比如保留两位小数。这就好比你在地图上画个点,结果画到了隔壁街道。对于打车软件或者外卖配送来说,这差的可不止一点点。我在优化一个地图标记功能时,发现标记点总是飘忽不定,后来发现是精度设置太低,导致同一个地点的不同用户提交的数据,在es眼里变成了好几个不同的点。把精度调整到合理范围,比如小数点后6位甚至更多,瞬间就稳了。
第二个坑,是查询方式的误区。很多人习惯用geo_distance查询,觉得直观。但在数据量大的时候,这种查询其实挺耗资源的,因为它要计算每个文档到中心点的距离。如果你做的是“附近的人”或者“周边商户”这种场景,更推荐用geo_distance_range或者geo_shape,甚至结合geo_hash来优化。geo_hash有个很妙的地方,它能通过前缀匹配来快速过滤掉不在范围内的数据,这比全表扫描效率高太多了。记得有一次,我们把查询逻辑从单纯的distance改成了结合geo_hash的前缀过滤,查询速度直接提升了近三倍,服务器负载也降了下来。
还有一个容易被忽视的点,就是数据的一致性。es的geo查询对数据的格式要求很严格。经纬度的顺序必须是[lon, lat],而不是很多人习惯的[lat, lon]。我就见过好几个同事,因为顺序搞反了,查出来的结果全跑到太平洋里去了。这种低级错误,排查起来能让人怀疑人生。所以,在数据入库前,一定要做好校验和清洗,别把垃圾数据喂给es,否则你得到的只能是垃圾结果。
其实,用好es的geo,核心不在于你会多少复杂的API,而在于你懂不懂你的业务场景。你是需要精确的距离计算,还是只需要粗略的范围筛选?你是要处理海量的实时数据,还是静态的历史数据?不同的场景,选择不同的geo类型和查询策略,才能事半功倍。
别再盲目追求新技术了,有时候,把基础打牢,把细节抠细,比什么都强。希望这些踩坑经验,能帮你少走点弯路。毕竟,代码是写给人看的,顺便让机器执行,别让自己和机器都难受。
本文关键词:es的geo