本篇文章主要介绍 ElasticSearch 中的倒排索引和分词器,与一些常见的 Search API 与应用实例。
倒排索引
倒排索引是搜索引擎和全文检索系统的核心数据结构,核心思想是通过建立「单词到文档」的映射关系从而实现 Keyword 快速定位包含该词的所有文档。例如当用户搜索「ElasticSearch」时,系统可直接通过倒排索引找到包含这一词汇的所有文档集合。
倒排索引的实现依赖于两个关键结构:单词词典(Term Dictionary)和倒排列表(Posting List)。
flowchart TD
A["文档集合"] --> B("分词处理")
B --> C{"单词词典"}
C --> D["B+树/哈希表"] & E["倒排列表"]
E --> F["文档ID"] & G["词频 TF"] & H["位置 Position"]
Analysis & Analyzer
Analysis 是通过 Analyzer 来实现的。
分词器
由三部分组成:
- Character Filters:针对原始文本处理,例如去除 html。
- Tokenizer:按照规则切分为单词。
- Token Filter:将切分的的单词进行加工,例如小写、删除 stopwords、增加同义词等。
Character Filters => Tokenizer => Token Filters
ElasticSearch 内置分词器
分词器 | 使用场景 | 分词逻辑 |
---|---|---|
Standard | 通用文本处理,支持大多数语言(默认选择)。 | 按 Unicode 标准分词,移除标点符号,转小写,支持多语言基础处理。 |
Simple | 快速简单分词,忽略标点符号和数字。 | 在非字母字符处分割文本,删除非字母字符,转小写(如 Hello-World → ["hello", "world"] )。 |
Whitespace | 按空格严格分割,保留原始格式(如代码、特定标识)。 | 仅按空格分割,保留大小写和标点(如 Quick-Brown → ["Quick-Brown"] )。 |
Stop | 需过滤常见停用词(如英文中的“the”、“is”)的文本。 | 按非字母字符分割出连续字母词条,转小写后移除停用词(如 The fox → ["fox"] )。 |
Keyword | 需精确匹配的字段(如 ID、状态码)。 | 将整个输入作为单一词条,不进行任何处理(如 Hello World → ["Hello World"] )。 |
Pattern | 需自定义分隔规则(如按特定符号分割)的文本。 | 通过正则表达式(默认 \W+ )分割文本,转小写(可自定义正则)。 |
Language(如 english ) | 针对特定语言优化(如英文词干提取、停用词过滤)。 | 按语言规则分词,处理停用词、转小写、词干提取等(如 running → ["run"] )。 |
analyzer API
通过 analyzer API 能够快速得到分词结果进行测试,以下提供了一些例子可以去到 Kibana 的 Dev Tools 进行使用。
#standard
GET _analyze
{
"analyzer": "standard",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#simple
GET _analyze
{
"analyzer": "simple",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#stop
GET _analyze
{
"analyzer": "stop",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#whitespace
GET _analyze
{
"analyzer": "whitespace",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#keyword
GET _analyze
{
"analyzer": "keyword",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#pattern
GET _analyze
{
"analyzer": "pattern",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
#english
GET _analyze
{
"analyzer": "english",
"text": "2 running Quick brown-foxes leap over lazy dogs in the summer evening."
}
示例:standard 分词器的分词结果
{
"tokens" : [
{
"token" : "2",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<NUM>",
"position" : 0
},
{
"token" : "running",
"start_offset" : 2,
"end_offset" : 9,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "quick",
"start_offset" : 10,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "brown",
"start_offset" : 16,
"end_offset" : 21,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "foxes",
"start_offset" : 22,
"end_offset" : 27,
"type" : "<ALPHANUM>",
"position" : 4
},
{
"token" : "leap",
"start_offset" : 28,
"end_offset" : 32,
"type" : "<ALPHANUM>",
"position" : 5
},
{
"token" : "over",
"start_offset" : 33,
"end_offset" : 37,
"type" : "<ALPHANUM>",
"position" : 6
},
{
"token" : "lazy",
"start_offset" : 38,
"end_offset" : 42,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "dogs",
"start_offset" : 43,
"end_offset" : 47,
"type" : "<ALPHANUM>",
"position" : 8
},
{
"token" : "in",
"start_offset" : 48,
"end_offset" : 50,
"type" : "<ALPHANUM>",
"position" : 9
},
{
"token" : "the",
"start_offset" : 51,
"end_offset" : 54,
"type" : "<ALPHANUM>",
"position" : 10
},
{
"token" : "summer",
"start_offset" : 55,
"end_offset" : 61,
"type" : "<ALPHANUM>",
"position" : 11
},
{
"token" : "evening",
"start_offset" : 62,
"end_offset" : 69,
"type" : "<ALPHANUM>",
"position" : 12
}
]
}
Search API
- URI Search
- Request Body Search
示例
# URI Search
GET kibana_sample_data_ecommerce/_search?q=customer_first_name:Eddie
GET kibana*/_search?q=customer_first_name:Eddie
GET /_all/_search?q=customer_first_name:Eddie
# Request Body Search
POST kibana_sample_data_ecommerce/_search
{
"profile": true,
"query": {
"match_all": {}
}
}
指定查询的索引
语法 | 范围 |
---|---|
/_search | 所有索引 |
/index1/_search | index1 |
/index1,index2/_search | index1 和 index2 |
/index*/_search | 以 index 开头的索引 |
URI Search
示例
GET /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=10&timeout=1s
{
"profile":"true"
}
参数:
- q 指定查询语句,使用 Query String Syntax。
- df 指定默认字段,不指定时会对所有字段进行查询。
- sort 代表排序。
- from 和 size 用于分页。
- profile 可以查看查询是如何被执行的。
泛查询
GET /movies/_search?q=2012
{
"profile":"true"
}
未指定 df
参数时 ES 会搜索所有字段,可能触发跨字段匹配,性能消耗较大。分析结果如下:
{
"type": "DisjunctionMaxQuery",
"description": "(title.keyword:2012 | id.keyword:2012 | year:[2012 TO 2012] | genre:2012 | @version:2012 | @version.keyword:2012 | id:2012 | genre.keyword:2012 | title:2012)"
}
可以看到在所有字段进行了匹配。
显式字段查询
GET /movies/_search?q=title:2012
{
"profile":"true"
}
通过 title:2012
显式指定字段,精准限定搜索范围,比泛查询更高效。分析结果如下:
{
"type": "TermQuery",
"description": "title:2012"
}
短语分割问题
GET /movies/_search?q=title:Beautiful Mind
{
"profile":"true"
}
实际执行 title:Beautiful OR Mind
,空格被识别为 OR 逻辑,返回包含任意词的文档。
精确短语匹配
GET /movies/_search?q=title:"Beautiful Mind"
{
"profile":"true"
}
使用双引号包裹词组,强制进行短语搜索,要求词语按顺序完整出现。
分组查询
GET /movies/_search?q=title:(Beautiful Mind)
{
"profile":"true"
}
括号实现逻辑分组,等效于 title:Beautiful OR title:Mind
,优先执行组内操作。
布尔运算符
GET /movies/_search?q=title:(Beautiful AND Mind)
显式布尔查询,要求同时包含两个词(AND 逻辑)。
范围查询
GET /movies/_search?q=title:beautiful AND year:[2002 TO 2018%7D
[2002 TO 2018}
表示闭区间包含 2002,开区间不包含 2018(%7D
为 URL 编码的 }
符号)。
通配符搜索
GET /movies/_search?q=title:b*
{
"profile":"true"
}
b*
匹配以 b 开头的任意长度字符,支持 ?
匹配单个字符,注意通配符在前端影响性能。
模糊匹配
GET /movies/_search?q=title:beautiful~1
{
"profile":"true"
}
GET /movies/_search?q=title:"Lord Rings"~2
{
"profile":"true"
}
~1
允许 1 个字符的编辑距离(拼写纠错)。"Lord Rings"~2
表示短语中允许间隔 2 个单词。
Request Body Search & Query DSL
通常生成环境都使用这种方法,更加强大、功能更丰富。
示例
POST movies/_search
{
"from":0,
"size":10,
"query": {
"match": {
"title": {
"query": "last christmas",
"operator": "and"
}
}
}
}
更多 DSL 语法请参考 官方文档。
基本 Match 查询
POST movies/_search
{
"query": {
"match": {
"title": "last christmas"
}
}
}
- 默认使用 OR 逻辑匹配分词结果。
- 自动对搜索词进行分词处理。
精确 AND 匹配
POST movies/_search
{
"query": {
"match": {
"title": {
"query": "last christmas",
"operator": "and"
}
}
}
}
通过 operator:"and"
强制要求所有分词必须同时存在。
短语搜索
POST movies/_search
{
"query": {
"match_phrase": {
"title": {
"query": "one love"
}
}
}
}
- 要求词语按顺序完整出现。
- 等效于 URI Search 中的引号。
模糊短语匹配
POST movies/_search
{
"query": {
"match_phrase": {
"title": {
"query": "one love",
"slop": 1
}
}
}
}
slop
参数允许词语间隔位置数(此处允许间隔 1 个词)。
Query String 搜索
GET /movies/_search
{
"query": {
"query_string": {
"default_field": "title",
"query": "Beafiful AND Mind"
}
}
}
- 支持 AND/OR/NOT 布尔逻辑。
多字段搜索
GET /movies/_search
{
"query": {
"query_string": {
"fields": ["title","year"],
"query": "2012"
}
}
}
fields
数组定义多个搜索字段。- 自动进行跨字段联合查询。
Simple Query 搜索
GET /movies/_search
{
"query": {
"simple_query_string": {
"query": "Beautiful +mind",
"fields": ["title"]
}
}
}
+
代替 AND 操作。- 自动忽略无效语法。
- 适合直接暴露给前端搜索框使用。
跨索引查询
POST /movies,404_idx/_search?ignore_unavailable=true
{
"query": {
"match_all": {}
}
}
- 逗号分隔多个索引名称。
ignore_unavailable=true
忽略不存在索引。
源过滤
POST kibana_sample_data_ecommerce/_search
{
"_source":["order_date"],
"query": {
"match_all": {}
}
}
_source
过滤返回字段。
脚本字段
GET kibana_sample_data_ecommerce/_search
{
"script_fields": {
"new_field": {
"script": {
"lang": "painless",
"source": "doc['order_date'].value+'hello'"
}
}
}
}
动态计算返回字段。
写在最后
这是该系列的第三篇,主要讲解 ElasticSearch 倒排索引、分词器、Search API 的内容,可以自己去到 Kibana 的 Dev Tool 实战操作,未来会持续更新该系列,欢迎关注👏🏻。
同时欢迎关注公众号:LanTech指南。不定时分享职场思考、独立开发日志、大厂方法论和后端经验❤️