分布式搜索和分析引擎——Elasticsearch(索引配置及优化)
在创建索引时,可以对索引进行各种配置,比如分片数量、字段限制、索引方式等,下面进行详细介绍
配置写法
Elasticsearch 支持两种等效的配置方式,分别为扁平式(Flat)和嵌套式(Nested),在早期版本的Elasticsearch更倾向于使用扁平式(如 1.x/2.x 时代)。随着JSON格式成为主流,嵌套式表达变得更直观,现在版本的Elasticsearch内部会将它们解析为相同的结构,两种方式完全等效。
1 2 3 4 5 6 7 8 9
| PUT /products
{ "settings": { "index.number_of_shards": 180, "index.number_of_replicas": 1, "index.recovery.max_bytes_per_sec": "40mb" } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| PUT /products
{ "settings": { "index": { "number_of_shards": 180, "number_of_replicas": 1, "recovery": { "max_bytes_per_sec": "40mb" } } } }
|
索引设置(Index Settings)
创建索引时,针对索引的配置,配置内容写在请求体settings
中
- index.number_of_shards
- 作用: 定义每个索引的主分片数量。
- 值: 整数,默认通常是5。一旦创建了索引,这个数值就不能修改。
- 注意事项:
- 分片数在索引创建后不可修改
- 根据数据总量合理分配分片数量,通常来说,建议每个分片的存储量在20GB-40GB之间最佳
- 主分片数量不得低于节点数量(>=nodes)
- 分片数量并非越多越好,过多的分片会使集群的负担加重,还可能引发 “分片爆炸” 问题
- 定期对分片大小进行监控,避免出现冷热数据分布不均的情况
- index.number_of_replicas
- 作用: 每个主分片的副本分片数量。
- 值: 整数,默认是1。
- 注意事项:
- 可以在索引创建后动态更新。
- 至少1个副本,保证单节点故障时数据可用
- 如果查询性能要求较高,需要适当增加副本数量
- 每多一个副本,存储空间就会翻一倍(不考虑索引占用的存储空间)
- index.analysis
- 作用: 控制文本分析过程,包括字符过滤器、分词器和词汇单元过滤器的自定义设置。
- 值: 可以通过指定不同的字符过滤器、分词器和词汇单元过滤器来自定义分析链。
- 详见analysis配置
- index.refresh_interval
- 作用: 设置刷新操作的时间间隔,刷新操作使得最近的更改对搜索可见。
- 值: 默认是1秒
- 注意事项:
- -1来禁用自动刷新,需手动刷新(
_refresh
)
- 当应用对实时性要求较高时,比如搜索框的即时建议功能,可保持默认的 1s 设置
- 在进行批量数据导入时,例如从数据库同步历史数据,可将刷新间隔增大,甚至设置为 -1 来禁用自动刷新
- 对于日志数据,由于其写入量大且对实时性要求相对较低,可将刷新间隔设置为 30 秒或 1 分钟
- index.store.type
- 作用:设置索引的存储类型
- 值:字符串
- fs(基于文件系统,默认)
- simplefs:简单文件系统存储(已弃用)
- niofs:基于 NIO 的文件系统存储
- mmapfs:内存映射文件存储(高性能)
- 注意事项:
- SSD 推荐使用 mmapfs,HDD 推荐使用 niofs
- index.codec
- 作用: 定义用于存储索引数据的压缩算法。
- 值: 例如
best_compression
使用DEFLATE算法实现更好的压缩率。
- 注意事项:
- 高压缩比会增加 CPU 开销,需权衡磁盘空间和性能
- index.lifecycle.name
- 作用: 关联一个索引生命周期管理策略。
- 值: 字符串,对应已定义的策略名称。
- 注意事项:
- index.hidden
- 作用: 控制索引是否隐藏,隐藏的索引不会出现在索引列表中除非明确请求它们。
- 值: 布尔值(true/false)。
- index.routing_partition_size
- 作用: 在自定义路由情况下影响文档如何分配到分片上。
- 值: 整数,必须小于主分片的数量。
- 注意事项:
- 默认计算公式:
分片号 = hash(routing) % 主分片数
。
- 设置routing_partition_size后的计算公式:
实际分片号 = (hash(routing) % (主分片数 * routing_partition_size)) / routing_partition_size
- 当使用高基数(如用户 ID、设备 ID)作为路由值时,数据可能在分片间分布不均。通过增大 routing_partition_size,不仅可以减少单个分片的负载压力,而且还提高并发写入和查询性能。
- 对于按时间路由的数据(如每天一个路由值),较大的 routing_partition_size 可以避免写入热点
- 取值范围:必须 >= 1,且通常建议设置为 2-10 之间的值。
- index.provided_name
- 作用: 索引的名字,通常由用户定义或系统生成。
- 值: 字符串。
- 通常用于自动或动态生成索引名的场景下。
- index.merge.policy.*
- 作用:控制 Lucene 段(Segment)的合并行为
- 值:
- index.merge.policy.max_merge_at_once
- index.merge.policy.floor_segment
- 注意事项:
- index.search.slowlog.threshold.query.warn
- 作用:设置查询慢日志的警告阈值
- 值:字符串(默认10s)
- 其他类似配置
- index.search.slowlog.threshold.query.info
- index.search.slowlog.threshold.query.debug
- 注意事项:
- 日志级别需在 elasticsearch.yml 中配置
- index.recovery.max_bytes_per_sec
- 作用:控制分片恢复时的最大带宽
- 值:字符串(默认40MB)
- 注意事项:
- index.auto_expand_replicas
analysis配置
在 Elasticsearch(ES)中,analysis 配置主要用于定义文本处理流程,包括分词、过滤和字符转换等操作。合理配置 analysis 对提高搜索准确性和效率至关重要
组成部分
- 字符过滤器(Character Filters):预处理原始文本,如替换 HTML 标签、转换特殊字符。
- 分词器(Tokenizers):将文本拆分为单个词元(tokens)。
- 词元过滤器(Token Filters):修改词元,如小写转换、停用词移除、词干提取等。
- 分析器(analyzer):将字符过滤器、分词器和词元过滤器组合在一起的 “执行单元”,用于定义完整的文本处理流程。
1 2 3 4 5 6 7 8 9 10
| { "settings": { "analysis": { "char_filter": { ... }, "tokenizer": { ... }, "filter": { ... }, "analyzer": { ... } } } }
|
字符过滤器(Character Filters)
在 Elasticsearch 中,字符过滤器(Character Filters)用于在文本被分词前对原始文本进行预处理。
1 2 3 4 5 6 7 8 9
| { "char_filter": { "my_html_filter": { "type": "字符过滤器类型", "参数1": "值1", "参数2": "值2" } } }
|
常用字符过滤器及配置
HTML 标签过滤(html_strip)
- 作用:移除 HTML 标签,保留文本内容。
- 配置项:
- escaped_tags:需要保留的 HTML 标签列表(数组)。
- read_ahead:预读缓冲区大小(整数,默认 1024)。
1 2 3 4 5 6 7 8 9
| { "char_filter": { "my_html_filter": { "type": "html_strip", "escaped_tags": ["b", "i"], "read_ahead": 2048 } } }
|
字符映射(mapping)
- 作用:基于映射规则替换特定字符或字符串。
- 配置项:
- mappings:映射规则数组(格式:”原字符=>目标字符”)。
- mappings_path:外部映射文件路径(支持URL或本地路径)。
- 注意,使用mappings_path配置项,不能实时读取外部映射文件的内容,只在配置时生效一次。
1 2 3 4 5 6 7 8 9 10 11 12
| { "char_filter": { "my_mapping": { "type": "mapping", "mappings": [ "&=>and", "u.s.=>united states", "ü=>ue" ] } } }
|
正则替换(pattern_replace)
- 作用:使用正则表达式匹配并替换文本。
- 配置项:
- pattern:正则表达式(字符串)。
- replacement:替换字符串(支持 $1、$2 引用捕获组)。
- flags:正则表达式标志(字符串,如 “CASE_INSENSITIVE|COMMENTS”)。
1 2 3 4 5 6 7
| { "extract_domain": { "type": "pattern_replace", "pattern": "^(?:(?<scheme>https?|ftp):\\/\\/)?(?:(?<auth>[^@\\/\\n]+)@)?(?<host>[^:\\/?#]+|\\[[^\\]]+\\])(?::(?<port>\\d+))?(?<path>[^?#]*)(?:\\?(?<query>[^#]*))?(?:#(?<fragment>.*))?$", "replacement": "${host}" } }
|
ICU 规范化(icu_normalizer)
- 作用:基于 Unicode 标准规范化文本(需安装 analysis-icu 插件)。
- 配置项:
- name:规范化方式(字符串,可选值:”nfc”、”nfkc”、”nfkc_cf”)。
1 2 3 4 5 6 7 8
| { "char_filter": { "my_icu_normalizer": { "type": "icu_normalizer", "name": "nfkc" } } }
|
同义词替换(synonym_graph)
- 作用:在分词前替换同义词(需安装 analysis-icu 插件)。
- 配置项:
- synonyms:同义词规则数组。
- synonyms_path:外部同义词文件路径。
- format:同义词文件格式(如 “wordnet”)。
1 2 3 4 5 6 7 8 9 10 11
| { "char_filter": { "my_synonym": { "type": "synonym_graph", "synonyms": [ "i-pod, i pod => ipod", "u.s.a, united states => usa" ] } } }
|
其他的字符过滤器
- 别名替换:
pattern_replace_char_filter
,基于预定义模式替换字符(实验性,不推荐生产环境使用)。
- Kuromoji 字符过滤器(日语):
kuromoji_iteration_mark
,处理日语重复符号(如 々)。
- Thai 字符过滤器(泰语):
kuromoji_iteration_mark
,泰语规范化。
分词器(Tokenizers)
分词器(Tokenizer)是文本分析(Analysis)的核心组件,负责将文本拆分为独立的词元(Tokens)。
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "settings": { "analysis": { "tokenizer": { "my_tokenizer": { "type": "分词器类型", "参数1": "值1", "参数2": "值2" } } } } }
|
- 分词器的核心作用
- 文本拆分:将连续文本按规则拆分为独立词元。
- 位置记录:为每个词元记录原始文本中的位置(用于短语搜索)。
- 偏移量记录:记录词元在原文中的起始和结束位置(用于高亮显示)。
常用分词器及配置
标准分词器(Standard Tokenizer)
- 特点:基于 Unicode Text Segmentation 算法,支持大多数语言,自动处理标点符号。
- 适用场景:通用文本,默认选择。
- 配置参数:
- max_token_length:最大词元长度(默认 255)。
1 2 3 4 5 6 7 8
| { "tokenizer": { "my_standard": { "type": "standard", "max_token_length": 512 } } }
|
空格分词器(Whitespace Tokenizer)
- 特点:仅按空格拆分文本,保留标点符号。
- 适用场景:不需要处理标点符号的文本(如代码、标签)。
1 2 3 4 5 6 7
| { "tokenizer": { "my_whitespace": { "type": "whitespace" } } }
|
关键词分词器(Keyword Tokenizer)
- 特点:不拆分文本,将整个输入作为单个词元。
- 适用场景:不需要分词的字段(如 URL、邮箱、ID)。
- 配置参数:
- buffer_size:缓冲区大小(默认 256),用于控制单个词元的最大长度。
正则分词器(Pattern Tokenizer)
- 特点:基于正则表达式拆分文本。
- 配置参数:
- pattern:正则表达式(默认 \W+,即非单词字符)。
- flags:正则标志(如 CASE_INSENSITIVE)。
- group:捕获组索引(默认 -1,表示使用整个匹配结果)。
1 2 3 4 5 6 7 8 9
| { "tokenizer": { "my_pattern": { "type": "pattern", "pattern": "[,;]+", "flags": "CASE_INSENSITIVE" } } }
|
注意:group的值不同时,会有不一样的效果(踩过坑了!!!)
group = -1
(默认值):正则表达式匹配的内容是分隔符,分隔符之间的文本被作为词元。
group = 0
,与-1相同
group >= 1
,正则表达式中第 group 个捕获组匹配的内容被作为词元,而非分隔符。
- 举例说明:
- 输入文本:
Hello,World!This.is,a.test.
- 正则表达式:
[,!\s\.]
group = -1
时的分词结果:["Hello", "World", "This", "is", "a", "test"]
group = 1
时的分词结果:[",", "!", ".", ",", ".", "."]
N-gram 分词器(Ngram Tokenizer)
- 特点:将文本拆分为固定长度(N)的连续字符块。
- 配置参数:
- min_gram:最小 N 长度(默认 1)。
- max_gram:最大 N 长度(默认 2)。
- token_chars:允许作为词元的字符类型(如 letter、digit)。
1 2 3 4 5 6 7 8 9 10
| { "tokenizer": { "my_ngram": { "type": "ngram", "min_gram": 2, "max_gram": 3, "token_chars": ["letter", "digit"] } } }
|
Edge N-gram 分词器(EdgeNGram Tokenizer)
- 特点:从文本开头生成前缀 N-gram,用于自动补全。
- 配置参数:
- min_gram/max_gram:最小 / 最大前缀长度。
- side:生成方向(front 或 back,默认 front)。
1 2 3 4 5 6 7 8 9 10
| { "tokenizer": { "my_edge_ngram": { "type": "edge_ngram", "min_gram": 1, "max_gram": 10, "side": "front" } } }
|
中文分词器(IK Analyzer)
- 特点:专为中文设计,支持细粒度和智能分词模式。
- 依赖:需安装 IK 分词器插件。
- 两种模式:
- ik_max_word:细粒度分词(尽可能多分词)。
- ik_smart:智能分词(粗粒度,适合搜索)。
1 2 3 4 5 6 7
| { "tokenizer": { "my_ik": { "type": "ik_max_word" } } }
|
分析器(Analyzer)
分析器是 Elasticsearch 文本分析的顶层组件,负责协调字符过滤器、分词器和词元过滤器的执行顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "analysis": { "analyzer": { "自定义分析器名称": { "type": "custom", "char_filter": [ "字符过滤器1", "字符过滤器2" ], "tokenizer": "分词器名称", "filter": [ "词元过滤器1", "词元过滤器2" ] } } } }
|
内置分析器
标准分析器(Standard Analyzer)
- 核心功能:基于 Unicode 文本分割算法分词,处理大多数语言,自动过滤标点符号。
- 适用场景:通用文本,默认选择。
- 处理流程:
- 分词:按词边界拆分文本(如空格、标点)。
- 小写转换:将所有词元转为小写。
- 停用词过滤:可选(默认不启用)。
- 示例:
1 2 3 4 5
| POST /_analyze { "analyzer": "standard", "text": "Hello World! This is a Test." }
|
1 2 3 4 5 6 7 8
| { "tokens": [ { "token": "hello", "start_offset": 0, "end_offset": 5 }, { "token": "world", "start_offset": 6, "end_offset": 11 }, { "token": "this", "start_offset": 13, "end_offset": 17 }, ] }
|
简单分析器(Simple Analyzer)
- 核心功能:按非字母字符分词,所有词元转为小写。
- 适用场景:纯文本,无需复杂处理。
- 处理流程:
- 分词:按非字母字符(如数字、标点、空格)拆分。
- 小写转换:将所有词元转为小写。
- 示例:
1 2 3 4 5
| POST /_analyze { "analyzer": "simple", "text": "Hello123World!_This" }
|
1 2 3 4 5 6 7
| { "tokens": [ { "token": "hello", "start_offset": 0, "end_offset": 5 }, { "token": "world", "start_offset": 8, "end_offset": 13 }, { "token": "this", "start_offset": 16, "end_offset": 20 } ] }
|
空格分析器(Whitespace Analyzer)
- 核心功能:仅按空格拆分文本,保留所有标点符号。
- 适用场景:不需要处理标点的文本(如代码、标签)。
- 处理流程:
- 分词:按空格拆分文本。
- 无其他处理:保留原始大小写和标点。
- 示例:
1 2 3 4 5
| POST /_analyze { "analyzer": "whitespace", "text": "Hello, World! This is a test." }
|
1 2 3 4 5 6 7 8
| { "tokens": [ { "token": "Hello,", "start_offset": 0, "end_offset": 6 }, { "token": "World!", "start_offset": 7, "end_offset": 13 }, { "token": "This", "start_offset": 14, "end_offset": 18 }, ] }
|
关键词分析器(Keyword Analyzer)
- 核心功能:不拆分文本,将整个输入作为单个词元。
- 适用场景:不需要分词的字段(如 URL、邮箱、ID)。
- 处理流程:
- 不分词:整个文本作为一个词元。
- 无其他处理:保留原始大小写和格式。
- 示例:
1 2 3 4 5
| POST /_analyze { "analyzer": "keyword", "text": "https://example.com" }
|
1 2 3 4 5
| { "tokens": [ { "token": "https://example.com", "start_offset": 0, "end_offset": 18 } ] }
|
停用词分析器(Stop Analyzer)
- 核心功能:类似标准分析器,但额外过滤停用词。
- 适用场景:过滤常见无意义词汇(如 “the”, “and”, “is”)。
- 处理流程:
- 分词:按标准规则拆分文本。
- 小写转换:转为小写。
- 停用词过滤:移除预定义的停用词(默认英语停用词)。
- 示例:
1 2 3 4 5
| POST /_analyze { "analyzer": "stop", "text": "The quick brown fox" }
|
1 2 3 4 5 6 7
| { "tokens": [ { "token": "quick", "start_offset": 4, "end_offset": 9 }, { "token": "brown", "start_offset": 10, "end_offset": 15 }, { "token": "fox", "start_offset": 16, "end_offset": 19 } ] }
|
模式分析器(Pattern Analyzer)
- 核心功能:基于正则表达式分词,支持自定义分隔符。
- 适用场景:需要复杂文本拆分规则的场景。
- 默认配置:
- 正则表达式:\W+(按非单词字符拆分)。
- 小写转换:启用。
- 停用词:可选(默认无)。
- 示例:
1 2 3 4 5 6 7 8
| POST /_analyze { "analyzer": { "type": "pattern", "pattern": "[,;]" }, "text": "apple,banana;cherry" }
|
1 2 3 4 5 6 7
| { "tokens": [ { "token": "apple", "start_offset": 0, "end_offset": 5 }, { "token": "banana", "start_offset": 6, "end_offset": 12 }, { "token": "cherry", "start_offset": 13, "end_offset": 19 } ] }
|
自定义分析器(Custom Analyzer)
通过组合字符过滤器、分词器和词元过滤器创建,需在 analysis.analyzer 中定义。
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 35
| { "settings": { "analysis": { "char_filter": { "my_html_strip": { "type": "html_strip" } }, "tokenizer": { "my_standard": { "type": "standard", "max_token_length": 512 } }, "filter": { "my_stopwords": { "type": "stop", "stopwords": "_english_" }, "my_stemmer": { "type": "stemmer", "language": "english" } }, "analyzer": { "my_custom_analyzer": { "type": "custom", "char_filter": ["my_html_strip"], "tokenizer": "my_standard", "filter": ["lowercase", "my_stopwords", "my_stemmer"] } } } } }
|
映射配置(mappings)
在 Elasticsearch(ES)中,mapping 的配置是用来定义索引的结构和字段类型的。它类似于传统数据库中的表结构定义,但是更加灵活和动态。
主要作用如下:
- 定义字段类型:指定每个字段的数据类型,如文本、数值、日期、布尔值等。
- 控制索引行为:决定字段是否应该被索引,以及如何被索引。
- 设置分词器:对于文本字段,指定使用的分词器来处理文本。
- 元数据配置:定义字段的元数据,如别名、父子关系等。
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "mappings": { "dynamic": false, "properties": { "surl_hosts": { "type": "keyword" }, "surl_md5": { "type": "keyword" } } } }
|
主要配置项及含义
type(字段类型)
用于定义字段类型
主要的字段类型详见《ES字段类型》
1 2 3 4 5 6 7 8 9
| { "properties": { "title": {"type": "text"}, "tags": {"type": "keyword"}, "views": {"type": "integer"}, "created_at": {"type": "date"}, "is_published": {"type": "boolean"} } }
|
index(是否索引)
控制字段是否被索引,默认为true。设为false时,字段不可被搜索,但会被存储。
1 2 3 4 5 6
| { "properties": { "user_id": {"type": "keyword"}, "private_note": {"type": "text", "index": false} } }
|
analyzer(分词器)
指定文本字段在索引和搜索时使用的分词器。
1 2 3 4 5 6 7 8 9
| { "properties": { "content": { "type": "text", "analyzer": "standard", "search_analyzer": "english" } } }
|
store(是否存储)
控制字段值是否单独存储(独立于_source字段)。默认为false,通常只需要存储_source即可。
1 2 3 4 5
| { "properties": { "title": {"type": "text", "store": true} } }
|
fields(多字段)
为同一个字段定义多个视图,用于不同的搜索场景。
1 2 3 4 5 6 7 8 9 10
| { "properties": { "email": { "type": "text", "fields": { "raw": {"type": "keyword"} } } } }
|
null_value(空值处理)
当字段值为null时,使用指定的值替代。
1 2 3 4 5 6 7 8
| { "properties": { "status": { "type": "keyword", "null_value": "unknown" } } }
|
dynamic(动态映射)
控制是否自动添加新字段。可选值:true(默认)、false、strict。
值含义:
- true:当检测到文档中存在mapping中未定义的新字段时,Elasticsearch会自动将新字段添加到 mapping中,并根据字段值自动推断其数据类型(例如,字符串可能被映射为text或keyword,数字被映射为long或double等)
- false:当检测到文档中存在mapping中未定义的新字段时,Elasticsearch会忽略这些字段,不会将它们添加到mapping中,也不会索引这些字段。但这些字段仍会保存在_source中,因此可以通过获取完整文档来访问它们,但无法对这些字段进行搜索或聚合。
- strict:当检测到文档中存在mapping中未定义的新字段时,Elasticsearch会拒绝整个文档的索引请求,并返回错误。这种模式强制要求所有字段都必须在mapping中预先定义,确保索引结构严格可控。
1 2 3 4 5 6
| { "dynamic": "strict", "properties": { "name": {"type": "text"} } }
|
指定日期字段的格式。
1 2 3 4 5 6 7 8
| { "properties": { "timestamp": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||epoch_millis" } } }
|
norms(归一化因子)
控制是否存储归一化因子,影响评分计算。对于不需要评分的字段(如过滤字段),可设为false以节省空间。
1 2 3 4 5 6 7 8
| { "properties": { "category": { "type": "keyword", "norms": false } } }
|
boost(字段权重)
设置字段的权重,影响搜索相关性评分。
1 2 3 4 5 6 7 8 9
| { "properties": { "title": { "type": "text", "boost": 2.0 }, "content": {"type": "text"} } }
|