目录

ES 基础

1. 倒排索引

1.1 什么是倒排索引

倒排索引是一种 从内容到文档的映射 数据结构,它和“正向索引”相反。

  • 正向索引:文档 → 内容
  • 倒排索引:内容(词语) → 出现该词语的文档列表

它的核心作用是 快速找到包含某个词的文档,是搜索引擎(比如 ElasticSearch、Lucene)最核心的数据结构。


1.2 举例说明

假设我们有 3 篇文档:

文档ID 内容
D1 我喜欢苹果和香蕉
D2 苹果很好吃
D3 香蕉和橘子都是水果

Step 1: 分词(简单按空格或词语切分)

  • D1 → 我, 喜欢, 苹果, 和, 香蕉
  • D2 → 苹果, 很好吃
  • D3 → 香蕉, 和, 橘子, 都是, 水果

Step 2: 构建倒排索引

把每个词对应出现的文档列出来:

词语 文档列表
D1
喜欢 D1
苹果 D1, D2
D1, D3
香蕉 D1, D3
很好吃 D2
橘子 D3
都是 D3
水果 D3

1.3 倒排索引的结构

/images/es/post_index.png

如上图所示,倒排索引有如下核心结构:

1️⃣ 词条(Term)

  • 文档经过 分析器(Analyzer) 处理后得到的最小索引单位。
  • 可以是单词、符号、或者中文分词后的词。

英文分词比较简单,常用分词器包括:

分词器 说明
standard 默认分词器,按照空格、标点符号切分,并做小写处理。
simple 按非字母字符切分,忽略大小写,不做停用词过滤。
whitespace 仅按空格分词,不做其他处理。
keyword 不拆分整个字段,作为一个整体索引,常用于精确匹配。
stop 类似 standard,但会去掉停用词(如 “the”, “is”)。

中文没有天然的空格分词,需要专门的中文分词器。ElasticSearch 官方没有默认中文分词器,但常用插件有:

分词器 来源/说明
ik_max_word IK 分词器插件,尽可能切分出所有可能的词(最大化匹配),适合搜索场景。
ik_smart IK 分词器插件,智能切分,倾向于最自然的分词,适合精确匹配或分析场景。
jieba 基于 Python 的中文分词库,ElasticSearch 可通过插件或自定义分析器集成。

es 分词器配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_chinese_analyzer": {
          "type": "ik_max_word"
        },
        "my_english_analyzer": {
          "type": "standard"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "my_chinese_analyzer"
      },
      "description": {
        "type": "text",
        "analyzer": "my_english_analyzer"
      }
    }
  }
}

2️⃣ 词典(Dictionary / Term Dictionary)

  • 保存了索引中 所有唯一词条的集合,通常按字典序排序。
  • 作用是快速查找某个词条是否存在,并定位它在倒排表中的位置。

3️⃣ 倒排表(Posting List)

  • 是倒排索引的核心数据结构,用于快速定位匹配文档。
  • 倒排表通常包含以下几个部分:
字段 说明
词项(Term) 被索引的词,比如 “苹果”
文档ID(DocID) 出现该词的文档标识
词频(TF) 词在文档中出现的次数,可选
位置(Position) 词在文档中的顺序,用于短语搜索
偏移量(Offset) 词在原文中的起止字符位置,用于高亮显示

4️⃣ 倒排文件(Inverted File)

  • 倒排索引在磁盘上的物理存储文件,通常包含 词典 + 倒排表
  • Lucene/ElasticSearch 会将倒排表和词典压缩存储,提高查询效率。

2. 主节点选举

方面 Zen1 Zen2 优势
脑裂防护 依赖 minimum_master_nodes 配置,容易出错 使用 quorum(法定票数)+任期(term) 自动保证单主节点,几乎杜绝脑裂
选举算法 基于 cluster state 版本和节点 ID 基于 Raft 共识变种,有明确的 term、投票和 Candidate 状态,更严格和可靠
状态同步 广播集群状态,可能有延迟和冲突 Leader-Follower 模型,新主节点选出后,自动与大多数节点对齐最新状态
选举稳定性 网络抖动时容易选出落后节点 随机重试时间 + 任期控制,选举冲突概率低,收敛快
操作简化 管理员需配置 minimum_master_nodes 无需配置,集群自动计算 quorum,提高易用性
故障恢复 网络分区后恢复慢,可能丢失元数据 网络分区恢复快,新 Leader 能自动同步最新集群状态

3. 集群元数据

4. Elasticsearch 写入与存储机制

4.1 延迟写策略(近实时搜索)

  • 写入不是直接 fsync 到磁盘,而是先写入内存(JVM 堆)和文件系统缓存(操作系统内存),提升写入性能,避免每条数据都触发磁盘 I/O。

  • 刷新(Refresh)机制

    • 默认每秒自动刷新一次,每个分片生成一个新的 段(Segment)
    • 内存中的数据刷新到文件系统缓存后即可被搜索,但尚未真正持久化到磁盘。
    • 手动刷新:POST /_refreshPOST /index/_refresh
    • 可调 refresh_interval 优化写入性能(如日志批量写入时可延长刷新间隔)。

4.2 事务日志(Translog)保证数据安全

  • 内存 + Translog 机制

    • 文档写入内存的同时,也追加写入事务日志。
    • 避免断电或异常导致的数据丢失。
    • Translog 用于重放未刷新到磁盘的写入。

4.3 Flush 机制(持久化)

  • 触发条件

    • 内存数据或 Translog 达到一定大小(默认 512MB)
    • 或时间超过阈值(默认 30 分钟)
  • 操作

    1. 内存数据写入新段(Segment)
    2. 文件系统缓存刷新到磁盘(fsync)
    3. 生成提交点(Commit Point)
    4. 清空旧 Translog,创建新的空日志
  • 结果:保证数据持久化安全,即使重启或断电,也能通过提交点和 Translog 恢复所有数据。

4.4 核心流程图(文字版)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
客户端写入文档
写入内存(JVM 堆) + 追加到 Translog(防丢失)
达到 refresh_interval 或内存阈值?
       ├─否─> 新数据继续写入内存
       └─是─> Refresh -> 生成新段(Segment),可搜索
达到 flush 条件?
       ├─否─> 等待下一次 flush
       └─是─> Flush -> 段写入磁盘(fsync) + 提交点 + 清空 Translog

4.5 关键特点

  • 近实时搜索:刷新后文档可搜索,但未必持久化。
  • 高写入性能:延迟写减少频繁 fsync,利用内存和文件系统缓存。
  • 数据安全:Translog + Flush 机制防止断电或重启丢失数据。
  • 可调性refresh_interval 和 flush 策略可根据业务需求优化写入性能或检索实时性。

5. term index

6. 参数调优

好的,我把你提供的文本内容整理成一个 Elasticsearch 性能优化参数表格,按参数/操作、作用、适用场景、注意事项列出:

参数 / 操作 作用 适用场景 注意事项
文档 ID 设计 使用有序、可压缩的序列模式 ID 批量写入、Lucene 压缩优化 避免随机 UUID-4,会降低压缩比、拖慢索引性能
Doc Values 禁用不需要聚合/排序的字段的 Doc Values 非聚合/排序字段 可以减少磁盘和内存开销,但无法用于聚合/排序
字段类型选择 不需要模糊检索的字段使用 Keyword 类型代替 Text 类型 精确匹配字段 Keyword 字段不分词,索引更轻量
refresh_interval 调整索引刷新间隔 搜索结果不要求近实时 默认 1s,可改为 30s 提高写入性能
refresh_interval(批量导入) 导入期间设置为 -1 关闭刷新 大批量数据导入 导入完成后需恢复刷新,数据在导入期间不可搜索
number_of_replicas 导入期间设置为 0 关闭副本 大批量数据导入 导入完成后需恢复副本,否则节点宕机会丢数据
分页查询优化 使用 Scroll 查询代替深度分页 深度分页(from 很大)查询 避免普通分页造成的优先队列排序和 CPU 消耗
减少映射字段 只保留需要检索、聚合、排序的字段 高性能索引/查询 其他字段可存储在外部存储(如 HBase)
Routing 创建索引和查询时指定路由值 精确查询、热点数据分片 路由值需保证数据均衡,避免单个分片压力过大

7. 向量数据库

7.3 版本开始,Elasticsearch 引入了 dense_vector 字段类型,用于存储和检索向量,逐步支持语义搜索、推荐系统、RAG 等场景。

所以现在 ES 既可以作为 全文搜索引擎,也能作为 向量数据库 使用。


7.1 向量存储方式

ES 提供两种主要的向量存储方式:

dense_vector

  • 存储 浮点向量(dense vector)
  • 典型用于存储 embedding 向量
  • 可以指定维度(如 dims: 768

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PUT my_index
{
  "mappings": {
    "properties": {
      "text_vector": {
        "type": "dense_vector",
        "dims": 768,
        "index": true,   
        "similarity": "cosine"
      }
    }
  }
}

sparse_vector

  • 存储稀疏向量,通常用于 BM25 或传统稀疏表示
  • 使用频率低于 dense_vector

7.2 相似度搜索

Elasticsearch 在向量检索上支持 ANN(Approximate Nearest Neighbor)索引,底层使用 HNSW (Hierarchical Navigable Small World) 图结构。

常见的相似度度量方式:

  • cosine(余弦相似度,常用于语义搜索)
  • l2_norm(欧几里得距离)
  • dot_product(点积)

查询示例:

1
2
3
4
5
6
7
8
9
POST my_index/_search
{
  "knn": {
    "field": "text_vector",
    "query_vector": [0.12, -0.34, 0.98, ...],
    "k": 10,
    "num_candidates": 100
  }
}
  • k: 返回的最近邻数量
  • num_candidates: 候选数量,越大越准,但查询速度变慢

ES 向量检索的一大优势是可以直接和 全文检索 (BM25) 结合,实现 Hybrid Search

常见模式:

  1. 向量搜索:语义相关性
  2. 关键字搜索:精确匹配
  3. 加权融合:比如 RRF(Reciprocal Rank Fusion)

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST my_index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "knn": {
            "field": "text_vector",
            "query_vector": [...],
            "k": 10,
            "num_candidates": 100
          }
        },
        {
          "match": {
            "content": "向量数据库"
          }
        }
      ]
    }
  }
}

7.4 向量数据库 vs 专门的向量引擎

Elasticsearch 的向量搜索和 Milvus / Weaviate / Pinecone / Qdrant 这些纯向量数据库相比,有以下特点:

优势

  • 生态成熟:全文检索 + 向量检索一体
  • 企业级安全、权限、集群、索引机制健全
  • 混合搜索能力强,适合 RAG(LLM + ES)
  • 内置 Kibana 可视化

劣势

  • 索引和存储开销大(HNSW 内存占用高)
  • 查询延迟比不上专门的向量库(但一般能接受)
  • 向量写入更新性能略弱(HNSW 索引需要重构)