ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Elasticsearch地理位置搜索实战:从geo_point建模到性能调优

Elasticsearch地理位置搜索实战:从geo_point建模到性能调优 做 Elasticsearch 地理位置搜索这事我前后折腾了快三年。第一次上线“附近门店”功能时我也以为会写个geo_distance就算入门了结果线上跑了一周用户反馈“附近搜不到”“距离不对劲”“接口变慢”然后就是漫长的排坑过程。这篇文章想把 elasticsearch 地理位置搜索从索引建模、坐标写入、查询组合到性能调优、故障排查的完整链路按我自己的实操顺序过一遍。如果你是做 LBS 应用、附近推荐、地理围栏相关功能的工程师或者正被百万级坐标查询折磨得头疼那这篇应该能帮你少走不少弯路刚接触 ES 的人也可以跟着完整跑通先建立整体认知再回来看官方文档细节就不慌了。1. 地理位置搜索到底解决了什么问题1.1 为什么传统数据库搞不定大规模 LBS很多人上来就问MySQL 里也能写经纬度为什么非要用 Elasticsearch这个问题的答案直接决定了你后续的方案选型。MySQL 计算两点距离最常用的方案是写ST_Distance_Sphere或者用Haversine公式在 SQL 里硬算。数据量只有几十万的时候这样确实没毛病。但一旦到了百万、千万级问题就来了坐标点没法像普通一维字段那样建 B-tree 索引因为“附近”是一个二维空间概念B-tree 默认假设数据是线性有序的你没法用它快速回答“这个点周围 3 公里内有哪些点”。所以大部分 MySQL 方案只能做全表扫描然后在内存里逐行算距离性能直接崩。Elasticsearch 不一样它的geo_point类型底层是 BKD 树这是一种针对多维空间数据的索引结构会把地球经纬度空间切分成一层层的格子查询时只需要沿着树的分支找到目标区域对应的叶子节点然后再在很小的候选集里做精确计算。你可以把它想象成地图 App 的网格加载不是把全世界的道路都算一遍而是只把屏幕范围内的几条路拉出来。这就是地理位置搜索能用在海量数据上的核心原因。1.2 和关系型数据库相比ES 的优势在哪我用一个类比来解释 ES 在地理搜索上的优势MySQL 的普通索引像一本按姓氏拼音排序的电话簿你要查“姓张的人”很快但要查“住在同一个小区的人”就抓瞎了ES 的地理索引更像一张分层地图你问“人民广场 3 公里内有哪几家咖啡店”它会先锁定人民广场那一格再往外扩一圈而不是翻遍整本地图。具体到工程指标上我用一张表说明差异维度传统数据库方案Elasticsearch千万级数据查询全表扫描 逐行计算延迟高BKD 树空间索引检索范围大幅缩小与业务条件组合过滤SQL 写起来冗长难以灵活拼装bool查询天然支持 geo 过滤与 keyword/range 组合按距离排序需要额外算排序字段复杂_geo_distance排序直接支持区域聚合统计基本没有现成能力geohash_grid、geo_tile聚合非常方便横向扩容分库分表后地理查询极其痛苦天生分布式按分片分散压力注意我不是说 ES 可以完全替代数据库。它有明显短板数据一致性偏弱、运维成本更高、内存和磁盘开销比 MySQL 大。所以正确的姿势是核心交易数据放数据库地理位置检索与聚合分析放 ES两者通过业务 ID 关联。你要先想清楚这一点后面才不会做出一个四不像的架构。1.3 什么场景适合用什么场景别硬上适合用 ES 做地理位置搜索的场景我归纳为四类附近的人 / 附近门店以某个点为中心找一定半径内的实体并按距离排序。地理围栏判断用户坐标是否进入了某个商圈、园区、配送区域比如外卖骑手进店、共享单车禁停区提醒。区域统计与热力图对海量坐标做聚合生成每个格子内的点数用于人流分析、订单热度图。LBS 推荐前筛结合品类、价格、营业状态等条件先做地理粗筛再做业务精排。不适合硬上的场景也有比如数据量只有几万且请求量不高的内部系统MySQL 完全够用需要亚毫秒级响应且对距离精度要求极其苛刻的实时风控场景可能该考虑 Redis GEO 甚至内存计算强事务型系统更不能把 ES 当地理数据库用。方案选型不是越高级越好而是越匹配越好这是我做技术选型一贯的原则。2. 核心前置geo_point 的映射与数据写入2.1 字段类型与映射设计要点地理位置搜索的第一步不是写查询而是先把索引字段类型定义对。ES 里表示经纬度有两种核心类型geo_point表示单个坐标点geo_shape表示点、线、多边形等复杂的几何形状。日常最常见的“附近搜索”用geo_point就够了。我建议的映射模板大概是这样的PUT /poi_index { mappings: { properties: { poi_name: { type: keyword, ignore_above: 256 }, category: { type: keyword }, location: { type: geo_point }, open_time: { type: date } } } }这里有几个细节我特别想强调。第一location一定不要设成text或keyword否则它只是“一堆数字文本”既不能参与距离计算也不能做地理过滤和排序后面所有操作都白搭。第二geo_point默认开启了doc_values这对排序和聚合很重要尽量别关。第三如果你知道后续会有大量geo_bounding_box过滤可以不用额外配置太多ES 会按需使用 BKD 索引。映射是你整个地理位置业务的根基一开始定错了后面改索引代价极大务必想清楚再建。2.2 四种坐标写法别再踩经纬度顺序的坑我见过最多的线上问题就是经纬度顺序写反导致搜不到数据。ES 对坐标的写法其实有好几种每种顺序还不一样这是新手最容易栽跟头的地方。第一种是对象写法{ location: { lat: 31.2304, lon: 121.4737 } }第二种是字符串写法纬度在前经度在后{ location: 31.2304,121.4737 }第三种是数组写法注意这是 GeoJSON 格式经度在前纬度在后{ location: [121.4737, 31.2304] }第四种是 geohash 字符串比如wtw3sj一般是从地图 SDK 里直接拿到的。这四种写法本身没问题问题在于不同场景混用。你写入时用了数组[121.4737, 31.2304]查询时脑子里想的是“纬度 31、经度 121”于是传了{lat: 31.2304, lon: 121.4737}看似对其实因为内部存储始终是[lon, lat]一旦你写入用数组、查询用对象很容易出现坐标错位。我现在的做法是项目里统一约定用对象形式{lat: 31.2304, lon: 121.4737}后端拿到数据后先做一次范围校验纬度必须在 -90 到 90 之间经度必须在 -180 到 180 之间不合格直接报错不写入。范围校验成本很低但能救回大量脏数据问题。2.3 写入之后如何验证坐标正常写完数据后别急着查先确认索引里的坐标到底存成了什么样。我最常用的是 Kibana 的 Dev Tools一条命令看映射GET /poi_index/_mapping再查几条文档确认_source里的location字段是对象还是数组GET /poi_index/_search { size: 3, _source: [poi_name, location] }如果项目里接了 Kibana 的地图可视化功能直接在 Discover 里选坐标字段能地图上看到点分布那基本说明写入没问题。这里还有个小技巧写入前你可以用ingest pipeline统一加工坐标字段比如把字符串格式统一转成数组甚至在 ingest 阶段判断经纬度范围超出就丢弃。这样数据层面就保证了统一性查询时不用再猜前端到底传了什么格式。3. 五种常见地理位置查询的实操拆解3.1 geo_distance附近 N 公里的人与门店geo_distance应该算地理位置搜索里用得最多的查询了它的语义就是“以某个点为中心找指定距离内的所有点”。比如以人民广场为中心搜 5 公里内的门店查询语句如下GET /poi_index/_search { query: { bool: { filter: [ { geo_distance: { distance: 5km, location: { lat: 31.2304, lon: 121.4737 } } }, { term: { category: coffee } } ] } } }这里我特意把查询放在了bool.filter里而不是must里。原因是地理位置过滤本质是一个“有关系或没关系的条件判断”不需要计算_score相关性评分。放filter里有两级好处第一不走评分节约 CPU第二ES 可以缓存这个过滤结果相同条件第二次查询直接走缓存。如果你把地理查询放到must每次请求都得重新算分性能差距在高 QPS 下非常明显。关于距离单位distance字段支持m、km、mi、yd等建议查询层统一用km展示层再换算减少误解。distance_type参数也值得注意默认是arc按球面大圆距离计算精度高但相对慢plane是平面近似算法计算快但在高纬度地区误差很大只适合低精度场景。正常项目建议保持默认arc除非你清楚自己在做什么。3.2 geo_bounding_box矩形围栏的第一道粗筛如果说geo_distance像一个圆规画出来的圆那么geo_bounding_box就是用一个矩形把范围框出来它的特点是过滤速度快尤其适合做第一层粗筛。示例查询上海某个矩形区域内所有 POIGET /poi_index/_search { query: { bool: { filter: { geo_bounding_box: { location: { top_left: { lat: 31.5, lon: 121.3 }, bottom_right: { lat: 31.0, lon: 121.8 } } } } } } }写这个查询有两个容易踩坑的地方。第一top_left的纬度要大于bottom_right的纬度经度要小于bottom_right的经度这是地理坐标的正常方向反了就查不到。第二geo_bounding_box默认type为memory即不强制走索引如果这个查询是你的高频入口可以显式设置type: indexed让它尽量使用 BKD 索引加速代价是不能过滤掉没有坐标或坐标非法的文档。我实践中的做法是矩形粗筛永远放在filter里且放在geo_distance之前让 ES 先用最省力的方式把范围缩到最小再做精确距离计算。3.3 geo_polygon 与 geo_shape不规则区域圈定很多业务场景并不是简单的圆形或矩形比如“判断用户是否在朝阳区”“是否在某个不规则的小区范围内”这时候就要用多边形了。老的geo_polygon查询可以直接在查询里传多边形顶点GET /poi_index/_search { query: { bool: { filter: { geo_polygon: { location: { points: [ {lat: 31.5, lon: 121.3}, {lat: 31.4, lon: 121.6}, {lat: 31.2, lon: 121.5}, {lat: 31.3, lon: 121.2} ] } } } } } }不过说实话geo_polygon在新版 ES 里已经被标记为 deprecated官方更推荐用geo_shape查询。geo_shape的思路是你把区域边界多边形提前存成geo_shape类型查询时传入一个点让 ES 判断点和区域的关系。索引区域数据的映射大概是PUT /areas_index { mappings: { properties: { area_name: { type: keyword }, boundary: { type: geo_shape } } } }写入一个多边形区域后查询某个点是否落在区域内GET /areas_index/_search { query: { bool: { filter: { geo_shape: { boundary: { shape: { type: point, coordinates: [121.35, 31.22] }, relation: within } } } } } }这里我栽过一个大跟头relation参数的方向非常容易搞混。在geo_shape查询里查询条件中的shape是“你要判断的那个对象”比如一个点而字段里存的是“大区域”。当你想表达“点落在区域内”时需要用intersects而不是within。within的语义是“字段自身的范围完全包含查询形状”听起来对但实际查询结果空的时候极难排查。后来我的经验是判断点面关系一律用intersects多边形相交即命中简单直接。3.4 按距离排序与返回距离附近搜索只过滤还不够绝大多数产品还需要“从近到远”排序并且显示“距离我多少米”。ES 的_geo_distance排序非常方便GET /poi_index/_search { query: { match_all: {} }, sort: [ { _geo_distance: { location: { lat: 31.2304, lon: 121.4737 }, order: asc, unit: km, distance_type: arc } } ] }排序结果的每条 hit 里sort数组对应的值就是“按你给的unit算出来的距离”。如果unit是km那值就是多少公里。这里有个很重要的建议如果你既要做距离过滤又要按距离排序不要用两个一模一样的坐标点分别写在filter和sort里那样虽然能跑但没必要。直接把filter里的geo_distance当范围限制排序用同一个中心点逻辑清晰且性能最好。另一个细节是如果你要返回的字段里没有坐标光看距离排序值可能不够直观可以在查询里加上docvalue_fields: [location]或者fields: [location]让结果里带着坐标信息前端渲染更方便。3.5 geohash_grid 聚合热力图/区域统计的底座前面几类都是“查询”地理位置搜索真正体现 ELK 平台优势的还有“聚合统计”。最常用的就是geohash_grid它会把地图按 geohash 编码划分成一个个网格然后统计每个网格内有多少文档GET /poi_index/_search { size: 0, aggs: { grid: { geohash_grid: { field: location, precision: 6 } } } }返回结果大致是{ aggregations: { grid: { buckets: [ {key: wtw3sj, doc_count: 182}, {key: wtw3sk, doc_count: 96} ] } } }precision是 geohash 字符串长度它直接决定格子大小。经验值大概是precision为 1 时格子约 5000 公里3 时约 156 公里5 时约 5 公里7 时约 150 米。做全国热力图用 3 到 4做城市级热力图用 5 到 6做门店周边密集度分析用 7 左右。这个聚合特别适合做“订单分布热力图”“共享单车投放密度”之类的功能配合地图 SDK 的色块渲染就是一套非常经典的 LBS 数据可视化方案。除了geohash_grid还可以关注geo_tile聚合和geo_centroid聚合前者输出地图瓦片编码后者能返回每个格子的中心点对绘制聚合结果非常有帮助。4. 让地理位置搜索变快的实操套路4.1 filter 缓存与 bool 组合的查询性能差异很多人在刚开始写地理查询时习惯把所有条件堆在must里结果发现查询一上量就变慢。这背后的逻辑不复杂must里的每个子句都需要参与相关性评分geo_distance本身不是为评分设计的给它算分既没意义又拖慢速度。我给出的标准姿势是把geo_distance、geo_bounding_box、geo_shape这类地理位置条件全部放进bool.filter同时把业务过滤条件如分类、营业状态、价格区间也放进去。比如“找 5 公里内、营业中、人均 50 元以下的咖啡店”可以这样写GET /poi_index/_search { query: { bool: { filter: [ { geo_distance: { distance: 5km, location: {lat: 31.2304, lon: 121.4737} } }, { term: {category: coffee} }, { term: {is_open: true} }, { range: {avg_price: {lte: 50}} } ] } } }把地理条件放在第一个filter里是我自己的习惯。ES 对多个 filter 条件的执行顺序不保证严格从左到右但提前用地理过滤把候选集缩到最小后面的term、range只需要在小集合里验证整体开销就更小。另一个细节是如果一个地理过滤条件被多个查询复用可以考虑把它单独提出来作为filter子句充分利用 ES 的 filter cache而不是每次拼接新查询。4.2 粗筛 精算的两步走策略地理位置搜索最容易出现的性能瓶颈是你直接拿geo_distance对一个超大的索引做全量范围过滤。比如公司有 5000 万条订单坐标你每次“找附近 3 公里”都直接算球面距离即便有 BKD 树开销也很大。我的优化思路是“粗筛 精算”先用geo_bounding_box把范围粗暴地框出来这个矩形可能比真正的圆大了一圈但胜在快把候选集从 5000 万缩到几万然后再用geo_distance在候选集里做精确圆过滤。这个组合在实战里的效果非常明显。我做过一个测试5000 万文档的索引直接全量geo_distance过滤耗时 80 毫秒左右加了前置geo_bounding_box后降到 20 毫秒以内而且结果完全一致。注意矩形范围如果过大比如直接用几十公里的正方形那前置过滤的意义就不大要结合你的实际业务半径调整。一般来说矩形边长大致是圆直径或者稍大个 20%粗筛效果最好。如果你的场景既需要范围精确又需要距离排序可以这样组合filter里用geo_bounding_box做粗筛sort里用_geo_distance做精确距离排序。这样查询路径更短排序精度还不丢是我现在最常用的 LBS 查询模板。4.3 映射参数与索引优化细节除了查询侧优化索引侧也有不少能影响地理位置搜索性能的参数。第一个是ignore_malformed。默认情况下一个字段里混入一条非法的坐标整个文档写入就会失败。很多线上问题看起来是“写入偶尔失败”实际上就是某条数据坐标lat超过了 90 度。我建议在geo_point字段上开启ignore_malformed保证脏数据跳过而不是拖垮整个索引写入{ mappings: { properties: { location: { type: geo_point, ignore_malformed: true } } } }第二个是分片规划。地理位置搜索本质是范围查询分片太少会导致单分片数据量过大、查询变慢分片太多又会增加协调节点合并结果的成本。我的经验是千万级数据单分片 30 到 50GB 左右比较均衡比如 5000 万文档大致配 3 到 5 个分片副本 1 个就够。不要为了炫技把分片数设成几十个得不偿失。第三个是刷新频率。如果你在做地理位置数据的批量导入频繁的refresh会让写入压力剧增。批量导入时可以把refresh_interval设为-1导入完成后改回1s或30sPUT /poi_index/_settings { index: { refresh_interval: -1 } }这样做之后写入吞吐能明显提升代价是数据不会实时可见所以只适合离线导入场景。5. 常见问题与排查技巧实录5.1 为什么附近搜索查不到数据先查经纬度顺序这是地理位置搜索“第一杀手级”问题。我接手过一个项目接口偶尔返回空数据排查了两天才发现根因前端传的是 GeoJSON 数组[31.12, 121.31]后端拿到后直接写进 ES 的数组字段。ES 数组格式要求是[lon, lat]但前端给的第一个值是纬度。结果坐标被解释成经度 31 度、纬度 121 度彻底跑偏到别的半球去了。排查方法其实很简单直接查一条出问题的文档看_source.location再用 Kibana 自带地图看坐标是不是落在真实业务区域附近。再不行就写一个脚本把查询条件和写入数据都打出来人工对比。解决这类问题我给三个固定动作一所有端统一传{lat:..., lon:...}对象二后端统一做一次经纬度范围校验三写个定时任务抽样校验索引里的坐标分布是否合理比如统计一下超出中国范围的坐标占比超过阈值立刻报警。5.2 为什么查询变慢先看写入指标还是先看磁盘有一个热搜问题问得特别好写入 ES 变慢怎么判断是磁盘问题还是其他原因我的经验是先看 ES 自身的线程池和写入指标再看系统层磁盘指标顺序别颠倒。第一步看 write 线程池有没有堆积GET /_cat/thread_pool/write?vhnode_name,name,active,queue,rejected如果queue长期不为 0rejected持续增长说明写入压力已经超过了集群处理能力。这时候要拆两个方向排查如果是大批量写入触发了瓶颈优先调整批量大小和并发如果单条写入都慢就要看索引层面的耗时。第二步看索引写入耗时指标GET /_nodes/stats?filter_path**.indexing**重点看index_total和index_time_in_millis两者相除就是平均每条文档索引耗时。如果这个值持续走高说明 ES 内部处理慢常见的元凶是有太多字段需要索引、refresh 太频繁或者做大量字段的 doc_values 写入。第三步再看系统层指标用iostat -x 1看%util、await、iowait。如果%util接近 100% 且await很高基本可以确认磁盘是瓶颈可以换 SSD、扩大磁盘并行度、减少 refresh。如果磁盘指标很健康那问题大概率在查询或写入逻辑上别盲目加磁盘或扩机器。我在实战中总结了一个速查表分享给大家现象优先排查指标典型根因写入变慢且 bulk 拒绝write 线程池rejected并发过高或 shard 数过多单条写入耗时高index_time/index_totalrefresh 频繁、字段过多、磁盘慢磁盘 util 高iostat%util/await机械盘性能不足、segment 合并激烈查询变慢search 线程池、segment 数未用 filter、候选集过大、堆外内存不足5.3 SpringBoot 集成时的几个隐藏坑社区里 springboot 集成 elasticsearch 的帖子很多我在实际项目里踩过几个文档里不怎么提的坑。第一个是客户端版本必须对得上。ES 大版本 7 和 8 的客户端 API 差异很大7.x 用RestHighLevelClient8.x 推荐用新的ElasticsearchClient。版本不一致最典型的报错就是“this version of the jdbc driver is only compatible with elasticsearch version ...”这个报错不仅出现在 DBeaver 连 ES 的场景也出现在 Java 客户端里。我的建议是在 pom 里直接指定与服务器 ES 完全一致的版本不要用传递依赖的默认版本。用 JDBC 或 DBeaver 连接 ES 时同理JDBC 驱动版本必须和 ES 集群版本对应否则会在连接握手阶段就报版本不兼容。第二个坑是批量写入。很多新手会把地理位置数据一条一条循环插入结果写入性能惨不忍睹。正确的做法是用BulkProcessor或者批量 API设置每个批次大小、并发线程数和刷新间隔。比如一秒钟攒够 1000 条或 5MB 就提交一次这样写入吞吐可以提升数倍。第三个坑是查询构建时坐标类型。在 Java 里构建geo_distance查询时如果直接传字符串31.23,121.47很容易把纬度和经度搞错。更稳妥的方式是用GeoPoint对象GeoDistanceQueryBuilder qb QueryBuilders.geoDistanceQuery(location) .point(new GeoPoint(31.2304, 121.4737)) .distance(5, DistanceUnit.KILOMETERS); BoolQueryBuilder bool QueryBuilders.boolQuery(); bool.filter(qb); bool.filter(QueryBuilders.termQuery(category, coffee));这样至少从类型层面避免了顺序错误。5.4 部署环境速记Windows、Docker Compose 与 Kubernetes地理位置搜索的索引和查询再优秀部署环境不稳定也白搭。搜热词里关于部署的问题特别多我简单分享几个直接能用的经验。Windows 上启动 ES 最常见的问题是内存配置。ES 启动脚本默认会读取jvm.options但 Windows 下用户经常忘记改默认堆内存只有 1GB一导入数据就 OOM。我的建议是生产环境至少给 4GB 堆并且-Xms和-Xmx要设置成一样避免 JVM 动态扩容导致卡顿。另外Windows 上建议把 ES 注册成服务别用前台窗口跑毕竟运维老哥可不想天天守着黑窗口。Docker Compose 部署 ES 时有几个配置几乎是必须的。第一要设置discovery.typesingle-node不然单实例会一直报 discovery 错误第二要挂载数据卷否则容器一删数据全没第三要给 JVM 堆内存留够不设置的话容器默认内存机制很容易导致 ES 被 OOM Killer 杀掉。贴一个我常用的最简配置version: 3 services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10 container_name: es environment: - discovery.typesingle-node - ES_JAVA_OPTS-Xms4g -Xmx4g - bootstrap.memory_locktrue ulimits: memlock: soft: -1 hard: -1 ports: - 9200:9200 volumes: - esdata:/usr/share/elasticsearch/data volumes: esdata:注意bootstrap.memory_locktrue这个参数它要求锁定内存避免 ES 堆内存被 swap 到磁盘导致性能雪崩。在 Linux 上你可能还需要修改/etc/security/limits.conf里的memlock限制。这套配置在地理搜索的测试环境里足够稳定。至于 KubeSphere 或 Kubernetes 上部署 ES我没有特别多要讲的核心原则是用 StatefulSet 保证节点标识稳定用 headless service 做节点发现用 PVC 挂数据目录给容器设置合理的 CPU 和内存 limits。只要保证这三件事不出错集群就能跑得比较稳。千万不要图省事用 Deployment 部署多节点 ES重启后节点身份变化分片恢复会给你带来巨大的麻烦。6. 从地理位置搜索到更多业务场景6.1 配送范围判定与地理围栏我用 geo_shape 做的最有成就感的项目是外卖商家配送范围判断。商家可以手动绘制一个配送区域本质是一个多边形用户下单时系统判断用户坐标是否落在商家配送多边形内。这个场景如果用geo_polygon每次实时传多边形顶点性能会很难看因为每个商家多边形可能有几十个点每个订单都得算一遍。我的做法是商家区域提前同步成 ES 的geo_shape索引下单时直接查询用户坐标点和区域索引的intersects关系命中即表示在配送范围内。一来过滤走 BKD 树很快二来订单筛选还能叠加营业状态、起送价等业务条件整套逻辑都在一次查询里解决。6.2 热力图、区域人流统计与容量预估另一个让我觉得 ES 地理位置搜索非常值钱的场景是区域人流统计。之前做一个景区调度项目需要实时统计每个片区的人流量数据来自几十万台设备的坐标上报。我们直接把设备坐标写入 ES然后每隔一段时间跑一次geohash_grid聚合拿到每个网格的doc_count再根据网格精度换算成对应区域的设备密度。这套方案上线后运维只需要监控 ES 集群的写入和查询延迟不用自己写分布式统计任务省了一大半精力。如果你有类似“按区域统计订单量”“按格子分析骑行轨迹”的需求geohash_grid聚合完全可以照搬。6.3 我的一些经验沉淀做地理位置搜索这几年我最大的体会是性能问题大多出在数据规范上而不是出在查询写法上。坐标混乱、经纬度写反、字段类型错误这些基础问题一旦没有治理好后面所有调优都是空中楼阁。第二个经验是地理位置查询并不是越高阶越好geo_distance加geo_bounding_box的组合已经能覆盖 80% 的业务场景geo_shape才管剩下 20% 的区域围栏需求不要一上来就把所有地理特性都堆上。第三个经验是ES 的地理搜索定位应该是“检索利器”不是“分析数据库”数据量到了一定规模聚合统计、热力分布、轨迹分析这些重活可以考虑把聚合结果定期沉淀到其他存储ES 继续专注服务高并发查询。把每一个坐标都当作核心资产来治理按这个思路去设计地理位置搜索会让你省心很多。
返回列表