做搜索这行八年了,见过太多人把 Elasticsearch 当成万能数据库用,结果一上生产环境,geo 查询直接崩盘,CPU 飙到 100%,接口响应慢得让人想砸键盘。今天不扯那些虚头巴脑的理论,就聊聊我最近帮一家本地生活平台重构位置服务时的真实血泪史。
很多新手一上来就喜欢用 distance 查询,觉得简单粗暴。但你知道最坑的是什么吗?当你的数据量达到百万级,且没有做好分片策略时,那个 geo_distance 查询简直就是性能杀手。我们之前有个客户,用户定位精度要求不高,但并发量大,结果每次搜附近的店,都要全表扫描,延迟高达 2 秒。后来我让他们改用 geo_point 类型,并且务必开启 doc_values,虽然索引体积稍微大了一点,但查询速度直接提升了十倍不止。这点小细节,很多人根本不在乎,直到线上报警才后悔莫及。
还有一个容易被忽视的点是坐标精度。别以为存经纬度随便写写就行。如果你做的是同城配送,那精度必须得高。我们当时遇到一个奇葩 bug,用户明明就在店铺隔壁,却搜不到。查了半天日志,发现是前端传过来的经纬度小数点后位数不够,导致坐标偏移了几百米。在 es geo检索 的场景下,这种精度丢失是致命的。建议前端传值时,至少保留 6 位小数,后端存储时也要确保字段类型是 double,千万别用 float,误差太大了,根本没法用。
再说回分片。很多兄弟为了追求写入性能,把分片设得特别大,比如 50 个分片起步。结果呢?geo 查询需要计算每个文档到目标点的距离,分片越多,协调节点的压力越大,内存直接爆满。我当时就遇到过一次,半夜三点报警,说是 heap 空间不足。排查后发现,是因为一个大的 geo_shape 查询触发了大量的内存分配。解决办法其实很简单,把分片数控制在合理范围,比如 5-10 个,同时利用 routing 参数,把同一区域的数据路由到同一个分片上。这样查询时,只需要扫描部分分片,效率自然就上去了。
另外,别忽略了缓存。es geo检索 虽然快,但也架不住高并发。我们在项目里引入了 Redis 缓存热点区域的查询结果。比如,某个商圈在晚高峰时,附近的餐厅查询量巨大。这时候,直接查 ES 反而不如查缓存快。当然,缓存的失效策略得设计好,不然数据不一致,用户投诉都找不到原因。我们采用的是 TTL 加主动失效相结合的方式,既保证了实时性,又降低了 ES 的压力。
最后,想说点心里话。做技术选型,别盲目跟风。ES 确实强大,但也不是银弹。如果你的业务场景只是简单的 CRUD,或者对位置精度要求极低,也许关系型数据库的 spatial 扩展更合适。但如果像我们这种,需要实时计算附近的人、附近的店,并且数据量还在快速增长,那 ES 绝对是首选。关键在于,你得懂它的底层逻辑,知道怎么优化,怎么避坑。
总之,es geo检索 不是调个 API 就完事了,背后的索引设计、分片策略、缓存机制,每一个环节都得抠细节。希望我的这些经验,能帮大家在踩坑的路上少摔几跤。毕竟,线上出故障,半夜起来修 bug 的日子,谁也不想再经历第二次。加油吧,各位同行。