1、Elasticsearch 是什么?
Elasticsearch 是一个基于 Lucene 构建的、开源的、分布式的、RESTful 的搜索和分析引擎。
- 基于 Lucene: 它的底层核心是 Apache Lucene,一个非常强大、高性能的 Java 搜索引擎库。Elasticsearch 在 Lucene 的基础上,提供了更简单易用的功能和分布式架构。
- 分布式: 它天生就是分布式的。数据可以被分片并分布到集群中的多个节点上,这使得它能够处理海量数据(PB级别),并提供高可用性和强大的横向扩展能力。
- 搜索和分析引擎: 它不仅用于传统的全文检索,还非常擅长进行复杂的数据分析,例如聚合操作(Aggregation),可以生成复杂的报表和指标。
常见应用场景:
- 网站搜索: 电商网站的商品搜索、新闻网站的文章搜索。
- 日志数据分析 (ELK Stack): 与 Logstash 和 Kibana 组成 ELK 栈,用于收集、存储、分析和可视化日志数据,是运维和开发的利器。
- 应用性能监控 (APM): 追踪应用程序的性能问题。
- 企业搜索: 搜索公司内部的各种文档、邮件、数据。
2、核心原理介绍
2.1、分词
分词 是将一段文本切分成一个个独立的、有意义的词条的过程。它是文本处理和搜索的基础。例如:
输入: "我爱北京天安门"
输出: ["我", "爱", "北京", "天安门"] # 智能分词
分词过程通常包括以下步骤:
- 字符过滤:使用字符过滤器(Character Filter)对原始文本进行预处理,例如去除HTML标签、替换特殊字符等。
- 分词:使用分词器(Tokenizer)将文本拆分成单个的词条(tokens)。例如,标准分词器会根据空格和标点将文本分成多个词条。
- 词条过滤:使用词条过滤器(Token Filter)对分词后的结果进行进一步处理,例如转为小写、删除停用词、添加同义词等。
原始文本
↓
字符过滤器(如:去除HTML标签)
↓
分词器(按规则切分)
↓
词条过滤器(如:转小写、去停用词)
↓
最终词条
2.2、核心原理:倒排索引
这是 Elasticsearch 能够实现快速全文搜索的基石。
传统数据库的困境:如果想在内容字段中搜索包含“搜索”的记录,传统数据库会使用 LIKE ‘%搜索%’
,这会导致全表扫描,效率极低。
传统数据库使用正排索引(文档 → 词条),而 Elasticsearch 使用倒排索引(词条 → 文档)。
假设有三个文档:
- 文档1:
"苹果公司发布了新手机"
- 文档2:
"这个苹果很好吃"
- 文档3:
"手机市场竞争激烈"
步骤1:文档分词
文档1 → ["苹果", "公司", "发布", "了", "新", "手机"]
文档2 → ["这个", "苹果", "很", "好吃"]
文档3 → ["手机", "市场", "竞争", "激烈"]
步骤2:标准化处理
- 去除停用词("了", "这个", "很"等)
- 统一大小写(如果有英文)
- 词干提取(如"发布了" → "发布")
步骤3:构建倒排索引表
词条 | 文档ID列表 | 位置信息 | 其他元数据 |
---|---|---|---|
苹果 | [1, 2] | 1:[0], 2:[1] | {doc_freq: 2} |
公司 | [1] | 1:[1] | {doc_freq: 1} |
发布 | [1] | 1:[2] | {doc_freq: 1} |
新 | [1] | 1:[4] | {doc_freq: 1} |
手机 | [1, 3] | 1:[5], 3:[0] | {doc_freq: 2} |
好吃 | [2] | 2:[3] | {doc_freq: 1} |
市场 | [3] | 3:[1] | {doc_freq: 1} |
竞争 | [3] | 3:[2] | {doc_freq: 1} |
激烈 | [3] | 3:[3] | {doc_freq: 1} |
2.3、搜索执行流程
例如我们需要搜索查询:"苹果手机"
步骤1:查询解析与分词
查询字符串被分词为:["苹果", "手机"]
步骤2:查找倒排索引
- 查找"苹果" → 得到文档列表
[1, 2]
- 查找"手机" → 得到文档列表
[1, 3]
步骤3:结果合并
对于布尔OR查询(默认):取并集 [1, 2, 3]
对于布尔AND查询:取交集 [1]
步骤4:相关性评分(TF-IDF算法)
TF(词频)- 在单个文档中的重要性:
- 文档1中:"苹果"出现1次,"手机"出现1次
- 文档2中:"苹果"出现1次,"手机"出现0次
- 文档3中:"苹果"出现0次,"手机"出现1次
IDF(逆文档频率)- 在整个索引中的重要性:
- "苹果"的IDF = log(3/2) = 0.176 (3个总文档,2个包含"苹果")
- "手机"的IDF = log(3/2) = 0.176 (3个总文档,2个包含"手机")
计算得分:
文档1得分 = TF("苹果") × IDF("苹果") + TF("手机") × IDF("手机")
= 1 × 0.176 + 1 × 0.176 = 0.352
文档2得分 = 1 × 0.176 + 0 × 0.176 = 0.176
文档3得分 = 0 × 0.176 + 1 × 0.176 = 0.176
步骤5:结果排序和返回
按得分排序返回:
- 文档1 (得分: 0.352)
- 文档2 (得分: 0.176)
- 文档3 (得分: 0.176)
这样我们就找到了对应的文档,并可以按得分排序。
3、总结
3.1、索引构建流程(数据准备阶段)
┌─────────────┐ ┌───────────────┐ ┌─────────────┐ ┌────────────────┐
│ 输入文本 │ -> │ 文本预处理 │ -> │ 分词 │ -> │ 构建倒排索引 │
│ │ │ │ │ │ │ │
│ • 文档数据 │ │ • 大小写转换 │ │ • 中文分词 │ │ • 词条→文档映射 │
│ • JSON格式 │ │ • 去除HTML标签 │ │ • 英文分词 │ │ • 位置信息记录 │
│ │ │ • 特殊字符处理 │ │ • 停用词过滤 │ │ • 词频统计 │
└─────────────┘ └───────────────┘ └─────────────┘ └────────────────┘
示例:
输入: "苹果公司发布了新手机"
预处理: "苹果公司发布了新手机"
分词: ["苹果", "公司", "发布", "新", "手机"]
倒排索引:
"苹果" → [文档1]
"手机" → [文档1]
"公司" → [文档1]
...
3.2、搜索查询流程(检索阶段)
┌─────────────┐ ┌─────────────┐ ┌────────────────┐ ┌─────────────┐ ┌────────────────┐
│ 搜索查询 │ -> │ 分词 │ -> │ 查找倒排索引 │ -> │ 结果合并 │ -> │ 相关性评分 │
│ │ │ │ │ │ │ │ │ │
│ • 用户输入 │ │ • 查询词分词 │ │ • 多词条并行查找 │ │ • 集合运算 │ │ • TF-IDF/BM25 │
│ • 查询语法 │ │ • 同义词扩展 │ │ • 分布式分片查找 │ │ • 去除重复 │ │ • 位置加权 │
│ │ │ │ │ │ │ │ │ • 字段权重 │
└─────────────┘ └─────────────┘ └────────────────┘ └─────────────┘ └────────────────┘
示例:
查询: "苹果 手机"
分词: ["苹果", "手机"]
查找:
"苹果" → [文档1, 文档2]
"手机" → [文档1, 文档3]
结果合并: [文档1] (AND操作)
相关性评分: 文档1得分最高(包含两个词条)
3.3、完整架构视图
┌─────────────────┐ ┌─────────────────┐
│ 索引构建流程 │ │ 搜索查询流程 │
│ (数据准备) │ │ (检索执行) │
├─────────────────┤ ├─────────────────┤
│ 输入文本 │ │ 搜索查询 │
│ ↓ │ │ ↓ │
│ 文本预处理 │ │ 分词 │
│ ↓ │ │ ↓ │
│ 分词 │ │ 查找倒排索引 │
│ ↓ │ │ ↓ │
│ 构建倒排索引 │◄─────────────┤ 结果合并 │
└─────────────────┘ 依赖关系 │ ↓ │
│ │ 相关性评分 │
│ └─────────────────┘
│
┌─────────┴─────────┐
│ 倒排索引存储 │
│ (核心数据结构) │
│ • 词条词典 │
│ • 文档列表 │
│ • 位置信息 │
└───────────────────┘