Appearance
Hello ElasticSearch
1. 简介
ElasticSearch 是一种基于 Lucene 的开源分布式搜索引擎,擅长全文搜索和日志分析。
ELK 栈是 ElasticSearch、Logstash 和 Kibana 的组合,用于日志收集、处理和可视化。Logstash 负责数据收集和处理,从多种来源(如日志文件、数据库)采集数据,进行过滤、转换后发送到 ElasticSearch,ElasticSearch 是存储和搜索数据的核心引擎,最终由 Kibana 提供可视化交互式仪表板,分析和展示 ElasticSearch 中的数据。
1.1. 核心概念
ElasticSearch 采用倒排索引,倒排索引是从词语到文档的映射,记录每个词语出现在哪些文档中,以及相关信息(如位置、频率)。
例如,有如下索引结构:
| 词条 | 文档 Id 列表 |
|---|---|
| 我 | {文档1} |
| 中国 | {文档1, 文档2} |
| 爱 | {文档1} |
| 地图 | {文档2} |
用户输入查询词(例如 “中国地图”)后,整体处理流程如下:
ElasticSearch 中的其它核心概念:
- 索引:索引是文档的集合,类似数据库中的表;
- 文档:文档就是一条条的数据(JSON 格式),类似数据库表中的行;
- 字段:就是 JSON 文档中的字段,类似数据库表中的列;
- 映射:是索引中文档的约束,类似数据库的表结构;
- DSL:DSL 是 ElasticSearch 提供的 JSON 风格的请求语句,用来操作 ElasticSearch 实现 CRUD。
1.2. 企业系统架构
对于数据的增删改我们通常会有 “事务” 的需求,通过事务的 ACID(原子性 Atomicity、一致性 Consistency、隔离性 Isolation、持久性 Durability)来确保数据操作的可靠性。因此,通常采用数据库作为主存储,用于保障数据的完整性和一致性。
ElasticSearch 则擅长对海量数据进行高效的搜索、分析和聚合,适合用作搜索引擎或分析平台。
如常见的架构模式如下:
在该架构中:
- 用户的增删改查(CRUD)请求首先由业务服务(Services)处理;
- 业务服务将写操作(如新增、修改、删除)直接写入数据库,确保数据的事务性和一致性;
- 搜索请求则直接查询 ElasticSearch,以获得高性能的搜索体验;
- 数据库中的数据再通过同步机制同步到 ElasticSearch,保证搜索数据的实时性或准实时性。
2. 分词器
2.1. 分词器组成部分
ElasticSearch 中分词器(Analyzer)的组成包含三个部分:
- Character Filters:在 Tokenizer 之前对文本进行处理。例如删除字符、替换字符;
- Tokenizer:将文本按照一定的规则切割成词条(Term);
- Tokenizer Filters:对 Tokenizer 输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等。
2.2. 自定义分词器
在创建索引库时可以通过 settings 来配置自定义分词器。
简单示例:
JSON
PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "my_analyzer"
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
说明:
char_filter:使用html_strip过滤器去除 HTML 标签;tokenizer:使用standard分词器,按单词边界分割文本;filter:应用lowercase(将文本转换为小写)和asciifolding(将非 ASCII 字符转换为 ASCII 等效字符)。
2.2.1. 拼音分词器
关于拼音分词器的详细说明请参考这篇文档。
创建索引库
JSONPUT /my_index { "settings": { "analysis": { "analyzer": { "my_analyzer": { "tokenizer": "ik_max_word", "filter": "pinyin_filter" } }, "filter": { "pinyin_filter": { "type": "pinyin", "keep_full_pinyin": false, "keep_joined_full_pinyin": true, "keep_original": true, "limit_first_letter_length": 16, "remove_duplicated_term": true, "none_chinese_pinyin_tokenize": false } } } }, "mappings": { "properties": { "title": { "type": "text", "analyzer": "my_analyzer", "search_analyzer": "ik_smart" } } } }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
28
29
30
31
32
33分词器测试
JSONPOST /my_index/_analyze { "text": [ "刘德华 AT2016" ], "analyzer": "my_analyzer" }响应示例
JSON{ "tokens": [ { "token": "刘德华", "start_offset": 0, "end_offset": 3, "type": "CN_WORD", "position": 0 }, { "token": "liudehua", "start_offset": 0, "end_offset": 3, "type": "CN_WORD", "position": 0 }, { "token": "ldh", "start_offset": 0, "end_offset": 3, "type": "CN_WORD", "position": 0 }, { "token": "at2016", "start_offset": 4, "end_offset": 10, "type": "LETTER", "position": 1 }, { "token": "2016", "start_offset": 6, "end_offset": 10, "type": "ARABIC", "position": 2 } ] }
3. 索引库
3.1. Mapping
Mapping 是对索引库中文档的约束,常见的 Mapping 属性包括:
type:字段的数据类型;常见的简单类型有:
- 字符串:
text(可分词的文本)、keyword(精确值,例如:国家、品牌等); - 数值:
long、integer、short、byte、double、float; - 布尔:
boolean; - 日期:
date; - 对象:
object。
ElasticSearch 支持数组类型,但无需显式声明字段为数组,任何字段都可以存储数组值,只要值的类型与映射一致。
以下数据类型与地理坐标相关:
geo_point:存储地理坐标(经纬度)的数据类型,适合表示单个地理位置点;geo_shape:用于存储复杂的地理形状,如点、线、多边形、圆等,支持更复杂的空间关系查询。
- 字符串:
index:是否创建索引,默认为true;analyzer:指定分词器;properties:对象类型字段的子字段。
3.2. 创建索引库
ElasticSearch 通过 Restful 请求创建索引库、文档。请求内容用 DSL 语句来表示。
以如下文档结构为例:
JSON
{
"title": "小米14智能手机 16GB+512GB 黑色",
"brand": "Xiaomi",
"sku": {
"name": "型号",
"value": "Mi14-512GB-Black"
}
}DSL 请求:
JSON
PUT /product
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_smart"
},
"brand": {
"type": "keyword",
"index": false
},
"sku": {
"type": "object",
"properties": {
"name": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
}
}响应:
JSON
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "product"
}上述 DSL 创建一个名为 product 的索引,并定义其映射。映射结构包含三个字段:
title:类型为text,使用ik_smart分词器;brand:类型为keyword,字段不可被索引;sku:类型为object,包含嵌套的name和value字段,均为keyword类型。
3.3. 查看索引库
JSON
GET /<index>3.4. 修改索引库 Mapping
可以在索引中添加新的字段或更新部分映射设置,但不能修改已有字段的类型。
使用 PUT /<index>/_mapping API,如:
JSON
PUT /product/_mapping
{
"properties": {
"price": {
"type": "double"
}
}
}3.5. 修改索引库 Settings
在 Elasticsearch 中,PUT /<index>/_settings API 用于更新索引的设置(settings)。这些设置控制索引的运行时行为,例如分片数量、刷新间隔、分析器配置等。通过该 API,可以动态调整某些设置以优化性能或适应特定需求,但需要注意,有些设置在索引创建后是不可更改的。
如添加或更新分析器(analyzer)、分词器(tokenizer),但需要先关闭索引:
JSON
POST /product/_close
PUT /product/_settings
{
"analysis": {
"analyzer": {
"completion_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": [
"pinyin_filter",
"lowercase"
]
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
}
POST /product/_open1
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
28
29
30
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
28
29
30
3.6. 删除索引库
JSON
DELETE /<index>3.7. 字段拷贝
当我们需要对多个字段进行统一搜索,例如全文搜索或跨字段查询时,可以使用 copy_to 映射参数。它可以将一个或多个字段的值复制到一个虚拟的 “组合字段” 中,以便在该字段上进行聚合搜索。
以下是一个使用 copy_to 的映射配置示例:
JSON
PUT /my_index
{
"mappings": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}4. 文档
4.1. 新增文档
JSON
POST /<index>/_doc/<id>
{
"field1": "value1",
"field2": "value2",
...
}信息
如果未指定文档 Id,ElasticSearch 会自动为其生成文档 Id。
信息
ElasticSearch 的文档 Id 始终以字符串形式存储,并作为 keyword 类型处理。如果需要使用数字类型的 Id,可以在文档中定义一个额外的字段(如 numeric_id)并将其映射为 long 或 integer。
4.2. 根据 Id 查询文档
JSON
GET /<index>/_doc/<id>4.3. 全量修改
JSON
PUT /<index>/_doc/<id>
{
"field1": "value1",
"field2": "value2",
...
}与新增文档的 API 相比,区别仅在于将 POST 请求改为 PUT 请求。实际上,若文档 Id 不存在,PUT 请求也能自动创建该文档。
4.4. 局部修改
JSON
POST /<index>/_update/<id>
{
"doc": {
"field1": "value1",
"field2": "value2",
...
}
}4.5. 删除文档
JSON
DELETE /<index>/_doc/<id>5. 查询 DSL
5.1. 查询分类
常见的查询类型包括:
查询所有:
match_all查询所有数据,一般测试用;全文检索:利用分词器对用户输入内容分词,然后去倒排索引库中匹配;
matchmulti_match
精确查询:查询
keyword、数值、日期、布尔等类型字段;idsrangeterm
地理查询:根据经纬度查询;
geo_distancegeo_bounding_box
复合查询:将各种查询条件组合起来,合并查询条件。
boolfunction_score
查询 DSL 的基本语法如下:
JSON
GET /<index>/_search
{
"query": {
"<query_type>": {
"<field>": "<value>"
}
}
}5.1.1. 查询所有
JSON
GET /product/_search
{
"query": {
"match_all": {}
}
}5.1.2. 全文检索
全文检索会对查询的内容分词,然后去倒排索引库中进行匹配。
match通常先在创建索引时使用
copy_to映射参数构建虚拟的 “组合字段”,然后在该字段上执行全文检索。JSONGET /product/_search { "query": { "match": { "title": "iphone" } } }multi_match功能与
match类似,但可同时查询多个字段,任一字段匹配即返回结果。其性能低于基于虚拟组合字段的查询。JSONGET /product/_search { "query": { "multi_match": { "query": "iPhone", "fields": [ "title", "brand" ] } } }
5.1.3. 精确查询
精确查询不会对查询的内容分词。
term根据词条精确匹配。
JSONGET /product/_search { "query": { "term": { "brand": "Apple" } } }range根据值的范围进行查询。
JSONGET /product/_search { "query": { "range": { "price": { "gte": 5000, "lte": 8000 } } } }
5.1.4. 地理查询
geo_distance查询到指定中心点小于某个距离值的所有文档。
JSONGET /product/_search { "query": { "geo_distance": { "distance": "12km", "geo": { "lat": 39.915, "lon": 116.414 } } } }geo_bounding_box查询
geo_point落在某个矩形范围内的所有文档。JSONGET /product/_search { "query": { "geo_bounding_box": { "geo": { "top_left": { "lat": 39.915, "lon": 116.414 }, "bottom_right": { "lat": 39.879, "lon": 116.46584 } } } } }
5.1.5. 复合查询
bool布尔查询用于组合多个叶子查询或复合查询子句,子查询类型包含:
must:所有must下的条件都必须满足,文档才会被返回。会影响评分;should:满足should下任意一个条件的文档就会被返回。会影响评分,匹配的越多,评分越高;filter:所有filter下的条件都必须满足,文档才会被返回。不影响评分;must_not:排除所有满足must_not下条件的文档。不影响评分;
通常将用户输入的查询内容放到
must、should中参与评分,其余筛选条件放到filter、must_not。JSONGET /product/_search { "query": { "bool": { "must": [ { "match": { "title": "iPhone" } } ], "filter": [ { "term": { "sku.name": "SKU Name 1" } }, { "term": { "sku.value": "SKU Value 1" } } ] } } }function_score允许用户通过自定义评分函数修改文档的相关性得分,从而实现更灵活的搜索结果排序。
JSON{ "query": { "function_score": { "query": { /* 原始查询,例如 match 等 */ }, "functions": [ { /* 函数 1 */ "filter": { /* 可选,过滤条件,限制函数应用的文档范围 */ }, /* 评分函数,如 weight: 10 */ }, { /* 函数 2 */ } ], "score_mode": "multiply | sum | avg | max | min | first", "boost_mode": "multiply | sum | replace | max | min", "max_boost": 10 /* 可选,限制最大得分 */, "min_score": 10 /* 可选,过滤得分低于此值的文档 */ } } }关键字段说明:
query:原始查询;functions:一个数组,包含多个评分函数,每个函数定义如何修改文档得分;filter:可选,限制函数应用的文档范围;- 评分函数。
score_mode:定义多个函数得分如何组合:multiply:函数得分相乘(默认);sum:函数得分相加;avg:函数得分的平均值;max:取最大值;min:取最小值;first:仅使用第一个匹配的函数得分。
boost_mode:定义函数得分与原始查询得分的组合方式:multiply:原始得分与函数得分相乘(默认);sum:原始得分与函数得分相加;replace:仅使用函数得分,忽略原始查询得分;max:取原始得分和函数得分的较大值;min:取较小值。
max_boost:限制函数得分的放大倍数;min_score:过滤掉得分低于此值的文档。
常用评分函数类型:
weight:为匹配的文档分配固定权重;field_value_factor:根据文档中的字段值调整得分;random_score:生成随机得分,用于随机排序;script_score:通过脚本自定义得分计算逻辑;- decay functions(
gauss、exp、linear):基于数值或地理位置的衰减函数。
JSONGET /product/_search { "query": { "function_score": { "query": { "match": { "title": "iPhone" } }, "functions": [ { "filter": { "term": { "sku.value": "SKU Value 1" } }, "weight": 2 } ], "boost_mode": "sum" } } }
5.2. 排序
ElasticSearch 支持对搜索结果进行排序,默认是根据相关性得分进行排序的。可以排序的字段类型有 keyword、数值、日期、地理坐标等。
如果在查询时指定了排序规则,那么 ElasticSearch 将会放弃相关性得分评分。
示例:
普通字段排序
JSONGET /product/_search { "query": { "match_all": {} }, "sort": [{ "price": "asc" }] }地理坐标距离排序
JSONGET /product/_search { "query": { "match_all": {} }, "sort": [ { "_geo_distance": { "geo": { "lat": 39.915, "lon": 116.414 }, "order": "asc", "unit": "km" } } ] }距离目标地理坐标对应单位的距离将会作为
sort对应的分值。
5.3. 分页
ElasticSearch 默认情况下只返回前 10 条记录。如果需要查询更多数据,则需要指定分页参数。
JSON
GET /product/_search
{
"query": {
"match_all": {}
},
"sort": [{ "price": "asc" }],
"from": 0,
"size": 2
}可以使用 from 和 size 参数。from 参数定义了需要跳过的命中结果数量,默认值为 0。size 参数则指定了要返回的最大命中结果数量。
5.3.1. 深度分页问题
由于 ElasticSearch 是分布式搜索引擎,深度分页会带来性能挑战。在分布式环境中,ElasticSearch 的分页过程如下:
查询分片:ElasticSearch 将查询分发到所有相关分片(Shards);
排序与收集:每个分片根据查询条件和排序规则返回
from+size条记录。例如,from=990,size=10,每个分片需要返回前1000条记录;合并结果:协调节点(Coordinating Node)收集所有分片的结果,合并后根据排序规则选择最终的
size条记录(第 991 ~ 1000 条);返回结果:将最终结果返回给客户端。
因此 ElasticSearch 将结果集查询的上限设定为 10000。
5.3.2. 深度分页解决方案
为了解决深度分页的性能问题,ElasticSearch 提供了以下替代方案和优化策略:
search_after:基于上一次查询的最后一个文档的排序值(Sort Values)来获取下一页数据,避免使用from偏移。需确保查询中有明确的唯一且稳定的排序字段(如_id或时间戳);此方案适合顺序扫描(如导出数据),不适合向前翻页或随机跳转到某页。
Scroll API:维护一个查询的 “快照”(Scroll Context),允许按顺序逐批获取数据。
使用方式是,先发起一个带有
scroll参数的查询,指定快照的存活时间(如1m),再使用返回的scroll_id继续获取后续批次数据。注意:Scroll 快照会占用内存,需及时清理(通过
_delete_by_query或自动过期)。
5.4. 高亮
在 ElasticSearch 中可以在搜索结果中突出显示与查询匹配的文本部分,从而帮助用户快速识别相关内容。通过在返回的搜索结果中添加一个 highlight 字段来实现:
JSON
GET /product/_search
{
"query": {
"match": {
"title": "iPhone"
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "<em>",
"post_tags": "</em>",
"require_field_match": "true"
}
}
}
}fields:指定需要高亮的字段;pre_tags和post_tags:自定义高亮标签,默认是<em>和</em>;require_field_match:默认为true,表示只高亮包含查询匹配的字段。
提示
若需在搜索虚拟组合字段时高亮原始字段,可将 require_field_match 设置为 false,以高亮未直接匹配查询的字段。
5.5. 查询解释
JSON
GET /product/_search
{
"explain": true,
"query": {
"match_all": {}
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
当 explain 设置为 true 后,Elasticsearch 会在每个命中文档的返回结果中附加详细的评分计算过程、分片及节点信息。这有助于分析查询得分的原因,定位相关性问题,或优化查询和索引设置。
6. 聚合
6.1. 聚合基本概念
聚合通常与查询一起使用,查询负责过滤数据,聚合负责对过滤后的数据进行分析。聚合的结果可以嵌套,形成复杂的分析逻辑。
text 类型字段不支持直接用于聚合。text 类型字段经过分词(Tokenization)处理后,存储的是分词后的词项(terms),而不是原始值,这使得它不适合用于聚合操作(如 terms 聚合)。
6.2. 聚合语法结构
聚合在查询的 aggs 字段中定义,基本结构如下:
JSON
{
"query": { /* ... */ },
"size": 0,
"aggs": {
"<aggregation_name>": {
"<aggregation_type>": {
/* <aggregation_config> */
},
"aggs": {
/* 子聚合 */
}
}
}
}信息
默认情况下,terms 聚合返回前 10 个桶,可通过 size 参数调整。
aggregation_name:用户定义的聚合名称;aggregation_type:聚合类型(如terms、avg);aggregation_config:聚合的具体配置(如字段、间隔、排序等)。
6.3. 聚合的分类
ElasticSearch 的聚合主要分为以下三大类:
6.3.1. 桶聚合(Bucket)
将数据分组为多个 “桶”(buckets),每个桶代表一个数据分组,类似于 SQL 的 GROUP BY。每个桶可以包含子聚合,进一步分析分组后的数据。
常见桶聚合:
terms:按字段值分组,类似于按类别分组;histogram:按数值字段的固定间隔分组(例如按价格区间);date_histogram:按时间间隔分组(例如按天、月);range:按用户定义的范围分组;filter:根据过滤条件创建一个桶;geo_distance:按地理距离分组;nested:处理嵌套文档的分组。
默认情况下,桶聚合会统计同内的文档数量,并记为 _count,按其降序排序。
示例:
JSON
GET /product/_search
{
"query": { "match_all": {} },
"size": 0,
"aggs": {
"by_brand": {
"terms": {
"field": "brand",
"order": { "_count": "asc" }
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
部分响应
JSON
{
/* ... */
"aggregations": {
"by_brand": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Apple",
"doc_count": 1
},
{
"key": "Brand 2",
"doc_count": 1
}
/* ... */
]
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
此查询按 brand 字段分组,返回每个类别的文档数量,结果按文档的数据量升序排序。
6.3.2. 指标聚合(Metric)
用于计算数据的统计指标,例如求和、平均值、最大值、最小值等。
常见指标聚合:
avg:计算字段的平均值;sum:计算字段的总和;min:查找字段的最小值;max:查找字段的最大值;value_count:统计字段值的总数;stats:返回字段的统计信息(包括min、max、avg、sum、count);extended_stats:扩展统计,包含方差、标准差等;percentiles:计算字段的百分位数;cardinality:计算字段的唯一值数量(类似 SQL 的DISTINCT)。
示例:
JSON
GET /product/_search
{
"query": { "match_all": {} },
"size": 0,
"aggs": {
"by_brand": {
"terms": {
"field": "brand",
"order": { "by_price.avg": "desc" }
},
"aggs": {
"by_price": {
"stats": {
"field": "price"
}
}
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
部分响应
JSON
{
/* ... */
"aggregations": {
"by_brand": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Apple",
"doc_count": 1,
"by_price": {
"count": 1,
"min": 9999,
"max": 9999,
"avg": 9999,
"sum": 9999
}
},
{
"key": "Brand 3",
"doc_count": 1,
"by_price": {
"count": 1,
"min": 6003,
"max": 6003,
"avg": 6003,
"sum": 6003
}
}
/* ... */
]
}
}
}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
28
29
30
31
32
33
34
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
28
29
30
31
32
33
34
此查询先根据 brand 分组,再计算各 brand 的 price 统计信息,最后按照 price 的平均值降序排序。
6.3.3. 管道聚合(Pipeline)
对其他聚合的结果进行二次处理,生成新的聚合结果。例如,计算聚合结果的平均值、累积和等。
常见管道聚合:
avg_bucket:计算桶的平均值;sum_bucket:计算桶的总和;max_bucket:查找桶的最大值;min_bucket:查找桶的最小值;cumulative_sum:计算累积和;derivative:计算导数(变化率);bucket_sort:对桶进行排序。
7. 自动补全
Elasticsearch 提供多种方式来实现自动补全,主要有以下几类:
7.1. Prefix Query
JSON
GET /product/_search
{
"query": {
"prefix": {
"brand": "Ap"
}
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 优点:简单直接;
- 缺点:性能较差(尤其在大数据量下),因为
prefix会扫描大量倒排索引。
7.2. Edge N-gram 分词器
通过 Edge N-gram 索引,让每个词前缀都能被检索。
配置索引:
JSON
PUT /product
{
"settings": {
"analysis": {
"analyzer": {
"autocomplete_analyzer": {
"tokenizer": "autocomplete_tokenizer",
"filter": ["lowercase"]
}
},
"tokenizer": {
"autocomplete_tokenizer": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20,
"token_chars": ["letter", "digit"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "autocomplete_analyzer",
"search_analyzer": "standard"
}
}
}
}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
28
29
30
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
28
29
30
搜索:
JSON
GET /product/_search
{
"query": {
"match": {
"title": "iph"
}
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 优点:性能比前缀查询好,能更高效支持模糊搜索;
- 缺点:索引体积较大(因为每个词都要拆分多个前缀)。
7.3. Completion Suggester(推荐)
Elasticsearch 专门提供的 Completion Suggest 功能,性能最好,适合实时提示。
配置索引:
JSON
PUT /product
{
"mappings": {
"properties": {
"suggest": {
"type": "completion"
}
}
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
添加数据:
JSON
POST /product/_doc/10
{
"title": "HUAWEI Mate XT 非凡大师",
"price": 19999,
"brand": "HUAWEI",
"suggest": ["HUAWEI", "MateXT"]
}1
2
3
4
5
6
7
2
3
4
5
6
7
自动补全查询:
JSON
GET /product/_search
{
"suggest": {
"product-suggest": {
"prefix": "hua",
"completion": {
"field": "suggest", // 补全查询的字段
"skip_duplicates": true, // 跳过重复
"size": 10 // 获取前 10 条结果
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
返回结果中会包含推荐候选词。
- 优点:速度快,适合实时提示;
- 缺点:功能有限(只支持特定字段,不能做复杂的打分、排序)。
7.4. Search as you type(7.x 新增)
专门用于搜索即打(Search as you type)的场景,比 Edge N-gram 更节省存储。
配置索引:
JSON
PUT /product
{
"mappings": {
"properties": {
"name": {
"type": "search_as_you_type"
}
}
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
将 name 字段设置为这个类型时,Elasticsearch 会自动为 name 字段创建以下子字段:
name: 标准的分词字段(使用默认或指定的分析器,如standard);name._2gram: 将所有术语(词元)拆分为 2-gram(二元语法)碎片;例如:
["ip", "ph", "ho", "on", "ne", "e 1", "13", "3 p", "pr", "ro"]。name._3gram: 将所有术语(词元)拆分为 3-gram(三元语法)碎片;例如:
["iph", "pho", "hon", "one", "ne 1", "e 13", " 13 ", "13 p", "3 pr", " pro", "pro"]。name._index_prefix: 一个特殊字段,它会对name._3gram的子串进行进一步优化,用于完成查询。
查询:
JSON
GET /product/_search
{
"query": {
"multi_match": {
"query": "iph",
"type": "bool_prefix",
"fields": ["name", "name._2gram", "name._3gram"]
}
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
"type": "bool_prefix"- 它将输入的查询字符串分解成术语(terms);
- 对于最后一个术语,它将其视为前缀匹配;
- 对于最后一个术语之前的所有术语,它进行精确匹配。
对于上述这个简单例子,只有一个术语(也是最后一个术语)
"iph",它将其视为前缀进行匹配。
7.5. 方案对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Prefix Query | 简单 | 性能差 | 小数据量 |
| Edge N-gram | 灵活、支持模糊匹配 | 占用空间大 | 中等规模数据 |
| Completion Suggester | 性能最佳 | 功能有限 | 实时提示、输入建议 |
| Search as you type | 平衡性能和存储 | 依赖新版本 ES | 搜索即打 |
7.6. 案例:支持拼音的自动补全
配置索引:
JSON
PUT /product/_settings
{
"settings": {
"analysis": {
"analyzer": {
"completion_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": ["pinyin_filter", "lowercase"]
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"suggest": {
"type": "completion",
"analyzer": "completion_analyzer",
"search_analyzer": "keyword"
}
}
}
}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
28
29
30
31
32
33
34
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
28
29
30
31
32
33
34
添加数据:
JSON
POST /product/_doc/10
{
"suggest": ["华为", "非凡大师"]
}1
2
3
4
2
3
4
自动补全查询:
JSON
GET /product/_search
{
"suggest": {
"product-suggest": {
"prefix": "hw",
"completion": {
"field": "suggest",
"skip_duplicates": true,
"size": 10
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
8. API Key
ElasticSearch 提供了 API Key 功能,用于代替用户名密码进行认证。
8.1. 创建 API Key
可以使用 POST /_security/api_key 接口来创建 API Key。请求格式:
JSON
POST /_security/api_key
{
"name": "my-api-key",
"expiration": "1d",
"role_descriptors": {
"my_custom_role": {
"cluster": ["monitor"],
"index": [
{
"names": ["logs-*"],
"privileges": ["read"]
}
]
}
}
}响应示例:
JSON
{
"id": "i1ALxpgBFOsTFAgadJqY",
"name": "my-api-key",
"api_key": "MYgbKaCbeYFo2onVuKSCeQ",
"encoded": "aTFBTHhwZ0JGT3NURkFnYWRKcVk6TVlnYkthQ2JlWUZvMm9uVnVLU0NlUQ=="
}主要参数:
name(必须):API Key 的名称(便于管理);expiration(可选):过期时间,支持1d、12h、30m等。如果不设定,则永不过期;role_descriptors(可选):角色描述,可以限制该 API Key 拥有的权限;如果不提供,则继承调用者的权限。
8.2. 使用 API Key 认证
在 HTTP 请求头中添加:
Text
Authorization: ApiKey <encoded>例如:
Text
Authorization: ApiKey aTFBTHhwZ0JGT3NURkFnYWRKcVk6TVlnYkthQ2JlWUZvMm9uVnVLU0NlUQ==