# 配置
# index template
:索引模板。用于在创建索引时进行初始化配置。
创建索引时,会根据索引模式匹配索引模板。如果存在多个匹配的索引模板,则采用优先级最高的那个。
- 创建索引时,如果加上了配置信息,则会覆盖索引模板中的对应配置。
- 创建、修改索引模板时,只会影响之后新创建的索引,不会影响已创建的索引。
相关 API :
GET /_template # 查询所有的索引模板 GET /_template/<template> # 查询指定的索引模板 PUT /_template/<template> <request_body> # 创建索引模板 DELETE /_template/<template> # 删除索引模板
例:创建一个索引模板
PUT /_template/my_template_1 { "index_patterns": ["mysql-log-*", "nginx-log-*"], # 索引模式,用于匹配一些索引 "aliases": { "test-alias-1": {} }, "settings": {...}, "mappings": {...}, # "version" : 1, # 声明该索引模板的版本 # "_meta": { # 配置该索引模板的元数据,可以加入任意名称的参数,并不会被 ES 使用 # "description": "This is a log template." # } }
- 至少要声明 index_patterns ,其它配置则可以省略。
ES v7.8 版本引入了可组合的索引模板(composable index template),API 为 _index_template 。
- 优点:可组合使用,更灵活。
- 可设置
"priority": 100
参数,表示该模板的优先级。- 默认为 0 ,即优先级最低。
- 如果一个索引匹配多个索引模板,则采用优先级最高的那个。
- 如果一个索引同时匹配 _template 和 _index_template ,则优先采用 _index_template 。
# index pattern
:索引模式。一个用于匹配任意个索引名的字符串。
- 可以是多种格式:
my-index-1 # 一个索引名 my-index-1,my-index-2 # 多个索引名,用逗号分隔 my-index-* # 可以使用通配符 * 匹配多个索引名 my-index-*,-my-index-2 # 可以在索引名之前加上 - ,从而排除它
# alias
- 每个索引可以创建一个或多个别名(alias)。
- ES 的大多数 API 都支持输入索引的别名,会自动解析到原名。
- 别名与索引名共享一个命名空间,不允许重名。
- 相关 API :
GET /_alias # 查询所有索引的所有别名 GET /_alias/<alias> # 加上一个 alias 名称进行筛选,支持使用通配符 * GET /<index>/_alias # 查询指定索引的所有别名 GET /<index>/_alias/<alias> HEAD /_alias/<alias> # 检查索引是否存在 HEAD /<index>/_alias/<alias> PUT /<index>/_alias/<alias> # 给索引创建别名 DELETE /<index>/_alias/<alias> # 删除索引的别名
# mappings
:映射,用于定义某个 index 中存储的文档的数据结构、字段类型。
定义方式分为两种:
- explicit mapping
- :显式映射。是在 properties 区块,根据字段名定义一些字段。
- index 在创建之后,不能修改显示映射中已有的字段,只能增加字段。也可以创建一个拥有新 mappings 的新 index ,然后将旧 index 的文档通过 reindex 拷贝过去。
- dynamic mapping
- :动态映射。是在 dynamic_templates 区块,根据至少一个匹配条件,为还没有显式映射的字段添加显式映射。
- explicit mapping
例:
PUT test_log { "mappings": { "_source": { "enabled": true # 是否存储 _source 字段,默认为 true }, "dynamic_templates" : [ # 动态映射 { "integer_fields": { # "match" : "*", # 匹配字段名 # "path_match" : "test", # 匹配字段的 JSON 路径 "match_mapping_type": "long", # 匹配字段值的 JSON 数据类型 "mapping": { # 为匹配的字段生成显式映射 "type": "integer" # 该效果为:如果新增的文档中,任意字段满足匹配条件,则存储为 integer 数据类型 } } }, { "string_fields" : { "match_mapping_type" : "string", "mapping" : { "type" : "text" } } } ], "properties": { # 显式映射 "@timestamp" : { "type" : "date" }, "pid" : { "type" : "long" }, "level" : { "type" : "text" }, "message": { "type": "text" # "index": true, # 是否对该字段建立倒排索引,从而允许搜索该字段,默认为 true # "doc_values": true, # 是否存储 doc_values 数据,默认为 true # "fielddata": false, # 是否存储 fielddata 数据,默认为 false # "store": false, # 是否存储该字段的原始值(称为 store_values ),默认为 false # "norms": true, # 是否启用 norms # "ignore_malformed": false, # 新增一个文档时,如果某个字段的数据类型不符合 mappings ,则整个文档会写入失败。启用该配置,会依然写入文档,只是不对该字段建立索引 } } } }
- 将字符串类型的值存储到 ES 时,建议同时写入一个 text 类型的字段、一个 keyword 类型的子字段。如下:
"message" : { # 一个名为 message 的字段,类型为 text "type" : "text", "fields" : { # 定义子字段 "keyword" : { # 一个名为 keyword 的子字段,类型为 keyword "type" : "keyword", "ignore_above" : 256 # 限制字段取值的长度,默认为无限大。如果超过限制,则保存该字段时,不会建立倒排索引,因此不能被 search 查询到 } } }
- norms :为每个字段额外存储一个字节,记录多种有利于计算 score 的调节因子(normalization factors)。
- text 类型的字段默认启用 norms ,keyword 类型的字段默认关闭 norms 。
- 将字符串类型的值存储到 ES 时,建议同时写入一个 text 类型的字段、一个 keyword 类型的子字段。如下:
当用户新增一个文档时,ES 会解析文档中每个字段的值。如果当前 index mappings 定义了该字段的值应该是 T 数据类型,则尝试将字段的原始值转换成 T 数据类型(即数据类型转换)。
- 例如 index mappings 中定义了 message 字段为 text 类型,则 _source 中可包含
"message": 1
或"message": "hello"
,但不能接受"message": {}
,因为它的值属于 object 类型。 - 如果一个字段在 index mappings 中未定义 properties ,则采用 dynamic_templates 。如果 dynamic_templates 也不匹配该字段,则 ES 会自动判断数据类型,通常转换为 number、text 类型。
- 如果一个文档的所有字段都解析成功,才能将该文档写入 ES 。
- 如果一个文档的任意字段解析失败,则 ES 默认会拒绝写入该文档,并报错 HTTP 400 ,例如:
document_parsing_exception , failed to parse field [message] of type [text] in document
- 例如 index mappings 中定义了 message 字段为 text 类型,则 _source 中可包含
# 数据类型
每个文档包含多个字段,字段名为字符串类型(区分大小写),而字段值的常见类型如下:
- number :数字类型,细分为 integer、long、byte、double、float 等。
- boolean :布尔值。
- date :日期,可以是 UNIX 时间戳,或 ISO8601 格式的字符串。
- array :数组,包含一组元素,且这些元素的数据类型相同。例如
[1, 2]
、["one", "two"]
- 字符串 :分为两种:
- text
- 适合存储非结构化数据,比如一篇文章,然后进行模糊搜索。
- 支持 search 操作,默认不支持 aggregations、sorting、scripting 操作,除非启用了 doc_values 或 fielddata 。
- keyword
- 适合存储结构化数据,比如文章标题、编号,然后进行精确搜索。
- 支持 search、aggregations、sorting、scripting 操作。
- text
- object :一个 JSON 对象。
- nested :用于嵌套一组文档。这会在当前文档之外,创建独立的 Lucene 文档。
- join
- :用于声明父子文档。
- 父文档与子文档必须位于同一索引、同一 shard 。
- 例:
PUT test_index { "mappings": { "properties": { "test_join": { "type": "join", "relations": { "question": "answer" # 定义一个 join 字段,声明 question 对 answer 的父子关系 } } } } }
PUT test_index/_doc/1 { "text": "This is a question", "test_join": { "name": "question" # 新增一个文档,join 名称为 question ,因此为父文档 } }
PUT test_index/_doc/2?routing=1 # 设置 routing 参数,将 HTTP 请求路由到 _id=1 的文档所在 shard { "text": "This is an answer", "test_join": { "name": "answer", # 新增一个文档,join 名称为 answer ,因此为子文档 "parent": "1" # 父文档是 _id=1 的文档 } }
# doc_values
当用户新增一个文档时,ES 会存储以下内容到 Lucene 中:
默认会对文档的每个字段建立倒排索引,并存储,从而支持对该字段执行 search 操作。
默认会将整个文档的原始 JSON 内容,存储在内置字段 _source 中。
- _source 字段只是用于存储文档的原始内容。该字段没有建立索引,因此不支持搜索。
- _source 在 Lucene 中存储为单个字段。读取 _source 时,只能一次性读取 _source 的全部内容,不能只读取 _source 的子字段,因此开销较大,建议一般不要读取 _source 。
- 如果 index mappings 中配置了
"_source": false
,则不会存储文档的 _source 字段,可节省大量磁盘空间。此时,可通过 doc_values、store_values 读取字段的原始值,例如:GET test_log/_search { "_source": false, "docvalue_fields": ["pid"] }
默认会将文档每个字段的原始值,额外存储一份,采用列式存储,称为 doc_values ,从而支持对该字段执行 aggregations、sorting、scripting 操作。
- text 类型的字段不支持存储 doc_values 。
- number、boolean、date、keyword 等类型的字段默认会存储 doc_values ,因此用户可以不建立倒排索引。
- 此时,不能执行 search 操作,只能执行 aggregations、sorting、scripting 操作。
- 此时,相当于从 ES 数据库变成了 Mongo 数据库。这样占用的磁盘存储空间减少,但是查询速度变慢。
如果启用了 store_values ,则会将文档每个字段的原始值,额外存储一份,采用行式存储。
- 与 doc_values 相比,_source、store_values 只能用于读取字段的原始值,不支持 search、aggregations、sorting、scripting 操作。
- 如果需要经常读取文档个别字段的原始值,则建议不要读取整个 _source ,而是单独读取该字段的 doc_values 或 store_values 。
- 如果需要经常读取文档多个字段的原始值,则建议读取整个 _source ,然后筛选出目标字段,这样效率更高。例如:
GET test_log/_search { "_source": ["message"] }
执行 aggregations、sorting、scripting 操作时,需要读取字段的原始值,因此用 doc_values 比倒排索引的效率更高。
- 如果用倒排索引,则 ES 需要先从磁盘读取文档内容,然后在内存中建立从文档 _id 到 term 的正向索引,这些临时数据称为 fielddata 。查询的文档越多,fielddata 占用的内存越多。
- 如果用 doc_values ,则 ES 可直接从磁盘读取 doc_values 数据,此时使用文件系统的缓存,不占用内存。
# 文本分析
文本分析(Text analysis):ES 存储 text 类型的字段时,会先经过 analyzer 处理,再建立索引。从而方便模糊搜索。
- 查询时,也先将查询字符串经过 analyzer 处理,再进行查询。
- text 字段存储之后的字符内容、顺序可能变化,因此使用 term、regexp 查询时可能不匹配,应该用 match 查询。
一个分析器(analyzer)包含多个组件:
- character filter
- :字符过滤器,用于添加、删除、转换某些字符。
- tokenizer
- :分词器,用于将字符串拆分成一个个单词,称为 token 。
- 例如英语 "Hello World" 会拆分成 2 个单词,汉语 "你好啊" 会拆分成 3 个单词。
- token filter
- :用于添加、删除、转换某些 token 。
- 例如 the、on、to 等单词通常不影响查询,可以删除。
- character filter
ES 内置了多种 analyzer ,参考官方文档 (opens new window),例如:
- standard :默认采用。根据大部分标点符号进行分词。
- 还启用了 Lower Case Token Filter ,将 token 全部小写。
- simple :根据非字母的的字符进行分词,还启用了 Lower Case Token Filter 。
- whitespace :根据空白字符进行分词。
- stop :与 simple 相似,但启用了 Stop token filter ,会删除 a、an、and、are、it、no、the 等冠词、介词、连词。
- standard :默认采用。根据大部分标点符号进行分词。
index 的 analyzer 配置示例:
{ "settings": { "analysis": { "analyzer": { "default": { # 设置存储时的默认 analyzer "type": "standard" }, "default_search": { # 设置查询时的默认 analyzer "type": "standard" } } } }, "mappings": { "properties": { "message": { "type": "text", "analyzer": "standard" # 设置指定字段的 analyzer } } } }
可用 API
/_analyze
测试分析一个字符串:POST /_analyze { "analyzer": "standard", "text": "Hello World! -_1" }
结果如下:
{ "tokens": [ { "token": "hello", "start_offset": 0, "end_offset": 5, "type": "<ALPHANUM>", "position": 0 }, { "token": "world", "start_offset": 6, "end_offset": 11, "type": "<ALPHANUM>", "position": 1 }, { "token": "_1", "start_offset": 14, "end_offset": 16, "type": "<NUM>", "position": 2 } ] }
# settings
:用于控制索引的存储等操作。
settings 中的配置参数按修改类型分为两种:
- dynamic :支持在运行的索引上配置。
- static :只能在创建索引时配置,有的也支持在 closed 索引上配置。
index.number_of_replicas
属于动态配置,index.number_of_shards
属于静态配置。因此索引在创建之后,不能修改主分片数量。
例:
"settings": { "index": { "codec": "LZ4", # Lucene 存储 segments 时的压缩算法,默认为 LZ4 。设置为 best_compression 则会采用 DEFLATE 压缩算法,压缩率更高,但增加了读写文档的耗时 "number_of_shards": 3, # 主分片的数量,默认为 1 。该参数属于 static settings 。 "number_of_replicas": 1, # 每个主分片的副本数量,默认为 1 。设置为 0 则没有副本 "auto_expand_replicas": "0-1", # 根据 ES 节点数,自动调整 number_of_replicas ,最小为 0 ,最大为 1 。默认禁用该功能 "refresh_interval" : "5s", # 每隔一定时间自动刷新一次索引。默认为 1 s ,接近实时搜索。设置成 -1 则不自动刷新 "max_result_window": 10000, # 限制分页查询时 from + size 之和的最大值 "mapping": { "total_fields.limit": 1000, # 限制索引包含的字段数 "depth.limit": 50, # 限制字段的深度 "field_name_length.limit": 1000, # 限制字段名的长度,默认不限制 "ignore_malformed": false # 是否所有字段都允许写入错误的数据类型 } "blocks": { # 用于禁止对索引的一些操作 "read": true, # 禁止读文档 "write": true, # 禁止写文档 "metadata": true, # 禁止读写元数据 "read_only": true, # 禁止写文档、元数据,也不能删除索引 "read_only_allow_delete": true, # 在 read_only 的基础上,允许删除索引(但依然不允许删除文档,因为这样并不会释放磁盘空间) } } }
ES 默认未开启慢日志,可以给某个索引,或所有索引添加慢日志配置:
PUT /_all/_settings { "index": { "search": { "slowlog": { "threshold": { "fetch": { "warn": "10s" # 如果 search 请求在 fetch 阶段的耗时超过该阈值,则打印一条 warn 级别的日志到 stdout "info": "5s", "debug": "100ms", "trace": null, # 赋值为 null 即可关闭该日志 }, "query": { "warn": "10s" "info": "5s", "debug": "100ms", "trace": null, } } } }, "indexing": { "slowlog": { "threshold": { "index": { "warn": "10s" "info": "5s", "debug": "100ms", "trace": null, } } } } } }
# component template
:组件模板。可以被索引模板继承,从而实现更抽象化的模板。
- 例:创建一个组件模板
PUT /_component_template/my_component_template { "template": { "mappings": { "properties": { "@timestamp": { "type": "date" } } } } }
# shard
ES 的 shard 根据用途分为两种:
- 主分片(primary shard)
- :每个索引可以划分 1 个或多个主分片,用于分散存储该索引的所有文档。
- 副分片(replica shard)
- :每个主分片可以创建 0 个或多个数据副本,称为副分片。
- 主分片能处理用户的读、写请求。而副分片只支持读请求,不支持写请求。
- 主分片(primary shard)
关于 shard 数量。
- 一个索引不应该划分太多主分片。因为:
- 每个分片都要存储一份元数据到内存中。查询文档时,合并所有 shard 的查询结果也有耗时。
- 如果主分片数大于节点数,则不能发挥并行查询的优势。
- 建议给每个索引默认划分 1 个主分片。如果数据量大,则增加节点数、划分更多分片。
- 单个分片的体积越大,会增加查询耗时、故障后恢复耗时。
- 建议将单个分片的体积控制在 10G~50G 。
- 如果要存储海量数据,比如日志,建议每天创建一个新索引用于存储数据,比如 nginx-log-2020.01.01 。
- ES 默认配置了
cluster.max_shards_per_node=1000
,表示每个数据节点最多打开的 shard 数量(包括主分片、副分片、unassigned_shards,不包括 closed 索引的分片)。- 整个 ES 集群最多打开的 shard 数量为 max_shards_per_node * data_node_count ,这也限制了 open 状态的 index 总数。
- 一个索引不应该划分太多主分片。因为:
# 分配
ES 集群中,master 节点会决定将每个 shard 分配到哪个 ES 节点进行存储。
- 如果一个索引划分了多个主分片,则会尽量存储到不同节点,从而分散 IO 负载、实现并发查询。
- 如果主分片数大于节点数,则一些主分片会存储到同一个节点,它们会竞争 CPU、内存、磁盘 IO 等资源,不能发挥并行查询的优势。
- 如果一个主分片创建了副分片,则必须存储到与主分片不同的节点,否则不能实现灾备,失去了副分片的存在价值。
- 如果主分片之外没有其它可用节点,则副分片会一直处于未分配(unassigned)状态,导致该索引的状态为 yellow 。
- 如果一个索引划分了多个主分片,则会尽量存储到不同节点,从而分散 IO 负载、实现并发查询。
默认启用了自动分配。配置如下:
PUT /_cluster/settings { "transient": { "cluster.routing.allocation.enable": "all" } }
- enable 可以取值为:
- all :自动分配所有分片。
- primaries :只分配主分片。
- new_primaries :只分配新索引的主分片。
- none :禁用自动分配。
- 改变索引、分片、节点数量时,都可能触发 shard 的自动分配。
- enable 可以取值为:
默认启用了基于磁盘的分配规则。配置如下:
"cluster.routing.allocation.disk.threshold_enabled": true # 是否启用基于磁盘阈值的分配规则 "cluster.routing.allocation.disk.watermark.low": "85%" # 低阈值。超出时,不会将新的副分片分配到该节点 "cluster.routing.allocation.disk.watermark.high": "90%" # 高阈值。超出时,会尝试将该节点上的分片迁移到其它节点 "cluster.routing.allocation.disk.watermark.flood_stage": "95%" # 洪水位(危险阈值)。超出时,会找出该节点上存储的所有分片,将它们所属的 index 开启 read_only_allow_delete "cluster.info.update.interval": "30s" # 每隔多久检查一次各节点的磁盘使用率
- 这里可以设置磁盘使用率的一组阈值。超出阈值时会自动开启限制,低于阈值时会自动解除限制。
- 阈值可以设置为磁盘的剩余大小,比如 "100gb" 。
可以添加基于过滤器的分配规则。配置如下:
"cluster.routing.allocation.exclude._name": "node1,node2" # 禁止分配分片到指定节点上,已有的分片也会被迁移走。可以指定 _ip、_name 等
默认会自动均衡各个节点上存储的分片数量。配置如下:
"cluster.routing.rebalance.enable": "all" # 对哪些类型的分片进行均衡。all 表示主分片+副分片 "cluster.routing.allocation.allow_rebalance": "indices_all_active" # 什么时候开始均衡。默认为主发片+副分片都已经被分配时
- rebelance 均衡会遵守其它分配规则,因此不一定完全均衡。
- 建议将一个索引的不同主分片分配到不同节点,从而分散 IO 负载,也有利于并发查询。
可以手动迁移 shard 。示例:
POST /_cluster/reroute { "commands": [ { "move": { "index": "test1", "shard": 0, "from_node": "node1", "to_node": "node2" } } ] }
- 上例是将 test1 索引的 0 号主分片,从 node1 迁移到 node2 。
- 目标节点上不能存在该分片的副分片,否则不允许迁移。
- 迁移时,建议先禁用自动分配、自动均衡:
"cluster.routing.allocation.enable": "none" "cluster.routing.rebalance.enable": "none"
# _split
- API 格式:
POST /<index>/_split/<target-index>
- 用途:将一个索引分割为拥有更多主分片的新索引
- 例:
POST /my-index-1/_split/my-index-2 { "settings": { "index.number_of_shards": 2 } }
- 分割索引的前提条件:
- 源索引的 health 为 green 。
- 源索引改为只读模式。可执行以下请求:
PUT /my-index-1/_settings { "settings": { "index.blocks.write": true # 禁止写操作,但允许删除 } }
- 目标索引不存在。
- 目标索引的主分片数,是源索引的主分片数,的整数倍,比如 1 倍、2 倍。使得源索引的每个主分片,都可以平均拆分成多个目标索引的主分片。
- 分割索引的工作流程:
- 创建目标索引,继承源索引的配置,但主分片数更多。
- 将源索引的数据通过硬链接或拷贝,迁移到目标索引。
- 允许目标索引被客户端读写。
# _shrink
- API 格式:
POST /<index>/_shrink/<target-index>
- 用途:将一个索引收缩为拥有更少主分片的新索引。
- 例:
POST /my-index-1/_shrink/my-index-2 { "settings": { "index.number_of_shards": 1 } }
- 收缩索引的前提条件:
- 源索引的 health 为 green 。
- 源索引为只读,并且所有主分片位于同一个节点上。可使用以下配置:
PUT /my-index-1/_settings { "settings": { "index.number_of_replicas": 0, # 将主分片的副分片数量改为 0 ,方便迁移 "index.routing.allocation.require._name": "node-1", # 将主分片全部移到一个节点上 "index.blocks.write": true } }
- 目标索引不存在。
- 源索引的主分片数,是目标索引的主分片数,的整数倍。
# segment
# 相关 API
相关 API :
POST /<index>/_refresh # 触发一次索引的 Refresh POST /<index>/_flush # 触发一次索引的 Flush POST /_forcemerge # 执行一次 segment 的强制合并 POST /<index>/_forcemerge? # 将指定的索引强制合并 only_expunge_deletes=true # 只合并文档删除率超过 expunge_deletes_allowed 的 segment max_num_segments=1 # 将每个 shard 中的 segment 合并到只剩几个,不管文档删除率 GET /_tasks?detailed=true&actions=*forcemerge # 查看正在执行的 forcemerge 任务
- 删除一个文档之后,如果没有 Refresh ,则依然可以查询到该文档,并且再次请求删除该文档时会报错:
version conflict, current version [2] is different than the one provided [1]
- 发出 forcemerge 请求时:
- 会阻塞客户端,直到合并完成才返回响应。
- 如果客户端断开连接,也会继续执行合并任务。
- 如果服务器正在对该 index 执行 delete 任务或 forcemerge 任务,则会忽略客户端发出的合并请求。
- 如果合并请求没有加上 URL 参数,则也是按照自动合并的算法,只处理适合合并的 segment 。
- 删除一个文档之后,如果没有 Refresh ,则依然可以查询到该文档,并且再次请求删除该文档时会报错:
请求
GET /_cat/segments/test_segments?v
的结果示例:index shard prirep ip segment generation docs.count docs.deleted size size.memory committed searchable version compound test_segments 0 p 10.0.0.1 _x 33 6665 0 15.4mb 74939 true true 7.4.0 true test_segments 0 p 10.0.0.1 _12 38 13000 0 28mb 97976 true true 7.4.0 true test_segments 0 p 10.0.0.1 _14 40 39416 0 78.6mb 178092 true true 7.4.0 true
表中每行描述一个 segment 的信息,各列的含义如下:
index # 该 segment 所属的索引 shard # 所属的分片 prirep # 分片类型,为 primary 或 replica ip # 分片所在主机的 IP segment # segment 在当前 shard 中的 36 进制编号,从 _0 开始递增。新增的文档会存储在编号最大的 segment 中,但合并之后,文档所在的 segment 可能变化 generation # generation 的十进制编号,从 0 开始递增 docs.count # 可见的文档数。不包括 deleted 文档、尚未 refresh 的文档、副分片的文档 docs.deleted # deleted 文档数 size # 占用的磁盘空间,单位 bytes size.memory # 占用的内存空间,单位 bytes committed # 该 segment 是否已经提交到磁盘。取值为 false 则说明正在使用 translog searchable # 该 segment 是否允许搜索 version # 存储该 segment 的 Lucene 版本 compound # 该 segment 的所有文件是否已经合并为一个文件。这样能减少打开的文件描述符,但是会占用更多内存,适合体积小于 100M 的 segment
docs.count + docs.deleted
等于实际存储的文档总数。docs.deleted / (docs.count + docs.deleted)
等于文档删除率。
# 配置
- index 中可添加关于 Lucene segment 的配置:
PUT /my-index-1 { "settings": { "index": { "translog": { "durability": "request", # 当客户端发出写请求时,要等到将 translog 通过 fsync 写入磁盘之后,才返回响应 "sync_interval": "5s", # 每隔多久执行一次 fsync (不考虑是否有客户端请求) "flush_threshold_size": "512mb", # 当 translog 超过该大小时,就执行一次 Flush } "merge": { "policy": { "expunge_deletes_allowed": 10, # 调用 expungeDeletes 时,只合并文档删除率超过该值的 segment ,默认为 10% "floor_segment": "2mb", # 自动合并时,总是合并小于该体积的 segment ,不管文档删除率。这样能避免存在大量体积过小的 segment "max_merged_segment": "5gb", # 自动合并时,限制产生的 segment 的最大大小 "max_merge_at_once": 10, # 自动合并时,每次最多同时合并多少个 segment "max_merge_at_once_explicit": 30, # forcemerge 或 expunge_delete 时,每次最多同时合并多少个 segment "segments_per_tier": 10, # 每层最多存在的 segment 数,如果超过该值则触发一次自动合并 } } } } }
- 对于 max_merged_segment :
- 如果 n 个 segment 预计的合并后体积低于 max_merged_segment ,则自动合并。否则,让 n-1 再预计合并后体积。
- 即使 n=1 ,但如果存在 deleted 文档,也可能被自动合并。
- 如果某个 segment 的体积超过 max_merged_segment ,则可能一直不会被自动合并,除非其中的文档删除率足够大。
- 此时可以通过 forcemerge 强制合并,或者通过 reindex 重新创建该索引。
- 如果 n 个 segment 预计的合并后体积低于 max_merged_segment ,则自动合并。否则,让 n-1 再预计合并后体积。
# data stream
用 ES 存储大量按时间排序的文档时(比如日志),通常需要按天切分索引。为了方便管理这些索引,ES 引入了一种称为 data stream 的对象。
data stream 会根据每天的日期,自动创建后端索引(backing index),这些索引默认按以下格式命名:
.ds-<data-stream>-<yyyy.MM.dd>-<generation_id>
例如:
.ds-test_log-2020.01.01-000001 .ds-test_log-2020.01.01-000002
data stream 会反向代理它创建的所有后端索引。
- 用户不必知道后端索引的名称,只需读写 data stream 。
- 当用户新增文档到 data stream 时,总是会写入最新一个后端索引。
- 每个写入 data stream 的文档,必须包含一个
@timestamp
字段,采用 date 或 date_nanos 数据类型。
- 每个写入 data stream 的文档,必须包含一个
- 当用户查询 data stream 中的文档时,会同时查询多个后端索引。
- 用户不能通过 _id 直接读写 data stream 中的文档,只能使用 _search、_update_by_query、_delete_by_query 这些 API 。
例:写入文档到一个 data stream
POST /my-data-stream/_doc/ { "@timestamp": "2020-03-08T11:06:07.000Z", "message": "Hello" }
例:查询 data stream 中的文档
GET /my-data-stream/_search { "query": { "match": { "message": "Hello" } } }
# ILM
:索引生命周期管理(Index lifecycle Management),是一些自动管理索引的策略,比如减少副本的数量、超过多少天就删除索引。
# 线程池
ES 的每个节点创建了多个线程池(thread pool),分别处理 get、index 等不同类型的请求。
可通过
GET /_nodes/stats
查询各个节点的线程池状态,例如:"thread_pool": { "get": { # 处理 get 请求的线程池 "threads": 16, # 线程总数 "queue": 0, # 等待被处理的请求数 "active": 0, # 正在处理请求的线程数 "rejected": 0, # 已拒绝的请求数,ES 重启之后会重新计数 "largest": 16, # 最大的 active 数 "completed": 1580335 # 已完成的请求数,ES 重启之后会重新计数 }, "search": { "threads": 25, "queue": 0, "active": 1, "rejected": 0, "largest": 25, "completed": 3340013239 }, ...}
- 也可通过
GET /_cat/thread_pool?v&h=host,name,size,active,queue_size,queue,rejected,largest,completed
查询。 - 如果 active 的线程数达到线程池上限,即没有空闲的线程,则新增的请求会被放到 queue 缓冲队列,等待处理。
- 如果 queue 中的请求数达到队列上限,则新增的请求会被拒绝(rejected),对客户端报错 HTTP 429 。
- ES 集群的 queue 总量等于 node_count*queue_size 。
- 也可通过
用户可在 elasticsearch.yml 文件中,修改线程池的配置参数。
node.processors: 2 # 当前主机可用的 CPU 核数,默认为全部核数 thread_pool: write: # type: fixed # 线程池大小的类型,fixed 是固定数量,scaling 是自动调整数量 # size: 10 # 线程池大小 queue_size: 1000 # 队列大小,取值为 -1 则不限制
- 如果 ES 经常发生 rejected 报错,建议采用默认的线程池 size ,只是增加 queue_size ,或者增加节点数量、增加 CPU 核数。
几个线程池的默认容量:
线程池 处理操作 size queue_size search count,search,suggest CPU_count*3/2+1 1000 get get CPU_count 1000 write index,delete,update,bulk CPU_count 1000 force_merge force_merge 1 -1
# 缓存
为了加速 HTTP 查询请求,ES 会在 JVM 内存中,创建多种缓存:
fielddata cache # 用于缓存 fielddata 数据。可能占很多内存,建议禁用 fielddata ,改用 doc_values node query cache # 每个 node 会单独保存一份这种缓存,用于缓存 filter 子句的查询结果 shard request cache # 每个 shard 会单独保存一份这种缓存,用于缓存查询结果中的 hits.total、aggregations 等内容,不会缓存 hits.hits
- 当缓存空间写满时,会根据 LRU 算法,删除最近最少使用的数据。
elasticsearch.yml 文件中的相关配置:
indices.fielddata.cache.size: 30% # 默认最多占用 JVM 内存的 30% indices.queries.cache.size: 10% # 至于 request_cache ,它最多占用 JVM 内存的 1%
相关操作:
GET /_nodes/stats/indices # 查询每个 node 的缓存体积、命中率 GET _cat/fielddata?v&s=size:desc # 查看哪些 field 存储的 fielddata 数据最多 POST /<index>/_cache/clear # 清除某个 index 的缓存,默认会将 fielddata、query、request 三种缓存都清除 POST /<index>/_cache/clear?fielddata=true # 只清除指定类型的缓存 POST _cache/clear # 清除整个 ES 的缓存
如果 ES 占用内存不断增长,则可能触发 JVM full GC 而长时间停顿,或者触发 OOM 。为了避免这种情况发生,ES 创建了多种断路器,限制 ES 的内存开销。
- 断路器负责在 ES 占用内存达到一定阈值时,进入熔断状态:拒绝新的读、写请求,返回 HTTP 503: circuit_breaking_exception 。
- 相关配置:
indices.breaker.total.limit: 70% # parent 断路器,用于限制所有断路器的内存总量,默认为 JVM 堆内存的 70% indices.breaker.fielddata.limit: 40% # fielddata 断路器,用于限制 fielddata 缓存,默认为 JVM 堆内存的 40% indices.breaker.request.limit: 60% # request 断路器,用于限制所有请求的占用的内存,比如聚合查询
# 相关 API
# _cat
- ES 返回的响应内容默认采用 JSON 格式,方便程序处理。而使用 _cat API 会按列表形式返回一些统计信息,称为紧凑的文本信息(compact and aligned text),更适合供人阅读。
- 例:
GET /_cat/nodes # 查询节点的信息 GET /_cat/indices[/<index>] # 查询索引的信息 GET /_cat/shards[/<index>] # 查询分片的信息 GET /_cat/segments[/<index>] # 查询索引段的信息 GET /_cat/templates # 查询索引模板的信息
- _cat 支持在 URL 中加入以下请求参数:
?v # verbose ,增加显示一行表头 ?help # 显示所有可用的列名及其含义 ?v&h=ip,disk* # headers ,只显示指定的列。支持使用通配符 ?sort=index,docs.count # 按一个或多个字段升序排列 ?sort=index:desc # 降序排列
- _cat 支持在 URL 中加入以下请求参数:
# _tasks
- _tasks API 用于管理 ES 集群中执行的任务。
- 例:
GET /_tasks # 查询各个节点上正在执行的任务 GET /_tasks?detailed=true # 查询任务的详细信息(比如显示 _search 请求的 query 条件) GET /_tasks?nodes=node1,node2 # 查询指定节点上执行的任务 GET /_tasks?wait_for_completion=true&timeout=10s # 等待任务执行完,才返回响应 POST /_tasks/<task_name>/_cancel # 取消任务 POST /_tasks/_cancel?nodes=node1,node2 # 取消任务
# _reindex
_reindex API 用于将源 index 中的文档拷贝到目标 index 中。
- 源 index 可以是位于当前 ES ,也可以是位于其它 ES 。
- 拷贝时,不会拷贝 settings、mappings ,因此需要实现创建 dest index 或 index template 。
例:
POST /_reindex { "source": { "index": "index_1", # "query": { # 可以加上 query 子句,只拷贝部分文档 # "match": { # "test": "data" # } # }, # "_source": ["user.id", "_doc"] # 指定要拷贝的字段。默认为 true ,拷贝全部字段 # "max_docs": 1000, # 限制拷贝的文档数,默认不限制 }, "dest": { "index": "index_2", # "op_type": "create" }, # "conflicts": "proceed" # 拷贝时,如果遇到文档已存在的 version conflict 报错,默认会停止 reindex 。如果设置该参数,则会继续执行 reindex ,只是记录冲突的次数 }
例:从其它 ES 拷贝文档到当前 ES
- 在 elasticsearch.yml 中添加允许 reindex 的远程 ES 白名单:
reindex.remote.whitelist: "10.0.0.2:9200,10.0.1.*:*" # 可以填多个服务器地址,不需要加 http:// 前缀
- 调用 reindex API :
POST /_reindex { "source": { "remote": { # 要连接的远程 ES "host": "http://10.0.0.2:9200", # ES 的地址必须加协议前缀,且声明端口号 "username": "test", "password": "******" }, "index": "index_1", # 要拷贝的源 index 名称 }, "dest": { "index": "index_2" } }
- 查看 reindex 任务的进度:
GET /_tasks?detailed=true&actions=*reindex
- 在 elasticsearch.yml 中添加允许 reindex 的远程 ES 白名单:
# _rollover
- _rollover API 用于过渡,当满足指定条件时,将索引迁移到另一个索引。
- 用法:
POST /<rollover-target>/_rollover/<target-index> <request_body> # 给索引添加一个过渡规则
- 过渡目标 target-index 可以是索引别名或数据流。如果省略该值,则将原索引名递增 1 ,作为目标名。
- 例:
POST /my-index-1/_rollover/my-index-2 { "conditions": { # 这里指定了多个条件,只要满足其中之一即可 "max_age": "7d", # 索引创建之后的存在时长 "max_docs": 1000, "max_size": "5gb" } }
# _snapshot
_snapshot API 用于将索引保存为快照文件,从而备份数据。
用法:
- 在 elasticsearch.yml 中加入配置:
path.repo: ["backups"] # 声明存储备份仓库的目录
- 创建一个备份仓库:
PUT _snapshot/backup_repo_1 { "type": "fs", # fs 类型表示共享文件系统,需要挂载到所有 ES 节点 "settings": { "location": "backup_repo_1", # 指定该仓库的存储路径,这里使用相对路径,实际保存路径为 elasticsearch/backup/backup_repo_1 # "max_snapshot_bytes_per_sec": "20mb", # 限制生成快照的速度 #"max_restore_bytes_per_sec": "20mb" # 限制导入快照的速度 } }
- 创建快照:
PUT /_snapshot/backup_repo_1/snapshot_1 { "indices": "index_1,index_2" }
- 此时会立即返回响应报文,然后在后台创建快照。
- 如果已存在同名的 snapshot ,则会报错:
Invalid snapshot name [snapshot_1], snapshot with the same name already exists
- 生成的第一个快照文件是全量备份,而之后的快照文件是增量备份。
- 从快照中导入索引,从而恢复数据:
PUT /_snapshot/backup_repo_1/snapshot_1 { "indices": "index_1,index_2" }
- 在 elasticsearch.yml 中加入配置:
相关 API :
PUT /_snapshot/<repo> [request_body] # 创建备份仓库 PUT /_snapshot/<repo>/<snapshot> [request_body] # 生成快照 POST /_snapshot/<repo>/<snapshot>/_restore [request_body] # 导入快照 POST /_snapshot/<repo>/_cleanup # 删除未被现有快照引用的过时数据 DELETE /_snapshot/<repo>/<snapshot> # 删除快照 GET /_snapshot/_all # 查看所有备份仓库 GET /_snapshot/<repo> # 查看一个备份仓库的信息 GET /_snapshot/<repo>/_all # 查看一个备份仓库下的所有快照 GET /_snapshot/<repo>/<snapshot> # 查看指定快照的信息 GET /_snapshot/<repo>/<snapshot>/_status # 增加显示快照的 size_in_bytes GET /_recovery/ # 查看所有创建、恢复快照的记录