在当今信息大爆炸的时代,各种场景都需要在海量数据中快速找到想要的信息。Elasticsearch(ES)作为一个分布式、高可用的搜索引擎,在处理海量数据的全文检索场景下表现突出。而 BM25 又是 ES 默认使用的相关性评分算法之一,能够帮助我们从众多文档中找到“最匹配”的结果。本文将通过一个简单的示例,带你一步步理解 ES 的倒排索引机制,以及 BM25 算法如何计算出搜索结果的排序。
1. 初识 Elasticsearch 与文档检索
Elasticsearch(简称 ES)是一个分布式搜索和分析引擎,广泛应用于日志分析、全文检索、业务监控等领域。它之所以能够快速检索到海量数据中的关键信息,核心原因在于以下两点:
-
使用倒排索引(Inverted Index)来管理文档内容。 -
采用高效的相关性算法(默认是 BM25)来对匹配的文档进行排序。
全文检索:与常规数据库相比,ES 更偏重于对非结构化或半结构化数据的检索。比如自然语言、日志信息等,通过分词、倒排索引和基于统计的算法,能快速从大量文本中找到最相关的文档。
2. 倒排索引:搜索引擎的“读心术”
在传统的关系型数据库中,如果进行模糊查询,需要遍历整张表才能找到包含关键字的字段,这样既慢又耗资源。而搜索引擎发明了一种叫做“倒排索引”的结构,极大地提高了查询速度和效率。
2.1. 什么是倒排索引?
倒排索引的核心思路是:
-
将文档中的词语拆分(分词)并进行标准化处理。 -
记录“词语 → 出现在哪些文档、在哪些位置”。
这与字典(或图书索引)有些相似:
-
正排索引更像“文档 → 包含的词语”。 -
倒排索引则是“词语 → 出现的文档列表”。
通过倒排索引,想要查找包含某个词语的文档时,直接从索引中就能定位到所有相应的文档,从而大幅度加快检索过程。
3. 示例:搭建小型倒排索引
为了让你更好地理解倒排索引的构建,我们用一个极简的示例说明。假设我们有以下 3 个文档:
Doc1: "Elasticsearch is a powerful search engine"
Doc2: "Elasticsearch is used for full-text search"
Doc3: "Search engines are powerful tools"
第一步:分词与标准化
Elasticsearch 内置的分词器(如 Standard Analyzer)会对文档进行分词,并做一些常见的文本处理(如去除大小写、去除停用词、词干提取等)。在这里,我们假设分词的结果如下:
-
Doc1: ["elasticsearch", "is", "a", "powerful", "search", "engine"] -
Doc2: ["elasticsearch", "is", "used", "for", "full", "text", "search"] -
Doc3: ["search", "engines", "are", "powerful", "tools"]
第二步:构建倒排索引
我们把每个词语出现在哪些文档里,以什么位置存起来,就形成了一个简单的倒排索引表。示例如下:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
通过这样一个简单的表,我们就能快速找到一个词在哪些文档中出现。
4. 查询流程:从键入关键字到获取结果
当用户输入查询词,比如 “powerful search” 时,Elasticsearch 的做法是:
-
对查询进行分词和标准化
将用户输入的查询进行同样的分词处理,得到 ["powerful", "search"]。 -
在倒排索引中查找
-
“powerful” 出现在:Doc1, Doc3 -
“search” 出现在:Doc1, Doc2, Doc3
综合得出候选文档:Doc1, Doc2, Doc3。
-
计算相关性分数(BM25)
找到的文档可能都和查询有关系,但哪一个最相关?这就需要相关性算法来评分。在 ES 中,BM25 是默认算法。 -
根据评分排序并返回
最后将匹配到的文档按照分值从高到低排序,并返回给用户。 -
表示查询词的集合,比如 。 -
表示某个文档。 -
表示查询中的某个单词。 -
表示单词 在文档 中出现的次数(词频)。 -
表示单词 的逆文档频率,体现某个词在所有文档中“多么稀有”。它的常见公式是: -
:语料库中的文档总数。 -
:带有词 的文档数。 -
表示文档 的长度(词数)。 -
表示所有文档的平均长度。 -
和 是可调参数,常用 ,用来平衡词频和文档长度的影响。 -
文档总数:3 -
包含词 "powerful" 的文档数 :2(Doc1, Doc3) -
包含词 "search" 的文档数 :3(Doc1, Doc2, Doc3) -
Doc1: 6 个词 -
Doc2: 7 个词 -
Doc3: 5 个词 -
"powerful" 的 TF = 1 -
"search" 的 TF = 1 -
-
"powerful" 的 TF = 0 -
"search" 的 TF = 1 -
-
"powerful" 的 TF = 1 -
"search" 的 TF = 1(注意此处分词器把首字母大写的 "Search" 统一成 "search") -
-
Doc3:0.69 -
Doc1:0.66 -
Doc2:0.14
5. BM25:背后的数学公式
BM25(Best Matching 25)是一种计算“文档与查询”匹配度的打分函数。简化后的核心公式如下:
其中:
简单来说,BM25 在考虑一个词(或查询词)的稀有程度(IDF)以及该词在文档中出现的次数(TF),再结合文档长度做了一个平衡。最后得到的分数越高,说明该文档越符合用户查询意图。
6. BM25 计算示例:深度解析
让我们基于前面的示例来做一次“powerful search”最相关文档的打分。
1. 统计信息
2. 计算 IDF
3. 计算文档长度与平均长度
4. 逐文档计算 BM25
此处我们假设 ( k_1 = 1.2, b = 0.75 ),来演示计算过程。
Doc1: "Elasticsearch is a powerful search engine"
令 。
其中
Doc2: "Elasticsearch is used for full-text search"
其中 "powerful" 的 TF = 0,所以该项为 0。计算 "search" 部分:
Doc3: "Search engines are powerful tools"
(为了便于对比,我们稍微修改一下,可能与上表略有细微不同;但思路一样。)
5. 最终排序
显而易见,Doc3 在这个查询场景下获得最高分,Doc1 次之,Doc2 最后。
7. 总结
Elasticsearch 之所以能够在海量数据中快速找到最相关的文档,主要依赖以下关键点:
-
倒排索引:将文档中的单词拆分并记录到索引结构里,从而能高效搜索到包含特定词语的文档。 -
查询分词:对用户输入进行同样的分词处理,使得后续的匹配和打分更准确。 -
BM25 算法:结合词频、逆文档频率、文档长度等因素来给匹配文档打分,综合反映一个文档对当前查询的“匹配度”。
8. ES实战
8.1 安装与配置ES
首先,我们需要安装并启动Elasticsearch。以下是基本步骤:
-
下载ES
# 下载ES 8.x版本
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.x.x-linux-x86_64.tar.gz
# 解压
tar -xzf elasticsearch-8.x.x-linux-x86_64.tar.gz
# 进入ES目录
cd elasticsearch-8.x.x/
-
修改配置编辑 config/elasticsearch.yml:
# 集群名称
cluster.name: my-es-cluster
# 节点名称
node.name: node-1
# 数据和日志存储路径
path.data: /path/to/data
path.logs: /path/to/logs
# 网络设置
network.host: 0.0.0.0
http.port: 9200
# 开发环境设置
discovery.type: single-node
-
启动ES
# 启动
./bin/elasticsearch
# 检查是否启动成功
curl http://localhost:9200
8.2 创建索引并定义映射
让我们创建一个用于存储文章的索引:
PUT /articles
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"stop",
"snowball"
]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "my_analyzer",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"content": {
"type": "text",
"analyzer": "my_analyzer"
},
"author": {
"type": "keyword"
},
"publish_date": {
"type": "date"
}
}
}
}
8.3 索引文档
插入一些测试文档:
POST /articles/_doc/1
{
"title": "Elasticsearch Guide",
"content": "Elasticsearch is a powerful search engine based on Lucene",
"author": "John Doe",
"publish_date": "2024-03-15"
}
POST /articles/_doc/2
{
"title": "Advanced Search Techniques",
"content": "Learn how to use BM25 algorithm for better search results",
"author": "Jane Smith",
"publish_date": "2024-03-16"
}
POST /articles/_doc/3
{
"title": "Understanding Text Analysis",
"content": "Text analysis is crucial for search engine performance",
"author": "Mike Johnson",
"publish_date": "2024-03-17"
}
8.4 查询示例
-
简单全文搜索:
GET /articles/_search
{
"query": {
"match": {
"content": "search engine"
}
}
}
-
多字段搜索:
GET /articles/_search
{
"query": {
"multi_match": {
"query": "search engine",
"fields": ["title", "content"]
}
}
}
-
使用bool查询组合多个条件:
GET /articles/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"content": "search"
}
}
],
"should": [
{
"match": {
"title": "elasticsearch"
}
}
],
"filter": [
{
"range": {
"publish_date": {
"gte": "2024-03-15"
}
}
}
]
}
}
}
8.5 分析搜索结果
当执行搜索请求时,ES会返回包含相关性分数的结果:
{
"took": 5,
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.3862944,
"hits": [
{
"_index": "articles",
"_id": "1",
"_score": 1.3862944,
"_source": {
"title": "Elasticsearch Guide",
"content": "Elasticsearch is a powerful search engine based on Lucene"
}
},
{
"_index": "articles",
"_id": "2",
"_score": 0.9530659,
"_source": {
"title": "Advanced Search Techniques",
"content": "Learn how to use BM25 algorithm for better search results"
}
}
]
}
}
8.6 调优搜索相关性
-
自定义评分:
GET /articles/_search
{
"query": {
"script_score": {
"query": {
"match": {
"content": "search engine"
}
},
"script": {
"source": "_score * (doc['publish_date'].value.millis - 1710000000000) / 86400000"
}
}
}
}
-
调整字段权重:
GET /articles/_search
{
"query": {
"multi_match": {
"query": "search engine",
"fields": [
"title^3",
"content"
]
}
}
}
8.7 性能优化建议
-
合理设置分片数:
-
单个分片大小建议在20GB-40GB之间 -
分片数 = 数据总量 / 单个分片大小
使用合适的映射:
-
对于不需要分词的字段使用 keyword类型 -
对于需要分词的字段使用 text类型 -
合理使用 fields设置多字段映射
优化内存使用:
-
设置合适的JVM堆大小 -
使用 doc_values和fielddata优化聚合性能
查询优化:
-
尽量使用过滤器(filter)代替查询(query) -
避免使用通配符查询 -
合理使用缓存
好的,我来介绍Docker化安装ES以及界面化管理工具。
9. Docker化安装ES
9.1 安装单节点ES
-
创建docker-compose.yml文件
version: '3'
services:
elasticsearch:
image: elasticsearch:8.11.1
container_name: elasticsearch
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms512m -Xmx512m
- xpack.security.enabled=false
volumes:
- ./es/data:/usr/share/elasticsearch/data
- ./es/plugins:/usr/share/elasticsearch/plugins
ports:
- "9200:9200"
- "9300:9300"
networks:
- elastic
networks:
elastic:
driver: bridge
-
启动ES
# 创建目录
mkdir -p ./es/data ./es/plugins
# 设置目录权限
chmod 777 ./es/data ./es/plugins
# 启动容器
docker-compose up -d
-
验证安装
# 检查容器状态
docker ps
# 测试ES是否正常运行
curl http://localhost:9200
9.2 安装Kibana(官方可视化工具)
-
更新docker-compose.yml,添加Kibana服务
version: '3'
services:
elasticsearch:
# ... ES配置保持不变 ...
kibana:
image: kibana:8.11.1
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
- elastic
networks:
elastic:
driver: bridge
-
启动Kibana
docker-compose up -d
访问 http://localhost:5601 即可打开Kibana界面。
9.3 安装Elasticsearch-head(轻量级可视化工具)
version: '3'
services:
elasticsearch:
# ... ES配置保持不变 ...
elasticsearch-head:
image: mobz/elasticsearch-head:5
container_name: elasticsearch-head
ports:
- "9100:9100"
networks:
- elastic
访问 http://localhost:9100 即可使用elasticsearch-head。
10 可视化工具对比
10.1 Kibana(官方工具)
优点:
-
功能最完整 -
与ES版本完全兼容 -
支持复杂的数据可视化 -
支持开发工具控制台 -
支持安全认证
主要功能:
-
Dev Tools:
-
提供交互式控制台 -
支持自动补全 -
支持多请求组合
Index Management:
-
索引创建和管理 -
映射配置 -
索引模板管理
数据可视化:
-
支持多种图表类型 -
可创建仪表板 -
实时数据监控
10.2 Elasticsearch-head
优点:
-
界面简洁 -
启动快速 -
资源占用少 -
适合简单操作
主要功能:
-
集群监控:
-
查看集群状态 -
节点信息 -
索引状态
数据浏览:
-
查看索引数据 -
简单的增删改查 -
JSON格式化显示
REST请求:
-
简单的REST客户端 -
支持基本查询
10.3 Cerebro(另一个选择)
添加Cerebro到docker-compose.yml:
version: '3'
services:
# ... 其他服务配置 ...
cerebro:
image: lmenezes/cerebro:0.9.4
container_name: cerebro
ports:
- "9000:9000"
networks:
- elastic
优点:
-
现代化界面 -
支持多集群管理 -
操作简单直观
主要功能:
-
集群管理:
-
多集群监控 -
节点状态查看 -
分片分配可视化
索引管理:
-
创建/删除索引 -
管理别名 -
查看索引设置
REST API:
-
内置REST客户端 -
请求历史记录 -
响应格式化
11. 推荐使用方案
-
开发环境:
-
Kibana + Elasticsearch-head -
Kibana用于复杂操作和可视化 -
Elasticsearch-head用于快速查看数据
生产环境:
-
主要使用Kibana -
配置适当的安全认证 -
根据需要开放必要的功能
配置建议:
version: '3'
services:
elasticsearch:
image: elasticsearch:8.11.1
container_name: elasticsearch
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms512m -Xmx512m
- xpack.security.enabled=false
volumes:
- ./es/data:/usr/share/elasticsearch/data
- ./es/plugins:/usr/share/elasticsearch/plugins
ports:
- "9200:9200"
networks:
- elastic
kibana:
image: kibana:8.11.1
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch
networks:
- elastic
elasticsearch-head:
image: mobz/elasticsearch-head:5
container_name: elasticsearch-head
ports:
- "9100:9100"
networks:
- elastic
networks:
elastic:
driver: bridge
使用以上配置,你可以同时拥有Kibana和Elasticsearch-head的所有功能,既可以进行复杂的数据分析和可视化,也可以快速查看集群状态和数据。


