ElasticSearch索引模块

索引分片的分配

这个模块主要提供了每个索引的设置来达到每个索引的分片具体分配到哪个节点上去。主要从以下几个方面来介绍

  • 分片分配过滤器:用于控制哪个分片被分配到哪个节点上去
  • 延迟分配:由于节点的离开,导致未分配的分片延迟分配
  • 每个节点的总分片:每个节点对于来自同一索引的分片限制
  • 数据层的分配:控制索引到数据层的分配

索引的分片分配过滤器

你可以使用索引的分片分配过滤器来控制指定的索引分片分配到ElasticSearch的什么地方,也就是哪些节点。

分片分配过滤器可以基于自定义节点属性来分配分片到哪些节点,后者也可以使用_name, _host_ip, _publish_ip, _ip, _host, _id, _tier 、_tier_preference`这些内置属性来分配分片到哪些节点。

索引声明周期管理使用基于自定义节点属性的过滤器来决定在阶段之间移动时如何重新分配分片。

cluster.routing.allcation的设置是动态的,允许实时索引从一个节点移动到另一个节点上。只有在不破坏其他路由约束的情况下才可能重新定位分片,例如永远不要在同一节点上分配主分片和副本分片。

例如,您可以使用自定义节点属性来指示节点的性能特征,并使用分片分配过滤将特定索引的分片路由到最合适的硬件类别。

启用索引分片分配过滤器
  1. 在每个节点的elasticsearch.yml配置文件中配置自定义节点属性来指定分片分配过滤器的特性。例如你有small、medium、big的节点,你可以根据节点的大小添加一个size属性来标记该节点的特性。
node.attr.size: medium

你也可以在启动一个节点的时候自定义属性

./bin/elasticsearch -Enode.attr.size=medium
  1. 为一个索引增加一个路由分配过滤器。index.routing.allocation支持三种类型的过滤器include、exclude、require。例如告诉ElasticSearch将test索引分片分配到mediumbig节点上去。设置如下:
PUT test/_settings
{
  "index.routing.allocation.include.size": "big,medium"
}
索引分片分配过滤器的设置
  • index.routing.allocation.include.{attribute}:将索引分配给 {attribute} 至少具有逗号分隔值之一的节点。
  • index.routing.allocation.require.{attribute}:将索引分配给其 {attribute} 具有所有逗号分隔值的节点。
  • index.routing.allocation.exclude.{attribute}:将索引不分配给其 {attribute} 具有逗号分隔值的所有节点。

索引分片分配设置支持以下内部属性

属性

描述

_name

通过节点名称匹配节点

_host_ip

通过匹配节点IP匹配节点

_publish_ip

通过发布 IP 地址匹配节点

_ip

匹配 _host_ip 或 _publish_ip

_host

按主机名匹配节点

_id

按节点 id 匹配节点

_tier

按节点的数据层角色匹配节点。有关更多详细信息,请参阅数据层分配过滤

您可以在指定属性值时使用通配符,例如:

PUT test/_settings
{
  "index.routing.allocation.include._ip": "192.168.2.*"
}

当节点离线时的延迟分配

当节点由于某种原因从集群中离线的时候,主节点会做出以下反应:

  • 将副本分片提升为主分片以代替节点上的任意主分片
  • 分配副分片来代替丢失的副分片
  • 在其他节点之间均匀的重新平衡分片

这种行为就是为了尽快完全复制每个分片从而避免集群的数据丢失。

即使我们在节点级别和集群级别限制并发恢复,这种“分片洗牌”仍然会给集群带来很多额外的负载,如果丢失的节点可能很快就会返回,这可能是不必要的。想象一下这个场景:

  • 节点5丢失了网络连接
  • 主节点将节点5的副本分片提升为主分片
  • 主节点将新的副本分片分配给集群中的其他节点
  • 更多的分片在其他节点之间重新平衡分片
  • 节点5在几分钟后恢复了
  • 主节点通过重新想节点5分配分片来重新平衡分片

如果Master等待几分钟,那么丢失的分片就可以以少量的网络流量重新分配给节点5。

由于节点的离线,集群中会出现未分配的副本分片。可以通过index.unassigned.node_left.delayed_timeout来设置分片的延迟分配,默认为1m

可以在实时索引上更新此配置,配置如下:

PUT _all/_settings
{
  "settings": {
    "index.unassigned.node_left.delayed_timeout": "5m"
  }
}
取消分片重定位编辑

如果延迟分配超时,主节点将丢失的碎片分配给另一个节点,该节点将开始恢复。如果丢失的节点重新加入集群,并且它的分片仍然具有与主节点相同的同步id,那么分片重新定位将被取消,同步的分片将用于恢复。

出于这个原因,默认超时被设置为只有一分钟:即使分片重新定位开始,取消恢复以支持同步分片也是很便宜的。

监控延迟未分配的分片

通过cluseter health api可以看到已经延迟分片的数量:

curl -X PUT "localhost:9200/_all/_settings?pretty" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "index.unassigned.node_left.delayed_timeout": "0"
  }
}
永久删除节点

如果节点不会返回并且您希望 Elasticsearch 立即分配丢失的分片,只需将超时更新为零:

curl -X PUT "localhost:9200/_all/_settings?pretty" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "index.unassigned.node_left.delayed_timeout": "0"
  }
}

每个节点的总分片

集群中的分片分配过滤器尽可能的将分片散落在多个节点上。但是根据您的分片和索引的数量,可能始终无法均匀的分配分片。

以下动态设置指定了每个节点允许单个索引的分片数量:

  • index.routing.allocation.total_shards_per_node:分配给单个节点的单个索引的最大分片数量。默认为无限制。
  • cluster.routing.allocation.total_shards_per_node:分配给每个节点的单个索引的最大分片数量。默认无限制。
    例如如果集群中有三个节点。cluster.routing.allocation.total_shards_per_node设置为100。三个节点的分片数量分配如下:
  • 节点A:100
  • 节点B:98
  • 节点C:1

如果此时节点C离线了,那么该节点的副本分片只能分配到节点B上,因为节点A已经达到了100的限制。

注意:该设置可能导致集群中的索引始终无法分配分片。谨慎使用。

索引块

索引块限制了每个可用索引的操作类型。这些块有不同的风格,允许阻塞写、读或者元数据操作。这些索引块可以使用动态索引进行设置和删除,或者也可以使用专用的API添加索引块,这也确保了块一旦写入成功就会返回给用户,则索引的所有分片都可以正确的拥有该块,例如添加写块后,所有对索引的动态写入已完成。

索引块的设置

以下动态索引设置决定了索引上的块:

  • index.blocks.read_only:设置true使索引和索引元数据为只读,false允许在索引上写操作和改变索引元数据
  • index.blocks.read_only_allow_delete:与index.blocks.read_only类似,但是也允许删除索引来释放磁盘空间。基于磁盘的分片分配器可以自动删除和添加此块。
    删除索引中的文档会释放资源,而不是删除索引本身,索引会随着时间的推移而变得越大。设置为true时不允许删除文档,但是删除索引本身会释放索引只读块并使资源几乎立即可用。
    注意:当磁盘利用率低于高水位线时,Elasticsearch 会自动添加和删除只读索引块,由 cluster.routing.allocation.disk.watermark.flood_stage 控制。
  • index.blocks.read:设置为true时,禁用对这个索引的读操作
  • index.blocks.write:设置为true时,禁用对这个索引的数据写操作。跟read_only不一样,这个设置对索引元数据无效。例如你可以用index.blocks.write来关闭索引,但是不能用index.blocks.read_only来关闭索引
  • index.blocks.metadata:设置为true时,关闭索引元数据的读写。

添加索引块API

增加一个索引块到索引上

curl -X PUT "localhost:9200/my-index-000001/_block/write?pretty"
请求
PUT /<index>/_block/<block>
路径参数
  • index:可选,用于限制请求的以逗号分隔的索引名称列表或者通配符表达式。要将块添加到所有索引上,使用_all或者*.。如果你想要禁用使用_all*.这样的给所有索引添加块,需要设置集群的action.destructive_requires_name的参数为true。你也可以更新elasticsearch.yml配置文件,或者使用集群更新设置API。
  • block:必须,添加的块类型。有效值为:
  • metadata:关闭操作索引元数据,例如关闭索引。
  • read:关闭读操作.
  • read_only:关闭读操作和元数据的变更。
  • write:关闭写操作,但是元数据的变更仍然允许。
查询参数
  • allow_no_indices:可选的Boolean类型,默认值为true。如果为false,如果任何通配符表达式、索引别名、_all针对已经丢失或者关闭的索引,该请求将会返回错误。如果一个请求的目标为boo*bar*,在集群中boo*索引存在,而bar*索引却不存在,那么请求也会返回错误。
  • expand_wildcards:(可选,字符串),默认为open。通配符表达式可以匹配的索引类型。如果请求可以针对数据流,则此参数确定通配符表达式是否匹配隐藏的数据流。支持逗号分隔值,例如 open、hidden。有效值为:
  • all
    匹配任何数据流或索引,包括隐藏的。
  • open
    匹配开放的、非隐藏的索引。还匹配任何非隐藏数据流。
  • closed
    匹配封闭的、非隐藏的索引。还匹配任何非隐藏数据流。无法关闭数据流。
  • hidden
    匹配隐藏数据流和隐藏索引。必须与开放、封闭或两者结合使用。
  • none
    不接受通配符表达式。
  • ignore_unavailable:(可选,布尔值,默认为false),如果设置为 true,则响应中不包含缺失或关闭的索引。
  • master_timeout:(可选,时间单位。默认为 30 秒)等待连接到主节点的时间。如果在超时到期之前没有收到响应,则请求失败并返回错误。
  • timeout:(可选,时间单位,默认为 30 秒)等待响应的时间段。如果在超时到期之前没有收到响应,则请求失败并返回错误。
例子

增加一个索引块到索引中:

curl -X PUT "localhost:9200/my-index-000001/_block/write?pretty"

正确的响应:

{  "acknowledged" : true,  "shards_acknowledged" : true,  "indices" : [ {    "name" : "my-index-000001",    "blocked" : true  } ]}

合并

一个分片在ElasticSearch上是一个Lucene索引,一个Lucene索引会被分解成多个segment段。Segment存储的是索引数据,并且是不可变的。小的Segment段会定期合并成大的Segment段。

合并过程中在合并和处理其他活动之间会自动限制硬件资源的使用,也就是在不影响处理其他活动的前提下,来进行Segment段的合并。

合并调度

合并调度程序(ConcurrentMergeScheduler)在需要时控制合并操作的执行。合并在单独的线程中运行,当达到最大线程数时,将等待进一步合并,直到合并线程可用。

合并调度器支持以下动态设置:

  • index.merge.scheduler.max_thread_count:单个分片上可以同时合并的最大线程数。默认为 Math.max(1, Math.min(4, <<node.processors, node.processors>> / 2))适用于良好的固态磁盘 (SSD)。如果您的索引位于旋转盘片驱动器上,请将其减少到 1。

相似度

  • BM25相似度:默认。类型名字为BM25。
  • DFR相似度:类型名字为DFR。
  • DFI相似度:类型名字为DFI。
  • IB相似度:类型名字为IB。
  • LM Dirichlet相似度:类型名字为LMDirichlet。
  • LM Jelinek Mercer :类型名字为LMJelinekMercer。
  • 脚本相似度

慢查询日志

分片级别的慢查询允许生成慢查询日志到专门的日志文件中。可以为search阶段和phase设置阀值。例子如下:

index.search.slowlog.threshold.query.warn: 10sindex.search.slowlog.threshold.query.info: 5sindex.search.slowlog.threshold.query.debug: 2sindex.search.slowlog.threshold.query.trace: 500msindex.search.slowlog.threshold.fetch.warn: 1sindex.search.slowlog.threshold.fetch.info: 800msindex.search.slowlog.threshold.fetch.debug: 500msindex.search.slowlog.threshold.fetch.trace: 200msindex.search.slowlog.level: info

以下的所有设置都是动态的,使用更新索引API能够为每个索引设置慢查询。

PUT /my-index-000001/_settings{  "index.search.slowlog.threshold.query.warn": "10s",  "index.search.slowlog.threshold.query.info": "5s",  "index.search.slowlog.threshold.query.debug": "2s",  "index.search.slowlog.threshold.query.trace": "500ms",  "index.search.slowlog.threshold.fetch.warn": "1s",  "index.search.slowlog.threshold.fetch.info": "800ms",  "index.search.slowlog.threshold.fetch.debug": "500ms",  "index.search.slowlog.threshold.fetch.trace": "200ms",  "index.search.slowlog.level": "info"}

默认情况下,慢查询日志是未开启的。日志级别用于控制输出日志的级别。

日志记录在分片级别范围内完成,这意味着在特定分片内执行搜索请求。它不包含整个搜索请求,可以将其广播到多个分片以执行。与请求级别相比,分片级别日志记录的一些好处是将特定机器上的实际执行关联起来。

识别慢查询日志来源

识别触发慢查询日志的查询是非常有用的。如果使用 X-Opaque-ID 标头发起请求,则用户 ID 将作为附加 ID 字段包含在搜索慢日志中。

[2030-08-30T11:59:37,786][WARN ][i.s.s.query              ] [node-0] [index6][0] took[78.4micros], took_millis[0], total_hits[0 hits], stats[], search_type[QUERY_THEN_FETCH], total_shards[1], source[{"query":{"match_all":{"boost":1.0}}}], id[MY_USER_ID],

用户ID也包含在JSON串中

{  "type": "index_search_slowlog",  "timestamp": "2030-08-30T11:59:37,786+02:00",  "level": "WARN",  "component": "i.s.s.query",  "cluster.name": "distribution_run",  "node.name": "node-0",  "message": "[index6][0]",  "took": "78.4micros",  "took_millis": "0",  "total_hits": "0 hits",  "stats": "[]",  "search_type": "QUERY_THEN_FETCH",  "total_shards": "1",  "source": "{\"query\":{\"match_all\":{\"boost\":1.0}}}",  "id": "MY_USER_ID",  "cluster.uuid": "Aq-c-PAeQiK3tfBYtig9Bw",  "node.id": "D7fUYfnfTLa2D7y-xw6tZg"}
索引慢查询日志

索引日志与搜索慢日志类似。日志文件名以_index_indexing_slowlog.log结尾.日志和阈值的配置方式与搜索慢日志相同。索引慢日志示例:

index.indexing.slowlog.threshold.index.warn: 10sindex.indexing.slowlog.threshold.index.info: 5sindex.indexing.slowlog.threshold.index.debug: 2sindex.indexing.slowlog.threshold.index.trace: 500msindex.indexing.slowlog.level: infoindex.indexing.slowlog.source: 1000

以下的所有设置都是动态的,使用更新索引API能够为每个索引设置慢查询。

PUT /my-index-000001/_settings{  "index.indexing.slowlog.threshold.index.warn": "10s",  "index.indexing.slowlog.threshold.index.info": "5s",  "index.indexing.slowlog.threshold.index.debug": "2s",  "index.indexing.slowlog.threshold.index.trace": "500ms",  "index.indexing.slowlog.level": "info",  "index.indexing.slowlog.source": "1000"}

默认情况下,Elasticsearch 将在慢日志中记录 _source 的前 1000 个字符.你可以用index.indexing.slowlog.source来改变,如果该配置设置为false或者0,则忽略_source,如果为ture_source字段值会全部写入到日志中,默认情况下_source字段值会被格式化以后写入日志文件。如果想要保留_source字段值的原始样式,则可以设置index.indexing.slowlog.reformatfalse来关闭格式化,这将导致_source字段值可能跨多行。

默认情况下,索引的慢日志的log4j2.properties配置文件如下:

appender.index_indexing_slowlog_rolling.type = RollingFileappender.index_indexing_slowlog_rolling.name = index_indexing_slowlog_rollingappender.index_indexing_slowlog_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog.logappender.index_indexing_slowlog_rolling.layout.type = PatternLayoutappender.index_indexing_slowlog_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] [%node_name]%marker %.-10000m%nappender.index_indexing_slowlog_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_index_indexing_slowlog-%i.log.gzappender.index_indexing_slowlog_rolling.policies.type = Policiesappender.index_indexing_slowlog_rolling.policies.size.type = SizeBasedTriggeringPolicyappender.index_indexing_slowlog_rolling.policies.size.size = 1GBappender.index_indexing_slowlog_rolling.strategy.type = DefaultRolloverStrategyappender.index_indexing_slowlog_rolling.strategy.max = 4logger.index_indexing_slowlog.name = index.indexing.slowlog.indexlogger.index_indexing_slowlog.level = tracelogger.index_indexing_slowlog.appenderRef.index_indexing_slowlog_rolling.ref = index_indexing_slowlog_rollinglogger.index_indexing_slowlog.additivity = false

Translog

对Lucene的更改只有Lucene提交的时候才会持久化到磁盘,执行这样的操作非常消耗性能,因此不能再修改和删除每个索引的时候执行这样的操作。在进程退出和硬件异常的情况下,再一次提交之后和另一次提交之前的所有更改数据都会丢失。

Lucene提交太昂贵,无法对每个单独的索引操作执行提交,因此每个分片副本会将操作写入其事务文件中,改文件成为Translog。所有的索引和删除操作都会在提交之前操作之后被写入Translog中,在发生崩溃情况下,当分片恢复时,会将上次未提交的最近的操作从Translog中恢复过来。

ElasticSearch的flush操作是执行Lucene提交并生成新的Translog的一个过程。flush是后台自动执行的,就是为了防止Translog日志过大,导致在重放最近操作的时候消耗过长时间。我们也可以利用API执行手动flush操作,但是不建议那么做。

Translog设置

Translog中的数据只有Translogfsync和提交的时候才会持久化到磁盘上。如果硬件故障、JVM崩溃或者分片故障,自上一次提交一来的Translog数据都将丢失。

默认情况index.translog.durability设置为request,意味着ElasticSearch只会在Translog在主分片上和副本分片上成功提交以后,才会向客户端报告索引、删除、修改或者批量请求的成功。如果index.translog.durability设置为async,然后ElasticSearch仅在index.translog.sync_interval间隔内进行fsync并提交Translog,这意味着在节点恢复是可能会丢失在崩溃之前的任何操作。

根据以下可以动态更新来控制每个索引的Translog行为:

  • index.translog.sync_intervalfsync磁盘和提交的间隔。默认是5s,这个值不能少于100ms
  • index.translog.durability:是否在索引每次索引更新、删除或者批量操作的时候执行fsync和提交,该参数的有效值如下:
  • request:默认。每个请求都执行fsync和提交。在硬件故障情况下,所有的数据早已经提交到磁盘上了。
  • asyncasync和提交在后台以index.translog.sync_interval间隔自动执行。如果硬件的故障下,所有在最后一次提交之后的数据将全部丢失。
  • index.translog.flush_threshold_sizeTranslog用于存储为持久化到磁盘上的操作。当分片停止后重新启动,则必须从Translog中恢复最近的为持久化到磁盘的所有操作,此设置控制这些操作的总大小,防止过大导致恢复时间过长。达到了总大小则会触发一次刷新磁盘,生成一个新的提交点。默认为512MB

索引排序

当我们创建索引的时候,可以配置每个分片的segment内的排序方式。默认情况下Lucene不做任何排序。index.sort.*定义了segment里的那些字段需要排序。

注意:嵌套类型不能进行排序,如果在嵌套类型的字段上进行排序,ElasticSearch抛出异常。

以下定义了怎样在单独字段上设置排序策略:

curl -X PUT "localhost:9200/my-index-000001?pretty" -H 'Content-Type: application/json' -d'{  "settings": {    "index": {      "sort.field": "date",       "sort.order": "desc"      }  },  "mappings": {    "properties": {      "date": {        "type": "date"      }    }  }}

以下定义了怎么再多个字段上设置排序策略:

curl -X PUT "localhost:9200/my-index-000001?pretty" -H 'Content-Type: application/json' -d'{  "settings": {    "index": {      "sort.field": [ "username", "date" ],       "sort.order": [ "asc", "desc" ]           }  },  "mappings": {    "properties": {      "username": {        "type": "keyword",        "doc_values": true      },      "date": {        "type": "date"      }    }  }}

索引排序支持以下的设置:

  • index.sort.field:用于对索引进行排序的字段类表。列表中的字段的doc_values的值只能是boolean、numeric、date、keyword类型,其他类型不支持。
  • index.sort.order:对索引排序的顺序。asc升序,desc降序。
  • index.sort.mode:Elasticsearch 支持按多值字段排序。 mode 选项控制选择什么值来对文档进行排序。 mode 选项可以具有以下值:
  • min:选择最低值。
  • max:选择最高值。
  • index.sort.missing:missing 参数指定应如何处理缺少该字段的文档。缺失值可以具有以下值:
  • _last:没有字段值的文档排在最后。
  • _first:字段没有值的文档先排序。

注意:索引排序只能在创建索引时定义一次。不允许在现有索引上添加或更新排序。索引排序在索引吞吐量方面也有成本,因为必须在刷新和合并时对文档进行排序。在激活此功能之前,您应该测试对您的应用程序的影响。

提前终止搜索

默认情况下,一个检索请求,ElasticSearch会检索出匹配到的所有文档,然后按照指定排序规则提取出前N条文档。如果索引排序和检索排序相同的时候,可以限制访问segment里的文档数据量,以检索出排名前N条记录。例如以下有一个按照日期字段对索引进行排序的事件:

curl -X PUT "localhost:9200/events?pretty" -H 'Content-Type: application/json' -d'{  "settings": {    "index": {      "sort.field": "timestamp",      "sort.order": "desc"     }  },  "mappings": {    "properties": {      "timestamp": {        "type": "date"      }    }  }}

你可以利用以下语句查询按照日期降序排序的最后10条数据:

curl -X GET "localhost:9200/events/_search?pretty" -H 'Content-Type: application/json' -d'{  "size": 10,  "sort": [    { "timestamp": "desc" }  ]}

Elasticsearch 将在已经在索引中排序的文档中检索每个段的顶部文档,并且只会比较每个段的前 N 个文档。收集与查询匹配的其余文档以计算结果总数。

如果只想看最后10条文档而不想看命中的总条数,你可以设置track_total_hitsfalse来中断检索:

GET /events/_search{  "size": 10,  "sort": [       { "timestamp": "desc" }  ],  "track_total_hits": false}

这一次,Elasticsearch 不会尝试计算文档数量,并且一旦每个段收集了 N 个文档,就能够终止查询。

{  "_shards": ...   "hits" : {        "max_score" : null,      "hits" : []  },  "took": 20,  "timed_out": false}

注意:聚合查询的时候,track_total_hitsfalse无效的。

使用索引排序来加快连接

索引排序可以用来组织Lucene文档id(不要和_id混淆),以使连接(a AND b AND…)更有效。为了有效率,连词依赖这样一个事实:如果任何一个子句不匹配,那么整个连词就不匹配。通过使用索引排序,我们可以将不匹配的文档放在一起,这将有助于有效地跳过不匹配的大部分文档id。

这个技巧只适用于低基数的字段。一个经验法则是,您应该首先对具有低基数和经常用于过滤的字段进行排序。排序顺序(asc或desc)并不重要,因为我们只关心将匹配相同子句的值紧密地放在一起。