【架构实战】Elasticsearch搜索架构从倒排索引到集群调优一、一个搜索请求把整个集群打挂了2019年双12凌晨0点刚过5分钟ES集群全部节点CPU 100%查询超时监控疯狂告警。排查发现运营在后台跑了一句查询所有包含’优惠券’关键词的订单这条查询在3亿订单数据上做模糊匹配瞬间打爆了整个集群。更糟的是运维经验不足直接重启了全部节点——数据恢复花了6个小时期间所有搜索功能不可用。这就是ES运维的残酷现实开箱即用很简单扛住生产流量很难。二、倒排索引——ES的核心原理2.1 正排 vs 倒排【正排索引MySQL B树】 文档1 → 华为手机很好用 文档2 → 小米手机性价比高 文档3 → iPhone也很好用 查手机 → 需要扫描所有文档 → O(n) 【倒排索引Elasticsearch】 词项(Term) → 文档列表 华为 → [1] 手机 → [1, 2] 很好用 → [1, 3] 小米 → [2] 性价比 → [2] iPhone → [3] 查手机 → 直接定位Term → [1, 2] → O(1)2.2 分词器——搜索质量的根基// 中文分词对比// Standard分词器按字切分中华人民共和国→[中,华,人,民,共,和,国]// IK分词器按词切分中华人民共和国→[中华人民共和国,中华人民,中华,华人]// 搜索华人时// Standard: 匹配华人结果包含华为、中华等大量噪音// IK: 精准匹配华人结果准确率大幅提升三、ES集群架构设计3.1 节点角色分离┌──────────────┐ │ Client │ ← 负载均衡 请求分发 │ Nginx/Kibana│ └──────┬───────┘ │ ┌────────────┼────────────┐ │ │ │ ┌───────▼──────┐ ┌──▼──────┐ ┌───▼────────┐ │ Master Node │ │Data Node│ │Data Node │ │ × 3候选 │ │ Hot │ │ Cold │ │ 集群管理 │ │ SSD │ │ HDD │ │ 不存数据 │ │ 热数据 │ │ 冷数据归档 │ └──────────────┘ └─────────┘ └────────────┘角色分配原则Master节点3台只做集群管理不存数据配置不用太高Data Hot节点SSD存储热数据最近30天配置要高Data Cold节点HDD存储冷数据配置可降低Client节点可选的协调节点分散查询压力3.2 索引设计——分片策略// 创建索引时决定分片和副本PUT/orders{settings:{number_of_shards:10,number_of_replicas:2,refresh_interval:30s,routing.allocation.require.box_type:hot},mappings:{properties:{order_id:{type:keyword},user_id:{type:long},product_name:{type:text,analyzer:ik_max_word,search_analyzer:ik_smart},amount:{type:scaled_float,scaling_factor:100},status:{type:keyword},create_time:{type:date,format:yyyy-MM-dd HH:mm:ss}}}}分片数计算公式单分片最大容量 ≈ 50GBSSD或 30GBHDD 总分片数 ceil(总数据量 / 50GB) 例1TB数据 → 20个主分片 每个节点不超过 20分片/GB堆内存经验值四、深入查询优化4.1 避免深度分页// 问题Page 1000, Size 20GET/orders/_search{from:20000,size:20}// ES需要从每个分片取20020条汇总后排序取20条// 内存开销20020 * 分片数// 解决方案1Search After推荐GET/orders/_search{size:20,query:{match:{status:PAID}},sort:[{create_time:desc},{order_id:desc}],search_after:[1625097600000,123456]// 上一页最后一条的排序值}// 解决方案2业务限制深分页// 前端只允许查前100页超过提示用户缩小查询范围4.2 Filter vs Query// Query计算评分有缓存GET/orders/_search{query:{bool:{must:[{match:{product_name:手机}}// 需要评分],filter:[{term:{status:PAID}},// 精确过滤有缓存{range:{create_time:{gte:2025-06-01}}}]}}}// filter结果会被ES缓存重复查询直接走缓存4.3 聚合优化// 大数据量聚合优化GET/orders/_search{size:0,// 不需要原始文档只要聚合结果aggs:{by_category:{terms:{field:category_id,size:100,execution_hint:map// 直接使用字段值不计算全局序数},aggs:{total_amount:{sum:{field:amount}}}}}}五、集群监控与调优5.1 关键监控指标指标正常值告警阈值说明集群状态GreenYellow/Red有未分配分片节点CPU 60% 80%查询压力大堆内存使用 75% 85%接近GC临界点磁盘使用 70% 85%水位线保护Search Queue 100 500查询积压GC频率 5/min 20/min频繁Full GC5.2 堆内存配置# jvm.options# 堆内存不超过32GB压缩指针阈值-Xms16g-Xmx16g# 堆内存不超过物理内存的50%# 剩下50%留给Lucene的OS Cache文件缓存# GC选型-XX:UseG1GC-XX:MaxGCPauseMillis2005.3 索引生命周期管理ILM// 自动管理索引的生命周期PUT_ilm/policy/orders_policy{phases:{hot:{min_age:0ms,actions:{rollover:{max_size:50gb,max_age:1d}}},warm:{min_age:30d,actions:{forcemerge:{max_num_segments:1},shrink:{number_of_shards:1}}},cold:{min_age:60d,actions:{allocate:{require:{box_type:cold}}}},delete:{min_age:90d,actions:{delete:{}}}}}六、双11扛压实战压测发现的问题和优化 1. 热点分片 → 添加routing按user_id路由 2. 查询慢 → 用filter替代query利用缓存 3. 聚合慢 → eager_global_ordinals预加载 4. GC频繁 → 堆内存从32G降到16GLucene用OS Cache 5. 写入慢 → refresh_interval从1s改为30s 6. 凌晨数据倾斜 → 用rollover按时间切分索引 最终结果 - 峰值写入50万 docs/s - 查询P99 200ms - 集群规模15个Data Hot 5个Data Cold 3个Master七、总结ES的调优之路就是不断做减法的过程减少扫描、减少排序、减少聚合、减少不必要的字段。核心实践倒排索引是灵魂——理解分词和倒排原理才能写出高效查询能用filter不用query——filter有缓存query要计算评分深分页是集群杀手——用search_after代替from/size堆内存不超过32GB——剩下内存留给Lucene的OS CacheILM自动化索引生命周期——Hot/Warm/Cold/Delete四阶段托管监控先行——堆内存、队列长度、GC频率是三大关键指标血的教训ES集群挂了不要直接重启所有节点先停写入流量恢复Master节点等集群状态Green后再逐个恢复Data节点。个人观点仅供参考