Elasticsearch——待补充

之前写过很多es了,接触es也是比较早,很多项目也是用了,但是一直不成系统,零零散散的看着有很大的进步空间,所以这篇他来啦~

🎭 第一幕:ES 是谁?

首先咱先上一碟小菜:es是数据库吗,es和mysql的区别是什么?

""当你在电商站搜'红烧肉',MySQL 会用 LIKE '%红烧肉%' 扫描百万商品描述------用户等到泡面凉了还没结果。而 ES 用倒排索引 ,0.01 秒告诉你:'有 327 条结果,第 1 条是李佳琦推荐的秘制酱料'。------不是 ES 更快,而是它根本不用'找',它早就把答案按关键词排好了

  • MySQL / PostgreSQL :强一致性、事务、结构化查询 → "记账型数据库"
  • Elasticsearch :近实时、全文检索、模糊匹配、聚合分析 → "情报型搜索引擎"

好了,这第一幕 倾其所有 基本上已经道破天机了,寥寥数语 浓缩了这么多黄金屋,功力已经用了0.1成,呼~ 稍作休息、正餐马上开始啦;

🏙️ 第二幕:架构揭秘

ES = 分布式 Lucene 集群 + 自动容灾 + RESTful API + 让你爱上搜索的魔法;

"单机 Lucene 最多扛几亿文档,再大就慢如蜗牛。ES 把索引拆成多个 Shard(分片) ,每个 Shard 是独立的 Lucene 实例------查询时,10 个分片并行干活,速度提升近 10 倍 ;写入时,数据自动路由到不同分片,避免单点瓶颈。Replica(副本) 更是救命稻草:主分片挂了?副本秒升主,服务不中断!"

📌 核心事实:

  • 真正的索引/搜索引擎是 Lucene(Apache 顶级项目)
  • ES 不存储原始数据 ,只存储 倒排索引 + 列存(Doc Values) + 源文档(_source)
  • ES 的所有"魔法",都建立在 Lucene Segment 不可变模型 之上

所以,不懂 Lucene,就永远看不懂 ES 的性能边界

Lucene 底层模型 ------ "不可变 Segment 的艺术"

1. Segment:一切性能的基石
  • 每次 refresh(默认 1s),Memory Buffer → 新 Immutable Segment
  • Segment 一旦生成,永不修改(Immutable)以此可不加锁 长缓存
  • 查询时,并行查所有 Segment,再 merge 结果

上面说的有点简约,下面咱们解释一下:

Elasticsearch 对写入的数据实行"一夫一妻制":Segment 一旦生成就永不更改,多线程读它连锁都懒得加 ,连 OS 的 Page Cache 都敢把它当长期饭票;可若所有数据挤在一个文件里,迟早胖成磁盘的"前任阴影",于是新来的先住小 Segment 单间,等后台 Merge 策略哪天心情好,就把几个小单间打通成大平层,顺手把那些"已失宠但还占编制"的幽灵文档扫地出门。

⚠️ 生产陷阱

如果写入太快(如日志场景),Segment 爆炸 → 查询变慢
对策 :调大 refresh_interval(如 30s),或用 Time-based Indexing(每天一个索引)

角色 职责 比喻
Index(索引) 逻辑数据库 "科幻小说区"、"美食菜谱馆"
Shard(分片) 数据分片 把"科幻小说区"拆成 5 个子馆(shard=5)
Replica(副本) 容灾备份 每个子馆配 1 个影子馆(replica=1)
Node(节点) 物理服务器 图书馆分馆(北京馆、上海馆)
Cluster(集群) 多节点协同 全国图书馆联盟

🔥 关键设计

  • 写入时,数据自动路由到某个 Primary Shard
  • 同时同步到 Replica Shard(保证高可用)
  • 查询时,并行查所有分片,再合并结果 → 速度起飞!

💡 为什么不能无限加分片

因为每个分片 = 一个 Lucene 实例 = 占内存 + 文件句柄;分片太多 → 集群变"老年机"(官方建议单分片 < 50GB)

🔍 第三幕:倒排索引

你搜 "红烧肉怎么做",ES 为什么能 0.02 秒返回结果?不是因为它聪明,而是因为它早就把答案按关键词排得明明白白 ------这就是 倒排索引(Inverted Index) 。 当你输入 "红烧肉",ES 直接查表,O(1) 定位到相关文档 ID 列表,根本不用扫描全文。

词语 出现在哪些文档?
红烧肉 [doc123, doc456, doc789]
土豆 [doc200, doc300]
糖色 [doc123, doc789]

🧠 台下十年功附赠的是

  • 自动分词("红烧肉" → "红烧" + "肉"?No!用中文分词器 like IK)
  • 相关性打分(TF-IDF / BM25)→ "红烧肉食谱" 比 "红烧肉新闻" 排更前
  • 模糊匹配("roast pork"~2 → 容忍2个词错位)

但问题来了:如果词典有 10 亿个词,内存岂不爆炸

🔍 关键优化技术:

(1) FST(Finite State Transducer)有限状态转换器
  • 普通 Trie 树存 "application" 和 "apply" 需要完整路径
  • 词典用 FST 压缩, 让它们共享前缀 "appl",比 Trie 树省 50%+ 内存
  • FST 支持**前缀自动补全,**如搜 "app" 能快速列出所有以 app 开头的词
(2) Doc ID 列表:Roaring Bitmap + Skip List跳表
  • 文档 IDDoc ID 列表有序存储(如 [1, 5, 9, 12, ...])
  • ES 用 差值编码(Delta Encoding) 存储相邻 ID 的间隔;
  • 再用 Roaring Bitmap 压缩稀疏段,10 亿 ID 列表内存 < 100MB
  • 查询时靠 Skip List跳表快速跳跃---比如找 doc_10000,不用遍历前 9999 个,避免全扫描
(3) 分词不是乱切,是可控的艺术
  • 中文默认不分词(会搜不到),需配 IK 分词器
  • "红烧肉" 可被切为 ["红烧肉"](精准匹配)或 ["红烧", "肉"](泛化匹配);
  • 甚至支持同义词扩展:"番茄" → "西红柿"。

所以,ES 的快,从来不是玄学------是 FST 省下的每 1KB 内存,是 Skip List 跳过的每一个无关 ID,是 Roaring Bitmap 压缩的每一比特空间,共同堆出了"秒搜十亿文档"的奇迹。

⚡ 第四幕:读写与近实时搜索

"ES 默认写入后 1 秒才可搜(refresh_interval=1s),这不是 bug,是吞吐与延迟的权衡

  • 刷新越频繁,Segment 越多,查询越慢;
  • 刷新越稀疏,数据可见越晚。
    若你强行 ?refresh=true,每写一条就刷一次,集群会像被踩了尾巴的猫,瞬间炸毛(CPU 飙升,IO 爆满)

"至于强一致?ES 默认只要 1 个副本 ACK 就返回成功(wait_for_active_shards=1)。

  • 想要 all 副本确认?可以,但是你要容忍**写入吞吐直接腰斩,**这能忍?"士可杀不可忍"------ES 的哲学:宁可短暂不一致,也不牺牲高可用。"

🔁 写入流程(带容错):

  1. 协调节点(Coordinating Node) 接收请求
  2. 根据 _idrouting 计算 目标 Primary Shard
  3. 转发请求到 Primary Shard 所在节点
  4. Primary Shard
    • 写入 Memory Buffer + Translog
    • 执行 refresh(可选)
    • 并发转发请求到 所有 Replica Shards
  5. Replica Shards
    • 同样写入 Buffer + Translog
    • 返回 ACK
  6. Primary 收到 quorum 副本 ACK (默认 wait_for_active_shards=1
  7. Primary 返回成功给协调节点
  8. 协调节点返回客户端
  9. 后台异步 flush(Translog → 持久化 Segment)
  10. 30 分钟 或 Translog 满,触发 flush → 写入磁盘 + 清空 Translog

⚠️ 一致性保证

  • 默认 最终一致(写入后可能短暂不可见)
  • 可通过 ?consistency=quorum + wait_for_active_shards=all 强一致(但性能差)

💡 所以

  • 默认 1 秒延迟 可见(可通过 ?refresh=true 强制立即可见,但别滥用!)
  • 即使宕机,也能从 Translog 恢复未持久化数据

查询流程:

  1. 协调节点广播查询到 所有相关 Shard
  2. 每个 Shard 本地执行查询(Lucene 层)
  3. 返回 Top-K 文档 ID + Score(非完整文档!)
  4. 协调节点 merge + re-rank(全局排序)
  5. 再去各 Shard fetch 完整文档 (如果需要 _source

💡 这就是为什么 size 越大越慢

  • 第一阶段:每个 Shard 返回 size
  • 第二阶段:协调节点要 merge (shard_count * size) 条再取 top size

🚫 深分页(from=100000)为何被禁止

  • 协调节点需持有 100000 + size 条结果
  • 内存爆炸!官方限制 index.max_result_window = 10000

替代方案

  • Search After(基于上一页最后一条的 sort 值)
  • Scroll API(用于导出,非实时)
  • Point in Time (PIT)(7.x+,快照式分页)

🧩 第五幕:聚合分析

咱们上面提到的倒排索引:"text 字段会被分词(如 '红烧肉' → ['红烧', '肉']),而聚合需要的是完整值 (如 '红烧肉食谱' vs '红烧肉新闻')。所以 ES 为 keyword 类型构建 Doc Values(列存) :按文档顺序存储原始值,聚合时直接扫描列,比倒排索引快 10 倍 !若你偏要用 text 聚合?ES 会默默加载 Field Data 到堆内存 ------等着 OOM 吧,少年。"

列式存储(Doc Values) + 高效聚合算法(不像 MySQL 那样全行扫描,ES 只读需要的字段)

  • 写入时,为每个字段额外构建列式存储
  • 结构:field → [value1, value2, ..., valueN](按 doc_id 顺序)
  • 存储在磁盘,但 OS 会缓存(mmap)

为什么不用倒排索引做聚合?

  • 倒排是 term → docs ,聚合需要 doc → value
  • 反向查找效率极低(尤其高基数字段)

💡 所以

  • text 字段默认 不开启 Doc Values(因为分词后无意义)
  • 聚合必须用 keyword / numeric / date 等类型
  • 可通过 "doc_values": false 关闭(节省空间,但不能聚合

🛡️ 第六幕:集群容灾与扩展

1. 脑裂(Split Brain)

  • 网络分区 → 多个 Master 同时存在 → 数据写坏
  • 对策
    • discovery.zen.minimum_master_nodes = (master_eligible_nodes / 2) + 1(7.x 前)
    • 7.x+ 用 Raft,自动防脑裂

场景:某台服务器宕机

  • 如果挂的是 Data Node
    • Primary Shard 挂了?→ 自动提升 Replica 为 Primary
    • 集群状态变 Yellow(副本缺失),但服务不中断
  • 如果挂的是 Master Node
    • 其他 Master-eligible 节点自动选举新老大(基于 Zen Discovery 或新版 Raft)

如何扩容?

  • 加机器 → 自动加入集群
  • 索引分片会自动 rebalance(数据迁移)
  • 查询自动路由到新节点 → 无缝扩展

💡 最佳实践

  • 至少 3 个 Master 节点(防脑裂)
  • Data 节点按角色分离(hot-warm-cold 架构)

第七幕:调优与问题

瓶颈 优化手段
Refresh 太频繁 refresh_interval: 30s
Translog 同步太勤 translog.durability: async
副本太多 写入时设 replicas: 0,写完再加
Bulk 太小 单次 Bulk 5--15MB(约 1000--5000 docs)
Mapping 动态膨胀 关闭 dynamic: strict

1. 慢查询雪崩

  • 一个复杂聚合占满 CPU → 其他查询排队 → 集群 hang
  • 对策
    • search.default_search_timeout(超时熔断)
    • indices.breaker.*(内存熔断)
    • 监控 thread_pool.search.rejected

2. Field Data 爆炸

  • text 字段做聚合 → 加载到堆内存(Field Data)
  • 对策永远不要对 text 做聚合!用 keyword

3.极限写入架构:

Logstash/Filebeat → Kafka → Spark/Flink → ES Bulk Write

  • 中间加消息队列削峰
  • Flink 做窗口聚合,减少 ES 写入量

🚫 ES 对 JVM 的特殊要求:

  • 堆内存 ≤ 32GB(否则指针压缩失效,内存翻倍)
  • 堆内存 ≤ 物理内存 50%(留一半给 OS Page Cache)
  • 禁用 Swapbootstrap.memory_lock: true
区域 用途 调优建议
Heap 存储查询上下文、聚合中间结果 ≤ 31GB,G1GC
Page Cache 缓存 Segment 文件 越大越好(靠 OS 管理)
Translog 事务日志 SSD 必备
场景 为什么不适合 正确姿势
强事务 ES 不支持 ACID 用 MySQL,ES 只做搜索同步
频繁更新 更新 = 删除+重建,性能差 少量更新 or 用 _update 脚本
大宽表 JOIN 不支持 JOIN 用 Nested / Parent-Child(慎用)或应用层关联
精确计数(10亿级) total: 10000+ 是估算 track_total_hits=true(性能代价大)

记住
ES 是"搜索加速器",不是"主数据库"

🎯 终极总结

原则 实现 目的
不可变性 Immutable Segments 高并发、缓存友好
近实时 Refresh + Translog 写入吞吐 vs 可见性平衡
列存聚合 Doc Values 高效 BI 分析
分片自治 Shard = Lucene Index 水平扩展
协调解耦 Coordinating Node 无状态,易扩展

ES 不是银弹,而是一套精密的权衡系统

  • 空间换时间(Doc Values + 副本)
  • 延迟换吞吐(Refresh 间隔)
  • 复杂度换能力(分布式协调)

真正的大神,不是会用 ES,而是知道什么时候不该用 ES

超能力 技术实现 效果
闪电搜索 倒排索引 + 分词 毫秒级全文检索
横向扩展 分片 + 集群 PB 级数据轻松扛
高可用 副本 + 自动故障转移 节点挂了照常工作
智能分析 聚合 + Doc Values 实时 BI 报表
近实时 Refresh + Translog 写入1秒可见,不丢数据

"如果你还在用 LIKE %keyword% 做搜索,那你不是在查数据,你是在给用户表演'系统正在思考人生'。"

相关推荐
Elastic 中国社区官方博客2 小时前
Elasticsearch:使用 Elastic Workflows 构建自动化
大数据·数据库·人工智能·elasticsearch·搜索引擎·自动化·全文检索
跨境卫士-小汪3 小时前
选品更稳的新打法:用“用户决策阻力”挑品——阻力越大,越有机会做出溢价
大数据·人工智能·产品运营·跨境电商·内容营销·跨境
迎仔3 小时前
11-NoSQL数据库HBase介绍:大数据世界的“快速检索图书馆”
大数据·nosql·hbase
迎仔3 小时前
01-Hadoop 核心三剑客通俗指南:从“单机搬砖”到“包工队”
大数据·hadoop·分布式
Elastic 中国社区官方博客3 小时前
Elastic 9.3:与数据对话、构建自定义 AI agents、实现全自动化
大数据·人工智能·elasticsearch·搜索引擎·ai·自动化·全文检索
双层吉士憨包3 小时前
乐天Rakuten开店:乐天Rakuten跨境店VS本土店?2026实战攻略
大数据
档案宝档案管理3 小时前
企业档案管理系统:从“资料存放”到“数据资产”的升级
大数据·人工智能·档案·档案管理
跨境卫士情报站3 小时前
用“渠道矩阵+内容节奏”把流量做成可控资产
大数据·人工智能·矩阵·产品运营·跨境电商·亚马逊
一只专注api接口开发的技术猿4 小时前
淘宝商品详情API的流量控制与熔断机制:保障系统稳定性的后端设计
大数据·数据结构·数据库·架构·node.js