(8.2)link
You know, for search (and analysis)
Elasticsearch是Elastic Stack中核心的分布式搜索和分析引擎。Logstash和Beats帮助收集,聚合以及丰富你的数据并存储在Elasticsearch。Kibana使你能够交互式地探索、可视化和分享对数据的见解,并管理和监视stack。Elasticsearch用于索引,查询以及分析。
Elasticsearch提供对所有类型的数据的近实时搜索(near real-time)和分析。无论是结构化还是非结构化的文本,数值类型的数据,或者地理位置数据,Elasticsearch都能有效的进行存储并以某种方式进行索引来实现快速查询。你可以不仅仅是简单的数据检索而是可以进一步的对信息进行聚合来发现你数据中的趋势和patterns。随着你的数据和查询体量的增大,Elasticsearch的分布式功能使你的部署能够无缝地(seamless)随之增长。
虽然不是每一个问题都是一个查询问题,Elasticsearch为在各种用例中处理数据提供了速度(speed)和灵活性(flexibility)。
添加一个搜索框(search box)到一个app或者网页中
存储和分析日志,指标以及安全事件数据
使用machine learning自动对你的数据行为构建实时的模型
使用Elasticsearch作为一个存储引擎自动化业务工作流(business workflows)
使用Elasticsearch作为一个管理,集成以及分析空间信息地理信息系统(GIS: geographic information system)
使用Elasticsearch作为一个生物信息学研究工具( bioinformatics research tool)来存储以及处理基因数据
我们不断的因为用户使用新颖(novel)的搜索方式而感到惊讶(amaze)。但是不管你的使用案例是否类似与这些中的一种,或者你正在使用Elasticsearch解决(tackle)一个新的问题,你在Elasticsearch中处理你数据, 文档以及索引都是一样的。
(8.2)link
Elasticsearch是一个分布式的文档存储。Elasticsearch 存储已序列化为 JSON 文档的复杂数据结构,而不是将信息存储为列式数据行。当你的集群中有多个Elasticsearch节点,存储的文档跨集群分布并能立即从任何节点访问。
文档在存储之后,它会被索引(index)并且在能在near real time--一秒内完全的用于搜索。Elasticsearch使用了称为倒排表(inverted index)的数据结构,它能够用于快速的全文检索。inverted index列出了出现在所有文档中的每一个unique word并识别出每一个单词所在的所有文档。
索引(index)可以认为是一个优化后的文档集合,每一篇文档是一个字段(field)的集合,每一个字段是包含数据的一个键值对(key-value pair)。默认情况下,Elasticsearch会索引所有字段中的数据并且每一种索引字段(indexed field)都有专门的优化后的数据结构。例如,text field存储在倒排索引(inverted index)中,numeric和geo field存储在BKD树中。使用每一种字段的数据结构进行组合(assemble)和返回查询结果的能力使得Elasticsearch特别的快。
Elasticsearch同样有schema-less的能力,意味着不用显示的指定如何处理一篇文档中的不同的字段就可以直接对文档进行索引。当开启dynamic mapping后,Elasticsearch能自动的检测并添加新的字段到索引中。默认的行为使得索引以及探索你的数据变得简单。只需要开始索引文档,Elasticsearch就会进行检测并将booleans,floating point,integer values,dates和strings映射成Elasticsearch中合适的数据类型。
最终,当你比Elasticsearch更了解自己的数据并且想要按照自己的方式来处理。你可以定义规则来控制dynamic mappings以及显示的(explicit)定义mapping来完全的控制如何对字段进行存储和索引。
定义你自己的mapping可以让你:
区分出全文检索字段(full-text string )跟精确匹配字段(exact value string field)
执行特定语言的文本分析
为部分匹配对字段进行优化
使用自定义的date formats
使用geo_point
和 geo_shape
这些不能被自动检测的数据类型
基于不同的目的,用不同的方式索引同一个字段通常是很有用的。例如你可能想要将一个字符串字段索引为text field用于全文检索以及keyword用于排序、聚合。或者你可能会选择使用多个语言分词器来处理包含用户输入的内容。
在索引期间应用到full-text字段的analysis chain在查询期间同样需要使用。当你查询一个full-text 字段,在索引中查找term前,它的请求文本(query text)也会经历(undergo)相同的analysis。
(8.2)link
当使用Elasticsearch作为一个文档存储(document store),检索文档以及文档的元信息(metadata)时,你能够轻松访问全套搜索能力,其能力来源是因为构建在Apache Lucene 搜索引擎库之上。
Elasticsearch提供了一套简单的,容易理解的(coherent)的REST API,用于管理集群,索引以及查询数据。出于测试的目的,你可以简单的通过命令行或者Kibana中的Developer Console直接提交一个请求。在你的应用中,你可以选择语言并使用Elasticsearch client:Java, JavaScript, Go, .NET, PHP, Perl, Python 或者 Ruby。
Elasticsearch REST APIs支持结构化查询(structured query),全文检索,以及复杂的查询,比如query的组合。结构化查询类似你在SQL中构造的查询类型。例如,你可以在employee
索引中查询gender
和age
字段并且根据hire_date
字段对匹配的结果进行排序。全文检索会找到满足查询条件的所有的文档并且根据相关性(relevance,how good a match they are for your search terms)排序。
除了查询不同的term,你还可以执行短语查询(phrase search),相似度查询(similarity search),前缀查询(prefix search)以及获得autocomplete suggestions。
想要查询地理位置或者其他数值类型的数据的话,Elasticsearch将这类非文本的数据索引到一个优化后的数据结构(BKD)使得支持高性能的地址位置和数值查询。
你可以使用Elasticsearch中JSON风格的查询语言(Query DSL)来访问所有的查询能力。你也可以构造SQL-style query查询/聚合数据,以及使用JDBC和ODBC驱动使得更多的第三方应用通过SQL使用Elasticsearch。
Elasticsearch的聚合(aggregation)能让你构建复杂的数据汇总并获得关键指标的洞见(insight),模式(pattern)以及趋势(trend)。聚合能让你回答下面的问题,而不是仅仅如谚语中所说的needle in a haystack: Near real-time search
haystack中有多少个needle?
needle的平均长度
每个生产商(manufacturer)制造的needle的median length
过去的六个月中,每个月添加到haystack的needle的数量
你可以使用聚合回答更多subtle问题,例如:
最受欢迎的needle生产商是哪家?
是否存在不寻常或者异常的(anomalous)needle?
由于聚合使用了查询中使用的相同的数据结构,所以非常的快,使得可以实时的分析以及可视化你的数据。报表跟dashboard可以随着你的数据的变更而更新,使得你可以基于最新的信息采取措施(take action)。
聚合是跟查询请求一起执行的。你可以在单个请求中对相同的数据进行查询,过滤,以及分析。因为聚合要在某个查询的上下文中计算,你不仅仅能展示70号needle的数量统计,你还能展示满足你的策略的needle:比如说70号的不沾针(non-stick embroidery needles)。
想要自动分析你的时序数据吗?你可以使用machine learning功能创建你的数据中普通行为(normal behavior)的准确基线以及识别出异常模式(anomalous pattern)。使用machine learning,你可以检测下面的信息:
Anomalies related to temporal deviations in values, counts, or frequencies
Statistical rarity
Unusual behaviors for a member of a population
And the best part? 你不需要指定算法,模型或者其他数据科学相关的配置就可以实现上面的功能。
(8.2)link
Elasticsearch总是可用的(available)并且根据你的需要进行扩展。It does this by being distributed by nature。你可以向一个集群中添加服务(节点)来提高承载力(capacity),Elasticsearch可以跨所有可用的节点,自动的分布/查询(distribute/query)数据。不需要overhaul你的应用,Elasticsearch知道如何平衡多个节点的集群来提供扩展以及高可用能力。The more nodes, the merrier。
Elasticsearch是如何工作的?在底层实现中,一个Elasticsearch index只是一个或多个物理分片(physical shards)的逻辑组合(logical grouping)。每一个分片实际上是一个self-contained index。通过将索引中的文档分布到多个分片,将分片分布到多个节点,Elasticsearch可以确保冗余(ensure redundancy),使得应对硬件故障以及节点添加到集群后,查询能力的提升。随着集群的增长(收缩),Elasticsearch能自动的迁移(migrate)分片来rebalance集群。
分片的类型有两种:主分片跟副本分片(primary and replica shard)。索引中的每一篇文档属于某一个主分片。一个副本分片是某个主分片的拷贝。副本分片提供了数据的冗余来应对硬件故障以及提高例如查询或者检索一篇文档的读取请求的能力。
某个索引中的主分片数量在索引创建后就固定了。但是副本分配的数量可以在任何时间内更改,不会影响(interrupt)索引或者查询操作。
对于分片大小和索引的主分片数量,有很多性能考虑点以及trade off。分片越多,维护这些索引的开销就越大。分片大小越大,当Elasticsearch需要rebalance集群时,移动分片花费的时间越大。
在很多较小的分片上查询时,在每一个分片上的查询很快,但是分片越多,查询次数就越多,开销就越大,所以在数量较小,体积较大的分片上的查询可能就更快。In short…it depends。
As a starting point:
将分片的平均大小保持在几个GB以及十几个GB之间。对于基于时间的数据,通常来说,分片的大小在20GB到40GB之间
避免出现大量分片的问题。一个节点可以拥有的分片数量跟可用的堆内存成正比。一般来说(as a general rule),每1GB的堆内存中的分片数量应该小于20个。
对于你的用例,确定最佳配置的最好方式是通过testing with your own data and queries。
集群中的节点之间需要良好的,可靠的连接。若要能提供一个较好的连接,你通常会将节点放在相同的数据中心或者附近的数据中心。然而为了高可用,你同样需要避免单点故障(single point of failure)。某个地区(location)发生停电后,其他地区必须能接管服务。这个问题的答案就是使用CCR(Cross-cluster replication)。
CCR提供了一种从主集群(primary cluster)自动同步索引到secondary remote cluster的方法,即secondary remote cluster作为一个热备(hot backup)。如果primary cluster发生了故障,secondary cluster可以进行接管。你可以使用CCR创建secondary cluster,给地理上靠近(geo-proximity)这个secondary cluster的用户提供读取请求。
与任何企业系统一样,你需要工具来secure,manage,以及monitor你的Elasticsearch 集群。Security,monitoring,以及administrative features都集成到了Elasticsearch,使得你可以使用Kibana作为控制中心来管理集群。比如data rollups、index lifecycle management这些功能能帮助你根据时间来管理你的数据。
这个章节介绍了如何设置Elasticsearch并且使其运行,包含的内容有:
下载
安装
启动
配置
官方给出的操作系统以及JVM的支持列表见Support Matrix.Elasticsearch在这些平台上都经过了测试,但是在列表外的其他平台上还是有可能可以正常工作的。
Elasticsearch使用Java构建,并且每次发布都捆绑了一个OpenJDK,这个OpenJDK由JDK维护者使用GPLv2+CE协议维护。建议使用捆绑的JDK,它位于Elasticsearch home目录中名为jdk的目录中。
可以通过设置环境变量ES_JAVA_HOME
来使用你自己的Java版本。如果你一定要使用一个跟捆绑的JVM不一样的Java版本,我们建议你使用Java的长期支持版本LTS 。如果使用了一个错误Java版本的,Elasticsearch将不会启动。当你使用了自己的JVM后,绑定的JVM目录可能会被删除。
在生产上,我们建议你在专用的主机或者主服务器上(primary service)运行Elasticsearch。假定Elasticsearch是主机上或者容器上唯一的资源密集型的应用,那么一些Elasticsearch的功能比如自动化分配JVM堆大小就能实现。比如说,你可能同时运行Metribeat跟Elasticsearch来做集群统计,那么就应该将resource-heavy的Logstash部署在它自己的主机上。
你可以在自己的设备上运行Elasticsearch或者也可以使用AWS, GCP, and Azure上专用的Elasticsearch服务器。
Elasticsearch以下列的打包方式呈现:
我们同样提供了下列的配置管理工具有助于大型的部署
在Linux和MacOS平台下,Elasticsearch是作为一个.tar.gz
的归档文件。
这个安装包同时包含了免费和订阅的特性,30天试用可以使用所有的功能。
Elasticsearch最新的稳定版本可以从这个页面下载,其他版本可以从过去的发布页面下载。
NOTE: Elasticsearch中捆绑了一个OpenJDK,这个OpenJDK由JDK维护者使用GPLv2+CE协议维护,如果要使用自己的Java版本,
Linux下Elasticsearch-7.15.2归档文件的下载以及安装如下所示:
1wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-linux-x86_64.tar.gz
2wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-linux-x86_64.tar.gz.sha512
3shasum -a 512 -c elasticsearch-7.15.2-linux-x86_64.tar.gz.sha512
4tar -xzf elasticsearch-7.15.2-linux-x86_64.tar.gz
5cd elasticsearch-7.15.2/
上文第三行Compares the SHA of the downloaded .tar.gz
archive and the published checksum,which should output elasticsearch-{version}-linux-x86_64.tar.gz: OK
。
上文第五行就是所谓的 $ES_HOME
。
MacOS下Elasticsearch-7.15.2归档文件的下载以及安装如下所示:
xxxxxxxxxx
51wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-darwin-x86_64.tar.gz
2wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-darwin-x86_64.tar.gz.sha512
3shasum -a 512 -c elasticsearch-7.15.2-darwin-x86_64.tar.gz.sha512
4tar -xzf elasticsearch-7.15.2-darwin-x86_64.tar.gz
5cd elasticsearch-7.15.2/
上文第三行Compares the SHA of the downloaded .tar.gz
archive and the published checksum,which should output elasticsearch-{version}-linux-x86_64.tar.gz: OK
。
上文第五行就是所谓的 $ES_HOME
。
一些商业功能会自动创建索引。默认情况下,Elasticsearch可以被配置为允许自动创建索引,并且不需要再做额外的步骤。然而,如果你关闭了自动创建索引, 你必须在elasticsearch.yml
中配置action.auto_create_index来允许商业功能创建下面的索引:
xxxxxxxxxx
11action.auto_create_index: .monitoring*,.watches,.triggered_watches,.watcher-history*,.ml*
IMPORTANT:如果你正在使用Logstash或者Beats,那么你很有可能需要在action.auto_create_index配置额外的索引名,确切的名称取决去你的本地配置。如果你不能保证正确的名称,你可以考虑将索引名设置为*,这样就会自动创建所有的索引
Elasticsearch可以通过下面的命令行启动:
xxxxxxxxxx
11./bin/elasticsearch
如果Elasticsearch keystore有密码保护,你会被提示(be prompted to)输入keystore's的密码,详细内容见安全配置。
默认情况下,Elasticsearch将日志打印到控制台(标准输出)以及日志目录的<clustername>.log
文件中。Elasticsearch在启动期间会生成一些日志,但一旦初始化完成,它将继续在前台(foreground)运行并且不再生成任何日志直到一些值的记录的信息产生。在Elasticsearch运行期间,你可以通过HTTP接口访问默认的9200端口与Elasticsearch交互。输入Ctrl-c
来停止Elasticsearch。
NOTE:Elasticsearch安装包中的所有脚本要求的Bash版本需要支持arrays并且假定/bin/bash是存在的,同样的,Bash在这个路径是存在的或者能通过动态链接找到
你能通过HTTP请求访问localhost:9200来测试Elasticsearch节点是否在运行中:
xxxxxxxxxx
11GET /
你应该能收到类似下面的回应(response)
xxxxxxxxxx
171{
2 "name" : "Cp8oag6",
3 "cluster_name" : "elasticsearch",
4 "cluster_uuid" : "AT69_T_DTp-1qgIJlatQqA",
5 "version" : {
6 "number" : "7.15.2",
7 "build_flavor" : "default",
8 "build_type" : "tar",
9 "build_hash" : "f27399d",
10 "build_date" : "2016-03-30T09:51:41.449Z",
11 "build_snapshot" : false,
12 "lucene_version" : "8.9.0",
13 "minimum_wire_compatibility_version" : "1.2.3",
14 "minimum_index_compatibility_version" : "1.2.3"
15 },
16 "tagline" : "You Know, for Search"
17}
在命令行指定-d
使得Elasticsearch成为一个后台程序,并且通过参数-p
生成一个pid
文件记录进程号。
xxxxxxxxxx
11./bin/elasticsearch -d -p pid
如果Elasticsearch keystore有密码保护,你会被提示(be prompted to)输入keystore's的密码,详细内容见安全配置。
在$ES_HOME/logs/
目录能找到日志信息。
通过杀死pid
文件中记录的进程号来关闭Elasticsearch。
xxxxxxxxxx
11pkill -F pid
NOTE:tar.gz的安装包中不包含systemd组件。如果要把Elasticsearch作为一个服务管理,用RPM或者Debian的安装包进行安装。
Elasticsearch默认装载(load)的配置文件路径为$ES_HOME/config/elasticsearch.yml
,这个配置文件中的格式见Configuring Elasticsearch。
在配置文件中可以指定的任何设置(settings)都可以通过命令行的参数指定,使用-E
语法:
xxxxxxxxxx
11./bin/elasticsearch -d -Ecluster.name=my_cluster -Enode.name=node_1
TIP:一般来说,任何集群范围(cluster-wide)的配置(比如说 cluster.name)都应该在elasticsearch.yml文件中配置,同时具体节点的任意配置比如说node.name可以通过命令行参数指定
归档发行版 (archive distribution)是self-contained(意思就是所有的文件都在同一个目录中,Elasticsearch还有安装包发行版package distribution,通过这种方式安装后,文件会分散在不同的目录中)。所有的文件和目录默认情况下都在$ES_HOME
下,$ES_HOME
在解压归档后会被创建。
这是一种非常方便的方式因为你不需要创建任何目录来开始使用Elasticsearch,并且当卸载Elasticsearch时只要简单的移除$ES_HOME
。然而我们建议把配置文件目录config directory、数据目录data directory、以及日志目录log directory的位置进行变更,以免在以后重要的数据被删除。
类型Type | 描述Description | 默认位置Default Location | 设置Setting |
---|---|---|---|
home | Elasticsearch home目录或者$ES_HOME | 解压归档后创建这个目录 | |
bin | 二进制脚本,包含elasticsearch 用于启动一个节点以及elasticsearch-plugin 用于安装插件 | $ES_HOME/bin | ES_PATH_CONF |
conf | 包含elasticsearch.yml 的所有配置文件 | $ES_HOME/config | |
data | 每一个索引/分片的数据文件的位置 | $ES_HOME/data | path.data |
logs | 日志文件位置 | $ES_HOME/logs | path.logs |
plugins | 插件文件位置。每个插件在一个子目录中 | $ES_HOME/plugins | |
repo | Shared file system repository locations. Can hold multiple locations. A file system repository can be placed in to any subdirectory of any directory specified here. | NOT_configured | path.repo |
现在你建好了一个Elasticsearch测试环境。在你开始认真做开发护着进入Elasticsearch的生产环境,你必须做一些额外的设置:
(8.2)link
Elasticsearch附带(ship with)了很好的默认配置并且只需要较小的配置。在一个运行中的集群中,大部分的设置可以通过Cluster update settings API进行更改。
配置文件应该包含对指定节点(node-specific)的设置,例如node.name
以及路径,或者是能让节点加入集群的配置,例如cluster.name
以及network.host
。
Elasticsearch有三个配置文件:
elasticsearch.yml
用于配置 Elasticsearch
jvm.options
用于配置Elasticsearch JVM 设置
log4j2.properties
用于配置 Elasticsearch日志记录
这三个文件都在config目录中,它们的默认路径取决于使用哪种方式安装:归档发行版(archive distribution)tar.gz
或者zip
还是安装包发行版(package distribution)Debian 或者RPM安装包。
对于归档发行版,config目录位置默认是$ES_HOME/config
。config目录的位置能通过ES_PATH_CONFIG
这个环境变量进行更改:
xxxxxxxxxx
11ES_PATH_CONF=/path/to/my/config ./bin/elasticsearch
或者,通过在命令行或profile文件中export
ES_PATH_CONFIG
这个环境变量。
对于安装包发行版,config目录的位置默认在/etc/elasticsearch
,config目录的位置可以通过ES_PATH_CONF
这个环境变量变更。注意的是在shell中设置这些是不够的。Instead, this variable is sourced from /etc/default/elasticsearch
(for the Debian package) and /etc/sysconfig/elasticsearch
(for the RPM package). You will need to edit theES_PATH_CONF=/etc/elasticsearch
entry in one of these files accordingly to change the config directory location.
配置格式是YAML,下面是更改数据和日志目录的例子
xxxxxxxxxx
31path:
2 data: /var/lib/elasticsearch
3 logs: /var/log/elasticsearch
配置也可以进行平铺(flattened)
xxxxxxxxxx
21path.data: /var/lib/elasticsearch
2path.logs: /var/log/elasticsearch
在YAML中,你可以格式化non-scalar的值写成序列:
xxxxxxxxxx
41discovery.seed_hosts:
2 - 192.168.1.10:9300
3 - 192.168.1.11
4 - seeds.mydomain.com
尽管下面的方式不是很常见(Thought less common),你也可以把non-scala的值写成数组:
xxxxxxxxxx
11discovery.seed_hosts: ["192.168.1.10:9300", "192.168.1.11", "seeds.mydomain.com"]
在配置文件中可以通过使用${...}
这类符号的方式引用环境变量:
xxxxxxxxxx
21node.name: ${HOSTNAME}
2network.host: ${ES_NETWORK_HOST}
环境变量的值必须是简单的String值,用逗号分隔的String值会被Elasticsearch解析成一个list。例如,Elasticsearch将下面的环境变量的String值根据逗号切分到list中。
xxxxxxxxxx
11export HOSTNAME="host1,host2"
集群跟节点的设置基于它们是如何被配置可以分类为:
在一个正在运行的集群上,你可以使用cluster update settings API来配置或更新动态设置。你也可以使用elasticsearch.yml
对本地未启动或者关闭的节点配置动态设置。
使用cluster update settings API更新可以是永久的(persistent),即使集群重启也生效,也可以是临时的(transient),集群在重启后就重置了。你也可以通过使用API把配置设置为null也能重置使用persistent或者transient更新过的设置。
如果你用多种方式配置了一样的设置,那么Elasticsearch会根据下面的优先顺序(order of precedence)来应用设置(apply settings)。
Transient Setting
Persistent Setting
elasticsearch.yml
setting
Default setting value
例如,你可以使用一个transient setting覆盖persistent setting或者elasticsearch.yml
。然而,在elasticsearch.yml
上的变更不会覆盖定义好的(defined)transient 或者 persistent setting。
TIP:如果你使用Elasticsearch Service,使用user settings功能来配置所有的设置。这个方法能让Elasticsearch自动的拒绝(reject)掉任何会破坏你集群的设置。 如果你在自己的设备(hardware)上运行Elasticsearch,可以使用cluster update settings API来配置集群动态设置。对于集群或者节点的静态设置只使用elasticsearch.yml来配置。使用API不会要求重启并且保证所有节点都被配置成相同的值。
WARNING: We no longer recommend using transient cluster settings. Use persistent cluster settings instead. If a cluster becomes unstable, transient settings can clear unexpectedly, resulting in a potentially undesired cluster configuration. See the Transient settings migration guide.
静态设置只能在未启动或者关闭的节点上使用elasticsearch.yml
来配置。
静态配置必须在集群中相关的所有节点上一一配置。
(8.2)link
Elasticsearch只需要很少的配置就能启动。但是在生产中使用你的集群时必须要考虑这些条目:
我们的Elastic Cloud service能自动配置这些条目,使得你的集群处在生产就绪状态。
Elasticsearch把你的索引数据和数据流(data streams)都写到data
目录中。
Elasticsearch本身也会生产应用日志,包含集群健康信息以及集群操作信息,并且写入到logs
目录中。
对于在MacOS和Linux下使用.tar.gz
, 以及Windows下使用zip
方式安装时,data
和logs
目录默认是$ES_HOME
下的子目录,然而在升级Elasticsearch时,位于$ES_HOME
下的文件是存在风险的。
在生产中,我们强烈建议在elasticsearch.yml
中设置path.data
和path.logs
将data
和logs
目录放到$ES_HOME
以外的位置。其他安装方式(rpm、macOS Homebrew、Windows .msi
)默认会把data
和logs
目录放到$ES_HOME
以外的位置。
支持在不同的平台,path.data
和path.logs
可以是不同的值:
Unix-like systems
Linux and macOS installations support Unix-style paths:
xxxxxxxxxx
31path:
2 data: /var/data/elasticsearch
3 logs: /var/log/elasticsearch
Windows
Windows installations support DOS paths with escaped backslashes:
xxxxxxxxxx
31path:
2 data: "C:\\Elastic\\Elasticsearch\\data"
3 logs: "C:\\Elastic\\Elasticsearch\\logs"
WARNING:不要更改data目录中的任何内容(content),也不要运行可能会影响data目录内容的程序。如果Elasticsearch以外的程序更改了data目录的内容,那么Elasticsearch可能会失败,报告损坏或者其他数据不一致性(data inconsistencies)的问题,也可能工作正常但是丢失了一些你的数据。
WARNING: 在7.13.0版本中废弃了
如果有这个需要,你可以在path.data
中指定多个路径。Elasticsearch会在这些路径上存储节点的数据,但是会在相同的路径上存储同一个shard的数据。
Elasticsearch不会在多个路径上去平衡shards的写入。在某个路径上的高磁盘使用率会触发整个节点的high disk usage watermark。触发后,Elasticsearch不会在这个节点增加shard,尽管这个节点的在其他路径上有可用的空间。如果你需要额外的磁盘空间,我们建议你增加新的节点而不是增加data的路径。
Unix-like systems
Linux and macOS installations support Unix-style paths:
xxxxxxxxxx
51path:
2 data:
3 - /mnt/elasticsearch_1
4 - /mnt/elasticsearch_2
5 - /mnt/elasticsearch_3
Windows
Windows installations support DOS paths with escaped backslashes:
xxxxxxxxxx
51path:
2 data:
3 - "C:\\Elastic\\Elasticsearch_1"
4 - "E:\\Elastic\\Elasticsearch_1"
5 - "F:\\Elastic\\Elasticsearch_3"
在7.13版本中弃用了多个数据路径的支持,将会在未来的版本中移除。
作为多个数据路径的替代方案,你可以通过使用硬件虚拟化层(如RAID)或软件虚拟化层(如Linux上的逻辑卷管理器(LVM)或Windows上的存储空间)来创建跨多个磁盘的文件系统。如果你希望在单台机器上使用多个数据路径,则必须为每个数据路径运行一个节点。
如果你正在一个highly available cluster中使用多个数据路径,你可以无需停机,使用类似rolling restart的方式对一个节点使用单个数据路径来实现迁移:轮流关闭每一个节点,并用一个或多个配置为使用单个数据路径的节点替换它。跟进一步的说,对于每一个当前有多个数据路径的节点来说,你应该按照下面的步骤进行迁移。原则上你可以在升级到8.0版本时执行这类迁移,但是我们建议在升级版本前先完成单个数据路径的迁移工作。
执行快照防止你的数据出现灾难性的问题
(可选)使用allocation filter将目标节点的数据进行迁移
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.exclude._name": "target-node-name"
5 }
6}
你可以使用cat allocation API来跟踪数据迁移的进度。如果某些分片没有迁移,那么cluster allocation explain API会帮助你查明原因。
按照rolling restart process中的步骤执行,包括关闭目前节点。
确保你的集群是yellow
或者green
,使得你的集群中至少有一个节点有每一个分片的拷贝。
如果执行了上面的第二步,现在需要移除allocation filter。
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.exclude._name": null
5 }
6}
通过删除数据路径的方式来丢弃被停掉的节点拥有的数据。
重新配置你的存储。使用LVM或者Storage Spaces将你的磁盘合并到单个文件系统中。确保你重新配置的存储有足够的空间存储数据
通过设置elasticsearch.yml
文件中的path.data
来重新配置你的节点。如果有需要的话,安装更多的节点,并使用它们自己各自的path.data
。
启动新的节点并按照rolling restart process中的其他步骤执行
确保你的集群健康是green
,使得所有的分片都已经分配结束
你也可以在你的集群中增加一些单数据路径的节点,使用allocation filters将你的数据迁移到新的节点中,然后将这些旧的节点从集群中移除。这个方法会临时增大集群中的体积,所以只有你的集群有这个膨胀能力才能使用这个方法。
如果你目前使用多个数据路径,但你的集群并不具备高可用性,则可以通过拍摄快照、创建具有所需配置的新集群并将快照还原到其中来迁移到非废弃配置。
在一个集群中,一个节点只有跟集群中的其他节点共享他的cluster.name
才能加入到这个集群中。默认的名称是elasticsearch
,但是你应该把这个名称改成一个合适的名称,这个名称能描述这个集群的目的。
xxxxxxxxxx
11cluster.name: logging-prod
IMPORTANT:在不同的环境中不要重复使用相同的集群名,否则节点可能会加入到错误的集群中。
Elasticsearch使用node.name
作为可读(human-readable)的一个特定的Elasticsearch实例。在很多APIs的返回中(response)会用到。当Elasticsearch启动后,节点名称默认是服务器的hostname,可以在elasticsearch.yml
中显示配置:
xxxxxxxxxx
11node.name: prod-data-2
默认清下,Elasticsearch只绑定类似127.0.0.1以及[::1]的回调地址(lookback address)。这样的话能够在单个服务上允许一个集群的多个节点用于开发以及测试。但是一个resilient production cluster必须是包含在其他服务上的节点。尽管还有许多newwork settings,但是通常你只需要配置network.host
:
xxxxxxxxxx
11network.host: 192.168.1.10
当你提供了network.host的值,Elasticsearch会假设你正在从开发模式转到生产模式,并且会在系统启动时升级warning到exception的检查。见development and production modes的差异。
在进入生产前配置两个重要的discovery和cluster formation设置,使得这个集群中的节点能互相发现并且选出一个master节点。
如果没有配置任何网络设置,Elasticsearch会绑定可见的回调地址并且扫描本地的9300到9305端口去连接同一个服务上的其他节点。这种行为提供了一种自动集群体验,而无需进行任何配置。即开箱即用(out of box)。
当你想要组成(form)一个集群,它包含的节点在其他服务器上时,可以使用静态的discovery.seed_hosts
设置。这个设置可以提供一个集群中其他节点的list,这些节点是候选的主节点(mater-eligible),并且有可能是live的,并且能在discovery process中作为可以联系的节点来选出种子选手(and contactable to seed the discovery process。翻译能力有限,只可意会,不可言传)。这个设置可以将集群中所有候选的主节点的地址用YAML中的序列或者数组的风格书写。每一个地址可以是IP地址或者hostname,hostname可以通过DNS关联一个或多个IP地址。
xxxxxxxxxx
51discovery.seed_hosts:
2 - 192.168.1.10:9300
3 - 192.168.1.11
4 - seeds.mydomain.com
5 - [0:0:0:0:0:ffff:c0a8:10c]:9301
如果IP地址中没有指定端口号,那么就是默认的9300,默认的端口号也可以被override
如果hostname关联多个IP 地址,那么当前节点会试图发现hostname关联的所有地址上的节点
IPv6地址使用方括号包裹(enclosed in square brackets)
如果候选的主节点的节点没有固定的名称或者地址,可以使用alternative hosts provider 来动态的找到它们的地址。
当你第一次启动Elasticsearch集群,在cluster bootstrapping阶段确定在第一次参与投票的候选主节点。在development mode中,with no discovery settings configured, this step is performed automatically by the nodes themselves。
因为auto-bootstrapping是内在不安全的,在生产模式中启动一个新的集群时,你必须显式列出候选的主节点并且它们的投票要被统计在第一轮的选票中。你可以使用cluster.initial_master_nodes
设置来列出这些候选的主节点。
IMPORTANT:当一个新的集群第一次形成之后,从每个节点的配置中移除cluster.initial_master_nodes设置。当重启一个集群或者往已存在的集群中添加一个新的节点时,不要使用该配置。
xxxxxxxxxx
91discovery.seed_hosts:
2 - 192.168.1.10:9300
3 - 192.168.1.11
4 - seeds.mydomain.com
5 - [0:0:0:0:0:ffff:c0a8:10c]:9301
6cluster.initial_master_nodes:
7 - master-node-a
8 - master-node-b
9 - master-node-c
通过node.name
来确定最初的master节点身份,默认是hostname。要确保cluster.initial_master_nodes
跟node.name
的值是一致的。如果节点的名称使用了全限定域名(fully-qualified domain name),比如master-node-a.example.com,那么你必须在cluster.initial_master_nodes中使用FQDN。相反的,如果只是仅仅使用了hostname而没有尾随的限定符,那么在cluster.initial_master_nodes也不能带尾随的限定符
见 bootstrapping a cluster和discovery and cluster formation settings。
默认情况下,Elasticsearch会根据节点的roles跟内存大小来自动的设置JVM堆的大小。我们建议使用默认的大小,它适用于大部分生产环境。
NOTE:堆大小的自动配置需要bundle JDK,如果使用自定义的JRE位置,需要Java 14以及更新的JRE。
如果有必要,你可以通过setting the JVM heap size手动覆盖默认值。
默认情况下,Elasticsearch会配置JVM,使得OOM异常转存(dump)到data
目录。在基于ROM跟Debian的安装包中,data
目录位于/var/lib/elasticsearch。在Linux、MacOs以及Windows发行版中,data
目录位于Elasticsearch安装目录的根目录。
如果默认的路径用于接受heap dump不是很合适,那么可以通过在文件jvm.options
中修改-XX:HeapDumpPath=...
。
如果你指定了一个目录,JVM将会基于正在运行的实例,用它的PID来为heap dump文件命名
如果你指定了一个固定的文件名而不是一个目录,那么这个文件必须不存在,否则heap dump会失败
默认情况下,Elasticsearch开启了垃圾回收日志。在jvm.options
文件中配置,并且输出到Elasticsearch的logs
文件中。默认的配置中,每64M就会轮换一次日志,最多消耗2G的磁盘空间。
你可以命令行来配置JVM的日志打印,见JEP 158: Unified JVM Logging。除非你直接更改jvm.options
文件,Elasticsearch的默认配置会被应用(apply)除了你自己的设置。如果要关闭默认配置,第一步先通过提供参数-Xlog:disable
关闭日志打印,然后提供你自己的命令选项。以上操作会关闭所有的JVM日志打印,所以要确定好可用的选项来满足你的需求。
见 Enable Logging with the JVM Unified Logging Framework了解更多在原始的JEP(origin JEP)中不包含的虚拟机选项。
下面的例子中创建了$ES_HOME/config/jvm.options.d/gc.options
并包含一些虚拟机选项:把默认的GC 日志输出到/opt/my-app/gc.log
。
x1# Turn off all previous logging configuratons
2-Xlog:disable
3
4# Default settings from JEP 158, but with `utctime` instead of `uptime` to match the next line
5-Xlog:all=warning:stderr:utctime,level,tags
6
7# Enable GC logging to a custom location with a variety of options
8-Xlog:gc*,gc+age=trace,safepoint:file=/opt/my-app/gc.log:utctime,pid,tags:filecount=32,filesize=64m
下面的例子中配置一个Elasticsearch Docker container,将GC的debug日志输出到stderr
,让容器编排器处理输出。
xxxxxxxxxx
21MY_OPTS="-Xlog:disable -Xlog:all=warning:stderr:utctime,level,tags -Xlog:gc=debug:stderr:utctime"
2docker run -e ES_JAVA_OPTS="$MY_OPTS"
默认情况下,Elasticsearch会使用一个私有的临时目录,它由一个启动脚本创建,并位于系统的临时目录中。
在一些Linux发行版中,一些系统工具会清楚 /tmp
目录下长时间未被访问的文件。这会导致Elasticsearch的私有临时目录可能会被删除。移除私有的临时目录会导致一些问题,比如一些功能随后会访问这些私有临时目录。
如果你使用.deb
或者.rpm
安装的Elasticsearch,并且在systemd
下运行,那么Elasticsearch使用的私有临时目录不会被周期性的清除(periodic cleanup)。
如果你想要在Linux跟MacOS上运行.tar.gz
发行版,可以考虑为Elasticsearch创建一个专用的临时目录,并且该目录所在路径中不包含旧的文件或目录,并且这个目录只有Elasticsearch有访问权限。最后在Elasticsearch启动前设置环境变量$ES_TMPDIR
。
默认情况下,Elasticsearch会配置JVM将fatal错误日志写到默认的日志目录中。在RPM
和Debian
安装中,目录位置为/var/log/elasticsearch
,在Linux、MacOs、Windows
发行版中,logs
目录位于Elasticsearch的安装目录。
当遇到一些fatal错误日志,它由JVM产生,比如说段错误(segmentation fault)。如果接受这些日志的路径不是很合适,可以在jvm.options
中修改-XX:ErrorFile=...
。
在发生灾难时,snapshot能防止数据永久丢失。Snapshot lifecycle management是最简单的办法来对集群实现定期备份。见Create a snapshot。
WARNING:快照是唯一可靠并且被支持的方式(supported way)来备份集群。 你不能通过拷贝data目录中的节点数据进行备份。没有一种支持方式(supported way)从文件系统层面的备份来恢复数据。如果你想尝试从这种备份中恢复集群,可能会失败并且Report出corruption或者文件丢失或者其他数据一致性的问题,或者出现成功的悄无声息的丢失一些你的数据
(8.2)link
有些设置是敏感的(sensitive),仅仅依靠文件系统的权限来保护这些值是不够。对于这种情况,Elasticsearch提供了keystore和 elasticsearch-keystore tool 来管理这些设置。
IMPORTANT:只有一些设置被设计为从keystore中读取。但是keystore没有验证一些不支持的设置。增加不支持的设置会导致Elasticsearch启动失败。
所有在keystore中的变更只有在Elasticsearch重启后才生效。
keystore中设置跟配置文件elasticsearch.yml
中的普通的设置项一样都需要在每个节点上指定。当前,所有的安全设置都是特定于节点的,即所有的节点上必须设置相同的值。
跟elasticsearch.yml
中的设置值一样,keystore中的内容无法自动的应用(apply)到正在运行的Elasticsearch节点,需要重启。然而有一些安全设置被标记为reloadable,这些设置可以被reload。
对于安全设置的值,不管是否可以reloadable,集群中的所有节点都必须一致。在安全设置变更后,执行bin/elasticsearch-keystore
调用下面的请求:
xxxxxxxxxx
41POST _nodes/reload_secure_settings
2{
3 "secure_settings_password": "keystore-password"
4}
Elasticsearch keystore会对密码进行加密。
上述API会解密并且重新读取集群中所有节点的整个keystore,但是只有reloadable的安全设置可以被应用(apply)。其他的安全设置不会生效直到下一次重启。一旦API调用返回了,意味着reload完成了,即所以依赖这些设置的内部数据结构都已经发生了变更。Everything should look as if the settings had the new value from the start。
当更改了多个reloadable安全设置后,在集群中的每一个节点上修改后,随后发起reload_secure_settings的调用,而不用在每一次变更后reload。
下面是reloadable安全设置
(8.2)link
你可以使用audit logging来记录安全相关的事件,比如认证失败,拒绝连接,以及数据访问事件。另外也会记录通过API访问安全配置的操作,例如创建、更新、移除native、built-in users,roles、role mappings 以及 API keys。
TIP:日志审计只在某些订阅中提供,详细信息见 https://www.elastic.co/subscriptions
在配置后,集群上的所有节点都需要设置一遍。静态设置,比如说xpack.security.audit.enabled
,在每个节点的elasticsearch.yml
中都必须配置。对于动态的日志设置,使用cluster update settings API 可以让所有节点的设置一致。
(Static)设置为true来开启。默认值为false。在每个节点上日志事件会被放到一个名为<clustername>_audit.json
的专用的文件中。
如果开启了,集群中的所有节点的elasticsearch.yml
都需要配置该设置。
日志事件以及其他一些信息比如获取什么样的日志可以通过下面的设置来控制:
(Dynamic)在日志输出中指定事件类型(kind of events),设置为_all
会彻底的记录所有的日志事件,但通常不建议这么做因为会非常的verbose。默认值是一个list包含:access_denied
, access_granted
, anonymous_access_denied
, authentication_failed
, connection_denied
, tampered_request
, run_as_denied
, run_as_granted
, security_config_change
。
(Dynamic)从包含的(kind of events)列表中指定排除部分选项。当xpack.security.audit.logfile.events.include的值设置为_all
时,然后用xpack.security.audit.logfile.events.exclude
来进行指定选项的排除是不错的方式。默认值是空的list。
(Dynamic)用于指定是否将REST请求的请求body作为日志事件的属性,这个设置可以用于audit search queries。
默认是false,那么请求body不会被打印出来。
IMPORTANT:注意的是,当日志事件中包含请求body时,一些敏感数据可能以明文形式被记录,即使是一些安全相关API,比如修改用户密码,认证信息在开启后有被泄露(filter out)的风险
(Dynamic)指定在每一个日志事件中,节点名称node name是否作为其中的一个字段。默认值是false。
(Dynamic)指定在每一个日志事件中,节点的地址是否作为其中的一个字段。默认值是false。
(Dynamic)指定在每一个日志事件中,节点的主机名是否作为其中的一个字段。默认值是false。
(Dynamic)指定在每一个日志事件中,节点的id是否作为其中的一个字段。不同于node name,管理员可以在配置文件中修改节点id,节点id在集群启动后将不可变更,默认值是true。
下面的设置会影响ignore policies,它们能更精细的(fine-grained)控制日志事件打印到日志文件中。拥有相同police_name的设置将会组合到一个策略(police)中。如何一个事件匹配到任意一条策略的所有条件,该日志事件将被忽略并不会被打印。大多数的日志事件都遵照(subject to)忽略策略。唯一的例外(sole exception)是security_config_change
类型的事件,无法被过滤掉(filter out),除非完全的通过xpack.security.audit.logfile.events.exclude
进行exclude。
(Dynamic)用户名称列表或者名称通配值(wildcards)。匹配该值的所有用户的日志事件不会打印出来。
(Dynamic)authentication realm的列表或者通配值,匹配该值的所有realm的日志事件不会打印出来。
(Dynamic)action名称的列表或者通配值,action的名称可以在日志事件的action
字段中找到, 匹配该值的所有action的日志事件不会打印出来。
(Dynamic)角色列表或者角色通配值(wildcards)。匹配该值并且拥有该角色的所有用户的日志事件不会打印出来。如果用户拥有多个角色,而一些角色没有被策略覆盖到,那么该策略不会覆盖其日志事件。
(Dynamic)索引名称列表或者通配值,日志事件中所有的索引名都匹配后才不会打印。如果事件中涉及(concern)了多条索引,并且一些索引没有被该策略覆盖到,那么该策略不会覆盖日志事件。
(8.2)link
Elasticsearch包含多种熔断器来防止导致OOM的操作。每一种熔断器指定了内存的使用限制。另外,父级别的熔断器指定了内存的总量,限制子级别的熔断器的内存使用总量。
除了特别的说明,这些设置能通过cluster-update-settings API在运行中的集群上进行动态的更新。
关于熔断器提供的报错信息,见Circuit breaker errors。
父级别的熔断器可以通过下面的设置进行配置:
(Static)如果该值为true,那么父级别的熔断器会考虑实际的内存使用量,否则考虑的是子级别的熔断器预定(reserved)的内存量总和。默认值为true。
(Dynamic)当indices.breaker.total.use_real_memory
为false,并且虚拟机的内存使用量达到70%,或者当indices.breaker.total.use_real_memory
为true时,并且虚拟机的内存使用量达到95%,所有的父熔断器开始限制内存使用。
Filed data熔断器会估算把字段filed载入到field data cache所使用的堆内存量。如果载入后会导致缓存超过了先前定义的内存限制值,熔断器将会停止该操作并返回错误。
(Dynamic)fielddata breaker的限制值,默认是40%的JVM堆内存量。
(Dynamic)该配置是一个常量,所有field data的估算值乘以这个常量来计算出最终的估算值。默认是值1.03。
Request熔断器允许Elasticsearch防止每一个请求的数据结构(例如,在一次请求中,聚合计算占用的内存量)超过一定量的内存量。
(Dynamic)Request熔断器的限制值,默认是60%的JVM堆内存量。
(Dynamic)该配置是一个常量,所有Request的估算值乘以这个常量来计算出最终的估算值。默认是值1。
in flight Request熔断器允许Elasticsearch限制所有在传输或者HTTP层的请求的内存使用量不超过在一个节点上的相对内存总量。内存使用量基于请求本身的content length。This circuit breaker also considers that memory is not only needed for representing the raw request but also as a structured object which is reflected by default overhead。
(Dynamic)in flight Request熔断器的限制值,默认是60%的JVM堆内存量。
(Dynamic)该配置是一个常量,所有in flight Request的估算值乘以这个常量来计算出最终的估算值。默认是值2。
accounting熔断器允许Elasticsearch限制某些请求已经结束,但是相关内存还未被释放的内存量。比如说Lucene的段占用的内存。
(Dynamic)accounting熔断器的限制值,默认是100%的JVM堆内存量。这意味着受到父级熔断器的配置限制。
(Dynamic)该配置是一个常量,所有accounting Request的估算值乘以这个常量来计算出最终的估算值。默认是值1。
和上文中的熔断器稍微不同的是,Script compilation熔断器限制了在一个时间周期内inline script compilation的数量。
见scripting 文档中prefer-parameters
章节的内容来了解更多。
(Dynamic)Limit for the number of unique dynamic scripts within a certain interval that are allowed to be compiled. Defaults to 150/5m, meaning 150 every 5 minutes。
写的不好的正则表达式会降低(degrade)集群稳定性以及性能。regex熔断器限制了Painless scripts中正则表达式的使用以及复杂性。
(Static))允许painless脚本中使用正则表达式,该值可以是:
limited(默认值)
允许使用正则表达式但是使用集群设置中的script.painless.regex.limit-factor限制正则表达式的复杂性
true
允许使用正则表达式并且不限制正则表达式的复杂性。regex熔断器关闭
false
不允许使用正则表达式。包含任何正则表达式的painless脚本都会返回错误
(Static))用来限制painless脚本中正则表达式的字符数量。Elasticsearch通过当前设置的值与脚本输入的值的长度的乘积值作为限制值。
比如说输入值foobarbaz
的长度为9,如果script.painless.regex.limit-factor
的值为6,那么基于foobarbaz
的正则表达式的长度为54(6 * 9)。如果超过这个限制值,将触发regex熔断器并返回错误。
只有script.painless.regex.enabled
为limited
时,Elasticsearch才会使用该限制值。
(8.2)link
Shard allocation说的是分配分片到节点的过程。该过程会在initial recovery、副本(replica)分配、rebalancing或者增加或者移除节点时发生。
master node的其中一个应用是决定如何分配分片到节点,何时在节点之间移动分片来平衡集群。
以下的一些设置用来控制分配分片的过程:
Cluster-level shard allocation settings用来控制分片和平衡操作
Disk-based shard allocation settings描述了Elasticsearch如何考虑(take into account)磁盘空间等等相关信息
Shard allocation awareness 和Forced awareness用来控制在不同的rack和可见区域上发布分片
Cluster-level shard allocation filtering允许一些节点或者组内的节点不会被分配分片,使得这些节点能够被关闭(decommissioned)
除此之外,还有一些其他的配置,见Miscellaneous cluster settings。
你可以使用下面的设置来控制分片的分配以及恢复:
(Dynamic)开启或者关闭某些分片类型的分配:
all
:(default)所有类型的分配都可以被分配
primaries
:只有主分片才能被分配
new_primaries
:只有主分片中新的索引才能被分配
none
:任何分片中的任何索引都不能被分配
在节点重启后,上述的配置不会影响本地主分片的恢复。重启后的节点上的未分配的主分片的allocation id如果匹配了集群状态中的active allocation ids,那么会立即恢复。
(Dynamic)在一个节点上允许同时进行恢复incoming recoveries的并发数量。incoming recoveries中的分片(大多数是副本分片或者就是分片正在重新分配位置relocating)将被分配到当前节点上。默认值是2。
(Dynamic)在一个节点上允许同时进行恢复outgoing recoveries的并发数量。outgoing recoveries中的分片(大多数是当前节点上的主分片或者就是分片正在重新分配位置relocating)将被分配到当前节点上。默认值是2。
(Dynamic)一种快捷方法shortcut来设置cluster.routing.allocation.node_concurrent_incoming_recoveries
和cluster.routing.allocation.node_concurrent_outgoing_recoveries
。
(Dynamic)在一个节点重启后,副本分片replica的恢复是通过网络实现的,然而一个未分配的主分片unassigned primary则是通过读取本地磁盘数据恢复的。在同一个节点上通过这种本地恢复的方式是很快的,默认值是4。
(Dynamic)该值允许执行一个检查,防止在单个主机上上分配多个相同的分片,通过主机名以及主机地址来描述一台主机。默认值是false。意味着默认不会进行检查。只有在同一台机器上的多个节点启动后,该值才会应用(apply)。
当每一个节点上的分片数量是相等并且在任何节点的任何索引上没有集中分片(concentration of shards),那么集群是平衡的。Elasticsearch会运行一个自动程序(automatic process)称为rebalancing,它会在集群中的节点之间移动分片来提高平衡性。rebalancing遵守类似 allocation filtering 和 forced awareness的分片分配规则。这些规则会阻止完全的平衡集群,因此rebalancing会努力(strive to)在你配置的规则下尽可能的去平衡集群。如果你正在使用 data tiers,那么Elasticsearch会自动的应用分配过滤规则将每一个分配放到合适的数据层,这些规则意味着平衡器在每一层中独立工作。
你可以使用下面的设置在集群中控制分片的平衡:
(Dynamic)为指定的分片类型开启或关闭rebalancing:
all
:(default)所有类型的分片都允许rebalancing
primaries
:只有主分片才允许rebalancing
replicas
:只有副本分片才允许rebalancing
none
:任何类型分片的任何索引都不允许rebalancing
(Dynamic)用于指定什么时候允许分片rebalancing
always
:总是允许rebalancing
indices_primaries_active
:只有当集群中的所有主分片都被分配后
indices_all_active
:(default)只有当集群中所有的分片(primaries and replicas)都被分配后
(Dynamic)用于控制集群范围(cluster wide)内分片平衡并发数,默认值是2。注意的是这个设置仅仅控制因为集群不平衡导致的分片重定位(shard relocating)的并发数。这个设置不会限制因 allocation filtering 和 forced awareness导致的分片重定位。
基于每个节点上分片的分配情况计算出一个weight来实现rebalancing,并且通过在节点之间移动分片来降低heavier节点的weight并且提高lighter节点的weight。当不存在可能使任何节点的权值与其他节点的权值更接近超过可配置阈值的分片移动时,集群是平衡的。下面的设置允许你控制计算的细节
(Dynamic)为节点上分配的分片总数定义一个weight因子。默认值是0.45f。提高这个值会使集群中所有节点的分片数量趋于均衡。
(Dynamic)为每一个索引的分片数量定义一个weight因子。默认值是0.55f。提高这个值会使集群中所有节点上的每一个索引的的分片数量趋于均衡。
(Dynamic)该值时候一个非负的浮点之后,Minimal optimization value of operations that should be performed。提高这个值将导致集群在优化分片平衡方面不那么积极(leess aggressive)。
NOTE:无论balancing算法的结果如何,rebalancing可能因为forced awareness或者allocation filtering而无法执行
基于磁盘的分片分配器能保证所有节点都有足够的磁盘空间而不用执行更多的分片移动。它基于一对阈值来分配分片:low wateremark 和 high watermark。主要的目的是保证在每一个节点不会超过high watermark,或者是暂时的超过(overage is temporary)。如果某个节点上超过了high watermark,那么Elasticsearch会通过把分片移动到集群中的其他节点来的方式来解决。
NOTE:节点上有时(from time to time)出现临时的超过high watermark是正常的
分配器总是尝试通过禁止往已经超过low watermark的节点分配更多分配的方式来让节点远离(clear of)high watermark。重要的是,如果所有的节点都已经超过了low watermark那么不会有新的分片会被分配并且Elasticsearch不会通过在节点间移动分片的方式来让磁盘使用率低于high watermark。你必须保证在你的集群中有足够的磁盘空间并且总存在一些低于low watermark的节点。
通过基于磁盘的分片分配器进行的分片移动必须满足(satisfy)其他的分片分配规则,例如 allocation filtering 和 forced awareness。如果这些规则过于严格,它们还可以防止碎片移动,以保持节点的磁盘使用在控制之下。如果你正在使用 data tiers,那么Elasticsearch会自动的应用分配过滤规则将每一个分配放到合适的数据层,这些规则意味着平衡器在每一层中独立工作。
如果某个节点填满磁盘的速度比Elasticsearch将分片移动到其他地方的速度还要快就会有磁盘被填满的风险。为了防止出现这个问题,万不得已的情况下( as a last resort),一旦磁盘使用达到flood-stage
watermark,Elasticsearch会阻塞受到影响的节点上的分片索引的写入。但仍然继续将分片移动到集群中的其他节点。当受到影响的节点上的磁盘使用降到high watermark,Elasticsearch会自动的移除write block。
TIP:集群中的节点各自使用容量不相同的磁盘空间是正常的。集群的balance取决与每一个节点上分片的数量以及分片中的索引。基于下面的两个理由,集群的balance既不会考虑分片的大小,也不会考虑每一个节点上磁盘可用的空间:
磁盘的使用随着时间发生变化。平衡不同节点的磁盘使用将会有很多更多的分片移动,perhaps even wastefully undoing earlier movements。移动一个分片会消耗例如I/O资源以及网络带宽,并且可能从文件系统缓存中换出(evict)数据。这些资源最好用于你的查询和索引。
集群中每一个节点有相同的磁盘使用在性能上通常没有有不同磁盘使用的性能高,只要所有的磁盘不是太满
你可以使用下面的设置控制基于磁盘的分配:
(Dynamic)默认值为true
。设置为false
则关闭基于磁盘的分配。
(Dynamic)控制磁盘使用量的水位下限(low watermark)。默认值为85%
。意味着Elasticsearch不会将分片分配到磁盘使用超过85%的节点上。该值也可以是一个字节单位的值(例如500mb
),使得当磁盘空间小于指定的值时就不让Elasticsearch分配分片到这个节点。这个设置不会影响新创建的索引的主分片,但是会组织副本分配的创建。
(Dynamic)控制磁盘使用量的水位上限。默认值为90%
,意味着Elasticsearch将对磁盘使用量超过90%的节点上的分片进行relocate。该值也可以是一个字节单位的值(跟low watermark类似)。使得当磁盘空间小于指定的值时就把该节点上的分片进行relocate。这个设置会影响所有分片的分配,无论之前是否已经分配。
(Static) 在更早的发布中,当做出分配决策时,对于单个数据节点的集群是不会考虑disk watermark的。在7.14之后被值为deprecated并且在8.0移除。现在这个设置唯一合法的值为true
。这个设置在未来的发布中移除。
(Dynamic)控制flood stage watermark。只要至少有一个节点超过该值,分配到这个节点的一个或者分片对应的索引会被Elasticsearch强制置为read-only index block(index.blocks.read_only_allow_delete
)。这个设置是防止节点发生磁盘空间不足最后的手段。当磁盘使用量降到high watermark后会自动释放index block。
NOTE:你不能在设置中混合使用比例值(percentage)和字节值(byte value)。要么所有的值都是比例值,要么都是字节值。这种强制性的要求使得Elasticsearch可以进行一致性的处理。另外要确保low disk threshold要低于high disk threshold,并且high disk threshold要低于flood stage threshold。
下面的例子中在索引my-index-000001
上重新设置read-only index block:
xxxxxxxxxx
41PUT /my-index-000001/_settings
2{
3 "index.blocks.read_only_allow_delete": null
4}
(Dynamic)用于专用的frozen node,控制flood stage watermark,默认值为95%
(Dynamic)用于专用的frozen node,控制flood stage watermark的head room。当cluster.routing.allocation.disk.watermark.flood_stage.frozen
没有显示设置时默认值为20GB。该值限制(cap)了专用的frozen node上磁盘的空闲量。
(Dynamic)Elasticsearch定时检查集群中每一个节点上磁盘使用情况的时间间隔。默认值为30s
。
NOTE:比例值说的是(refer to)已使用的磁盘空间,而字节值说的是剩余磁盘空间。这可能会让人疑惑,因为它弄反了高和低的含义。比如,设置low watermark为10GB,high watermark为5GB是合理的,反过来设置的话就不行
下面的例子讲low watermark的值设置为至少100gb,high watermark的值设置为至少50gb,flood stage watermark的值为10gb,并且每一分钟进行检查:
xxxxxxxxxx
91PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.disk.watermark.low": "100gb",
5 "cluster.routing.allocation.disk.watermark.high": "50gb",
6 "cluster.routing.allocation.disk.watermark.flood_stage": "10gb",
7 "cluster.info.update.interval": "1m"
8 }
9}
你可以自定义节点属性作为感知属性(awareness attributes)让Elasticsearch在分配分片时考虑你物理硬件的配置。如果Elasticsearch知道哪些节点在同一台物理机上、同一个机架(rack)、或者同一个区域,使得在发布主分片跟副本分片时能最低限度的降低丢失所有副本分片的风险。
当使用(Dynamic)的cluster.routing.allocation.awareness.attributes
的设置开启awareness attribute后,分片只会被分配到设置了awareness attributes的节点上。当你使用多个awareness attribute时,Elasticsearch在分配分片时会单独地(separately)考虑每一个attribute。
NOTE: attribute values的数量决定了每一个位置上副本分配的分配数量。如果在每一个位置上的节点数量是unbalanced并且有许多的副本,副本分片可能不会被分配
开启分片的分配awareness,需要:
使用一个自定义的节点属性来指定每个节点的位置。如果你想要Elasticsearch在不同的机架间发布分片,你可能需要在配置文件elasticsearch.yml
上设置一个名为rack_id
的属性。
xxxxxxxxxx
11node.attr.rack_id: rack_one
你也可以在启动的时候设置自定义的属性。
xxxxxxxxxx
11./bin/elasticsearch -Enode.attr.rack_id=rack_one
在每一个master-eligible node的配置文件elasticsearch.yml
上设置cluster.routing.allocation.awareness.attributes
,告诉Elasticsearch在分配分片的时候要考虑一个或者多个awareness attribute。
xxxxxxxxxx
11cluster.routing.allocation.awareness.attributes: rack_id
用逗号分隔多个属性。
你也可以使用cluster-update-settings API来设置或者更新集群的awareness attributes。
基于上述的配置例子,如果你启动了两个节点并且都设置node.attr.rack_id
为rack_one
并且为每个index设置5个主分片,每个主分片设置一个副本分片,那么这两个节点都包含所有的主分片以及副本分片。
如果你增加了两个节点并且都设置node.attr.rack_id
为rack_two
,Elasticsearch会把分片移动到新的节点,ensuring(if possible)相同的副本分片不会在相同的机架上。
如果rack_two
失败并关闭了它的两个节点,默认情况下,Elasticsearch会将丢失的副本分片分配给rack_one
中的节点。为了防止特定分片的多个副本被分配到相同的位置,可以启用forced awareness。
默认情况下,如果一个位置失败了(one location fails),Elasticsearch将缺失的分片分配到剩余的位置。可能所有的位置都有足够的资源来存放主分片跟副本分片,也有可能某个位置无法存储所有的分片。
为了防止某个位置因为失败事件(event of failure)而造成负载过高(overload),你可以设置cluster.routing.allocation.awareness.force
使得副本分片不会被分配直到其他位置可以被分配。
比如说,如果你有一个awareness attribute 名为zone
并且在两个节点分别配置zone1
跟zone2
,那么在只有一个zone可用的情况,你可以使用forced awareness来防止Elasticsearch分配副本分片。
xxxxxxxxxx
21cluster.routing.allocation.awareness.attributes: zone
2cluster.routing.allocation.awareness.force.zone.values: zone1,zone2
为awareness attribute指定所有可能的值。
基于上述的配置例子,如果启动了两个节点并且设置node.attr.zone
的值为zone1
并且为每个index设置5个分片,每个分片设置一个副本分片,那么Elasticsearch会创建索引并且分配5个主分片但是不会有副本分片。只有某个节点设置node.attr.zone
的值为zone2
才会分配副本分片。
你可以使用cluster-level shard allocation filters来控制Elasticsearch从任何索引分配分片的位置。结合per-index allocation filtering和allocation awareness来应用集群级别(cluster wide)的filter。
Shard allocation filters可以基于自定义的节点属性或者内建的属性:_name
, _host_ip
, _publish_ip
, _ip
, _host
,_id
和_tier
。
cluster.routing.allocation
设置是动态的Dynamic,允许将live indices从一组节点上移动到其他组。只有在不破坏路由约定(routing constraint)下才有可能重新分配分片,比如说不会将主分片和它的副本分片分配到同一个节点上。
最常用的cluster-level shard allocation filtering用例是当你想要结束(decommission)一个节点。要在关闭节点之前将分片移出节点,您可以创建一个过滤器,通过其 IP 地址排除该节点:
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent" : {
4 "cluster.routing.allocation.exclude._ip" : "10.0.0.1"
5 }
6}
(Dynamic)将分配分片到一个节点,这个节点的{attribute}
至少是用逗号隔开的多个属性中的一个。
(Dynamic)只将分片分配到一个节点,这个节点的{attribute}
包含所有用逗号隔开的多个属性。
(Dynamic)不将分片分配到一个节点,这个节点的{attribute}
包含用逗号隔开的多个属性中的任意一个。
cluster allocation settings支持下面的内建属性:
_name | 根据node name匹配节点 |
---|---|
_host_ip | 根据host IP 地址(hostname关联的IP)匹配节点 |
_publish_ip | 根据发布的IP地址匹配节点 |
_ip | 根据_host_ip或者_publish_ip匹配节点 |
_host | 根据hostname匹配节点 |
_id | 根据node id匹配节点 |
_tier | 根据节点的data tier角色匹配节点 |
NOTE:
_tier
的过滤基于 node roles,只有部分角色是 data tier 角色,并且generic data role将会匹配任何的tier filtering。a subset of roles that are data tier roles, but the generic data role will match any tier filtering.
在指定attribute values时可以使用通配符,例如:
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.exclude._ip": "192.168.2.*"
5 }
6}
通过下面的设置可以将整个集群设置为read-only:
(Dynamic) 让整个集群read only(索引不支持写操作),metadata不允许更改(创建或者删除索引)
(Dynamic) 与cluster.blocks.read_only
一样,但是允许删除索引来释放资源。
WARNING: 不要依赖这些设置来防止集群发生变更。任何有cluster-update-settings API 访问权限的用户都可以让集群再次read-write
基于集群中的节点数量,集群中分片的数量有一个soft limit。这是为了防止可能无意中破坏集群稳定性的操作。
IMPORTANT:这个限制作为一个安全网(safety net),不是一个推荐的大小。你集群中可以安全的支持准确的分片数量取决于你的硬件配置跟工作负载,但是在大部分case下,这个limit应该还不错,因为默认的limit是非常大的。
如果一个操作,比如创建新的索引,恢复snapshot index,或者打开一个关闭的索引会导致集群中的分片数量超过限制。那这个操作会失败并且给出shard limit的错误。
如果由于node membership的变化或者设置的变更使得集群已经超过了限制,所有创建或者打开索引的操作都会失败直到下文中将介绍的提高limit或者关闭删除一些索引使得分片的数量降到了limit以下。
对于normal indices(non-frozen),每一个non-frozen data node上cluster shard limit的默认值是1000,对于frozen indices,每一个frozen data node上的默认值是3000。所有打开的索引的主分片跟副本分片的数量都会纳入到统计中(count toward the limit),包括未分配的分片。例如5个主分片和2个副本的数量为15个分片。关闭的索引不会参与分片的统计。
你可以使用下面的方式动态的调整cluster shard limit:
(Dynamic) 限制集群中主分片跟副本分片的总数量。Elasticsearch使用下面的公式统计limit:
cluster.max_shards_per_node * number of non-frozen data nodes
关闭的索引不会纳入到统计中。默认是1000
。没有data node的集群是没有限制的。
超过上限后,Elasticsearch会reject创建更多分片的请求。比如集群的cluster.max_shards_per_node
的值是100
,并且三个data node就有300
的分片数量限制。如果集群中已经包含了296个分片,Elasticsearch会reject增加5个或者更多分片数量的请求。
注意的是frozen shard有它们自己独立的限制。
(Dynamic) 限制集群中主分片跟replica frozen shard的总数量。Elasticsearch使用下面的公式统计limit:
cluster.max_shards_per_node * number of frozen data nodes
关闭的索引不会纳入到统计中。默认是3000
。没有frozen data node的集群是没有限制的。
超过上限后,Elasticsearch会reject创建更多frozen shard的请求。比如集群的cluster.max_shards_per_node.frozen
的值是100
,并且三个frozen data node就有300
的分片数量限制。如果集群中已经包含了296个分片,Elasticsearch会reject增加5个或者更多frozen shard数量的请求。
NOTE: These setting do not limit shards for individual nodes. 可以使用设置cluster.routing.allocation.total_shards_per_node来限制每一个节点上的分片数量。
可以通过Cluster Settings API来存储以及查询用户自定义的集群元信息(User-defined cluster metadata)。可以用来存储任意的(arbitrary),很少更改(infrequently-changing)的关于集群的信息,而不需要创建索引来存储这些信息。使用以cluster.metadata
为前缀的任意的key来存储信息。例如,在cluster.metadata.administrator
这个key下存储管理员的email地址,如下所示:
xxxxxxxxxx
61PUT /_cluster/settings
2{
3 "persistent": {
4 "cluster.metadata.administrator": "sysadmin@example.com"
5 }
6}
IMPORTANT: User-defined cluster metadata不要存储敏感的或者机密信息。任何人都可以通过Cluster Get Settings API访问存储在User-defined cluster metadata的信息,并且会被记录在Elasticsearch日志中。
集群状态(cluster state)维护者index tombstone来显示的告知(denote)被删除的索引。集群状态维护tombstone的数量,通过下面的设置来进行控制:
(Static)当发生删除时,Index tombstones会阻止不属于集群的节点加入集群并重新导入索引,就好像delete was never issued。为了防止增长的太大,我们只保留最新的cluster.indices.tombstones.size
数量的删除,默认值是500。You can increase it if you expect nodes to be absent from the cluster and miss more than 500 deletes. We think that is rare, thus the default. Tombstones don’t take up much space, but we also think that a number like 50,000 is probably too big.
如果 Elasticsearch 遇到当前集群状态中不存在的索引数据,则这些索引被认为是dangling。 例如,如果你在 Elasticsearch 节点离线时删除超过 cluster.indices.tombstones.size
的索引,就会发生这种情况。
你可以使用Dangling indices API来进行管理。
这个设置用来控制日志,使用logger.
为前缀的key来动态的更新。发布下面的请求(issue this request)来提高indices.recovery
模块的日志等级为DEBUG
:
xxxxxxxxxx
61PUT /_cluster/settings
2{
3 "persistent": {
4 "logger.org.elasticsearch.indices.recovery": "DEBUG"
5 }
6}
Plugin可以创建一系列称为persistent task的任务。这些任务通常是long-lived并且存储在cluster state中,并且允许在一次full cluster restart后恢复(revive)这些任务。
每一次创建一个persistent task时,master node会负责将任务分配到集群中的一个节点。然后被分配的节点会pick up这个任务然后在本地执行。通过下面的设置来控制被分配的persistent task的运行:
(Dynamic)开启或者关闭persistent task的分配:
all - (default) 允许persistent task被分配到节点
none - 不允许分配任何类型的persistent task
这个设置不会影响已经执行的persistent task。只有最新创建的,或者必须要重新被分配的persistent task才会收到这个设置的影响。
(Dynamic)master node会自动的检查在cluster state发生重大变化(change significantly)后persistent tast是否需要被分配。然而,也会有其他的一些因素,比如说memory usage,这个会影响persistent task能否被分配但这不会引起cluster state的变更。这个设置控制了检查的间隔。默认是30秒。最小允许的时间为10秒。
(8.2)link
可以使用cluster update settings API动态更新集群中的CCR(cross-cluster replication)设置。
下面的设置可以用于在remote recoveries中限制数据传输速率:
用来限制每一个节点上inbound和outbound remote recovery traffic。由于这个设置是应用到每一个节点上,但是可能存在许多的节点并发的执行remote recoveries,remote recovery占用的流量可能比这个限制高。如果这个值设置的太高,那么会存在这么一种风险,即正在进行中的recoveries会过度消费带宽 ,使得集群变得不稳定。这个设置应该同时被leader和follower clusters使用。例如,如果在leader上设置了20mb
,leader将只能发送20mb/s
到follower,即使follower可以达到60mb/s
。默认值为40mb
。
下面的专家级设置(expert settings)用于管理remote recoveries时的资源消费。
控制每个recovery中并发请求文件块(file chunk)的数量。由于多个remote recoveries已经在并发运行,增加这个专家级设置可能只有助于没有达到总的inbound and outbound remote recovery traffic的单个分片的remote recovery,即上文中的ccr.indices.recovery.max_bytes_per_sec
。ccr.indices.recovery.max_concurrent_file_chunks
的默认值为5
。允许最大值为10
。
控制在文件传输时,follower请求的文件块的大小。默认值为1mb
。
控制recovery的活跃(activity)超时时间。这个超时时间主要是应用在leader集群上。在处理recovery期间,leader cluster必须打开内存资源提供数据给follower。如果leader在周期时间内没有接收到follower的接收请求,leader会关闭资源。默认值为60s
。
控制在remote recovery处理期间每一个网络请求的超时时间。某个单独的动作(individual action)超时会导致recovery失败。默认值为60s
。
Discovery and cluster formation受到下面设置的影响:
(Static))提供集群中master-eligible node的地址列表。可以是单个包含地址,用逗号隔开的字符串。每一个地址有host:port
或者host
的格式。host
可以是hostname(可以用DNS解析)、IPv4地址、IPv6地址。IPv6地址必须用{}包裹。如果hostname用DNS解析出多个地址,Elasticsearch会使用所有的地址。DNS基于JVM DNS caching进行查找(lookup)。如果没有指定port
,会有序检查下面的设置来找到port
:
transport.profiles.default.port
transport.port
如果没有设置上面的配置,则port
的默认值为9300
。discovery.seed_hosts
的默认值为["127.0.0.1", "[::1]"]
。见discovery.seed_hosts。
(Static))指定seed hosts provider的类型,用于获取启动发现程序(discovery process)时需要用到的seed nodes地址。默认情况为settings-based seed hosts provider,它包含了discovery.seed_hosts
中的seed node 地址。
(Static))指定Elasticsearch是否形成一个多节点的(multiple-node)的集群,默认值为multi-node
,意味着Elasticsearch在形成一个集群时会发现其他节点并且允许其他节点随后加入到这个集群。如果设置为single-node
,Elasticsearch会形成一个单节点(single-node)集群并且suppresses the timeout set by cluster.publish.timeout
。当你想要使用这个设置的时候,见Single-node discovery了解更多的信息
(Static))在全新的集群中初始化master-eligible node集合。默认情况下是空值,意味着这个节点想要加入到一个引导后(bootstrapped)的集群。集群一旦形成后需要移除这个设置。当重启一个节点或者添加一个新的节点到现有的集群中时不要使用这个设置。见cluster.initial_master_nodes。
Discovery and cluster formation受到下面专家级设置(expert settings)的影响,当然我们不推荐修改这些设置的默认值。
WARNING:如果你调整了集群中的这些设置,可能会无法正确的形成一个集群或者可能集群变得不稳定,或者无法处理(intolerant)对于一些故障。
(Static))尝试形成一个集群时,多长时间后仍然没有形成集群,开始记录未形成集群的警告日志的设置。默认值为10s
。如果在discovery.cluster_formation_warning_timeout
的时间内没有形成一个集群,那节点会记录一个包含master not discovered
的warnning message,它描述了当前discovery process当前的状态。
(Static))一个节点尝试新一轮discovery的时间间隔。默认值为1s
。
(Static))尝试连接每一个地址的连接等待时间。默认值为30s
。
(Static))尝试通过handshake识别remote node的等待时间。默认值为30s
(Static))在认为请求失败时,询问其peer后的等待时间。默认值为3s
。
(Static))节点尝试discover它的peers并开始记录为什么尝试连接失败的verbose message的时间。默认值为3m
。
(Static))DNS并发解析seed node的地址时允许的并发量。默认值为10
。
(Static))DNS解析seed note的地址的超时时间。默认值为5s
。
(Dynamic)控制voting configuration是否自动的去除(shed)不能担任voting的节点(departed node),只要它至少还包含3个节点。默认值为true
。如果设置为false
。voting configuration不会自动的进行收缩,你必须通过voting configuration exclusions API手动的移除不能担任voting的节点(departed node)。
(Static)) (未完成)
(8.2)link
field data cache中包含了field data和global ordinals,它们都是用来让某些字段能够用于聚合。由于是on-heap的数据结构,所以很有必要监控缓存的使用情况。
构建cache中实体(entities)的开销是很昂贵的,所以默认情况下会保留载入到内存的entities。默认的cache大小是没有限制的,cache大小会不断增大直到达到field data circuit breaker设定的上限。这种行为可以配置。
如果设置了cache大小的上限,cache将会清除cache中最近更新的实体(If the cache size limit is set, the cache will begin clearing the least-recently-updated entries in the cache)。这个设置会自动的避免circuit breaker limit,代价就是需要重新构建cache。
如果达到了circuit breaker limit,后续会增加cache大小的请求将被阻止。在这种情况下你必须要手动clear the cache。
(Static)) field data cache的最大值。比如节点堆空间大小的38%
,或者是一个absolute value,12GB
。默认是没有限制。如果选择设置这个值,这个值应该小于Field data circuit breaker limit。
你可以使用nodes stats API和cat fielddata API来监控filed data的内存使用情况以及 field data circuit breaker。
(8.2)link
indexing buffer用来缓存新添加的文档,当缓冲区满后,缓存中的文档会被写入到一个段中并且存放到磁盘上。Indexing buffer的大小被节点上的所有分片共享。
下面的设置都是静态的必须在集群中的每一个节点上配置:
(Static)可以是百分比或者是字节单位的数值。默认是10%
意味着堆内存的10%会被分配给节点用于Indexing buffer,并且被节点上所有的分片共享。
(Static)如果index_buffer_size
指定为百分比,那这个设置用来指定为一个绝对最小值(absolute minimum)。默认是是48m
。
(Static)如果index_buffer_size
指定为百分比,那这个设置用来指定为一个绝对最大值(absolute maximum)。默认是无限制的。
当一个节点加入到集群,如果它发现本地数据目录中存在一些分片,这些分片在集群中的其他节点上不存在,那么就认为这些分片属于"dangling" index。你可以使用Dangling indices API列出,导入或者删除dangling index。
(8.2)link
任何时候在你启动了一个Elasticsearch的实例时,即你正在启动一个节点(Node)。相互连接的节点集合成为一个集群(cluster)。如果你运行了 一个单个节点的Elasticsearch,那么你拥有一个只有一个节点的集群。
默认情况下,集群中的每个节点都可以处理 HTTP and transport的传输。transport layer只用来处理节点之间的交互,HTTP layer用于REST clients 。
所有的节点都知道集群中的其他节点并且可以转发客户端的请求到合适的节点。
你可以在elasticsearch.yml
中设置node.roles
来定义一个节点的角色。如果你设置了node.roles
,这个节点只会被分配为你指定的角色。如果你不指定node.roles
,这个节点会被分配为下面的角色:
master
data
data_content
data_hot
data_warm
data_cold
data_frozen
ingest
ml
remote_cluster_client
transform
IMPORTANT:如果你设置了
node.roles
,你要保证你的集群需要你指定的这些角色。每一个集群需要有下面的角色:
master
(data_content和data_hot) 或者 data
一些Elastic Stack 功能同样要求指定下面的节点角色:
跨集群搜索(Cross-cluster search)和跨集群副本(Cross-cluster replication)需要
remote_cluster_client
角色Stack Monitoring和ingest pipeline需要
ingest
角色Fleet,Elastic Security应用,和transforms需要
transform
角色。remote_cluster_client
角色同样需要用于这些功能中的cross-cluster searchMachine learning功能,比如异常检测(anomaly detection)需要
ml
角色
随着集群的增长,特别是集群中有大规模的machine learning任务、continuous transforms,我们建议从专用的data node、machine learning node、transform node中分离出master-eligible节点。
Master-eligible node | 拥有master 角色的节点,使得节点有资格被选举为master node来控制整个集群 |
---|---|
Data node | 拥有data 角色的节点,data node保留数据并且执行相关操作例如CRUD、查询、聚合。一个拥有data 角色的节点可以添加任何其他特定的数据节点角色,例如hot 、warm等 |
Ingest node | 拥有ingest 角色的节点,ingest node可以对文档进行ingest pipeline使得可以在索引前transform或者丰富文档。对于繁重的ingest,使用专用的ingest node并且让拥有master 或者data 角色的节点不要有ingest 角色 |
Remote-eligible node | 拥有remote_cluster_client 角色的节点,使得这个节点有资格成为一个remote client |
Machine learning node | 拥有ml 角色的节点,如果你想要使用machine learning功能,你的集群中至少要有一个machine learning node。见Machine learning settings和Machine learning in the Elastic Stack了解更多信息 |
Transform node | 拥有transform 角色的节点。如果想要使用transform,你的集群中至少要有一个transform node。见Transforms settings和Transforming data了解更多信息 |
NOTE:Coordinating node 一些像查询请求或者块索引(bulk-indeing)的请求可能涉及不同data node上的数据。一次查询请求,例如,搜索请求分两个阶段执行,由接收客户端请求的节点进行协调 - coordinating node。
在scatter阶段,coordinating node将请求转发给持有数据的数据节点。 每个数据节点在本地执行请求并将其结果返回给coordinating node。 在收集(gather)阶段,协调节点将每个数据节点的结果减少为单个全局结果集。
每个节点都隐含的是一个coordinating node。 这意味着通过设置
node.roles
为空的角色列表的节点将作为coordinating node,并且不能被关闭这种设定。 因此,这样的节点需要有足够的内存和 CPU 才能处理收集阶段。
master node负责轻量级(lightweight)的集群范围(cluster-wide)的一些工作,比如创建、删除一个索引,跟踪哪些节点是集群的一部分以及决定给哪里节点分配分片。稳定的master node对于集群的健康是十分重要的。
一个不是voting-only的master-eligible node会在master election process中被选为master node。
IMPORTANT: master node必须有一个
path.data
目录,其内容在重启后仍然存在,跟data node一样,这个目录中存放了集群的元数据(cluster metadata)。集群元数据描述了如何读取存储在data node上的数据,所以如果丢失了集群元数据就无法读取data node上的数据。
满足被选举为master的节点为了能完成它的职责所需要的资源对于集群的健康是十分重要的。如果被选举为master的节点因为其他的工作导致超负荷了,那么集群将无法较好的运行。避免master node工作超负荷最可靠的方法就是将所有master-eligible node只配置为master
角色,让它们作为专用的master-eligible node(dedicated master-eligible node),让它们集中做好管理集群的工作。Master-eligible node可以跟coordinating nodes一样将客户端的请求路由到集群中的其他节点,但是你不应该让专用的master-eligible node做这些事情。
在一些小规模或者轻负载(lightly-loaded)的集群中,如果master-eligible node有其他的角色和职责也可以正常的运行,但是,一旦集群中包含多个节点,通常使用专用的master-eligible node是有意义的。
通过设置下面的配置来创建一个专用的master-eligible节点:
xxxxxxxxxx
11node.roles: [ master ]
voting-only master-eligible node是会参与master elections的节点,但是不会成为集群的master节点。特别在于一个voting-only node可以成为选举过程中的tiebreaker。
使用"master-eligible"这个词来描述一个voting-only node看起来有点让人困惑,因为这种节点是完全没资格成为master节点的。使用这个术语是因为历史的原因(This terminology is an unfortunate consequence of history):master-eligible node是那些参与选举、集群状态发布(cluster state publication)期间执行一些任务的节点,而voting-only node有着相同的工作职责(responsibility)只是它们不会被选举为master节点。
通过添加master
和voting_only
角色来配置一个master-eligible node为voting-only node。下面的例子创建了一个voting-onyl data node:
xxxxxxxxxx
11node.roles: [ data, master, voting_only ]
IMPORTANT:只有拥有
master
角色的节点才可以标记为拥有voting_only
角色。
高可用(HA:high availability)的集群要求至少有三个master-eligible node,它们中的至少两个节点不是voting-only node。这样的集群才能够在其中一个节点发生故障后选出一个master节点。
由于voting-only node不会被选举为master节点,所以相较于真正的master node,它们需要较少的堆以及较低性能的CPU。然而所有的master-eligible node,包括voting-only node,相较于集群中的其他节点,它们需要相当快速的持久存储以及一个可靠的低延迟的网络连接,因为它们处于publishing cluster state updates的关键路径(critical path)上。
Voting-only master-eligible node在集群中可能被分配其他的角色。比如说一个节点同时是data node或者Voting-only master-eligible node。一个专用的voting-only master-eligible node 在集群中是不会有其他的角色的。通过下面的方式来创建一个专用的voting-only master-eligible nodes:
xxxxxxxxxx
11node.roles: [ master, voting_only ]
data note保留了你索引的文档的分片。data note处理像CRUD、查询、聚合的操作。这些操作有I/O-, memory-, and CPU-intensive。监控这些资源以及工作负载大后增加更多的data node是非常重要的。
拥有专用的data node最主要的好处是将master和data角色分开出来。
通过下面的方式创建一个专用的data node:
xxxxxxxxxx
11node.roles: [ data ]
在多层部署架构(multi-tier)中,你可以根据为data node分配不同数据层对应的数据角色(data role):data_content
,data_hot
, data_warm
, data_cold
, 或者data_frozen
。一个节点可以属于多个层,但是已经拥有一个指定数据角色的节点不能有通用的data
角色(generic data role)。
Content data node属于content tier的一部分。存储在content tier上的数据通常是iterm的集合比如说产品目录(product catalog)或者文章归档(article archive)。跟时序数据不同的是,这些内容的价值随着时间的流逝相对保持不变的,所以根据这些数据的寿命(age)将它们移到性能不同的数据层是不合理的。Content data通常有长时间保留(retention)的要求,并且也希望无论这些数据的寿命的长短,总是能很快的检索到。
Content tier node会为查询性能做优化-优先优化IO吞吐使得可以处理复杂的查询,聚合并且能很快的返回结果。同时这类节点也用来索引,content data通常没有例如log和metric这种时序数据一样高的ingest rate。从弹性的角度(resiliency perspective)看,当前层的索引应该配置为使用一个或者多个副本(replica)。
Content tier是必须要有的(required)。系统索引以及其他不是data stream的索引都会被自动分配到content tier。
通过下面的方式创建一个专用的content node:
xxxxxxxxxx
11node.roles: [ data_content ]
hot data node是 hot tier的一部分。hot tier是Elasticsearch中时序数据的入口点(entry point),并且保留最近,最频繁搜索的时序数据。hot tier节点上的读写速度都需要很快,要求更多的硬件资源和更快的存储(SSDs)。出于弹性目的,hot tier上的索引应该配置一个或多个副本分片。
hot tier是必须要有的。data stream中新的索引会被自动分配到hot tier。
通过下面的方式创建一个专用的hot node:
xxxxxxxxxx
11node.roles: [ data_hot ]
warm data node是warm tie的一部分。一旦时序数据的访问频率比最近索引的数据(recently-indexed data)低了,这些数据就可以移到warm tier。warm tier通常保留最近几周的数据。允许更新数据,但是infrequent。warm tier节点不需要像hot tier一样的快。出于弹性目的,warm tier上的索引应该配置一个或多个副本分片。
通过下面的方式创建一个专用的warm node:
xxxxxxxxxx
11node.roles: [ data_warm ]
cold data node是cold tier的一部分。当你不再经常(regular)搜索时序数据了,那可以将它们从warm tier移到cold tier。数据仍然可以被搜索到,在这一层的通常会被优化成较低的存储开销而不是查询速度。
为了更好的节省存储(storage saveing),你可以在cold tier保留fully mounted indices的searchable snapshots。跟常规索引(regular index)不同的是,这些fully mounted indices不需要副本分片来满足可靠性(reliability),一旦出现失败事件,可以从底层(underlying)snapshot中恢复。这样可以潜在的减少一般的本地数据存储开销。snapshot仓库要求在cold tier使用fully mounted indices。Fully mounted indices只允许读取,不能修改。
另外你可以使用cold tier存储常规索引并且使用副本分片的方式,而不是使用searchable snapshot,这样会帮你在较低成本的硬件上存储较老的索引,但是相较于warm tier不会降低磁盘空间。
通过下面的方式创建一个专用的cold node:
xxxxxxxxxx
11node.roles: [ data_cold ]
frozen data node是frozen tier的一部分。一旦数据不需要或者很少(rare)被查询,也许就可以将数据从cold tier移到frozen tier,where it stays for the rest of its life。
frozen tier需要用到snapshot repository。frozen tier使用partially mounted indices的方式存储以及从snapshot repository中载入数据。这样仍然可以让你搜索frozen数据并且可以减少本地储存(local storage)和操作开销(operation cost)。因为Elasticsearch必须有时从snapshot repository中提取(fetch)数据,在frozen tier的查询速度通常比cold tier慢。
Ingest node可以执行pre-processing pipelines。由一个或者多个ingest processor组成。基于ingest processor执行的操作类型以及需要的资源,拥有一个专用的ingest node还是有意义的,使得这个节点只运行指定的任务。
通过下面的方式创建一个专用的Ingest node:
xxxxxxxxxx
11node.roles: [ ingest ]
如果你的节点不需要(take away)处理master职责(duty)、保留数据、pre-process文档的能力,那么你还剩下一个coordinating only node,只能处理路由请求、查询的reduce phase、以及分布式的块索引(bulk Indexing)。本质上coordinating only node表现为像一个智能的负载均衡节点。
在大规模的集群中将coordinating node角色从data node和master-eligible node中剥离(offload)出来能体现coordinating only nodes的益处。这些节点加入到集群中并且接收完整的cluster state,就像每一个其他的节点一样,它们使用cluster state将请求路由到合适的地方。
WARNING:集群中添加太多的coordinating only nodes会增加整个集群的负担(burden),因为被选举为master的节点必须等待从每一个节点更新后的cluster state的回应。不能过度夸大(overstate)coordinating only node的益处-data node也可以实现跟coordinating only nodes相同的目的(same purpose)
通过下面的方式创建一个专用的coordinating node:
xxxxxxxxxx
11node.roles: [ ]
一个remote-eligible node扮演一个cross-cluster client的角色以及连接remote clusters。一旦连接后,你可以使用cross-cluster search搜索远程的集群。你可以使用cross-cluster replication在集群间同步数据。
xxxxxxxxxx
11node.roles: [ remote_cluster_client ]
Machine learning node运行任务并且处理machine learning API的请求。见Machine learning settings查看更多的信息
通过下面的方式创建一个专用的machine learning node:
xxxxxxxxxx
11node.roles: [ ml, remote_cluster_client]
remote_cluster_client
是可选的,但是强烈建议配置进去。否则当使用machine learning jobs 或者 datafeed时,cross-cluster search会失败。如果在你的异常检测任务中使用corss-cluster search,在所有的master-eligible node上同样需要remote_cluster_client
这个角色。否则 datafeed无法开始,见Remote-eligible node。
Transform node运行transforms并且处理transform API的请求。见Transforms settings。
通过下面的方式创建一个专用的transform node:
xxxxxxxxxx
11node.roles: [ transform, remote_cluster_client ]
remote_cluster_client
是可选的,但是强烈建议配置进去。否则当使用transforms时,cross-cluster search会失败。见Remote-eligible node。
每一个data node在磁盘上维护下面的数据:
分配给节点的每一个分片的分片数据
分配在这个节点的每一个分片的索引元数据(index metadata),以及
集群范围(cluster-wide)的元数据,例如settings和index templates
同样的,每一个master-eligible node在磁盘上维护下面的数据:
集群中每一个索引的索引元数据,以及
集群范围(cluster-wide)的元数据,例如settings和index templates
每一个节点在启动时会检查数据路径的内容。如果发现了非预期(unexpected)的数据,节点将拒绝启动。这样就可以避免导入会导致红色的集群健康的unwanted dangling indices。更准确的说,如果在启动时发现磁盘上有分片数据但是这个节点没有data
角色,那么这个节点不会启动。如果在启动时发现磁盘上有索引元数据但是这个节点没有data
和master
角色,那么这个节点不会启动。
可以通过更改elasticsearch.yml
然后重启节点来修改节点的角色。这就是众所周知的 repurposing
一个节点。为了能满足上文中描述的对非预期的检查,在启动没有data
或者master
角色的节点前,你必须执行一些额外的步骤来为repurposing做准备。
如果你想要通过repurposing来移除一个data note的data
角色,你应该先通过allocation filter安全的将节点上的所有分片数据迁移到集群中的其他节点上。
如果你想要通过repurposing来移除data
和master
节点,最简单的办法是启动一个全新的节点,这个节点设置一个空的数据路径以及想要的节点角色。你会发现使用allocation filter可以安全的先将分片数据迁移到集群中的其他地方
如果没有办法按照这些额外的步骤实现你的目的,你可以使用elasticsearch-node repurpose工具来删除阻止节点启动的多余的数据(excess data)。
每个data node、master-eligible node都要访问一个数据目录,这个目录中存放了分片、索引和集群元数据。path.data
的默认值是$ES_HOME/data
但是可以在elasticsearch.yml
中配置一个全路径或者一个相对于$ES_HOME
的相对路径:
xxxxxxxxxx
11path.data: /var/elasticsearch/data
跟其他的节点设置一样,可以在命令行上指定:
xxxxxxxxxx
11./bin/elasticsearch -Epath.data=/var/elasticsearch/data
path.data
的内容在启动时必须存在,因为这是你数据存储的位置。Elasticsearch 要求文件系统像由本地磁盘支持一样运行,但这意味着只要远程存储正常运行,它就可以在配置正确的远程块设备(例如 SAN)和远程文件系统(例如 NFS)上正常工作,与本地存储没有什么不同。你可以在同一个文件系统上运行多个Elasticsearch节点,但是每个节点要有自己的数据路径。
Elasticsearch集群的性能常常受制于底层存储的性能,所以你必须保证你的存储能有可以接受的性能范围内。一些远程存储的性能很差,尤其是在 Elasticsearch 施加(impose)的那种负载下,所以在使用特定的存储架构之前,请确保仔细地对你的系统进行基准测试。
TIP:如果使用了
.zip
或者.tar.gz
的发行版,path.data
的值应该设置到Elasticsearch home目录以外的路径上,这样删除home目录时不会删除你的数据!RPM和Debian发行版已经为你做了这些工作。
IMPORTANT:不要修改数据目录中的任何东西或者运行可能会影响这个目录中内容的程序。如果一些不是Elasticsearch的操作修改了数据目录的内容,那么Elasticsearch可能会发生故障,报告出corruption或者其他data inconsistencies,或者工作正常但是轻微的丢失了一些你的数据。不要尝试做文件系统级别的数据目录的备份。因为没有可用的方法来恢复这类备份。而是应该用 Snapshot and restore做安全的备份。不要在数据目录做病毒扫描,病毒扫描会使得Elasticsearch不能正确的工作并且可能会修改数据目录的内容。数据目录不包含可执行文件,因此病毒扫描只会发现误报
更多的跟节点相关的设置可以在Configuring Elasticsearch 和 Important Elasticsearch configuration找到,包括:
(8.2)link
filter context中的查询结果缓存在node query cache中使得可以快速的查询,这属于每个节点上的查询缓存(query cache),由所有的分片共享。缓存使用了LRU eviction policy:当缓存满了以后,最近最少使用的查询结果会被踢出(evict),为新的查询查询让路。你不能查看(inspect)查询缓存的内容。
Term query以及在filter context使用外的查询没有缓存资格(eligible)。
默认情况下,缓存保留最多10000个query,以及占用最多10%的总内存。若要判断某个query是否有资格被缓存,Elasticsearch维护了一个query history来track occurrence。
缓存在每个段上进行,并且段中至少包含10000篇文档并且每个段中的文档数量至少是分片中文档总数的3%。由于是基于段的缓存,因此段的合并会使缓存的查询失效。
下面的设置是static
并且必须在集群中每个data node上配置:
(Static)为filter cache控制内存大小。可以是一个百分比的值,比如5%或者一个精确值,例如512mb
。默认值为10%
。
下面的设置属于index setting,可以在每一个索引上配置。只能在索引创建时或者一个closed index上设置。
(Static)控制是否开启查询缓存。可以是true
(默认值)或者false
。
(8.2)link
下面的expert setting用来管理全局的查询和聚合限制。
(Static integer)一个查询能包含的clause的最大数量。默认值为4096
。
这个设置限制了一颗query tree能包含的clause的最大数量。4096这个默认值已经是非常高了,通常来说该值是够用的。这个限制会应用于重写query(rewrite query),所以不仅仅是bool
query,所有会重写为bool query的query,例如fuzzy
query,都会有较高数量的clause。这个限制的目的是防止查询的范围变得很大,占用过多的CPU和内存。如果你一定要提高这个值,保证你已经竭尽全力尝试了所有其他的选项,否则就不要这么做。较高的值会导致性能降低以及内存问题,特别是在负载高或者资源较少的集群上。
Elasticsearch提供了一些工具来防止遇到这个clause数量上限的问题,例如在terms query中,它允许查询许多不一样的值,但是它只作为一个clause。或者是带有index_prefixes选项的text字段,它会执行prefix query,这个query会膨胀为较高数量的term,但是只作为一个term query。
(Dynamic,integer)在一次响应中,aggregation buckets 的最大分桶数量。默认值为65535。
尝试返回超过这个限制的请求会返回一个错误。
(Static integer)bool query的最大深度。默认值为20
。
这个设置限制了bool query的嵌套深度。深度大的bool query可能会导致stack overflow。
(8.2)link
当一个查询请求运行在一个或者多个索引上时,每一个参与的分片会在本地执行查询然后将结果返回到coordinating note,节点会结合(combine)分片级别(shard-level)的结果到一个"全局的"的结果集中。
分片层的请求缓存模块会在每一个分片上缓存本地的结果。这允许经常使用(并且可能很重)的搜索请求几乎立即返回结果。请求缓存(request cache)非常适用于日志用例,因为这类数据只有最近的索引会持续更新,从旧的索引中获取的结果可以直接从缓存中返回。
IMPORTANT:默认情况下,请求缓存在
size=0
时仅仅缓存查询请求的结果。所以不会缓存hits
,但是它会缓存hits.total
, aggregations以及suggestions。大多数使用
now
(见 Date Math)的请求不能被缓存。使用脚本的查询使用了一些不确定的API调用不会被缓存,比如说
Math.random()
或者new Date()
。
The cache is smart。它可以跟非缓存的查询一样的近实时查询。
当分片refresh后文档发生了变更或者当你更新了mapping时,缓存的结果会自动的被废止。换句话说,你总是可以从缓存中获得与未缓存搜索请求相同的结果。
refresh的间隔时间越长意味着缓存的结果保留时间越长,即使文档已经发生了变化。如果缓存满了,基于LRU(least recently used)机制来丢弃缓存。
可以通过clear-cache API手动将缓存置为过期的(expired):
xxxxxxxxxx
11POST /my-index-000001,my-index-000002/_cache/clear?request=true
缓存默认开启,可以在创建一个新的索引时来关闭缓存:
xxxxxxxxxx
61PUT /my-index-000001
2{
3 "settings": {
4 "index.requests.cache.enable": false
5 }
6}
可以通过update-settings API来手动的开启或关闭缓存:
xxxxxxxxxx
21PUT /my-index-000001/_settings
2{ "index.requests.cache.enable": true }
request_cache
这个query-string参数可以用于在每一次请求中来开启或关闭缓存。如果设置了这个参数,会覆盖索引层(index-levele)的设置:
xxxxxxxxxx
111GET /my-index-000001/_search?request_cache=true
2{
3 "size": 0,
4 "aggs": {
5 "popular_colors": {
6 "terms": {
7 "field": "colors"
8 }
9 }
10 }
11}
请求中的size
如果大于0的话则不会缓存即使在索引设置中开启了缓存。要缓存这些请求,你需要使用的query-string参数。
使用完整的JSON计算出的hash值作为cache key。这意味着如果JSON发生了变更,比如说key的先后顺序发生了变化,那么对应的cache key就无法被识别。
TIP:大多数 JSON 库都支持规范模式(canonical mode),可确保 JSON的key始终以相同的顺序发出。这种规范模式可以在应用程序中使用,以确保始终以相同的方式序列化请求。
缓存在节点层进行管理,缓存大小的最大值默认是堆内存的1%。可以通过config/elasticsearch.yml
修改:
xxxxxxxxxx
11indices.requests.cache.size: 2%
同样的你可以使用indices.requests.cache.expire
为缓存结果设置一个TTL,但是并没有理由要这么做。因为过时的结果(stale result)会在refresh后自动的废止。这个设置只是为了completeness(This setting is provided for completeness' sake only)。
缓存的大小(字节)和evictions的数量可以通过indices-stats接口来查看:
xxxxxxxxxx
11GET /_stats/request_cache?human
或者通过nodes-stats接口查看:
xxxxxxxxxx
11GET /_nodes/stats/indices/request_cache?human
(8.2)link
一个节点会使用多个线程池来管理内存消费(memory consumption)。许多的线程池使用队列hold住pending中的请求而不是丢弃。
节点中有很多的线程池,下面介绍主要的几个线程池:
用于一般的操作(例如background node discovery)。线程池类型是scaling。
用于count/search/suggest操作。线程池类型是fixed。线程数量为int((# of allocated processors * 3) / 2) + 1。队列大小为1000
。
用于在search_throttled indices
上的count/search/suggest/get操作。线程池类型为fixed
,线程数量为1
,队列大小为100
。
用于轻量级的查询相关的协同操作(search-related coordination)。线程池类型为fixed
,线程数量为a max of min(5, (# of allocated processors) / 2),队列大小为1000
。
用于get操作,线程池类型为fixed
,线程数量为# of allocated processors,队列大小为1000
。
用于analyzer请求,线程池类型为fixed
,线程数量为1
,队列大小为16
。
用于单篇文档的 index/delete/update以及bulk的请求,线程池类型为fixed
,线程数量为# of allocated processors,队列大小为10000
。这个线程池的最大值是1 + # of allocated processors。
用于snapshot/restore请求,线程池类型为scaling,keep-alived的值为5m
以及a max of min(5, (# of allocated processors) / 2)。
用于读取snapshot repository metadata的请求,线程池类型为scaling,keep-alived的值为5m
以及a max of min(50, (# of allocated processors) / 3)。
用于segment warm-up的操作,线程池类型为scaling,keep-alived的值为5m
以及a max of min(5, (# of allocated processors) / 2)。
用于refresh的操作,线程池类型为scaling,keep-alived的值为5m
以及a max of min(10, (# of allocated processors) / 2)。
用于列出分片状态(list shard state)的操作,线程池类型为scaling,keep-alived的值为5m
以及a default maximum size of 2 * # of allocated processors。
用于列出分片存储(list shard store)的操作,线程池类型为scaling,keep-alived的值为5m
以及a default maximum size of 2 * # of allocated processors。
用于flush和translog fsync
的操作,线程池类型为scaling,keep-alived的值为5m
以及a max of min(10, (# of allocated processors) / 2)。
用于force merge操作,线程池类型为fixed,线程数量为1
,队列大小为unbounded
。
用于集群管理,线程池类型为scaling,keep-alived的值为5m
以及a default maximum size of 5。
用于读取系统索引的操作,线程池类型为fixed,keep-alived的值为5m
以及a default max of min(5, (# of allocated processors) / 2)。
用于写入系统索引的操作,线程池类型为fixed,keep-alived的值为5m
以及a default max of min(5, (# of allocated processors) / 2)。
用于读取关键系统索引(critical system indices)的操作,线程池类型为fixed,keep-alived的值为5m
以及a default max of min(5, (# of allocated processors) / 2)。
用于写入关键系统索引(critical system indices)的操作,线程池类型为fixed,keep-alived的值为5m
以及a default max of min(5, (# of allocated processors) / 2)。
用于watch executions。线程池类型为fixed,以及a default max of min(5 * (# of allocated processors) , 50),队列大小为50
。
线程池的设置是static,通过编辑elasticsearch.yml
更改。下面的例子中更改了write
线程池中的线程数量:
xxxxxxxxxx
31thread_pool:
2 write:
3 size: 30
下文中介绍的是线程池的类型以及对应的参数:
fixed
类型的线程池中有固定数量的线程,以及一个队列(通常是有界的),当线程池中没有可用线程时,队列会hold住请求。
size
参数控制了线程的数量。
queue_size
控制了队列中的请求数量,当没有可用线程时,队列会hold住请求。默认情况下,该值为-1
意味着队列是无界的。但一个新的请求到来时,如果队列已满则abort这个请求。
xxxxxxxxxx
41thread_pool:
2 write:
3 size: 30
4 queue_size: 1000
scaling
类型的线程池中拥有动态数量的线程。线程数量跟工作负载成正比(proportional)。线程数量在参数core
和max
之间变化。
keep_alive
参数用于决定当线程池中的线程没有任何工作时,呆在线程池中的最大时间。
xxxxxxxxxx
51thread_pool:
2 warmer:
3 core: 1
4 max: 8
5 keep_alive: 2m
处理器(processor)的数量会被自动的检测。线程池的会基于处理器的数量自动的设置。在有些情况下,覆盖(override)处理器的数量是很有用的。可以显示的(explicit)设置node.processors
。
xxxxxxxxxx
11node.processors: 2
以下的情况要显示的覆盖设置node.processors
:
If you are running multiple instances of Elasticsearch on the same host but want Elasticsearch to size its thread pools as if it only has a fraction of the CPU, you should override the node.processors setting to the desired fraction, for example, if you’re running two instances of Elasticsearch on a 16-core machine, set node.processors to 8. Note that this is an expert-level use case and there’s a lot more involved than just setting the node.processors setting as there are other considerations like changing the number of garbage collector threads, pinning processes to cores, and so on.
Sometimes the number of processors is wrongly detected and in such cases explicitly setting the node.processors setting will workaround such issues.
若要检查检查到的处理器的数量,使用nodes info AP带上os
这个flag。
(8.2)link
根据你安装Elasticsearch时使用的安装包,以及你正在使用的操作系统,系统设置的配置位置略有不同。
当使用.zip
或者.tar.gz
的安装包时,可以通过下面的方式配置系统设置:
在ulimit中临时设置
当使用RPM或者Debian发行版时,大多数的系统设置在system configuration file中设置,然而在使用systemd的系统中,要求在systemd configuration file中指定system limit。
在linux系统中,ulimit
用于临时更改资源限制(resource limit)。Limits通常需要使用root用户进行设置,然后切换到普通用户启动Elasticsearch。例如,将文件句柄(file handler)的最大数量设置为65,536(ulimit -n),你可以按照下面的方式进行设置:
xxxxxxxxxx
31sudo su
2ulimit -n 65535
3su elasticsearch
第1行,切换到root用户
第2行,设置打开的文件最大数量(max number of open files)
第3行,切换到普通用户来启动Elasticsearch
这个新的限制(new limit)只适用于当前会话(current session)
你可以通过ulimit -a
查询(consult)当前目前所有的limit
在linux系统中,可以通过编辑文件/etc/security/limits.conf
为linux的某个系统用户设置persistent limits。如果要为elasticsearch
这个系统用户设置可以打开的文件最大数量为65,535,在文件中添加一行下面的内容:
xxxxxxxxxx
11elasticsearch - nofile 65535
这个变更只有在elasticsearch
这个用户下次打开一个新的session时才会生效。
NOTE: Ubuntu and
limits.conf
在Ubuntu中,由init.d
启动的进程会忽略limits.conf
文件,如果需要让这个文件生效,可以编辑/etc/pam.d/su
然后注释下面这一行的内容:# session required pam_limits.so
当使用RPM或者Debian安装包时,可以在系统配置文件中指定系统变量:
RPM | /etc/sysconfig/elasticsearch |
---|---|
Debian | /etc/default/elasticsearch |
然而system limits需要通过systemd来指定。
当在使用systemd的系统上使用RPM或者Debian安装包时,system limits需要通过systemd来指定。
systemd service file(/usr/lib/systemd/system/elasticsearch.service
)中包含了默认的limit。
你可以新增一个文件名为/etc/systemd/system/elasticsearch.service.d/override.conf
(或者你可以执行sudo systemctl edit elasticsearch
,它会在默认编辑器中自动打开文件),在这个文件中做如下的变更:
xxxxxxxxxx
21[Service]
2LimitMEMLOCK=infinity
完成更改后,运行下面的命令来reload units:
xxxxxxxxxx
11sudo systemctl daemon-reload
(8.2)link
大多数的操作系统会为文件系统缓存(file system cache)尽可能多的使用内存并且急切的(eagerly)将unused application memory交换出去(swap out)。这会导致部分JVM堆甚至executable page被交换到磁盘上。
Swapping对性能、节点稳定影响是非常大的,无论如何(at all cost)都要避免。这会导致垃圾回收的时间达到分钟级别而不是毫秒级别,使得节点的响应时间变慢甚至与节点的断开连接。在弹性分布式系统中,让操作系统杀死节点反而更有效。
有三种方法关闭swapping。较好的方法(prefer option)是完全的关闭swapping。如果不能用这个方法,那么基于你的环境选择最小化swappiness还是内存锁定。
通常Elasticsearch是一个box中唯一运行的服务,并且通过JVM参数来控制内存的使用,就没有必要开启swapping。
在linux系统中,你可以运行下面的命令临时关闭swapping:
xxxxxxxxxx
11sudo swapoff -a
这个操作不需要重启Elasticsearch。
若要永久关闭,你需要编辑文件/etc/fstab
,然后将所有包含swap
的内容都注释。
在Windows上,可以通过System Properties → Advanced → Performance → Advanced → Virtual memory
达到相同的目的。
要确保Linux系统上另一个可用的选项:sysctl的vm.swappiness
的值是1
。这个选项会降低kernel进行swap的趋势,在正常情况下(normal circumstances)不会进行swapping,并且允许在紧急情况下运行将整个系统进行swapping。
另一种方式是通过在Linux/Unix系统上使用mlockall或者是Windows系统上的VirtualLock,将进程地址空间锁定到RAM,防止Elasticsearch堆内存被swapping。
NOTE: 一些平台在使用了memory lock后仍然会swap off-heap memory。为了防止swap off-heap memory,转而尝试disable all swap files。
在elasticsearch.yml
中设置bootstrap.memory_lock
为true
来开启memory lock:
xxxxxxxxxx
11bootstrap.memory_lock: true
WARNING:如果尝试分配比可用内存更多的内存量,
mlockall
可能会导致JVM或者shell session退出。
在启动Elasticsearch之后,你可以通过下面这个请求输出的mlockall
的值检查是否应用了设置:
xxxxxxxxxx
11GET _nodes?filter_path=**.mlockall
如果你看到mlockall
的值为false
,那么意味着mlockall
请求失败了。你可以在日志中看到包含Unable to lock JVM Memory
的更详细的信息。
在Linux/Unix系统上最有可能导致失败的原因是运行中的Elasticsearch没有权限来lock memory。可以通过下面的方式授权:
.zip
and .tar.gz
在启动Elasticsearch前切换到root用户并执行ulimit -l unlimited,或者在/etc/security/limits.conf
中设置memlock
为unlimited
。
xxxxxxxxxx
31# allow user 'elasticsearch' mlockall
2elasticsearch soft memlock unlimited
3elasticsearch hard memlock unlimited
RPM and Debian
在systemd configuration中将LimitMEMLOCK
设置为infinity
另一个导致mlockall
可能的原因是the JNA temporary directory (usually a sub-directory of /tmp) is mounted with the noexec option,可以使用ES_JAVA_OPTS
环境变量为JNA指定一个临时的目录来解决:
xxxxxxxxxx
21export ES_JAVA_OPTS="$ES_JAVA_OPTS -Djna.tmpdir=<path>"
2./bin/elasticsearch
或者在配置文件jvm.options中设置这个JVM标志。
(8.2)link
NOTE:File Descriptors仅仅适用于Linux以及MacOs,在Windows平台上运行Elasticsearch可以忽略。在 Windows 上,JVM 使用仅受可用资源限制的 API
Elasticsearch会使用很多file descriptors 或者 file handles。文件描述符用完可能是灾难性的,并且很可能会导致数据丢失。要确保将open files descriptors的数量提高到65,536或者更高用于Elasticsearch的运行。
对于.zip
和.tar.gz
的安装包,在Elasticsearch启动前使用root用户执行ulimit -n 65535
,或者在/etc/security/limits.conf
设置nofile
的值为65535
。
在MacOs平台上,你必须要使用JVM参数-XX:-MaxFDLimit
让Elasticsearch使用更高的file descriptors限制。
RPM和Debian已经默认将file descriptors的最大值设置为65535并且不需要进一步的配置。
你可以使用Nodes stats API检查每一个节点上的max_file_descriptors
:
xxxxxxxxxx
11GET _nodes/stats/process?filter_path=**.max_file_descriptors
(8.2)link
Elasticsearch默认使用mmapfs存储索引文件。默认情况下操作系统会将mmap count限制的太低导致OOM的异常。
在Linux下,你可以使用root通过下面的命令来提高上限:
xxxxxxxxxx
11sysctl -w vm.max_map_count=262144
可以在/etc/sysctl.conf
中更新vm.max_map_count
来永久的设置该值。在重启后使用sysctl vm.max_map_count
命名来验证刚才的变更。
RPM跟Debian安装包会自动进行配置。不需要进行额外的配置。
(8.2)link
Elasticsearch会使用线程池用于不同类型的操作。重要的是当有需要时就会创建新的线程。保证Elasticsearch能创建的线程数量至少可以有4096个。
在启动Elasticsearch前切换到root设置ulimit -u 4096,或者在/etc/security/limits.conf中将nproc
设置为4096。
在systemd
下运行的service会自动的为Elasticsearch进程设置线程数量。不需要额外的配置。
(8.2)link
Discovery and cluster formation负责节点发现,选举master,形成集群,以及每一次发生变更时集群状态的发布。
下面的processes和settings是discovery and cluster formation的 一部分:
Discovery是当master未知时,节点之间用来相互发现的步骤,例如一个节点刚刚启动或者当上一个master node发生故障时
Elasticsearch如何基于quorum-based voting mechanism来做出决策,即使一些节点发生了故障
Elasticsearch在节点加入以及离开集群时如何自动的更新voting configuration
当Elasticsearch集群第一次启动时Bootstrapping a cluster是必须。在development node中,如果没有配置discovery settings,这将由节点本身自动执行。由于auto-bootstrapping是inherently unsafe,在production mode中要求节点显示配置(explicitly configure)bootstrappping。
Adding and removing master-eligible nodes
建议在集群中设置一个小规模的并且固定数量的master-eligible node,只通过添加或者移除master-ineligible node来扩大或者缩小集群。然而也有一些情况需要将一些master-eligible node 添加到集群,或者从集群中移除。 这块内容描述的是添加或者移除master-eligible node的过程,包括在同一时间移除超过一半数量的master-eligible node时额外必须要执行的步骤。
Cluster state publishing描述的是被选举为master的node更新集群中所有节点的集群状态的过程
Elasticsearch执行健康检查来检测以及移除有故障的节点
让用户通过设置来影响(influence)discovery,cluster formation,master election和fault detection processes
(8.2)link
Discovery是cluster formation module发现其他节点并形成一个集群的过程。这个过程发生在当你启动一个Elasticsearch节点或者当一个节点认为master node发生故障并且等到发现master node或者选出新的master node后继续运行时。
这个过程和上一次已知的集群中的master-eligible node的地址一起,开始于一个或者多个seed hosts providers中提供的seed address列表。这个过程有两个阶段:首先,通过连接每一个地址来尝试明确节点是否连接并且验证是否为master-eligible node。然后如果成功了,它会共享它已知的master-eligible node的remote node并且等待响应。然后该节点探测(prove)它刚刚发现的所有新节点,请求它们的对等节点(peer),等等。
如果当前节点不是master-eligible node,那它会继续处理直到它发现了被选举为master的node。如果没有发现master node那么这个节点将在discovery.find_peers_interval
后重试,默认值是1s
。
如果当前节点是master-eligible node,那它会继续处理直到它要么发现了一个master node,要么已经发现了足够多masterless master-eligible node并完成master的选举。如果这两个都没有发生,则在discovery.find_peers_interval
后重试,默认值是1s
。
一旦一个master被选举出来,通常情况下它会一直是master node,直到它被故意(deliberate)停止。如果falult detection发现集群发生了故障,也有可能作为master并停止。如果一个节点不是master node,它则继续discovery的过程。
大多数情况下,discovery和选举的过程会很快完成。并且master node在很长的一段时间内都是会master。
如果你的集群没有一个稳定(stable)的master,许多的功能不能正确工作。那么Elasticsearch会报出错误给客户端并记录日志。你必须在解决其它问题前先处理master node的不稳定问题。master node选举出来之前或者master node稳定前将无法解决其他任何的问题。
如果你有一个稳定的master node但是一些节点不能发现它或者加入,这些节点会报出错误给客户端并记录日志。在解决其它问题前你必须先解决这些节点无法加入到集群的障碍(obstacle)。节点不能加入集群前将无法解决其他任何问题。
如果集群在超过好几秒后都没有选举出master,说明master不稳定或者其他节点无法发现或者加入join a stable master。那Elasticsearch会在日志中记录其原因。如果这个问题超过好几分钟还存在,Elasticsearch会在日志中记录额外的日志。若要正确的处理discovery和选举问题,可以收集并分析所有节点上最新的5分钟内的日志。
下面的内容介绍了一些常见的discovery和选举问题。
当一个节点赢的选举,日志中会包含elected-as-master
的信息并且所有的节点会记录一条包含master node changed
的信息来标识最新赢的选举的master node。
如果没有选出master node并且没有节点赢得选举,所有的节点会重复的在日志中记录问题:org.elasticsearch.cluster.coordination.ClusterFormationFailureHelper
。默认情况下,每10秒发生一次。
master的选举只会涉及master-eligible node,所以这种情况下集中查看master-eligible node的日志。这些节点的日志会说明master选举的要求,比如用于discovery的相关节点。
如果日志显示Elasticsearch不能从quorum中发现足够多的节点。你必须解决阻止Elasticsearch发现缺失节点的原因。缺失的节点需要用于重新构造(reconstruct)cluster metadata。没有cluster metadata,你集群中的数据是没有意义的(meaningless)。cluster metadata存储在集群中master-eligible node的子集中。如果不能发现quorum ,那么缺失的节点是拥有cluster metadata的节点。
保证有足够多的节点来形成quorum并且每个节点能相互通过网络联系到对方。如果选举问题持续了好几分钟,Elasticsearch会额外的报出网络连接的细节。如果你不能启动足够多的节点来形成quorum,那么启动一个新的集群并且从最新的snapshot中恢复数据。参考Quorum-based decision making 了解更多信息。
如果日志显示Elasticsearch已经发现了 a possible quorum of nodes。那么集群不能选出master的原因通常是其中的节点不能发现quorum。检查其他master-eligible node上的日志并且保证它们已经发现了足够多的节点来形成一个quorum。
当一个节点赢得了选举,它的日志中会包含elected-as-master
的信息。如果重复出现这个日志,说明选举出的节点不稳定。在这种情况下,集中在master-eligible node中的日志中查看为什么选举出的胜利者后来又停止成为master并且又触发另一个选举的原因。
如果选举出的master node是一个稳定的节点,但是某个节点不能发现并加入到集群,它会在日志中使用ClusterFormationFailureHelper
重复的报出日志。其他受到影响的节点和选举为master的节点上可能会有一些关于这个问题的额外的日志。
如果某个节点加入到了集群但是Elasticsearch认为它出了故障并随后将再次从集群中移除。见Troubleshooting an unstable cluster了解更多信息。
默认情况下,cluster formation module提供了两个seed hosts provider来配置seed node列表:settings-based和file-based。可以扩展为支持云环境以及通过discovery plugins的其他形式的seed hosts provider。seed hosts provider使用discovery.seed_providers
进行配置,这个是settings-base 这种provider的默认方式。这个设置接受一个不同的provider的列表,允许你能用多种方式来为你的集群找到seed hosts。
每一个seed hosts provider提供(yield)seed node的IP地址或者hostname。如果返回的是hostname,那么需要使用DNS来查找对应的IP地址。如果一个hostname对应了对个IP地址,那么Elasticsearch会尝试找到所有的地址。如果hosts provider没有显示的(explicit)给出节点的TCP port,则会隐式的(implicit)使用port range中的第一个:transport.profiles.default.port
或者transport.port
(如果没有设置transport.profiles.default.port
)。通过DSN查找IP地址的并发数量由discovery.seed_resolver.max_concurrent_resolvers
控制,默认值是10
。超时时间由discovery.seed_resolver.timeout
控制,默认值是5s
。注意的是DNS查找IP地址受限于JVM DNS caching。
Settings-based seed hosts provider使用了节点设置来配置一个静态的seed nodes的地址列表。这些地址可以是hostname或者是IP地址。使用hostname时会在每一次discovery时去查找对应的IP地址。
下面使用了discovery.seed_hosts静态设置:
xxxxxxxxxx
41discovery.seed_hosts:
2 - 192.168.1.10:9300
3 - 192.168.1.11
4 - seeds.mydomain.com
第3行,将默认使用transport.profiles.default.port
,如果没有配置transport.profiles.default.port
则使用transport.port
第4行,如果这个hostname对应了多个IP地址,Elasticsearch会尝试连接每一个IP地址
File-based seed hosts provider通过外部的文件来配置hosts列表。这个文件发生变更后,Elasticsearch就会重新加载,所以不需要重启节点就可以使用变更后的seed nodes的地址。例如运行在Docker容器中的Elasticsearch就可以很方便的在节点启动时,如果给定的IP地址无法连接时就可以动态的提供。
若要开启file-based的discovery,那么在elasticsearch.yml
中配置file
即可:
xxxxxxxxxx
11discovery.seed_providers: file
然后按照下面的格式在$ES_PATH_CONF/unicast_hosts.txt
中创建一个文件。任何时刻在unicast_hosts.txt
文件中发生的变更都会被Elasticsearch捕获并使用新的hosts列表。
注意的是file-based discovery 插件增强了elasticsearch.yml
中的unicast hosts list:如果在discovery.seed_hosts
中有合法的seed address,那么Elasticsearch会使用这些地址并额外使用unicast_hosts.txt
中的地址。
unicast_hosts.txt
文件中每一行是一个node entry。每一个node entry由host(hostname或者IP地址)和一个可选的transport port 组成。如果指定了port,必须是写在host后面并且用:
分隔。如果没有指定port,默认使用transport.profiles.default.port
,如果没有配置transport.profiles.default.port
则使用transport.port
。
例如,下面的unicast_hosts.txt
中有四个node参与discovery,其中一些使用了默认的端口号:
xxxxxxxxxx
5110.10.10.5
210.10.10.6:9305
310.10.10.5:10005
4# an IPv6 address
5[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:9301
可以使用hostname来代替IP地址,但是需要上文描述的DNS。IPv6的地址必须要给定端口号。
你可以在文件中添加注释, 所有的注释必须以#
开头
EC2 discovery plugin使用AWSAPI来 查找seed node。
Azure Classic discovery plugin使用Azure Classic API来查找seed node。
GCE discovery plugin 使用GCE API来查找seed node。
(8.2)link
选举出master node和变更集群状态是两个基本的任务,需要所有的master-eligible node一起工作执行。重要的是即使一些节点发生了故障也要让这些工作具有鲁棒性。Elasticsearch中达到这个鲁棒性的方式是:考虑每一个从quorum
中接受到成功的响应。quorum
即集群中的master-eligible node集合。只需要一部分节点响应的好处在于在一些节点发生故障后也不会影响集群继续工作。quorum是经过谨慎选择的,这样集群中不会出现裂脑(split brain)的场景:集群被划分为两块,每一块会作出与另一块不一样的决策。
Elasticsearch允许你向正在运行中的集群添加或者移除master-eligible node。在很多场景中只要按需简单的启动或者节点即可。见Adding and removing nodes。
通过更新集群中的voting configuration使得在添加/移除节点时,Elasticsearch能维护一个最佳的容错能力(Elasticsearch maintains an optimal level of fault tolerance)。voting configuration是master-eligible的集合,它们的响应会被计数用来对选举出新的master和提交(commit)新的集群状态做出决策。只有voting configuration中的超过半数才能做出决策。通常情况下,voting configuration跟当前集群中所有的master-eligible集合是相同的,然而在有些情况下会有不同。
为了保证集群仍然可用(remain available),你必须不能在同一时间(at the same time)停止一半或者更多的voting configuration中的节点。只要超过一半的voting node是可用的,集群就能正常的工作。例如,如果有3个或者4个master-eligible node,集群可以容忍一个不可用的节点。如果2个或者更少的master-eligible node,它们必须都是可用的。
一个节点加入/离开集群时,选举为master的节点必须发布(issue)集群状态的更新,调整(adjustment)voting configuration,这个过程可能会花费一点时间。在移除更多节点之前等待调整结束是非常重要的。
Elasticsearch在启动阶段以及当master node发生故障时使用选举的处理方式来达成一致选出master node。任何的master-eligible都可以开始一个选举,通常来说第一次选举都会成功(normally the first election that takes place will succeed)。只有当两个节点碰巧同时开始选举时,选举才会失败,所以每个节点上的选举都是随机安排的,以降低这种情况发生的概率。节点会重试选举,直到选举出master node。backing off on failure,使得选举会最终的成功。The scheduling of master elections are controlled by the master election settings。
许多的集群维护任务包括临时的关闭一个或者多个节点然后重新启动它们。默认情况下,如果其中一个master-eligible node下线了,Elasticsearch仍然可用。比如在rolling upgrade期间。如果多个节点停止并且再次启动,Elasticsearch会自动的进行恢复,例如在full cluster restart期间。上述说到的场景中,不需要使用APIs来做出额外的措施,because the set of master nodes is not changing permanently。
(8.2)link
每一个Elasticsearch集群中都有一个voting configuration,通过master-eligible nodes集合的投票计数来作出一些决策,例如选举出新的master或者提交一个新的集群状态。决策只有在voting configuration 中节点大多数都响应后(超过半数)可以通过。
通常情况下voting configuration和当前集群中所有的master-eligible node集合相同。然而在某些情况下可能会不同。
IMPORTANT:为了保证集群仍然可用(remain available),你必须不能在同一时间(at the same time)停止一半或者更多的voting configuration中的节点。只要超过一半的voting node是可用的,集群就能正常的工作。例如,如果有3个或者4个master-eligible node,集群可以容忍一个不可用的节点。如果2个或者更少的master-eligible node,它们必须都是可用的。
在某个节点加入或者离开集群时,Elasticsearch会自动的对voting configuration作出对应的变更来保证集群尽可能的具有弹性(resilient)。非常重要的一点是:你从集群中移除更多节点之前,要等待调整(adjustment)完成,见Adding and removing nodes.。
当前的voting configuration存储在集群状态中,你可以按照以下的方式来查看当前的内容:
xxxxxxxxxx
11GET /_cluster/state?filter_path=metadata.cluster_coordination.last_committed_config
NOTE:当前的voting configuration不需要跟集群中的所有的可用的master-eligible node集合相同。改变(alert)voting configuration会涉及进行投票,所以节点加入或者离开集群时会花一点时间来调整configuration。同样的,也会存在most resilient configuration包含不可用的节点或者不包含可用的节点。在这种情况下,voting configuration就会跟集群中的master-eligible node不相同。
规模大(large)的voting configuration 通常更具有弹性。所以Elasticsearch更喜欢(prefer to)在master-eligible node加入到集群后把它们添加到voting configuration中。同样的,如果一个在voting configuration中的节点离开了集群并且集群中有另一个不在voting configuration中的master-eligible node,那么会去交换这两个节点。voting configuration的大小没有发生变化但是提高了弹性。
节点离开集群后,从voting configuration中移除节点不是一件简单的事情。不同的策略有不同的好处跟缺点,所以正确的选择取决于集群如何被使用。你可以通过cluster.auto_shrink_voting_configuration setting来控制voting configuration是否要自动的收缩(shrink)。
NOTE:如果
cluster.auto_shrink_voting_configuration
设置为true
(默认值,推荐值)。那么集群中会至少有3个master-eligible node。Elasticsearch仍然可以处理集群状态的更新,只要除了其中一个maste-eligible node,其他的所有节点都是健康的。
在某些情况下,Elasticsearch能够容忍多个节点的丢失, but this is not guaranteed under all sequences of failures。如果cluster.auto_shrink_voting_configuration
设置为false
。你必须手动的去voting configuration中移除离开集群的节点(departed node)。使用voting exclusions API 来达到想要的弹性等级。
无论怎么配置,Elasticsearch都不会受到裂脑导致不一致的困扰。cluster.auto_shrink_voting_configuration
这个设置只会只会影响在一些节点发生故障后的可用性问题,以及影响节点离开/加入集群时 administrative task的执行。
正常来说集群中应该有奇数个master-eligible node。如果是偶数个,Elasticsearch会将其中一个排除在voting configuration以外来保证奇数个。这种omission不会降低集群的容错能力。事实上还有略微提升:如果集群受到网络分区的影响,将其划分为大小相等的两部分,那么其中一部分将包含大多数voting configuration,并能够继续运行。如果所有的master-eligible node的投票都进行了统计,那么任何一方都不会包含严格多数的节点,因此集群将无法做任何处理(make any progress)。
例如,如果在集群中有4个master-eligible node并且voting configuration包含这四个节点。任何quorum-based的决策都要求至少三个投票。这意味着这个集群可以容忍单个master-eligible node的丢失。如果集群被两等分,没有一半可以获得三个master-eligible node,集群就不会做任何处理。如果voting configuration只有三个master-eligible node,集群仍然可以容忍丢失一个节点,但是quorum-based的决策要求3个投票节点中的2个。即使发生了分裂,其中一半会包含3个投票节点的2个节点,所以这一半仍然是可用的。
当一个全新的集群第一次启动,必须选出第一个master node。要完成这个选举,需要知道用于投票计数的master-eligible集合。这个初始化的voting configuration即bootstrap configuration
并且在cluster bootstrapping process中设置。
重要的一点是bootstrap configuration
明确指定了在第一次选举中参与的节点。 It is not sufficient to configure each node with an expectation of how many nodes there should be in the cluster. It is also important to note that the bootstrap configuration must come from outside the cluster: there is no safe way for the cluster to determine the bootstrap configuration correctly on its own。
如果boostrap configuration没有正确设置,当你启动一个全新的集群时,会有形成两个不同集群的风险。这种情况会导致数据丢失:很有可能在你注意到这个问题前已经启动了2个集群并且不可能在随后合并这两个集群。
NOTE:To illustrate the problem with configuring each node to expect a certain cluster size,想象一下启动一个三个节点的集群,每一个节点知道自己将成为三个节点集群中的一员。三个节点的大多数是2,所以正常情况下前两个启动的节点能发现对方并且形成一个集群,并且第三个节点会在随后很短的时间内加入它们。然而想象下如果错误当启动了四个节点而不是三个。在这种情况下,就会有足够的节点来形成两个集群。当然,如果每一个节点是手动启动的,那不大可能启动太多的节点。如果使用了自动化编排(orchestrator),这当然有可能会发生这种情况。特别是编排器不具备弹性(resilient)而导致网络分区的错误。
最初的quorum只有在整个集群最开始启动的时候需要。加入到一个已经建立好的集群的节点可以安全的从被选举为master节点上获取所有的信息。注意的是之前作为集群一部分的节点将在重新启动时会将所有需要的信息存储到磁盘中。
(8.2)link
最开始启动Elasticsearch时要求一个集群中的一个或者多个初始化的master-eligible node集合,需要显示的定义,即cluster bootstrapping
。仅要求在集群第一次启动时:已经加入到一个集群的节点会将信息存储在它们的数据目录中用于full cluster restart并且新加入到集群的节点会从集群中被选举master的节点上获取信息。
最初的master-eligible node集合定义在cluster.initial_master_nodes setting中。配置中要包含每一个master-eligible node的下面其中一个条目:
节点的node name
如果没有设置node.name
则要求有节点的hostname。因为node.name
默认就是节点的hostname。你可以使用fully-qualified hostname或者bare hostname,取决于depending on your system configuration。
如果无法使用node.name
,则使用节点的transport publish address的IP地址。通常是network.host对应的IP地址,但是this can be overridden
节点的publish address的IP地址跟端口号。如果无法使用node.name
并且多个节点共享同一个IP地址,可以使用的格式为:IP:PORT
。
在你启动一个master-eligible node时,你可以通过命令行或者elasticsearch.yml
的方式提供这个设置。集群形成之后,将这个设置从每一个节点中移除。不能对master-ineligible node,已经加入集群的master-eligible node以及重启一个或者多个节点配置这个设置。
技术上来讲只在集群中的单个master-eligible node上设置cluster.initial_master_nodes
就足够了并且只在设置中提及这个单个节点。但是会在集群完全形成前缺乏容错能力。因此最好使用三个master-eligible node进行引导,每一个节点中都配置了包含三个节点的cluster.initial_master_nodes
。
WARNING:你必须在每一个节点上设置完全相同的
cluster.initial_master_nodes
,使得在引导期间只会形成一个集群,避免数据丢失的风险。
对于拥有三个master-eligible node(node name是 master-a, master-b and master-c)的集群,配置如下:
xxxxxxxxxx
41cluster.initial_master_nodes:
2 - master-a
3 - master-b
4 - master-c
跟其他的节点设置一样,也可以通过命令行的方式指定the initial set of master nodes 来启动Elasticsearch:
xxxxxxxxxx
11bin/elasticsearch -E cluster.initial_master_nodes=master-a,master-b,master-c
cluster.initial_master_nodes
列表中使用的node name必须和节点的属性node.name
完全一样。默认情况下node name被设置为机器的hostname,这个值在你的系统配置中可能不是fully-qualified。如果每一个node name是fully-qualified domain name,例如master-a.example.com
,那你在cluster.initial_master_nodes
列表中也必须使用fully-qualified domain name。如果你的node name是bare hostname (without the .example.com suffix) ,那你在cluster.initial_master_nodes
列表中也必须使用bare hostname。如果你同时使用了fully-qualified 和 bare hostnames,或者其他无法匹配node.name 和 cluster.initial_master_nodes的值,那集群无法成功形成并会看到下面的日志信息:
xxxxxxxxxx
41[master-a.example.com] master not discovered yet, this node has
2not previously joined a bootstrapped (v7+) cluster, and this
3node must discover master-eligible nodes [master-a, master-b] to
4bootstrap a cluster: have discovered [{master-b.example.com}{...
这个消息显示了node name:master-a.example.com
和 master-b.example.com
以及cluster.initial_master_nodes
中的值:master-a
和master-b
。日志中明显的指出它们无法完全匹配。
cluster.name这个设置能让你创建多个彼此相互隔离的集群。节点之间在第一次相互连接时会验证集群名。Elasticsearch只会形成一个所有节点的集群名都相同的集群。默认值是elasticsearch
,不过建议修改这个值来体现出集群的逻辑名称。
默认情况下,每一个节点在第一次启动时会自动的引导(bootstrap)自己进入一个单节点的集群。如果配置了下面任意的一个设置,自动引导会被替代:
discovery.seed_providers
discovery.seed_hosts
cluster.initial_master_nodes
若要添加一个节点到现有的一个集群中,那么配置discovery.seed_hosts
或者其他相关的设置,这样新的节点就可以发现集群中现有的master-eligible节点。若要引导一个多节点的集群,配置上文中section on cluster bootstrapping描述的cluster.initial_master_nodes
。
一旦Elasticsearch的节点加入到一个现有的集群,或者引导出一个集群后,他不会加入到其他的集群。Elasticsearch不会将两个不同的已经形成的集群进行合并,即使你随后尝试配置所有的节点到一个节点中。这是因为没法在不丢失数据的情况下合并不同的集群。你可以通过GET /
在每一个节点上获取集群的UUID来检查形成的不同集群。
如果你想要添加一个节点到现有的集群中而不是引导出不同的单节点集群,那你必须:
关闭节点
通过删除data folder的方式来完全擦除(wipe)节点
配置discovery.seed_hosts
或者 discovery.seed_providers
以及其他相关的discover 设置
重启节点并且验证下节点是否加入到集群而不是形成自己的单节点集群
如果你是要形成一个新的多节点集群而不是引导出多个单节点集群,那你必须
关闭所有的节点
通过删除每一个节点的data folder的方式来完全擦除(wipe)所有的节点
按照上文描述的方式来配置cluster.initial_master_nodes
配置discovery.seed_hosts
或者 discovery.seed_providers
以及其他相关的discover 设置
重启所有的节点并验证它们形成了单个集群
(8.2)link
master node是集群中唯一可以对集群状态(cluster states)做出变更的节点。master node一次处理一批集群状态的更新。计算变更请求并更新集群状态到集群中的所有节点。每一次的发布开始于master向集群中所有的节点广播更新后的集群状态。每一个节点都响应确认,但未应用最新接收的状态。一旦master收集到足够的来自master-eligible node的确认,这个新的集群状态被认为是提交(commit)了,然后master node广播另一个消息让节点应用提交的集群状态。每一个节点收到这条消息后,应用更新后的状态,然后发送一个确认给master。
master允许在有限的时间内完成集群状态更改到发布给所有的节点。定义在cluster.publish.timeout
设置中,默认值30s
。从publication开始算时间。如果在新的集群状态提交前达到了这个时间,集群状态的变更会被reject然后master会认为自己出现了故障。然后开始新的选举。
如果在达到cluster.publish.timeout
之前新的集群状态提交了,master会认为这次变更成功了。master会等待所有节点应用更新状态后的确认或者等待超时,然后开始处理并发布下一次的集群状态更新。如果master没有收到一些确认(比如一些节点还没有确认它们已经应用了当前的更新)这些节点被认为是它们的集群状态落后于master最新的状态。默认值是90s
。如果仍然没有成功的应用集群状态更新,那么它会被认为发生了故障并且从集群中移除。
发布的集群状态的更新内容通常是跟上一次状态的差异,这样能减低时间以及网络带宽。例如为集群状态中部分索引的更新mappings时,只要节点上有上一次的集群状态,那么就可以只发布这些索引更新对应的差异到集群中的节点上。如果一个节点缺失了上一个集群状态,比如说重新加入到了集群,master会发送完整的集群状态到这个节点,使得这个节点后续能接收差异更新。
NOTE:Elasticsearch是基于点对点的系统,节点之间相互直连。high-throughput的APIs(index,delete,Search)通常不会跟master node交互。master node负责全局的集群状态并且当节点加入或者离开时重新分配分片。每次集群状态发生变化后,最新的状态会被发布到集群中的节点上
(8.2)link
被选举为master的节点会周期性的检查集群中的每个节点来保证它们仍是连接的并且健康的。每一个节点同样周期性的检查master node的健康。这些检查分别被称为follower checks
和leader checks
。
Elasticsearch允许这些检查偶尔的出现失败或者超时的问题并且不采取任何行动。只有在连续多次检查(consecutive check)失败后才会认为这个节点发生了故障。你可以通过cluster.fault_detection.* settings来控制故障检测的行为。
如果master node检测到某个节点失去了连接,这种情况会被视为立即失败(immediate failure)。master会绕过(bypass)超时和重试这些设置,并将这个节点从集群中移除。同样的,如果某个节点检测到master node失去了连接,这种情况也会被视为立即失败。这个节点会绕过(bypass)超时和重试这些设置,尝试discovery阶段来尝试找到/选举一个新的master。
另外,每一个节点会周期性的往data path中写入一个小文件然后删除它来验证data path是否健康。如果节点发现它的data path不健康,随后会从集群中移除直到data path恢复。你可以通过monitor.fs.health settings来控制这个行为。
如果某个节点在一个合理的时间内无法应用更新后的集群状态,master node也会将其从集群中移除。超时时间默认是2分钟,这个时间从更新集群状态开始。参考Publishing the cluster state了解更多信息。
一般情况下,节点只有在故意关闭(deliberately shutdown)时才离开集群。如果某个节点意外的(unexpectedly)离开集群,解决这个问题是非常重要的。集群中的节点意外的离开集群会造成集群不稳定并且会产生一些问题。比如说:
集群的健康可能是黄色或者红色
一些分片可能会初始化并且其他的分片可能会失败
查询,索引以及监控可能会失败并且在日志中抛出异常
索引.security
可能会不可用,阻塞集群的访问
master node可能会由于频繁的集群状态更新变的忙碌
若要解决集群中的问题,首先要保证集群中有一个stable master,优先于其他的问题,集中精力关注意外退出的节点。直到集群中有一个稳定的master node以及稳定的节点成员才有可能解决其他的问题。
诊断数据(Diagnostics)和统计数据在一个不稳定的集群中通常是没有参考价值的。这些工具只是实时的在某个时间给出集群状态的视图。应该查看一段时间内集群的行为模式(pattern of behavior)。特别要关注master node上的日志,当某个节点离开集群时,master node的日志中会有以下类似的信息(为了可读性添加了换行符)
xxxxxxxxxx
71[2022-03-21T11:02:35,513][INFO ][o.e.c.s.MasterService ]
2 [instance-0000000000] node-left[
3 {instance-0000000004}{bfcMDTiDRkietFb9v_di7w}{aNlyORLASam1ammv2DzYXA}{172.27.47.21}{172.27.47.21:19054}{m}
4 reason: disconnected,
5 {tiebreaker-0000000003}{UNw_RuazQCSBskWZV8ID_w}{bltyVOQ-RNu20OQfTHSLtA}{172.27.161.154}{172.27.161.154:19251}{mv}
6 reason: disconnected
7 ], term: 14, version: 1653415, ...
这个消息说的是master node(instance-0000000000)上的MasterService
正在处理一个node-left
的任务。并列出了被移除的节点以及移除的原因。其他的节点可能有类似的日志,但是细节较少:
xxxxxxxxxx
51[2020-01-29T11:02:36,985][INFO ][o.e.c.s.ClusterApplierService]
2 [instance-0000000001] removed {
3 {instance-0000000004}{bfcMDTiDRkietFb9v_di7w}{aNlyORLASam1ammv2DzYXA}{172.27.47.21}{172.27.47.21:19054}{m}
4 {tiebreaker-0000000003}{UNw_RuazQCSBskWZV8ID_w}{bltyVOQ-RNu20OQfTHSLtA}{172.27.161.154}{172.27.161.154:19251}{mv}
5 }, term: 14, version: 1653415, reason: Publication{term=14, version=1653415}
集中关注master node上MasterService
中抛出的日志,它包含更多的细节。如果你无法查看到来自MasterService
的日志,检测下面的内容:
你正在查看的是被选举为master的节点上的日志
日志记录的时间段是正确的
日志等级需要INFO
节点在开始/结束 follow一个master node时会记录包含master node changed
的日志,你可以使用这些信息来观察一段时间内的view of the state of the master 。
节点重启的会先离开集群然后再次加入到集群中。重新加入后,MasterService
会记录它正在处理node-join
的任务。你可以从master的日志中得知节点重启的信息,因为node-join
相关日志描述的是节点的joining after restart
。在较老的Elasticsearch版本中,你可以通过查看node-left
和node-join
中的"ephemeral" ID来判断节点的重启。每次节点的启动,这个 ephemeral ID各不相同。如果某个节点意外的重启,你需要查看这个节点的日志了解它为什么关闭了。
如果节点没有重启,你应该在node-left
日志中查看节点离开集群(departure)的原因。有下面三个可能的原因:
disconnected
:master node到被移出集群的节点之间的连接关闭了
lagging
:master发布了集群状态的更新,但是被移出集群的节点没有在允许的时间内应用这次更新。默认情况下是2分钟。参考Discovery and cluster formation settings了解更多信息
followers check retry count exceeded
:master node往节点连续多次发送了健康检查,这些检查被reject或者超时了。默认情况下,每次的健康检查会在10秒后超时,Elasticsearch会在连续3次的健康检查失败后移除这个节点。参考Discovery and cluster formation settings了解更多信息
Elasticsearch被设计为运行在一个相对可靠的网络上。它在节点间打开一定数量的TCP端口并且期望这些连接一直保持打开。如果连接关闭,Elasticsearch会尝试重新连接。因此要限制偶尔的小故障(occasional blip)对集群的影响,即使是受到影响的节点暂时离开了集群。相反,重复丢失连接会严重影响运行。
集群中master node和其他节点之间的连接是特别重要的。被选举为master的节点从来不会自发地(spontaneous)关闭跟其他节点的outbound连接。同样的,一旦建立了连接,节点也不会自发地关闭outbound连接,除非节点关闭。
如果你看到因为disconnected
的原因意外的(unexpected)关闭了。可能是Elasticsearch以外的原因导致了连接关闭。一个常见的问题是防火墙配置错误:错误的超时时间(improper timeout),或者策略跟Elasticsearch不兼容。也有可能是由于其他的连接问题,比如由于硬件故障网络阻塞(network congestion)导致包的丢失。如果你是高级用户(advanced user),你可以配置下面的内容来获取更多关于网络异常的信息:
xxxxxxxxxx
21logger.org.elasticsearch.transport.TcpTransport: DEBUG
2logger.org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport: DEBUG
极端的情况下,你可能需要进行抓包并使用tcpdump
对其进行分析,查看节点之间的是否出现丢包或者被网络上的其他设备reject。
Elasticsearch需要每一个节点能合理的快速处理集群状态的更新。节点花费太长的时间来处理集群状态的更新对集群是不利的。master会以lagging
的原因将节点移除。参考Discovery and cluster formation settings了解更多的信息。
通常导致lagging的原因是节点的性能问题。然而也有可能因为严重的网络延迟。若要排除(rule out)网络延迟,确保合理配置了net.ipv4.tcp_retries2
。包含warn threshold
的日志信息可能会提供更多的根因。
如果你是高级用户(advance user),你可以通过配置下面的内容获取更多节点被移除时的正在处理的信息:
xxxxxxxxxx
11logger.org.elasticsearch.cluster.coordination.LagDetector: DEBUG
当开启这个日志后,Elasticsearch会尝试在发生故障的节点上运行Nodes hot threads 并在master节点的日志里面记录运行结果。
Elasticsearch需要每一个节点要成功响应网络消息(network message)并且合理的快速响应。如果某个节点reject请求或者完全不响应请求,可能会对集群造成损坏。如果多个连续的检查都失败了,那么Elasticsearch会以follower check retry count exceeded
的原因将节点移除。并且在node-left
消息中指出多少个连续的不成功的检查以及有多少个请求超时了。参考Discovery and cluster formation settings了解更多的信息。
超时和失败(timeout and failure)可能是因为网络延迟或者节点的性能问题。确保合理的配置net.ipv4.tcp_retries2
来消除可能导致这种不稳定的网络延迟。日志消息中包含warn threshold
的日志可能会有导致这种不稳定的原因。
如果上一次检查失败并抛出了异常,那这个异常会被报出,通常会报出需要解决的问题。如果发生了超时,也许需要理解下成功的检查中涉及到的步骤顺序:
master上运行在线程elasticsearch[master][scheduler][T#1]
的FollowerChecker
告知TransportService
往follower节点发送检查请求的消息。
master上运行在线程elasticsearch[master][transport_worker][T#2]
的TransportService
将检查请求消息传给操作系统。
master上的操作系统将消息转化成一个或者多个包通过网络发送出去。
在master与follower之间的各种的路由,防火墙和设备转发这些包,这个过程中可能有fragmenting或者defragmenting
在follower节点上的操作系统接收到包并通知Elasticsearch
follower上运行在线程elasticsearch[follower][transport_worker][T#3]
的TransportService
读取了包,然后重新构造并处理了检查请求。通常来说,检查很快就成功。如果是这样,线程马上构造一个响应并传递给操作系统
如果检查没有马上成功(例如,最近一次选举开始了)
a. follower上运行在线程elasticsearch[follower][cluster_coordination][T#4]
的FollowerChecker
处理这次请求。它开始构造响应并告诉TransportService
向master发送响应。
b. follower上运行在线程elasticsearch[follower][transport_worker][T#3]
的TransportService
将响应传给操作系统。
follower上的操作系统将响应转化成一个或者多个包通过网络发送出去。
在master与follower之间的各种的路由,防火墙和设备转发这些包,这个过程中可能有fragmenting或者defragmenting
在master节点上的操作系统接受到包并通知Elasticsearch
master上运行在线程elasticsearch[master][transport_worker][T#2]
的TransportService
读取了包,重新构造了检查响应,然后进行处理,只要这次检查没有超时。
存在很多的事情会延缓检查的完成并且导致它超时:
将检查请求传递给TransportService
后可能有long GC或者virtual machine pause。
可能会长时间的等待transport_worker
线程变成可用,或者在将检查请求传递给操作系统前可能有long GC或者virtual machine pause。
master上的系统错误(例如,a broken network card)可能会延缓通过网络发送消息,possibly indefinitely。
一路上交互的设备可能会延缓,丢弃,或者破坏包。master上的操作系统会等待并根据net.ipv4.tcp_retries2
重新发送(retransmit)没有确认的或者被破坏的包。我们建议reducing this value,因为默认值会导致很长的延迟。
follower上的系统错误(例如,a broken network card)可能会延缓通过网络发送的消息,possibly indefinitely。
可能会长时间的等待transport_worker
变成可用,或者在follower上处理请求的过程中可能有long GC或者virtual machine pause。
可能会长时间的等待cluster_coordination
变成可用,可能又要等待transport_worker
变成可用。处理请求的过程中可能有long GC或者virtual machine pause。
follower上的系统错误(例如,a broken network card)可能会延缓通过网络发送的响应
一路上交互的设备可能会延缓,丢弃,或者破坏包导致重发(retransmissions)、
master上的系统错误(例如,a broken network card)可能会延缓通过网络接收响应
可能会长时间的等待transport_worker
变成可用来处理响应,可能有long GC或者virtual machine pause。
若要确定follower check超时的原因。我们可以根据下面的内容来缩小下(narrow down)原因:
GC pause会在GC日志中记录,Elasticsearch默认会emit这些信息。通常也可以根据main node日志中的JvmMonitorService
。使用这些日志来确定是否是GC导致的延迟
VM pause同样会影响在同一个host上的其他process。VM pause还会导致系统时钟中断,Elasticsearch将在其日志中报告这一点
Packet capture可以展示系统层和网络层的错误。特别是当你同时捕获master和故障节点上的网络流量时。用于follower check的连接不用于任何其他流量,因此仅从流模式就可以很容易地识别出来,即使使用了TLS:几乎每秒钟都会有几百个字节发送到每个方向,首先是master的请求,然后是follower的响应。你应该能够观察到这种连接上的任何重传、数据包丢失或其他延迟。
长时间等待特定线程成为可用的这种问题可以通过跟踪stack dump(例如使用jstack
)或者profiling trace(例如使用Java Flight Recorder)来观察节点离开集群前(departure)几秒的信息。Nodes hot threads API有时候也能产生一些有用的信息。但需要记住的是,这个API还需要许多transport_worker
和cluster_coordination
线程用于across集群中的节点。API可能会受到你正在诊断的问题的影响。jstack
更可靠点因为他不要求任何JVM线程。涉及到follower check的线程是transport_worker
和cluster_coordination
。因此不应该长时间等待这两个线程。在Elasticsearch的日志中可能也有长时间等待线程的信息。参考Networking threading model了解更多的信息。
(8.2)link
当你启动了一个Elasticsearch的实例就是启动了一个节点。Elasticsearch集群就是一组节点的集合,这些节点有相同的cluster.name
的属性。节点加入或者离开节点,集群都会自动的识别并且最终将数据分布到可用的节点上。
如果你正在运行单个实例的Elasticsearch,你的集群中只有一个节点。所有的主分片在单个节点上。副本分片无法被分配,因此你的集群颜色是黄色的。集群有完整的功能但是在出现故障时有丢失数据的风险。
向集群中添加节点能提高承载力和可靠性(capacity and reliability)。默认情况下,一个节点既是data node并且又是一个有资格成为master的节点来控制集群。你也可以配置一个新的节点用于特定的目的,例如处理ingest request,见Nodes了解更多信息。
向集群中添加更多节点后,它会自动的分配副本分片。当所有的主分片和副本分片都可用后(active),集群状态会变更为绿色。
你可以在你的本地机器上运行多个节点来体验多个节点的集群的行为。若要添加一个节点到一个运行在你本地机器上的集群,你需要:
启动一个新的Elasticsearch实例
在elasticsearch.yml
文件中的cluster.name
配置中指定集群名称。例如若要将一个节点添加到名为logging-prod
的集群中,那么在elasticsearch.yml
中添加一行: cluster.name: "logging-prod"
若要添加一个节点到运行在多个机器上的集群,你必须要set discovery.seed_hosts,使得新的节点可以发现集群中的其他节点。
更多discover和shard allocation的信息见Discovery and cluster formation和Cluster-level shard allocation and routing settings。
随着节点的添加和移除,Elasticsearch通过自动的更新集群的投票配置(cluster's voting configuration)来保持最佳的容错水平。通过master-eligible nodes的投票计数来作出一些决策,例如选举出新的master或者提交一个新的集群状态。
建议在集群中设置一个小规模的并且固定数量的master-eligible node,只通过添加或者移除master-ineligible node来扩大或者缩小集群。然而也有一些情况需要将一些master-eligible node 添加到集群,或者从集群中移除。
如果你想要在你的集群中添加一些节点,对新节点进行简单配置来找到现有的集群然后启动它们。如果这样做是合适的话( if it is appropriate to do so),Elasticsearch会将新的节点添加到voting configuration中。
在master选举或者加入到一个现有的已经形成的集群中时,节点会发送一个join request到master,使得正式的添加到集群中。
在移除master-eligible node时,非常重要的一点是不要在同一时间移除太多的节点。例如如果当前有七个master-eligiable node,然后你想要降到三个。不是简单的马上将其中四个节点停止就行:这么做将只剩下三个节点,少于voting configuration中的一半,意味着集群不能再执行任何的动作。
更准确的说,如果在同一时间关闭一半或者更多的master-eligible node,集群通常会变成不可用。可以通过再次启动被移除的节点就可以让集群重新上线(back online)。
只要集群中至少有3个master-eligible node,作为一般的规则要一个接着一个的去移除节点,给于集群足够的时间自动的调整(adjust)voting configuration,适应新的节点集合对应的容错级别。
如果只剩下两个master-eligible node,它们都不能被安全的移除因为它们都依靠对方才能进行可靠的运行。若要移除这些节点中的一员,需要通知Elasticsearch让这个节点不再是voting configuration中的一部分。投票权利应该给于其他的节点。这样你就可以让这个节点下线并且不会阻止其他节点的正常运行。排除在voting configuration之外的节点仍然可以正常工作,但是Elasticsearch会尝试将这个节点从voting configuration中移除并不再需要它的投票。重要的是,Elasticsearch不会自动的将排除在voting configuration之外的节点重新回到voting configuration中。一旦节点成功的自动配置为voting configuration之外的节点,它就可以安全的关闭并且不会影响集群master-level的可用性。可以通过Voting configuration exclusions 将节点排除在voting configuration之外:
xxxxxxxxxx
81# Add node to voting configuration exclusions list and wait for the system
2# to auto-reconfigure the node out of the voting configuration up to the
3# default timeout of 30 seconds
4POST /_cluster/voting_config_exclusions?node_names=node_name
5
6# Add node to voting configuration exclusions list and wait for
7# auto-reconfiguration up to one minute
8POST /_cluster/voting_config_exclusions?node_names=node_name&timeout=1m
(8.2)link
可能会存在一些情况where you want to perform a full-cluster restart 或者rolling restart。对于full-cluster restart,需要关闭并重启集群中所有的节点,而对于rolling restart,则是需要在某一时间只关闭一个节点,这样提供的服务就不会停止。
Disable shard allocation
当你关闭了一个data node,allocation process将位于该节点上的主分片复制到其他节点前会等待index.unassigned.node_left.delayed_timeout
(默认一分钟),因为这会占用很多I/O。由于节点很快就要重启,那这个I/O是没有必要的。你可以在关闭data node前disabling allocation of replica,免得在超时前没有重启好节点。
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.enable": "primaries"
5 }
6}
Stop indexing and perform a flush
执行flush加快分片恢复。
xxxxxxxxxx
11POST /_flush
2.1 Temporarily stop the tasks associated with active machine learning jobs and datafeeds(Optional)
Machine leanring功能需要subscription。
当你关闭一个集群时,你有两个选项来处理machine learning jobs和datafeeds。
临时暂停跟你的machine learning jobs和datafeeds相关的任务,使用set upgrade mode API防止打开新的任务:
xxxxxxxxxx
11POST _ml/set_upgrade_mode?enabled=true
当你关闭了upgrade mode,会使用上一次自动保存的模型状态(model state)来恢复任务。这个选项可以避免在关机期间管理活跃的任务(active job)的开销,并且快于显示的停止datafeeds和关闭任务jobs。
Stop all datafeeds and close all jobs。这个选项会在关闭时保存模型状态。当集群重启后,你重新打开jobs时,它们能使用完全相同的模型。然而相较于使用upgrade mode,保存最新的模型状态会需要更长的时间,特别是你拥有很多的jobs或者说jobs有很大的模型状态。
2.2 Shut down all nodes
如果你使用systemd
运行Elasticsearch:
xxxxxxxxxx
11sudo systemctl stop elasticsearch.service
如果你使用SysV init
允许Elasticsearch:
xxxxxxxxxx
11sudo -i service elasticsearch stop
如果将Elasticsearch作为一个后台应用运行:
xxxxxxxxxx
11kill $(cat pid)
Perform any needed changes
Restart nodes
如果你有专用的master node,那可以在处理你的data node前,先启动它们,然后等待它们形成一个集群并选举出一个master。你可以通过日志查看处理进程。
一旦有足够的master-eligible node相互的发现了对方,它们就会形成一个集群并且选举出master。在这个时候,你可以使用cat health和cat nodes来监控加入到集群的节点。
xxxxxxxxxx
31GET _cat/health
2
3GET _cat/nodes
_cat/health
返回的status
列显示了集群中每一个节点的状态: red
,yellow
或者green
。
Wait for all nodes to join the cluster and report a status of yellow
当一个节点加入到集群,它开始恢复存储在本地的所有的主分片。_cat/health API最开始会报出值为red
的status
,意思是还有主分片没有被分配。
某个节点一旦恢复了本地的分片,status
会切换到yellow
,意思是所有的主分片已经恢复,但不意味着所有的副本分片已经分配好。这是意料之中的事,因为你还没有重新开启副本分片的分配。延迟副本分片的分片直到所有的节点的status
为yellow
,使得master能将副本分片分配到那些已经有本地分片拷贝的节点上。
Re-enable allocation
当所有的节点加入到集群后,并且恢复好了它们的主分片后,将cluster.routing.allocation.enable
恢复到它的默认值来重新开启分配:
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.enable": null
5 }
6}
一旦重新开启了分配,集群开始将副本分片分配到data node。在这个时间点恢复索引和查询是安全的,但如果你能等待所有的分片和副本分片成功的分配结束并且所有节点的状态都是green
,那你的集群能更快的恢复。
你可以使用_cat/health 和_cat/recovery APIs来监控处理进程:
xxxxxxxxxx
31GET _cat/health
2
3GET _cat/recovery
Restart machine learning jobs(Optional)
如果你临时暂停了machine learning jobs相关的任务,使用set upgrade mode API将它们返回到活跃状态:
xxxxxxxxxx
11POST _ml/set_upgrade_mode?enabled=false
如果你在停止节点前关闭了所有的machine learning jobs,可以在Kibana中打开jobs并开始datafeed或者使用open jobs和start datafeed APIs。
Disable shard allocation
当你关闭了一个data node,allocation process将该节点上的主分片复制到其他节点前会等待index.unassigned.node_left.delayed_timeout
(默认一分钟),因为这会占用很多I/O。由于节点很快就要重启,那这个I/O是没有必要的。你可以在关闭data node前disabling allocation of replica,免得在超时前没有重启好节点。
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.enable": "primaries"
5 }
6}
Stop non-essential indexing and perform a flush(Optional)
尽管你可以在rolling restart期间继续执行索引操作,但如果你临时的停止没有必要的索引操作并执行flush可以让分片恢复的更快
xxxxxxxxxx
11POST /_flush
Temporarily stop the tasks associated with active machine learning jobs and datafeeds (Optional)
Machine leanring功能需要subscription。
当你关闭一个集群时,你有两个选项来处理machine learning jobs和datafeeds。
临时暂停跟你的machine learning jobs和datafeeds相关的任务,使用set upgrade mode API防止打开新的任务:
xxxxxxxxxx
11POST _ml/set_upgrade_mode?enabled=true
当你关闭了upgrade mode,会使用上一次自动保存的模型状态(model state)来恢复任务。这个选项可以避免在关机期间管理活跃的任务(active job)的开销,并且快于显示的停止datafeeds和关闭任务jobs。
Stop all datafeeds and close all jobs。这个选项会在关闭时保存模型状态。当集群重启后,在你重新打开jobs时,它们能使用完全相同的模型。然而相较于使用upgrade mode,保存最新的模型状态会需要更长的时间,特别是你拥有很多的jobs或者说jobs有很大的模型状态。
如果你执行了一个rolling restart,你也可以让machining learning jobs继续运行。当你关闭一个machine leaning node时,它的jobs会自动的移动到其他节点并恢复模型状态(model state)。这个选项可以让你的jobs继续运行但是会增加集群中的负载。
Shut down a single node in case of rolling restart
如果你使用systemd
运行Elasticsearch:
xxxxxxxxxx
11sudo systemctl stop elasticsearch.service
如果你使用SysV init
允许Elasticsearch:
xxxxxxxxxx
11sudo -i service elasticsearch stop
如果将Elasticsearch作为一个后台应用运行:
xxxxxxxxxx
11kill $(cat pid)
Perform any needed changes
Restart the node you changed
启动节点后并通过查看日志文件或者提交一个_cat/nodes
请求来确认该节点加入到了集群:
xxxxxxxxxx
11GET _cat/nodes
Reenable shard allocation
对于data node,一旦它加入到集群后,就可以移除cluster.routing.allocation.enable
这个设置来开启分片分配并开始使用这个节点:
xxxxxxxxxx
61PUT _cluster/settings
2{
3 "persistent": {
4 "cluster.routing.allocation.enable": null
5 }
6}
Repeat in case of rolling restart
当节点恢复后并且集群已经稳定,对每一个需要变更的节点重复这些步骤。
Restart machine learning jobs(Optional)
如果你临时暂停了machine learning jobs相关的任务,使用set upgrade mode API将它们返回到活跃状态:
xxxxxxxxxx
11POST _ml/set_upgrade_mode?enabled=false
如果你在停止节点前关闭了所有的machine learning jobs,可以在Kibana中打开jobs并开始datafeed或者使用open jobs和start datafeed APIs。
(8.2)link
你可以将本地集群(local cluster)与其他Elasticsearch集群进行连接,即remote cluster。remote cluster可以位于不同的数据中心或者地理区域,其包含的index和data stream可以被CCR(cross-cluster replication)用于复制或者在一个本地集群使用CCS(cross-cluster search)。
在CCR中,提取(ingest)的是remote cluster上的索引数据。leader index
可以被复制(replicate)到你本地集群的一个或者多个只读的follower index上。使用CCR创建一个多集群可以提供灾备(discovery recovery),让数据的地理位置靠近你的用户,或者建立一个中心化的集群用于出报表。
Cross-cluster search能让你对一个或者多个remote cluster运行一个查询请求。这个能力可以让每一个区域都有一个全局视图的集群。允许你从本地集群执行单个查询请求,并从所有连接的remote cluster中返回数据。
开启并配置security对本地集群和remote cluster都是很重要的。当本地集群连接到一个remote cluster,本地集群上Elasticsearch的superuser能获取remote cluster上完整的读取权限。为了能安全的使用CCR和CCS,在所有连接的节点上开启enable security并且至少在每一个节点的transport level配置 Transport Layer Security (TLS) 。
此外,操作系统级别的本地管理员如果能够充分访问Elasticsearch配置文件和私钥,就有可能接管remote cluster。确保你的安全策略中包括在操作系统级别保护本地和remote cluster。
若要注册一个remote cluster,使用sniff mode 或proxy mode connect the local cluster和remote cluster中的节点。注册完remote cluster后,为CCR和CCS configure privileges。
在sniff mode中,使用名称和seed node list来创建集群。当注册了一个remote cluster后,它的cluster state从seed node中获取并且最多三个gateway nodes
作为remote cluster requests的一部分。这个模式要求gateway node 的publish address能被本地集群访问到。
Sniff模式是默认的连接模式。
gateway node
的选择取决于下面的标准:
Version:remote node必须和注册的集群兼容:
相同主版本(major version)的节点可以相互连接。例如7.0可以跟任何7.x节点通信
只有主版本的最后一个小版本可以跟下一个主版本通信。在6.x系列,6.8可以跟任意的7.x的节点通信。同时6.7只可以跟7.0通信
版本兼容性是对称的,意味着如果6.7可以跟7.0通信,那么7.0也可以跟6.7通信。下面的表描述(depict)了本地集群跟remote cluster之间的兼容性
Remote cluster | 5.0–5.5 | 5.6 | 6.0–6.6 | 6.7 | 6.8 | 7.0 | 7.1–7.16 | 7.17 | 8.0–8.2 |
---|---|---|---|---|---|---|---|---|---|
5.0–5.5 | √ | √ | × | × | × | × | × | × | × |
5.6 | √ | √ | √ | √ | √ | × | × | × | × |
6.0–6.6 | × | √ | √ | √ | √ | × | × | × | × |
6.7 | × | √ | √ | √ | √ | √ | × | × | × |
6.8 | × | √ | √ | √ | √ | √ | √ | √ | × |
7.0 | × | × | × | √ | √ | √ | √ | √ | × |
7.1–7.16 | × | × | × | × | √ | √ | √ | √ | × |
7.17 | × | × | × | × | √ | √ | √ | √ | √ |
8.0–8.2 | × | × | × | × | × | × | × | √ | √ |
IMPORTANT:Elastic仅支持在这些配置的一个子集上进行跨集群搜索,见Supported cross-cluster search configurations。
Role:默认情况下,任何non-master-eligible node 都可以作为一个gateway node。专用的master node永远不会被选为gateway node
attributes:你可以通过设置cluster.remote.node.attr.gateway设置为true
来定义gateway node。然而,这样的节点仍然需要上面的要求
在proxy模式中,使用一个名称和单个代理地址来创建一个集群。当你注册了一个remote cluster,数量可配置的socket 连接对代理地址开放。这个proxy要求将这些连接路由到remote cluster。Proxy mode不要求remote cluster node‘有用于访问的publish address。
proxy模式不是默认的连接模式并且必须要配置。Proxy mode跟sniff mode有相同的版本兼容要求。
IMPORTANT:Elastic仅支持在这些配置的一个子集上进行跨集群搜索,见Supported cross-cluster search configurations。
(8.2)link
本地集群使用 transport interface建立于remote cluster的连接。本地集群中的coordinating node会跟remote cluster中指定的节点建立long-lived TCP连接。Elasticsearch要求这些连接保持打开,即使连接空闲(idle)了很长一段时间。
NOTE:你必须要有
manage
cluster privilege用于跟remote cluster进行连接
若要在Kibana的Stack Management 中添加一个remote cluster:
从侧边导航栏选择Remote Clusters
指定remote cluster(Cluster A)的Elasticsearch endpoint URL,或者IP地址,hostname以及transport端口(默认值9300
)。例如cluster.es.eastus2.staging.azure.foundit.no:9400
或者192.168.1.1:9300
或者使用cluster update settings API 添加一个remote cluster。你也可以为本地集群中的每一个node dynamically configure remote cluster。若要对本地集群中不同的节点配置remote cluster,那么在每一个节点的elasticsearch.yml
中定义static settings。
连接remote cluster之后,configure roles and users for remote clusters。
下面的请求添加了一个名为cluster_one
的remote cluster,cluster alias
是一个唯一标识符代表连接的remote cluster用于区分本地和远程的索引。
xxxxxxxxxx
141PUT /_cluster/settings
2{
3 "persistent" : {
4 "cluster" : {
5 "remote" : {
6 "cluster_one" : {
7 "seeds" : [
8 "127.0.0.1:9300"
9 ]
10 }
11 }
12 }
13 }
14}
第6行,这个remote cluster的cluster alias
是cluster_one
·
第8行,指定remote cluster中seed node 的hostname跟transport port
你可以使用remote cluster info API 来验证本地集群是否于remote cluster连接成功。
xxxxxxxxxx
11GET /_remote/info
API的响应指出本地集群跟cluster alias
为cluster_one
的集群连接成功:
xxxxxxxxxx
131{
2 "cluster_one" : {
3 "seeds" : [
4 "127.0.0.1:9300"
5 ],
6 "connected" : true,
7 "num_nodes_connected" : 1,
8 "max_connections_per_cluster" : 3,
9 "initial_connect_timeout" : "30s",
10 "skip_unavailable" : false,
11 "mode" : "sniff"
12 }
13}
第7行,在remote cluster中,跟本地集群连接的节点数量 第10行,如果通过CCS进行查询时节点不可用,是否要跳过这个集群
使用cluster update settings API动态的配置集群中每一个节点上的remote 设置。下面的请求增加了三个remote cluster:cluster_one
,cluster_two
,cluster_three
。
参数seed
指定了remote cluster中seed node的hostname和transport port(默认值9300
)
参数mode
决定了连接模式,默认值是sniff。因为cluster_one
没有指定mode
,所以使用默认值。cluster_two
和cluster_three
都显示的使用了不同的模式:
xxxxxxxxxx
261PUT _cluster/settings
2{
3 "persistent": {
4 "cluster": {
5 "remote": {
6 "cluster_one": {
7 "seeds": [
8 "127.0.0.1:9300"
9 ]
10 },
11 "cluster_two": {
12 "mode": "sniff",
13 "seeds": [
14 "127.0.0.1:9301"
15 ],
16 "transport.compress": true,
17 "skip_unavailable": true
18 },
19 "cluster_three": {
20 "mode": "proxy",
21 "proxy_address": "127.0.0.1:9302"
22 }
23 }
24 }
25 }
26}
你可以在初始化配置后动态的为一个remote cluster更新。下面的请求为cluster_two
更新了compress settings,为cluster_three
更新 了compress settings 以及ping schedule settings
NOTE:当compression和ping schedule settings更改后,所有现有的节点连接必须关闭并重新打开,可能会导致正在运行的请求失败。
xxxxxxxxxx
161PUT _cluster/settings
2{
3 "persistent": {
4 "cluster": {
5 "remote": {
6 "cluster_two": {
7 "transport.compress": false
8 },
9 "cluster_three": {
10 "transport.compress": true,
11 "transport.ping_schedule": "60s"
12 }
13 }
14 }
15 }
16}
你可以通过将每一个remote cluster settings的值赋值为null
的方式将一个remote cluster从一个cluster settings中删除。下面的请求将cluster_two
从cluster settings中移除,留下了cluster_one
和cluster_three
:
xxxxxxxxxx
151PUT _cluster/settings
2{
3 "persistent": {
4 "cluster": {
5 "remote": {
6 "cluster_two": {
7 "mode": null,
8 "seeds": null,
9 "skip_unavailable": null,
10 "transport.compress": null
11 }
12 }
13 }
14 }
15}
如果你在elasticsearch.yml
中指定了设置,带有这些设置的节点才能连接remote cluster并且服务remote cluster的请求。
NOTE:对于每一个节点,使用cluster update settings API指定的cluster settings优先在
elasticsearch.yml
中的设置
在下面的例子中,cluster_one
, cluster_two
, 和cluster_three
是随意的(arbitrary)取的名称(aliases)用来代表连接的集群。这些名称随后用于区分本地和远程的索引。
xxxxxxxxxx
121cluster:
2 remote:
3 cluster_one:
4 seeds: 127.0.0.1:9300
5 cluster_two:
6 mode: sniff
7 seeds: 127.0.0.1:9301
8 transport.compress: true
9 skip_unavailable: true
10 cluster_three:
11 mode: proxy
12 proxy_address: 127.0.0.1:9302
第8行,cluster_two
中显示的开启了Compression
第9行,cluster_two
中失去连接的remote cluster是可选的
第12行,cluster_three
中用于连接的代理地址
(8.2)link
在connecting remote clusters之后,你需要同时在本地和远程集群中创建一个用户角色(user role)并且赋予必要的privilege。这些角色要求可以使用CCR(cross-cluster replication)和CCS(cross-cluster search)。
IMPORTANT:你必须在本地和远程集群使用相同的角色。例如,下面用于CCR的配置在本地和远程集群上使用了
remote-replication
角色名。然而,你可以在每一个集群上指定不同的角色定义。
你可以用Kibana的Stack Management,从侧边导航栏中选择Security > Roles来管理用户和角色。你也可以使用role management APIs动态的添加,更新,移除并且检索角色。当你使用APIs在native
realm中管理角色时,角色存储在内部的Elasticsearch index中。
下面的请求使用了create or update roles API。你必须至少有manage_security
的cluster privilege来使用这个API。
CCR用户在本地集群和remote cluster上要求有不同的 index和cluster privilege。使用下面的请求分别在本地集群和remote cluster上创建不同的角色,然后创建一个用户拥有这些角色。
在包含leader index的remote cluster上,CCR角色需要read_ccr
的cluster privilege,以及在leader index上的monitor
和read
privilege
NOTE:如果请求导致了on behalf of other users,那么用户在remote cluster上必须要有
run_as
的privilege。
下面的请求在remote cluster上创建了一个remote-replication
的角色:
xxxxxxxxxx
171POST /_security/role/remote-replication
2{
3 "cluster": [
4 "read_ccr"
5 ],
6 "indices": [
7 {
8 "names": [
9 "leader-index-name"
10 ],
11 "privileges": [
12 "monitor",
13 "read"
14 ]
15 }
16 ]
17}
在包含follower index的本地集群上,remote-replication
角色需要manage_ccr
的cluster privilege,follower index的monitor
,read
,write
,和manage_follow_index
privilege。
下面的请求在本地集群上创建了一个remote-replication
的角色:
xxxxxxxxxx
191POST /_security/role/remote-replication
2{
3 "cluster": [
4 "manage_ccr"
5 ],
6 "indices": [
7 {
8 "names": [
9 "follower-index-name"
10 ],
11 "privileges": [
12 "monitor",
13 "read",
14 "write",
15 "manage_follow_index"
16 ]
17 }
18 ]
19}
在每一个集群上创建完remote-replication
的角色后,使用create or update users API在本地集群上创建一个用户,并赋予remote-replication
的角色。例如,下面的请求赋予一个名为cross-cluster-user
的用户remote-replication
的角色:
xxxxxxxxxx
51POST /_security/user/cross-cluster-user
2{
3 "password" : "l0ng-r4nd0m-p@ssw0rd",
4 "roles" : [ "remote-replication" ]
5}
NOTE:在本地集群上你只需要创建这个用户。
你可以随后configure cross-cluster replication来跨中心复制你的数据。
CCS用户在本地集群和remote cluster上要求有不同的 index和cluster privilege。使用下面的请求分别在本地集群和remote cluster上创建不同的角色,然后创建一个用户拥有这些角色。
在remote cluster上,CCS角色要求为目标索引有read
和read_cross_cluster
privilege。
NOTE:如果请求导致了on behalf of other users,那么用户在remote cluster上必须要有
run_as
的privilege。
下面的请求在remote cluster上创建了一个remote-search
的角色:
xxxxxxxxxx
141POST /_security/role/remote-search
2{
3 "indices": [
4 {
5 "names": [
6 "target-indices"
7 ],
8 "privileges": [
9 "read",
10 "read_cross_cluster"
11 ]
12 }
13 ]
14}
在本地集群上,集群用于初始化CCS,用户只需要remote-search
角色。role privilege可以是空。
下面的请求在本地集群上创建了一个remote-search
角色:
xxxxxxxxxx
51POST /_security/user/cross-search-user
2{
3 "password" : "l0ng-r4nd0m-p@ssw0rd",
4 "roles" : [ "remote-search" ]
5}
NOTE:在本地集群上你只需要创建这个用户。
有remote-search
角色的用户随后可以search across clusters。
当使用Kibana跨多个集群搜索时,一个两步骤的赋权过程决定了用户是否可以访问remote cluster上的data streams和index:
首先,本地集群确定了用户是否被授权访问remote cluster。本地集群就是Kibana连接的那个集群
如果用户有权限,remote cluster随后确定用户是否能访问指定的data stream和index
为了保证Kibana用户能访问remote cluster,赋予它们一个在remote cluster上指定索引的 read privilege。你可以在remote cluster中指定data streams和index:<remote_cluster_name>:<target>
。
例如,你可能将Logstash data索引到本地集群并且周期性的将older time-based的索引归档到你的remote cluster上。你想要在这两个集群上进行搜索,那你必须在两个集群上同时enable Kibana users。
在本地集群上,创建一个logstash-reader
的角色,然后在logstash-*
上授予(grant)read
和view_index_metadata
的privilege。
NOTE:如果你将本地集群配置为其他集群的remote cluster,你本地集群上的
logstash-reader
角色同样需要赋予read_cross_cluster
privilege。
xxxxxxxxxx
141POST /_security/role/logstash-reader
2{
3 "indices": [
4 {
5 "names": [
6 "logstash-*"
7 ],
8 "privileges": [
9 "read",
10 "view_index_metadata"
11 ]
12 }
13 ]
14}
给你的Kibana用户赋予一个角色能access to Kibana以及logstash_reader
角色。例如,下面的请求创建了cross-cluster-kibana
用户并赋予kibana-access
和logstash-reader
的角色。
xxxxxxxxxx
81PUT /_security/user/cross-cluster-kibana
2{
3 "password" : "l0ng-r4nd0m-p@ssw0rd",
4 "roles" : [
5 "logstash-reader",
6 "kibana-access"
7 ]
8}
在remote cluster上,创建一个logstash-reader
并赋予read_cross_cluster
的privilege以及logstash-*
索引的read
和view_index_metadata
的privilege。
xxxxxxxxxx
151POST /_security/role/logstash-reader
2{
3 "indices": [
4 {
5 "names": [
6 "logstash-*"
7 ],
8 "privileges": [
9 "read_cross_cluster",
10 "read",
11 "view_index_metadata"
12 ]
13 }
14 ]
15}
(8.2)link
Index Modules是用于索引创建的模块,控制与索引相关的所有方面。
索引层的设置(index level settings)可以根据每一个索引进行配置,索引配置可以划分为:
只能在索引创建期间或者对一个closed index进行设置。
使用update-index-settings对live index的索引配置进行变更。
xxxxxxxxxx
11在一个关闭的索引上更改静态或者动态的索引设置可能会导致错误的设置,这些设置可能会更改(rectify)或者重建(recreating)索引(不包含被删除的数据)
下面是所有的静态索引设置,这些设置跟任何特定的索引模块都没有关联(associate):
一个索引应该拥有的主分片数量。默认值是1。这个设置只能在索引创建期间设置。不能对关闭的索引进行设置。
xxxxxxxxxx
11每个索引的分片的数量上限是1024。这个限制值是一个安全限制,它基于资源分配防止索引的创建导致集群的不稳定(destabilize)。这个限制可以通过对集群中所有节点的系统属性(system property)export ES_JAVA_OPTS="-Des.index.max_number_of_shards=128"进行变更
整数值,跟index.number_of_shards
一起用来将文档路由到主分片中,见_routing field。
Elasticsearch在对索引进行split时会使用这个值。例如,一个拥有5个分片,并且index.number_of_routing_shards
的值设置为30时,那么每个分片可以按照因子2或者3进行划分,换句话说,可以按照下面的方式进行划分:
5 -> 10 -> 30 (5个分片中的每个分片先分为2份,然后10个分片的每个分片再分为3份)
5 -> 15 -> 30(5个分片中的每个分片先分为3份,然后15个分片中的每个分片再分为2份)
5 -> 30(5个分片中的每个分片分为6份)
默认值取决于索引的主分片数量,默认情况下根据切分因子2进行切分,并且最多切分出1024个分片。
xxxxxxxxxx
11在Elasticsearch 7.0及以后的版本中,该值会影响文档分配到分片中。当使用自定义的路由规则来重新写入索引reindexing时,你必须显示指定index.number_of_routing_shards来维护文档的分配。见related breaking change(https://www.elastic.co/guide/en/elasticsearch/reference/7.0/breaking-changes-7.0.html#_document_distribution_changes)。
默认使用LZ4对store data进行压缩,但是可以设置为best_compression
,其使用DEFLATE有更高压缩率,代价是降低了存储字段stored filed的性能。如果你更新了压缩类型compression type,新的数据在段合并后会被应用(apply)。可以使用force merge对段进行强制合并。
使用自定义的路由值,路由到一定数量的分片中(区别于路由到某一个分片,这里指的是可以被路由到某个分片集合中,集合的大小即index.routing_partition_size)。默认值是1并且只能在创建索引时设置,该值必须比index.number_of_shards
小,除非index.number_of_shards
的值就是1。见Routing to an index partition查看如何使用该值。
该值描述了当前索引是否开启soft deletes。Soft deletes只能在创建索引时配置并且只能在Elasticsearch6.5及以后的版本的索引上配置。默认值为true。
从7.6.0开始后,创建索引时设置该值为false的功能已被弃用。
在分片的history retention lease过期之前,保留的最长时间。Shard history retention leases能保证在Lucene层的段合并后soft deletes仍然能被保留(retain)。如果soft deletes在follower节点上生成副本期间被合并了merge away,那么在随后的处理中会因为在leader节点上不完整的history而导致失败。默认值为12h。
该值描述了cached filters是否为nested queries提前载入了pre-loaded。可选值是true(默认值)和false。
xxxxxxxxxx
11WARNING:该配置仅针对专家用户。因为该配置允许在分片启动时进行一些开销较大的操作(processsing)并且只有在诊断集群中的问题时才有用。如果你确定要使用,你应该只是临时使用并且在不需要后移除这个配置
Elasticsearch在分片生命周期的不同时刻自动对其内容执行完整性检查。比如当恢复一个副本replica时,会对传输的每一个文件进行校验和的验证或者拍摄快照时(take a snapshot)。当启动一个节点或者完成一个分片的恢复跟重分配时,同样的会在打开一个分片时对重要的文件进行完整性的验证。你可以在对其进行拍摄快照并放到新的仓库中(fresh repository)或者恢复到一个新的节点后,手动的对整个分片进行完整性的校验。
当前配置用来描述Elasticsearch在打开一个分片时是否要对其进行额外的完整性检查。如果检查出corruption,那么阻止这个分片被打开,该配置接受下面的值:
false
在打开分片时不进行额外的corruption检查,该值是默认值并且是推荐值
checksum
通过分片中的每一个文件的校验和来校验分片的内容,这将检测从磁盘读取的数据与Elasticsearch最初写入的数据不同的情况,例如由于未检测到的磁盘损坏或其他硬件故障。这些检查需要从磁盘读取整个分片,这需要大量的时间和IO带宽,并可能通过从文件系统缓存中清除重要数据来影响集群性能
true
执行跟checksum
一样的检查,同样要检查分片的logical inconsistencies,比如RAM问题或者硬件故障导致正在写入的索引被corrupted。这些检查要求从磁盘中读取所有的分片,所以它会带来大量的时间跟IO带宽并且对分片内容进行各种检查时又会带来大量的时间,CPU以及内存开销。
下面列出的是所有的动态索引设置,这些设置与任何特定的index module没有关联。
每个主分片的副本分片(replica)的数量,默认值是1。
xxxxxxxxxx
11WARNING::该配置如果设置为0,可能导致在节点重启时临时可用性损失或者导致数据永久丢失
基于集群中节点的数量自动增加(auto-expand)副本分片的数量。该值可以设置为一个用破折号来区分上下界的值(例如: 0-5),或者使用all
作为上界(例如:0-all)。默认值是false。注意的是这种自动增加副本分片数量只会考虑allocation filtering规则,会忽视其他的分配规则例如total shards per node,如果适用的规则(applicable rules)阻止分配所有的副本分片,会导致集群变成yellow
。
如果上界是all
,那么shard allocation awareness跟cluster.routing.allocation.same_shard.host这两个配置在这个索引上会被忽略。
某个分片在多久未收到搜索或者请求后被认定为空闲的,默认值是30秒。
多久执行一次refresh操作。该操作使得最近的修改能够被搜索到。默认值是1秒。可以被设置为-1
来关闭refresh。如果没有显示的(explicit)设置这个值,那么分片在至少index.search.idle.after
时间内没有收到搜索请求则不会收到后台刷新,直到分片收到查询请求。搜索时如果遇到某个分片正在进行refresh,那么会等待下一次后台refresh,这种行为的目的是在没有搜索请求时能优化bulk Indexing。为了避免这种行为,应该显式设置1s的值作为刷新间隔。
当前索引返回的结果最大值,基于from + size
,默认值是10000。搜索请求占用的堆内存和时间与from + size
成比例。见scroll或Search After ,替换成这些方式来获得更多的搜索结果。
from + size
的最大值,用于索引的inner hits definition以及top hits aggregation。默认值为100
。inner hits和top hits aggregation占用堆内存并且跟from + size
的大小成比例,该值用于限制内存的使用。
rescore
请求中window_size
的最大值。默认值为index.max_result_window
,即10000
。查询请求会占用堆内存并且跟max(window_size, from + size)
的大小成比例,该值用于限制内存的使用。
某次查询中允许docvalue_fileds
数量最大值。默认值为100
。查询Doc-value字段有一定的开销因为可能需要查看对每一个字段中的每一个文档。
某次查询中允许script_fields
数量最大值。默认值为32
。
The maximum allowed difference between min_gram and max_gram for NGramTokenizer and NGramTokenFilter。默认值为1
。
shingle token filter中max_shingle_size和min_shingle_size之间允许的最大不同。默认值为3
。
索引中每一个分片上的可用的refresh listeners数量最大值。这些listeners用于实现refresh=wait_for。
使用_analyze API允许生成token数量最大值。默认值为10000
。
在高亮请求中,允许处理(analyze)的字符数量最大值。这个设置只有在text上请求高亮,并且没有设置offset或者term vectors才会应用。默认值为1000000
。
Terms Query中可以使用的term数量最大值。默认值为65536
。
Regexp Query中可以使用的regex的长度最大值。默认值为1000
。
(string or array of strings)通配模版(Wildcard(*
) patterns)会匹配到一个或者多个字段。下面的请求类型默认查询这些匹配到的字段:
默认值为*
,意味着匹配所有eligible字段用于term-level queries,除了metadata字段。
用于控制索引的分片分配。该参数可以设置为:
all
(默认值)- 允许对所有的分片使用shard rebalancing
primaries
- 只允许对主分片使用shard rebalancing
replicas
- 只允许对副本分配(replica)使用shard rebalancing
none
- 不允许对分片使用shard rebalancing
a deleted document’s version number仍然可用的时长,它用于further versioned operations。默认值为60s
。
用于索引的默认的ingest pipeline。如果设置了默认的pipeline但pipeline不存在,索引请求则会失败。可以使用pipeline
参数覆盖默认的pipeline。特定的pipeline名称none
意味着不允许任何pipeline 。
索引的final ingest pipeline。如果设置了final pipeline并且该pipeline不存在,索引请求则会失败。 final pipeline总是在请求中指定的pipeline以及默认的pipeline之后运行。特定的pipeline名称none
意味着不允许任何pipeline 。
NOTE:你不能使用一个final pipeline修改
_index
字段,如果pipeline尝试进行修改,索引请求则会失败。
该值描述的是索引是否默认隐藏。隐藏的索引不会在匹配到通配表达式到后返回。使用了expand_wildcards
参数的请求会受到该参数的控制。可选值为true
或者false
。
index module中其他可用的index setting:
用于定义analyzers, tokenizers, token filters and character filters。
控制分片如何,何时分配到哪一个节点。
为索引开启/关闭dynamic mapping。
控制后台合并处理程序如何对分片进行和并。
配置自定义的Similarity设置自定义查询结果的打分方式。
控制如何通过日志记录slow Query和slow fetch。
配置用于访问分片数据的文件系统。
控制transaction log以及后台flush操作。
控制索引的历史操作。
Configure indexing back pressure limits
为索引指定生命周期策略以及rollover alias。
(8.2)link
The index analysis module acts as a configurable registry of analyzers用于将一个字符串字段转化为不同的term:
添加到倒排索引(inverted index)中使得文档可以被搜索到
用于high level query,比如match query,生成用于搜索的term
见Text analysis了解配置细节。
(8.2)link
这个组件提供了per-index settings来控制分片到节点的分配:
Shard allocation filtering:控制哪些分片分配到哪些节点中。
Delayed allocation:节点离开集群后,未分配的(unassigned)分片延缓分配到节点。
Total shards per node::每一个节点上同一个索引的分片的数量限制
Data tier allocation:控制data tiers上的索引分配
(8.2)link
你可以使用shard allocation filtering来控制Elasticsearch如何为一个指定的索引进行分片的分配。索引层(index-level)的filtering跟 cluster-wide allocation filtering 和 allocation awareness结合应用。
Shard allocation filtering可以基于节点上自定义的属性或者内置属性例如_name
, _host_ip
, _publish_ip
, _ip
, _host
, _id
, _tier
以及_tier_preference
。 Index lifecycle management使用基于自定义的节点属性的filtering来决定在进行阶段转变时如何重新分配分片。
设置(setting)cluster.routing.allocation
是动态的,使得可以让live index从一些节点移到其他节点。只有在不会破坏其他路由限制(routing constraint)时才会重新分配分片,比如说不会在一台机器上同时分配主分片和副本。
例如,你可以使用自定义的节点属性来指示(indicate)一个节点的性能属性并且使用shard allocation filtering将指定的索引路由到级别最合适的硬件中(the most appropriate class of hardware)。
基于自定义的节点属性进行过滤(筛选):
在每一个节点的配置文件elasticsearch.yml
中指定过滤节点属性。例如,如果你有small
, medium
, 和 big
三类节点,你可以添加一个size
属性,基于节点的大小进行过滤。
xxxxxxxxxx
11node.attr.size: medium
你也可以在节点启动时指定自定义的属性:
xxxxxxxxxx
11./bin/elasticsearch -Enode.attr.size=medium
在索引中添加shard allocation filtering信息。index.routing.allocation
这个设置支持三种类型的过滤:include
, exclude
和require
。例如,告诉Elasticsearch使用index.routing.allocation.include
将索引text
的分片在值为big
或者medium
的节点上进行分配:
xxxxxxxxxx
41PUT test/_settings
2{
3 "index.routing.allocation.include.size": "big,medium"
4}
如果你指定多个条件并要求节点同时满足才能在其节点上进行分片的分配:
如果指定了任意require
类型的条件,所有的条件都需要满足
如果指定了任意exclude
类型的条件,所有的条件都不能满足
如果指定了任意include
类型的条件,至少一个条件需要满足
例如,将索引text
的分片移到机架值为ranck1
并且大小为big
的节点,你可以这么指定:
xxxxxxxxxx
51PUT test/_settings
2{
3 "index.routing.allocation.require.size": "big",
4 "index.routing.allocation.require.rack": "rack1"
5}
将索引分配给一个节点,这个节点的{attribute}
属性中至少包含用逗号隔开的值。
将索引分配给一个节点,这个节点的{attribute}
属性中包含所有用逗号隔开的值。
将索引分配给一个节点,这个节点的{attribute}
属性中不包含用逗号隔开的值。
索引的分配设置支持下列的内置属性:
_name | 根据节点名称进行匹配 |
---|---|
_host_ip | 根据host ip进行匹配 |
_publish_ip | 根据发布的IP(publish IP)地址进行匹配 |
_ip | 根据_host_ip 或者_publish_ip 进行匹配 |
_host | 根据hostname进行匹配 |
_id | 根据节点id进行匹配 |
_tier | 根据data tier角色匹配,见data tier allocation filtering了解更多细节 |
_tier
这种过滤属性基于node角色,只有部分角色是 data tier角色并且一般的(generic)data role会匹配到任意的tier filtering。
你可以使用通配符来指定属性值,例如:
xxxxxxxxxx
41PUT test/_settings
2{
3 "index.routing.allocation.include._ip": "192.168.2.*"
4}
(8.2)link
无论什么原因,有意为之,或者其他原因导致节点离开集群后,master的反应是:
将副本分片提升为主分片来代替在那个节点上的主分片
分配副本分片来代替缺失的副本分片(假设有足够的节点)
在剩余的节点上均匀地平衡分片
这些动作通过尽快的保证每一个分片拥有副本分片的方式来保护集群不会丢失数据。
尽管我们在node level和cluster level层限制(throttle)了并发恢复(concurrent recovery),但是这个"shard-shuffle"仍然会对集群造成一些额外的负载,如果节点很有可能很快重新加入到节点,那就没有必要做上面的动作了。想象下下面的场景:
节点5失去了网络连接
master将副本分片提升为主分片,原来的主分片在节点5上
master将新的副本分片分配到集群中的其他节点
每一个新的副本分片都要通过网络从主分片中复制获得
更多分片移动到其他节点来平衡集群
节点5在几分钟后回到了集群
master将分片分配给节点5来平衡集群
如果master可以等待几分钟,那么缺失的分片就可以以最小的网络代价将分片分配给Node5。已经被自动flush的空闲分片(没有收到索引请求的分片为idle shard)的处理过程会更快。
因为动态设置index.unassigned.node_left.delayed_timeout
,默认值为1m
,节点离开集群后,未分配(unassigned)的分配(allocate)时间可能会被延后。
可以在一个live index(或者所有的索引)上更新这个设置:
xxxxxxxxxx
61PUT _all/_settings
2{
3 "settings": {
4 "index.unassigned.node_left.delayed_timeout": "5m"
5 }
6}
开启延迟分配后,上面的场景就会变成:
节点5失去了网络连接
master将副本分片提升为主分片,原来的主分片在节点5上
master记录日志:unassigned shard的分片被延后了,以及延后时间
由于存在unassigned replica shards,集群的颜色为黄色
timeout
过期前,节点5在几分钟后回到了集群
缺失的副本分片重新分配到节点5(and sync-flushed shards recover almost immediately)
NOTE:This setting will not affect the promotion of replicas to primaries, nor will it affect the assignment of replicas that have not been assigned previously. In particular, delayed allocation does not come into effect after a full cluster restart. Also, in case of a master failover situation, elapsed delay time is forgotten (i.e. reset to the full initial delay).
如果延后分配超时,master会将缺失的分片分配给其他的节点并开始恢复。如果缺失的节点重新回到集群,并且它的分片跟主分片有相同的sync-id,shard recovery会被取消并synced shard会用于恢复。
因为这个理由,默认的timeout
只要设置为一分钟:即使shard relocation已经开始,取消恢复而是使用synced shard有更小的代价。
因为这个timeout设置导致延后分配的分片数量可以通过cluster health API查看:
xxxxxxxxxx
11GET _cluster/health
第1行,这个请求会返回一个delayed_unassigned_shards
的值。
如果某个节点不再回到集群,你可能想让Elasticsearch马上分配缺失的分片,只要将timeout的值更新为0:
xxxxxxxxxx
61PUT _all/_settings
2{
3 "settings": {
4 "index.unassigned.node_left.delayed_timeout": "0"
5 }
6}
缺失的分片开始恢复后,你就可以重新设置这个值。
(8.2)link
whenever possible,未分配的索引会根据优先级依次进行恢复。根据下面的条件对索引进行排序:
可选的index.priority
的值(higher before lower)
索引的创建时间(higher before lower)
索引的名称(higher before lower)
这意味着默认情况下,新的索引会先于旧的索引进行恢复。
使用每一个索引中可自动更新的index.priority
来自定义索引的优先顺序。例如:
xxxxxxxxxx
171PUT index_1
2
3PUT index_2
4
5PUT index_3
6{
7 "settings": {
8 "index.priority": 10
9 }
10}
11
12PUT index_4
13{
14 "settings": {
15 "index.priority": 5
16 }
17}
在上面的例子中:
首先恢复index_3
,因为它有最高的index.priority
接着恢复index_4
,因为它有第二高的index.priority
然后恢复index_2
,因为相较于index_1
它是最近创建的
最后恢复index_1
这个设置可以是一个整数,可以通过update indices settings更新。
xxxxxxxxxx
41PUT index_4/_settings
2{
3 "index.priority": 1
4}
(8.2)link
cluster-level的分片分配器(shard allocator)会尝试将一个索引的多个分片尽可能的分布在多个节点上。然而由于你拥有的分片和索引的数量,以及它们的大小,可能没法总是平均的进行分配。
下面的动态设置允许你在每一个节点上,硬性限制(hard limit)单个索引允许的分片数量:
单个节点上允许分配的分片(副本分片和主分片)总数。默认没有限制。
你也可以限制一个节点上的分片总数并且regardless of index:
(Dynamic)每一个节点上允许分配的主分片和副本分片的最大数量。默认值-1
(没有限制)。
Elasticsearch会在分片分配时检查这个值,例如,某个集群的cluster.routing.allocation.total_shards_per_node
的值为100
,并且三个节点使用下面的分片分配:
Node A: 100个分片
Node B:98个分片
Node C:1个分片
如果Node C发生故障,Elasticsearch会将分片分配到Node B。分配到Node A会超过node A的分片限制。
WARNING:这些设置强行(impose)设置了限制会导致一些分片无法被分配。使用时需要注意这个问题。
(8.2)link
你可以通过index-level的设置_tier_preference
来控制将索引分配到哪个data tier 中。
这个设置对应的data node roles:
NOTE:date role不是合法的data tier因此不能用于
_tier_preference
。The frozen tier stores partially mounted indices exclusively
该参数的值是一个list。首先将索引分配到list中第一个data tier中,该节点必须是可用的。防止无法在期望的(prefer)data tier分配索引时导致无法分配这个索引。例如,如果你设置index.routing.allocation.include._tier_preference
为data_warm,data_hot
,索引会分配到拥有data_warm
角色的节点上,如果在warm tier中没有节点,但是有拥有data_hot
角色的节点,那么就分配到这个节点中。Used in conjuction with data tiers。
(8.2)link
Elasticsearch中的一个分片即Lucene中的索引,Lucene中的索引由一个或多个段(segment)组成。Segment是内部存储单元(internal storage element),用于存储数据,并且是不可改变的(immutable)。小段会周期性的合并到大段中并expunge deletes。
合并过程使用auto-throttle来平衡合并跟其他行为,例如查询之间的硬件资源的使用。
merge scheduler(ConcurrentMergeScheduler)控制了在需要进行合并时合并的执行过程。合并在不同的线程中运行,当使用的线程数量达到最大值后,后续的合并需要等待merge thread可用后才继续执行。
merge scheduler支持下面的动态参数:
单个分片上可以同时合并的线程数量。默认值为Math.max(1, Math.min(4 <<node.processors, node.processors>> / 2))
。这个值在固态硬盘(solid-state-disk)上 work well。如果你的索引工作在spinning platter drives,将这个值降为1
。
(8.2)link
分片层的慢查询日志(slow search log)允许将慢查询(query and fetch phases)记录到一个指定的日志文件中。
可以同时为query跟fetch设置阈值,见下面的例子:
xxxxxxxxxx
91index.search.slowlog.threshold.query.warn: 10s
2index.search.slowlog.threshold.query.info: 5s
3index.search.slowlog.threshold.query.debug: 2s
4index.search.slowlog.threshold.query.trace: 500ms
5
6index.search.slowlog.threshold.fetch.warn: 1s
7index.search.slowlog.threshold.fetch.info: 800ms
8index.search.slowlog.threshold.fetch.debug: 500ms
9index.search.slowlog.threshold.fetch.trace: 200ms
上述的设置都是动态的,可以通过update indices settings设置,例如:
xxxxxxxxxx
111PUT /my-index-000001/_settings
2{
3 "index.search.slowlog.threshold.query.warn": "10s",
4 "index.search.slowlog.threshold.query.info": "5s",
5 "index.search.slowlog.threshold.query.debug": "2s",
6 "index.search.slowlog.threshold.query.trace": "500ms",
7 "index.search.slowlog.threshold.fetch.warn": "1s",
8 "index.search.slowlog.threshold.fetch.info": "800ms",
9 "index.search.slowlog.threshold.fetch.debug": "500ms",
10 "index.search.slowlog.threshold.fetch.trace": "200ms"
11}
默认情况下阈值是关闭的(默认值为-1)。
由于是分片层级别范围,意味着记录的是对于特定分片的查询。日志中不包含全部的查询请求,而是通过广播到每一个分片去执行日志的记录。跟请求级别(request level)相比,这种分片层级别的日志记录的好处是能关联到某个机器上的情况。
在log4j2.properties
文件中配置查询慢日志。
明确是什么导致了慢查询通常是非常有用的。如果在请求的header中带有X-Opaque-ID
,那么在查询慢日志(Search Slow log)中会增加一个额外的id
字段来描述user ID。
xxxxxxxxxx
191{
2 "type": "index_search_slowlog",
3 "timestamp": "2030-08-30T11:59:37,786+02:00",
4 "level": "WARN",
5 "component": "i.s.s.query",
6 "cluster.name": "distribution_run",
7 "node.name": "node-0",
8 "message": "[index6][0]",
9 "took": "78.4micros",
10 "took_millis": "0",
11 "total_hits": "0 hits",
12 "stats": "[]",
13 "search_type": "QUERY_THEN_FETCH",
14 "total_shards": "1",
15 "source": "{\"query\":{\"match_all\":{\"boost\":1.0}}}",
16 "id": "MY_USER_ID",
17 "cluster.uuid": "Aq-c-PAeQiK3tfBYtig9Bw",
18 "node.id": "D7fUYfnfTLa2D7y-xw6tZg"
19}
索引慢日志跟查询慢日志是类似的。以_index_indexing_slowlog.json.
为后缀的日志以及阈值使用跟查询慢日志相同的配置方式:
xxxxxxxxxx
51index.indexing.slowlog.threshold.index.warn: 10s
2index.indexing.slowlog.threshold.index.info: 5s
3index.indexing.slowlog.threshold.index.debug: 2s
4index.indexing.slowlog.threshold.index.trace: 500ms
5index.indexing.slowlog.source: 1000
上述的设置都是动态的,可以通过update indices settings设置,例如:
xxxxxxxxxx
81PUT /my-index-000001/_settings
2{
3 "index.indexing.slowlog.threshold.index.warn": "10s",
4 "index.indexing.slowlog.threshold.index.info": "5s",
5 "index.indexing.slowlog.threshold.index.debug": "2s",
6 "index.indexing.slowlog.threshold.index.trace": "500ms",
7 "index.indexing.slowlog.source": "1000"
8}
默认情况下,Elasticsearch会在慢日志(slowlog)中记录_source
中前1000个字符。你可以通过index.indexing.slowlog.source
更改这个限制。设置为0
或者false
将不会记录source
字段。如果设置为true
则会记录完整的_source
并且不关心其内容的大小。_source
中的内容被格式化(format)过使得可以作为日志中的单独一行。如果不格式化_source
的内容是件重要的事情,那么可以通过将index.indexing.slowlog.reformat
设置为false
来关闭formatting,这样会使得_source
中的内容以多行的形式出现在日志中。
在log4j2.properties
文件中配置索引慢日志。
可以通过设置查询\索引慢日志日志级别来设置合适的阈值,来关闭(switch off)冗长的(more verbose)设置。例如可以设置index.indexing.slowlog.level: INFO
那么我们就可以设置index.indexing.slowlog.threshold.index.debug
跟index.indexing.slowlog.threshold.index.trace
为-1
。
(8.2)link
Store模块允许你控制索引数据在磁盘上的存储和访问方式。
NOTE:这是一个low-level的设置。一些store的实现有较差的并发性或者没有对堆内存的使用进行优化。我们建议使用默认值(sticking to the defaults)
现在有不同的文件系统实现或者存储类型(file system implementations or storage types)。默认情况下,Elasticsearch会基于操作系统环境来选择最佳实现。
Storage type可以通过在config/elasticsearch.yml
文件中配置来显示的为所有的索引进行设置:
xxxxxxxxxx
11index.store.type: hybridfs
这是一个静态设置,可以在创建索引时基于每个索引进行设置:
xxxxxxxxxx
61PUT /my-index-000001
2{
3 "settings": {
4 "index.store.type": "hybridfs"
5 }
6}
WARNING:这是一个专家(expert-only)设置并且可能在未来的版本中移除。
下面的内容列出了所有支持的不同类型的存储:
默认的文件系统实现。会根据操作系统环境选择最佳的实现方式。目前在所有支持的系统上都是hybridfs
,但可能会发生变化。
deprecated::[7.15, "simplefs已经被弃用并将在8.0中移除",转而用niofs或者其他文件系统。Elasticsearch 7.15 及以后的版本中使用niofs作为simplefs这个存储类型会提供相对于simplefs更好或者相同的性能]
Simple FS 类型是使用随机访问文件的文件系统存储的简单实现(straightforward implementation)(对应Lucene中的SimpleFsDirectory)。这种实现有较差的并发性能(使用多线程会有瓶颈)并且没有对堆内存的使用进行优化。
NIO FS类型使用NIO将分片索引存储在文件系统上(对应Lucene中的NIOFSDirectory)。他允许多线程并发的读取相同的文件。由于在SUN Java实现中存在一个bug,所以不建议在Windows上使用这个类型并且没有对堆内存的使用进行优化。
MMap FS 类型通过将文件映射到内存(mmap)将分片索引存储在文件系统上(对应Lucene中的MMapDirectory)。内存映射占用了进程中的一部分虚拟内存地址空间,等于被映射文件的大小。在使用这个之前确保你拥有大量的virtual address space。
hybridfs
类型是niofs
和mmapfs
的混合类型,基于不同类型的文件的读取访问模式选择最佳的文件系统类型。目前只有Lucene term dictionary,norms和doc values文件是内存映射的。其他的文件使用Lucene的NIOFSDirectory打开。跟mmapfs
一样,在使用这个之前确保你拥有大量的virtual address space。
你可以通过node.store.allow_mmap
来限制(restrict)mmapfs
以及hybridfs
的使用。这是一个布尔类型的设置描述是否允许内存映射(memory-mapping)。默认值是允许的。这个设置是很有用的,比如说,如果你处于无法控制创建大量内存映射的环境中,那么你需要禁用使用内存映射的能力。
(8.2)link
Lucene中的commit会将更改(changes)持久化到磁盘,这是一种开销很大的操作,所以不能在每次索引或者删除操作后执行commit。如果在索引过程中退出或者硬件错误,那么在一次commit后,下一次commit前这个期间发生的变更会被移除(丢失)。
Lucene中,每一次单独的变更都执行commit的话其开销是很大的,所以每一个分片都会把写操作(writes operation)写入到事务日志(transaction log)中,即所谓的translog。所有的索引和删除操作在Lucene中建立索引后(未持久化到磁盘)会被添加到translog中,在发生crash时间后,最近被确认的(acknowledge)(已经写入Lucene的内存意味着确认,但未持久化到磁盘)但是不在Lucene commit中的操作,在分片恢复时会使用translog进行恢复。
Elasticsearch中执行flush即执行Lucene中的commit操作,并且开始一个新的translog generation。flush会在后台自动执行使得不会让translog记录太多操作,这样在恢复期间不会因为重新(replay)执行translog中的操作使得花费很多的时间。也可以通过API来执行flush,但这几乎是没必要的。
translog中的数据只有在它是fsync
并且commit后会持久化到磁盘上。硬件故障的事件或者是操作系统崩溃或者JVm崩溃或者分片失败,上一次commit之后的translog数据都会丢失。
index.translog.durability
默认设置为request
意味着在translog在成功fsynced
并且commit到主分片以及每一个分配的副本后,Elasticsearch将只报告成功的索引、删除、更新、bulk request给client。如果index.translog.durability
设置为async
,那么Elasticsearch只会每隔index.translog.sync_interval
对translog进行fsynced
并且commit,意味着当节点恢复时,任何crash之前的操作可能会丢失。
下面每个索引的dynamically updatable设置控制translog的行为:
translog fsynced 到磁盘并且commit的间隔,不考虑写操作,默认时间是5s。不允许设置100ms以下的值。
translog是否在每一次的index、delete、update或者bulk request都进行fsynced
并且commit。可以设置为下面的值:
(默认值)每一个请求都进行fsynced
并且commit。硬件错误事件时,所有确认的写操作已经commit到磁盘。
每隔index.translog.sync_interval
进行fsynced
并且commit。在出现失败事件时,上一次自动commit后的所有确认的写操作都会被丢弃
所有在Lucene中没有安全持久化的操作都会存储在translog中,这个操作对读操作是可见的,在分片被停后并且必须要恢复时,它们用于重新索引操作。这个设置控制了这些操作占用总量的最大值,防止恢复时占用太长的时间。当达到最大值时会触发flush,生成一个新的Lucene commit。默认值是512mb
。
(8.2)link
Elasticsearch有时候需要replay在分片上执行的一些操作。例如,如果一个副本分片简单的offline了,相较于从头开始(from scratch)构造这个副本分片,只replay它在offline期间丢失的操作有着更高的效率。同样的,cross-cluster replication的工作方式为:在leader cluster执行操作,然后在follower cluster上replay这些操作。
Elasticsearch对索引的写操作在Lucene层来说只有两个操作:索引(添加)一篇文档或者删除现有的文档。更新操作的实现方式是先删除旧的文档然后索引一篇新的文档。索引一篇文档到Lucene的操作包含了所有用于replay的信息,但是对于文档的删除不是这样的。为了解决这个问题, Elasticsearch使用了名为软删除
soft deletes的功能来保留Lucene索引上最近的删除信息,使得可以用于replay。
Elasticsearch只保留索引中一些最近删除的文档,是因为软删除的文档仍然占用一些空间。Elasticsearch最终会完全的丢弃这些软删除的文档来释放空间使得索引不会随着时间一直增长。Elasticsearch不需要replay在分片上执行的每一个操作,因为总是有可能在remote cluster执行完整的分片拷贝。然而,复制整个分片可能比replay一些丢失的操作要花费更多的时间,所以Elasticsearch会保留期望在未来用于replay的所有操作。
Elasticsearch使用了shard history retention leases
的机制来追踪期望在未来将用于replay的操作。每一个可能需要用于replay的操作的分片拷贝(shard copy)必须首先为自己创建一个shard history retention leases。例如,this shard copy might be a replica of a shard或者在使用cross-cluster replication时的follower index。每一个retention lease会追踪对应的分片拷贝第一个没有收到的操作的序号,随着分片拷贝收到新的操作,增加retention lease中的序号值,表示不会在未来replay这些操作。一旦soft-deleted operations没有被任何的retention lease占用(hold),Elasticsearch就会丢弃它们。
如果分片拷贝发生了故障,那它会停止更新它的shard history retention lease。意味着Elasticsearch会保留所有的新的操作,使得在分片拷贝恢复后执行replay操作。然而,retention lease只持续一个有限的时间。如果分片拷贝没有及时的恢复,那retention lease会到期。这可以保护Elasticsearch,防止出现某个分片拷贝永久性的故障后,retention history会永久保留的问题,一旦某个retention lease到期后,Elasticsearch可以开始丢失这些历史信息。如果分片拷贝在retention lease到期后才恢复,那Elasticsearch会回退到拷贝整个索引,因为这时候再也没法简单的replay丢失的操作了。retention lease的过期时间默认是12h
,这个时间已经足够长了,适用于大多数的场景。
索引是否开启soft deletes(Deprecated in 7.6.0)。Soft deletes只能在创建索引时配置并且是Elasticsearch6.5及以后的版本中的索引创建。默认值为true
。
在retention lease过期前保留shard history retention lease的最大时间。Shard history retention leases 能保证Lucene在执行段的合并时仍然会保留Soft delete对应的信息。如果soft delete在复制(replicate)到follower之前,在段的合并期间被合并了,那么follower中接下来的操作会因为leader上不完整的历史操作而失败。默认值12h
。
(8.2)link
在Elasticsearch中创建一个新的索引时,可以配置每一个分片中的段内文档的排序方式。默认情况下,Lucene不会使用任何的排序。index.sort.*
这些设置定义了使用哪些字段用于段中文档的排序。
WARNING:nested fields are not compatible with index sorting。because they rely on the assumption that nested documents are stored in contiguous doc ids, which can be broken by index sorting。An error will be thrown if index sorting is activated on an index that contains nested fields.
下面的例子定义如何在单个字段上定义一个排序
xxxxxxxxxx
161PUT my-index-000001
2{
3 "settings": {
4 "index": {
5 "sort.field": "date",
6 "sort.order": "desc"
7 }
8 },
9 "mappings": {
10 "properties": {
11 "date": {
12 "type": "date"
13 }
14 }
15 }
16}
第5行,根据date
字段排序
第6行,降序
也可以使用多个字段进行排序:
xxxxxxxxxx
201PUT my-index-000001
2{
3 "settings": {
4 "index": {
5 "sort.field": [ "username", "date" ],
6 "sort.order": [ "asc", "desc" ]
7 }
8 },
9 "mappings": {
10 "properties": {
11 "username": {
12 "type": "keyword",
13 "doc_values": true
14 },
15 "date": {
16 "type": "date"
17 }
18 }
19 }
20}
第5行,先根据username
字段排序,再根据date
字段排序
第6行,根据username
字段使用升序,根据date
使用降序
Index Sorting支持下面的设置:
index.sort.field
指定用于排序的一个或者多个字段。只允许开启doc_values
的boolean
、numeric
、 date
、 keyword
类型的字段。
index.sort.order
每一个排序字段的排序规则:
asc
: 升序
desc
:降序
index.sort.mode
Elasticsearch支持使用mutil-values的字段用于排序。这个参数描述的是使用哪个值用于文档排序。两个参数值可选:
min
:选择最小的值
max
:选择最大的值
index.sort.missing
这个参数描述的是,如果文档中缺失用于排序的字段该如何对这篇文档进行排序。两个参数值可选:
_last
:没有用于排序的字段的文档排在最后面
_first
:没有用于排序的字段的文档排在最前面
WARNING:Index Sorting只有定义在创建索引时。不能对现有的索引添加或者更新排序。Index Sorting在索引期间有一定的开销,因为文档必须在flush和merge时进行排序。你应该在启动这个功能前测试下对你的应用的影响
默认情况下,Elasticsearch中的一个查询请求需要访问匹配查询的所有文档才能获取到某个排序条件下的TopN的文档。然而当Index sort和查询中指定的排序(Search sort)一致时,每个段中的查询TopN的操作就有可能限制需要访问的文档数量。例如,如果索引中的文档根据timestamp
字段排序:
xxxxxxxxxx
161PUT events
2{
3 "settings": {
4 "index": {
5 "sort.field": "timestamp",
6 "sort.order": "desc"
7 }
8 },
9 "mappings": {
10 "properties": {
11 "timestamp": {
12 "type": "date"
13 }
14 }
15 }
16}
第6行,索引按照timestamp
字段降序排序(most recent first)
你可以搜索最新的10条事件:
xxxxxxxxxx
71GET /events/_search
2{
3 "size": 10,
4 "sort": [
5 { "timestamp": "desc" }
6 ]
7}
Elasticsearch会检测到每一个的top doc都已经排序过了,并且将只会对每个段中的TopN的文档进行比较。剩余的满足查询条件的文档用于统计结果的数量以及构造aggregation。
如果你只是想要查找最新的10条事件并且对于满足查询条件的文档的数量不感兴趣,那么你可以将track_total_hits
设置为false
:
xxxxxxxxxx
81GET /events/_search
2{
3 "size": 10,
4 "sort": [
5 { "timestamp": "desc" }
6 ],
7 "track_total_hits": false
8}
第4行,Index sort将用于top documents并且每个段都会在收集了前10篇文档后就提前退出。
这次Elasticsearch不会去统计满足查询条件的文档的数量,并且能在每一个段收集了N篇文档后就提前退出。
xxxxxxxxxx
91{
2 "_shards": ...
3 "hits" : {
4 "max_score" : null,
5 "hits" : []
6 },
7 "took": 20,
8 "timed_out": false
9}
第3行,由于提前退出了,所以不知道满足查询条件的文档数量。
NOTE:聚合操作会收集所有满足查询条件的文档,无视
track_total_hits
的值。
(8.2)link
Index sorting对organize Lucene doc id(不要跟_id
混淆(conflate))非常有用,使得conjunction更加高效(a AND b AND ...)。如果任意一个clause没有匹配到,那么整个conjunction都不会匹配,conjunction可以变的非常高效。通过使用Index sorting,我们可以把没有匹配的文档放一起,有助于在范围很大的ID区间内进行跳跃,跳过那些不匹配的文档。
这个trick只能在low-cardinality的字段上工作。A rule of thumb,你首先应该将拥有low-cardinality以及经常用于过滤的字段作为排序字段。The sort order (asc or desc) does not matter as we only care about putting values that would match the same clauses close to each other。
例如,如果你正在索引汽车用于销售,你应该根据fuel type, body type, make, year of registration and finally mileage进行排序。
(8.2)link
索引文档到Elasticsearch会引入系统的内存和CPU的负载。每一个索引操作,包含了coordinating, primary和replica这几个阶段。这些阶段在集群中的多个节点上运行。
Indexing pressure可以由外部的索引请求或者内部的,例如恢复,跨集群副本分片(cross-cluster replication)引起。如果系统中有过多的索引工作,集群会达到饱和(saturated)。这会对其他操作造成不好的(adversely)影响,例如查询,cluster coordination以及background processing。
为了防止出现这个问题,Elasticsearch内部会监控索引负载。当负载超过一定的限制,新的索引操作会被reject。
外部的索引操作(external indexing operation)会经历三个阶段:coordinating,primary以及replica。见Basic write model。
节点设置indexing_pressure.memory.limit
严格限制了未完成(outstanding)索引请求的可用的字节数量。这个设置默认值是堆内存的10%。
每一个索引阶段(indexing stage)的开头,Elasticsearch会统计一个索引请求占用的字节数。在索引阶段的结尾才会释放这个统计。意味着上游的阶段会统计请求开销直到所有的下游都完成。例如,coordinating请求会保留统计直到primary和replica阶段完成,primary请求会保留统计直到每一个同步的replica返回响应,如果有需要的话开启replica重试。
当未完成的coordinating, primary和 replica的索引占用字节超过配置的值后,节点会在coordinating或者primary阶段reject这个新的索引请求。
当未完成的replica阶段的索引字节超过配置限制的1.5倍后,节点会在replicate阶段reject这个索引请求。这种设计意味着由于indexing pressure应用于节点,它会天然的在发生未完成的replica工作时会停止coordinating和primary工作。
indexing_pressure.memory.limit
的默认为10%
。你应该深思熟虑后再去修改它。这个设置只应用于索引请求。这意味着其他的索引开销(buffers,listener,等等)同样需要堆内存。Elasticsearch的其他组件同样需要内存,这个值设置的太高会影响其他有内存操作(memory operation)的操作和组件。
你可以使用node stats API查询indexing pressure metrics。
索引请求可能需要消费的字节数。当达到或者超过这个限制后,节点会reject新的coordinating和primary操作。当replica操作消费超过1.5倍,节点会reject新的replicate请求。默认值是堆内存的10%。
(8.2)link
Mapping是定义一篇文档中的字段(field)如何存储以及索引的过程。
每篇文档都是字段的集合,每个字段都有自己的date type。在mapping你的数据时,你要创建一个mapping定义,定义中包含的字段的集合跟文档相关(pertinent)。mapping的定义同样包括了metadata fields。比如_source
字段,它自定义了文档相关metadata的处理方式。
使用dynamic mapping
和explicit mapping
来定义你的数据。每一种方式在你使用数据过程中有不同的好处。例如,显示的对字段进行映射使得不按照默认方式进行处理,或者你想要获得对字段的创建有更多的控制,随后你可以允许Elasticsearch对其他的字段进行动态映射。
NOTE:在7.0.0之前,mapping的定义包含一个类型名(type name)。Elasticsearch 7.0.0以及之后的版本再也不接受一个默认的mapping。见Removal of mapping types。
Experiment with mapping options
使用Define runtime fields in a search request来体验不同的mapping options,同时还可以在查询请求中通过覆盖mapping中字段值的方式来解决你的Index mapping中的错误。
Dynamic mapping适合你刚开始接触Elasticsearch/Mapping时体验数据的探索。Elasticsearch会自动的增加新的字段,你只需要索引文档就可以了。你可以添加字段到top-level mapping中,以及inner object和nested字段中。
使用dynamic templates自定义mappings,根据满足的条件动态的添加字段。
Explicit mapping允许你精确的选择并定义mapping definition,例如:
哪些string字段应该作为full text field
哪些字段包含数值,日期或者地理位置
日期的format
为dynamically added fields自定义规则来控制mapping
在不需要reindex的前提下,使用runtime fields作出策略变更。runtime field和indexed field配合使用来平衡资源使用和性能。索引文件体积会更小,但是会降低查询性能。
在一个索引中定义太多的字段会导致mapping explosion,引起OOM的错误以及难以恢复的情况。
想象这么一种场景,每一篇新的文档都会引入新的字段,比如使用了dynamic mapping。每一个字段添加到Index mapping中,随着mapping中字段的数量的增长便成长为了问题。
使用mapping limit settings限制mapping(手动或者动态创建)中字段的数量,防止因为文档导致mapping explosion。
(8.2)link
Elasticsearch最重要的一个功能就是能够让你快速的去探索数据。索引一篇文档,你不需要首先创建一个索引,然后定义好mapping,接着定义每一个字段,你只需要直接对一篇文档进行索引,索引名、类型、字段都会自动的配置好。
xxxxxxxxxx
21PUT data/_doc/1
2{ "count": 5 }
第1行,创建一个名为data的索引,以及_doc
类型的mapping,和一个字段名为count
,数据类型为long
的字段。
这种自动检测并且添加(addition of)一个新字段的机制称为动态索引dynamic mapping。可以基于下列方法来自定义你的动态索引规则:
Dynamic field mappings:用于管理字段的检测的规则
Dynamic templates:配置自定义的规则实现自动的添加字段
TIP:Index templates允许你为新的索引配置默认的mapping,settings 以及aliases ,不管是自动还是显示创建索引。
(8.2)link
当Elasticsearch检测到文档中有一个新字段,会默认将它添加到mapping中。参数dynamic控制了其行为方式。
你可以设置参数dynamic
的值为true
或者runtime
来显示的指引Elasticsearch对于即将到来的文档动态的创建字段。当开启了dynamic field mapping ,Elasticsearch会根据下表中的规则决定如何映射为不同类型的字段。
NOTE:Elasticsearch仅支持下表中字段的数据类型(field data type)的自动检测,你必须显式的指定其他数据类型。
JSON data type | "dynamic":"true" | "dynamic":"runtime" |
---|---|---|
null | No field added | No field added |
true or false | boolean | boolean |
double | float | double |
long | long | long |
object | object | No field added |
array | Depends on the first non-null value in the array | Depends on the first non-null value in the array |
string that passes date detection | date | date |
string that passes numeric detection | float or long | double or long |
string that doesn’t pass date detection or numeric detection | text with a .keyword sub-field | keyword |
你可以同时在文档层或者object层来关闭动态mapping。将参数dynamic
设置为false
来忽略新的字段,设置为strict
来当Elasticsearch遇到一个未知的字段时就reject这个文档。
TIP:可以通过update mapping API来对现有的字段的参数
dynamic
的值进行更新。
你可以为Date detection和Numeric detection自定义dynamic field mapping的规则。你可以使用dynamic_templates来自定义个性化的mapping规则并应用到额外的dynamic fields。
如果开启了date_detection
(默认开启),新来的string类型的字段会被检测是否其字段值能够被dynamic_date_formats
中指定的日期模板匹配到。如果匹配到了,就添加对应的新的date字段。
dynamic_date_formats
的默认值是:
[ "strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]
例如:
xxxxxxxxxx
61PUT my-index-000001/_doc/1
2{
3 "create_date": "2015/09/02"
4}
5
6GET my-index-000001/_mapping
第6行,create_date
字段以"yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"
这种format作为date被添加。
可以将date_detection
的值设置为false
来关闭date detection。
xxxxxxxxxx
111PUT my-index-000001
2{
3 "mappings": {
4 "date_detection": false
5 }
6}
7
8PUT my-index-000001/_doc/1
9{
10 "create_date": "2015/09/02"
11}
第8行,create_date
被添加为text字段。
另外,dynamic_date_formats
也能定制为你自己的formats):
xxxxxxxxxx
111PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_date_formats": ["MM/dd/yyyy"]
5 }
6}
7
8PUT my-index-000001/_doc/1
9{
10 "create_date": "09/25/2015"
11}
JSON原生支持浮点型以及整数的数据类型,但是有些应用或者语言有时会将数值类型作为(render)字符串类型。通常正确的解决办法是显示的映射这些字段。但是numeric detection能使得自动实现(默认关闭):
xxxxxxxxxx
121PUT my-index-000001
2{
3 "mappings": {
4 "numeric_detection": true
5 }
6}
7
8PUT my-index-000001/_doc/1
9{
10 "my_float": "1.0",
11 "my_integer": "1"
12}
第10行,my_float
被添加为float字段
第11行,my_integer
被添加为integer字段
(8.2)link
Dynamic template能让你在默认的dynamic field mapping rules之外,更好的控制Elasticsearch如何映射你的数据。通过设置dynamic参数的值为true
或者runtime
来开启dynamic mapping。然后你就可以基于下面的匹配条件使用dynamic template来自定义mapping,动态的应用(apply)到字段:
match_mapping_type对Elasticsearch检测到的数据类型进行操作
match and unmatch 使用一个pattern对字段名进行匹配
path_match and path_unmatch对full dotted path的字段名进行操作
如果dynamic template没有‘定义match_mapping_type
,match
或者path_match
,那它不会匹配任何字段。 it won’t match any field. You can still refer to the template by name in dynamic_templates section of a bulk request。
使用{name}
和{dynamic_type}
这些template variables作为mapping中的占位符。
IMPORTANT:只有当某个字段包含一个具体的值才能添加Dynamic field mappings。当字段中包含
null
或者空的数组时,Elasticsearch不会添加一个dynamic field mapping。如果在dynamic_template
中使用了null_value
选项,只有在一篇有具体的值的文档索引后才能应用这个选项。
Dynamic template由一组带名称的object数组组成:
xxxxxxxxxx
91 "dynamic_templates": [
2 {
3 "my_template_name": {
4 ... match conditions ...
5 "mapping": { ... }
6 }
7 },
8 ...
9 ]
第3行,模板的名称可以是任意的string value
第4行,匹配条件可以包含: match_mapping_type
, match
, match_pattern
, unmatch
, path_match
, path_unmatch
第5行,匹配到的字段应该使用的mapping
如果提供的mapping中包含了一个非法的mapping 片段(snippet),则返回一个validation error。Validation会在索引期间应用dynamic template时发生,并且在大多数情况下发生在dynamic template更新后。提供一个非法的mapping片段可能会因为下面的一些条件导致更新或者dynamic template的校验失败:
如果你创建了一个动态模板(dynamic template)但没有指定match_mapping_type,这个模板仍然可以是有效的,前提是它至少对一个预定义的映射类型(如字符串)有效。然而,如果匹配了模板的某个字段索引为不同的类型,那么会返回validation error。例如,配置一个没有match_mapping_type
的dynamic template在索引期间认为string类型是合法的,但是匹配了模板的某个字段索引为了long类型,那么会返回一个validation error。建议在mapping片段中配置mathc_mapping_type
为期望的JSON类型或者想要的type
如果在mapping片段中使用{name}
占位符,那当更新dynamic template时会跳过validation。这是因为在那个时间点字段名是未知的。在索引期间应用模版时才进行validation。
模版是有序处理的,使用匹配到的第一个模板。当通过update mapping API更新新的dynamic template后,所有现有的模板会被覆盖。这使得最开始添加的模板可以被重新排序或者被删除。
如果你想要Elasticsearch动态映射某些类型的新字段作为runtime field,那么在索引mapping中设置"dynamic":"runtime"
。这些字段不会被索引,在查询期间从_source
中获取。
或者你可以使用默认的动态mapping规则,然后创建dynamic模版将指定的字段映射为runtime field。你可以在索引mapping中设置"dynamic":"true"
然后创建一个dynamic template将某些类型的新字段映射为runtime field。
比如说你的数据中的每一个字段的字段名都是以ip_
开始的。基于dynamic mapping rules,Elasticsearch将通过了numeric
检测的字段映射为float
或者long
,然而你可以创建一个dynamic template将string类型的值映射为runtime filed并且字段的类型为ip
。
下面的请求中定义了一个名为strings_as_ip
的dynamic template。当Elasticsearch检测到新的字段值为字符串字段匹配到了ip*
这个pattern,它就会将这些字段映射为runtime field并且字段的类型为ip。因为ip
字段没有动态映射,你可以使用带"dynamic":"true"
或者"dynamic":"runtime"
的模版。
xxxxxxxxxx
161PUT my-index-000001/
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "strings_as_ip": {
7 "match_mapping_type": "string",
8 "match": "ip*",
9 "runtime": {
10 "type": "ip"
11 }
12 }
13 }
14 ]
15 }
16}
见this template来了解如何使用dynamic template将字符串字段映射为indexed filed或者runtime field。
match_mapping_type
是数据类型,用于JSON parser。由于JSON不能区分integer
和long
,double
和float
,浮点型数值都被认为是double
的JSON数据类型,integer
数值都被认为是long
。
NOTE:在dynamic mappings中,Elasticsearch总是选择wider data type。唯一特例是
float
,它需要比double
更少的存储空间并且对于大多数应用来说精度是足够的。Runtime field不支持float
,这就是为什么"dynamic":"runtime"
使用的是double
。
Elasticsearch会自动检测下面的数据类型:
JSON data type | "dynamic":"true" | "dynamic":"runtime" |
---|---|---|
null | 不增加字段 | 不增加字段 |
true/false | boolean | Boolean |
double | float | double |
long | long | long |
object | object | 不增加字段 |
array | 取决于数组中第一个不是null的值 | 取决于数组中第一个不是null的值 |
string(通过了Numeric detection的解析) | float或者long | double或者long |
string(通过了date detection的解析) | date | date |
string没有通过date或者numeric的解析 | text以及.keyword的子字段 | keyword |
使用通配符(*
)匹配所有的数据类型。
例如,如果想要将数值类型的字段都映射为integer
而不是long
,并且所有的字符串字段映射为text
和keyword
,我们可以使用下面的模板:
xxxxxxxxxx
351PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "integers": {
7 "match_mapping_type": "long",
8 "mapping": {
9 "type": "integer"
10 }
11 }
12 },
13 {
14 "strings": {
15 "match_mapping_type": "string",
16 "mapping": {
17 "type": "text",
18 "fields": {
19 "raw": {
20 "type": "keyword",
21 "ignore_above": 256
22 }
23 }
24 }
25 }
26 }
27 ]
28 }
29}
30
31PUT my-index-000001/_doc/1
32{
33 "my_integer": 5,
34 "my_string": "Some string"
35}
第33行,my_integer
字段映射为一个integer
第34行,my_string
字段映射为text
以及一个keyword
(见multi-field)
match
参数使用pattern来匹配字段名(field name),同时unmatch
使用pattern排除match
参数匹配到的字段。
match_pattern
参数调整了match
参数的行为,支持Java regular expression来匹配字段名而不是使用简单的通配符,例如:
xxxxxxxxxx
21 "match_pattern": "regex",
2 "match": "^profit_\d+$"
下面的例子匹配了所有字段名以long_
开头的字段,但是排除了以_text
结尾的字段,并将它们的字段值映射为long
:
xxxxxxxxxx
231PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "longs_as_strings": {
7 "match_mapping_type": "string",
8 "match": "long_*",
9 "unmatch": "*_text",
10 "mapping": {
11 "type": "long"
12 }
13 }
14 }
15 ]
16 }
17}
18
19PUT my-index-000001/_doc/1
20{
21 "long_num": "5",
22 "long_text": "foo"
23}
第21行,long_num
字段映射为long
第22行,long_text
使用了默认的string
映射
path_match
和path_unmatch
参数的工作方式跟match
和unmatch
是一样的,不同的是,它们是应用在full dotted path上,而不是final name,比如:some_object.*.some_filed
。
下面的例子将object
类型的字段name
中的除了middle
的所有字段都copy to
顶层的full_name中:
xxxxxxxxxx
261PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "full_name": {
7 "path_match": "name.*",
8 "path_unmatch": "*.middle",
9 "mapping": {
10 "type": "text",
11 "copy_to": "full_name"
12 }
13 }
14 }
15 ]
16 }
17}
18
19PUT my-index-000001/_doc/1
20{
21 "name": {
22 "first": "John",
23 "middle": "Winston",
24 "last": "Lennon"
25 }
26}
注意的是path_match
和pathc_match
除了匹配leaf filed(上图中的first
、middle
、last
就是leaf field)还会匹配object path(下图中的name.tittle
就是object path)。例如,索引下面的文档会报错因为path_match
这个设置同样会匹配到name.tille
,这个字段是个object不能映射为text:
xxxxxxxxxx
111PUT my-index-000001/_doc/2
2{
3 "name": {
4 "first": "Paul",
5 "last": "McCartney",
6 "title": {
7 "value": "Sir",
8 "category": "order of chivalry"
9 }
10 }
11}
{name}
和{dynamic_type}
是mapping中的占位符,它们分别用字段名和检测到的动态类型(detected dynamic type)进行值的替换。下面的例子中将所有字段值为字符串的字段名作为analyzer的值,并且关闭所有不是字符串类型的字段的doc_values:
xxxxxxxxxx
321PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "named_analyzers": {
7 "match_mapping_type": "string",
8 "match": "*",
9 "mapping": {
10 "type": "text",
11 "analyzer": "{name}"
12 }
13 }
14 },
15 {
16 "no_doc_values": {
17 "match_mapping_type":"*",
18 "mapping": {
19 "type": "{dynamic_type}",
20 "doc_values": false
21 }
22 }
23 }
24 ]
25 }
26}
27
28PUT my-index-000001/_doc/1
29{
30 "english": "Some English text",
31 "count": 5
32}
第30行,因为字段名为english
的字段值是string
类型,那么它的字段名将作为anlyzer参数的值
第31行,因为count
的字段值是个数值,所以它会被动态映射为long
类型并且关闭doc_values
下面是一些可能比较有用的dynamic template:
当你设置了"dynamic":"true"
,Elasticsearch会将字符串字段映射为text
字段以及keyword
的子字段。如果你只要索引结构化的内容并且对不需要全文检索,你可以让Elasticsearch只映射为keyword
字段。然而,你在查询那些被索引的字段时,你必须提供精确的关键字。
xxxxxxxxxx
151PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "strings_as_keywords": {
7 "match_mapping_type": "string",
8 "mapping": {
9 "type": "keyword"
10 }
11 }
12 }
13 ]
14 }
15}
与上一个例子相反的(contrary)是,如果你只关心字符串字段上的全文检索并且不计划使用聚合,排序或者精确(exact )查询,你可以让Elasticsearch映射为text
了:
xxxxxxxxxx
151PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "strings_as_text": {
7 "match_mapping_type": "string",
8 "mapping": {
9 "type": "text"
10 }
11 }
12 }
13 ]
14 }
15}
或者你可以创建一个dynamic template将你的字符串字段在mapping的runtime块映射为keyword
字段。当Elasticsearch检测到string
类型的字段,会将这些字段创建为runtime field并且字段的类型为keyword
。
尽管你的字符串字段不会被索引,但是它们的值会存储在_source
中并且可以用于查询请求,聚合,过滤和排序。
例如下面的请求创建了一个dynamic template将字符串字段映射为runtime field并且字段的类型为keyword
,尽管runtime
的定义是空白的。但是基于Elasticsearch用于添加字段的类型到mapping的dynamic mapping rules,新的字符串字段会被映射为runtime field并且字段的类型为keyword
。所有没有通过date detection和numeric detection的string
都会被自动的映射为keyword
:
xxxxxxxxxx
131PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "strings_as_keywords": {
7 "match_mapping_type": "string",
8 "runtime": {}
9 }
10 }
11 ]
12 }
13}
你可以索引一篇简单的文档:
xxxxxxxxxx
51PUT my-index-000001/_doc/1
2{
3 "english": "Some English text",
4 "count": 5
5}
当你查看mapping时,你会看到english
字段是一个runtime field并且字段的类型为keyword
。
xxxxxxxxxx
11GET my-index-000001/_mapping
xxxxxxxxxx
241{
2 "my-index-000001" : {
3 "mappings" : {
4 "dynamic_templates" : [
5 {
6 "strings_as_keywords" : {
7 "match_mapping_type" : "string",
8 "runtime" : { }
9 }
10 }
11 ],
12 "runtime" : {
13 "english" : {
14 "type" : "keyword"
15 }
16 },
17 "properties" : {
18 "count" : {
19 "type" : "long"
20 }
21 }
22 }
23 }
24}
Norms是索引期间(index-time)的打分因子。如果你不关心打分,那你就不能根据文档分数对文档进行排序,你可以禁用索引中的打分因子的存储来节省一些空间。
xxxxxxxxxx
221PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "strings_as_keywords": {
7 "match_mapping_type": "string",
8 "mapping": {
9 "type": "text",
10 "norms": false,
11 "fields": {
12 "keyword": {
13 "type": "keyword",
14 "ignore_above": 256
15 }
16 }
17 }
18 }
19 }
20 ]
21 }
22}
模版中出现的keyword
字段是dynamic mappings中默认存在的。当然如果你不需要在这个字段上执行精确查询或者聚合,你可以不需要它们,你可以正如上文中描述的那样来移除它们。
当在Elasticsearch中处理时序数据时,通常有很多的数值类型的字段,你会经常使用它们用于聚合但是从来不用于过滤。在这种场景下,你可以不索引这些字段来节省磁盘空间并且能获得一些索引速度的提升:
xxxxxxxxxx
251PUT my-index-000001
2{
3 "mappings": {
4 "dynamic_templates": [
5 {
6 "unindexed_longs": {
7 "match_mapping_type": "long",
8 "mapping": {
9 "type": "long",
10 "index": false
11 }
12 }
13 },
14 {
15 "unindexed_doubles": {
16 "match_mapping_type": "double",
17 "mapping": {
18 "type": "float",
19 "index": false
20 }
21 }
22 }
23 ]
24 }
25}
第18行,正如默认的dynamic mapping rules,double会被映射为float
,因为通常来说精确是足够的,并且只要一半的磁盘空间。
(8.2)link
相较于Elasticsearch猜测你的数据类型,如果你更了解数据类型,你可能想要指定自己的显示的mapping(explicit mapping)。
你可以在create an index 或者add fields to an existing index时创建字段的mapping(field mapping)。
你可以使用create index API创建一个explicit mapping的新的索引。
xxxxxxxxxx
101PUT /my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "age": { "type": "integer" },
6 "email": { "type": "keyword" },
7 "name": { "type": "text" }
8 }
9 }
10}
第5行,创建一个名为age
,类型为integer的字段
第6行,创建一个名为email
,类型为keyword的字段
第7行,创建一个名为name
,类型为text的字段
你可以使用update mapping API在一个现有的(existing)索引中增加一个或者多个字段。
下面的例子中创建了一个名为employee-id
,类型为keyword
,mapping参数为index的字段。这意味着employee-id
字段的字段值会被存储但是不会被索引,即无法用于查询。
xxxxxxxxxx
91PUT /my-index-000001/_mapping
2{
3 "properties": {
4 "employee-id": {
5 "type": "keyword",
6 "index": false
7 }
8 }
9}
除了支持更新mapping parameters,你不能对现有的字段更改mapping或者字段的类型。更改一个现有的字段会invalidate已经写入到索引文件中的数据。
如果你更改data stream的backing index中字段的类型,见Change mappings and settings for a data stream。
如果你需要在其他索引中更改字段的mapping,那么使用正确的mapping创建一个新的索引,然后reindex你的数据到那个索引中。
重命名一个字段会invalidate使用旧的字段名索引的数据,你可以增加一个alias字段创建一个替换的字段名。
你可以使用get mapping API查看现有的索引的mapping。
xxxxxxxxxx
11GET /my-index-000001/_mapping
下面的API返回下面的响应:
xxxxxxxxxx
211{
2 "my-index-000001" : {
3 "mappings" : {
4 "properties" : {
5 "age" : {
6 "type" : "integer"
7 },
8 "email" : {
9 "type" : "keyword"
10 },
11 "employee-id" : {
12 "type" : "keyword",
13 "index" : false
14 },
15 "name" : {
16 "type" : "text"
17 }
18 }
19 }
20 }
21}
如果你只想指定一个或者多个字段并查看它们的mapping,你可以使用get field mapping API。
如果你的索引包含很多的字段或者你不需要完整的索引的mapping,这个接口是很有用的。
下面的请求展示了employee-id
字段的mapping。
xxxxxxxxxx
11GET /my-index-000001/_mapping/field/employee-id
这个API返回下面的响应:
xxxxxxxxxx
151{
2 "my-index-000001" : {
3 "mappings" : {
4 "employee-id" : {
5 "full_name" : "employee-id",
6 "mapping" : {
7 "employee-id" : {
8 "type" : "keyword",
9 "index" : false
10 }
11 }
12 }
13 }
14 }
15}
(8.2)link
runtime field
是在查询阶段计算出来的字段,可以让你用于:
添加新的字段到现有的文档并且不需要重新索引你的数据
不需要你理解是如何结构化( how it’s structured)的就可以开始处理你的数据
在查询期间覆盖索引中字段的值
在不用修改现有策略情况下,定义用于特殊用途的字段
你可以通过search API访问runtime field
,就像访问其他字段一样,Elasticsearch不会对runtime field
区别对待。你可以在index mapping或者search request中定义runtime field
,提供不同的方式体现了runtime field
内部的灵活性。
在_search
API上使用fields参数来 retrieve the values of runtime fields,runtime filed不会在_source
中展示,但是fields
API可以在所有字段上使用,即使这些字段不在_source
中。
Runtime fields用于处理日志数据(log data)时候特别好用(见例子),特别是当你不确定数据结构时。尽管查询性能会降低,但是你能更快的索引日志数据,并且索引体积较小。
由于runtime fiels没有被索引,所以新增的runtime field不会增加索引大小。你直接在索引mapping中定义runtime fields来节省存储开销以及提高提取(ingestion)速度。你能够更快的把数据提取到Elastic Stack并可以正确的访问。当你定义了一个runtime field,你可以在在查询请求中使用它用于聚合、过滤、和排序。
如果你让一个runtime field成为一个索引字段(indexed field),你不需要修改任何请求来指定runtime filed。甚至你可以指定某个字段在某些索引中是runtime field,同时在某个索引中是一个索引字段。你可以灵活的选择哪些字段作为索引字段还是runtime field。
Runtime fileds最核心、最重要的好处就是它提供了在提取(ingest)文档后可以在这篇文档中添加字段的能力。这种能力简化了mapping的设计,因为你不需要提前决定用哪种数据类型进行解析,可以在任何时候修改。使用runtime field使得有更小的索引和更快的提取时间,这将使用更少的资源并降低你的操作成本。
使用脚本的_search
API中的很多方法可以用runtime field替代。runtime field的使用会受到runtime field中定义的脚本需要处理的文档数量的影响。例如,如果你在_search
API上使用fields
参数来retrieve the values of a runtime field,脚本跟script fields 一样,都只会对top hits进行处理。
你可以使用script fields访问_source
中的值并且返回基于脚本计算出的值。runtime fields有相同的能力,但是能提供更好的灵活性,因为在查询请求中可以对runtime fields进行查询以及聚合。Script field只能获取数据。
同样的,你可以在查询请求中基于脚本写一个script query来过滤文档。runtime field提供一个类似的功能并且更加灵活。你编写一个脚本来创建字段值,它们能在任何地方都可见,比如fields,all queries以及aggregation。
你可以使用脚本来sort search results,但使用runtime field可以完成完全相同的工作。
如果你将查询请求中所有的脚本替换为runtime field,性能是差不多的,因为它们计算的文档数量是一样的。这个功能的性能很大程度上取决于脚本需要计算的文档数量。
Runtime fields使用更少的磁盘空间并且在查询你的数据上提供灵活性,但是会因为runtime脚本中定义的计算而影响查询性能。
若要平衡性能和灵活性,将那些你常用于查询,过滤,例如timestamp这类字段,对这些字段进行索引。在执行查询时,Elasticsearch会自动的使用这些索引字段(indexed field)在很快的响应时间内获得结果。随后你可以使用runtime fields来限制Elasticsearch需要用于计算的字段的数量。索引字段和runtime field的配合使用,可以提供数据弹性以及如何为其他的字段定义查询。
使用asynchronous search API用于执行包含runtime field的查询。这种查询方法可以抵偿计算每一篇包含runtime field的文档的性能影响。如果query不能同步返回数据集,你将在这些结果变得可见后异步获取到。
IMPORTANT:对runtime field的查询是昂贵的,如果search.allow_expensive_queries设置为
false
,Elasticsearch不允许昂贵的查询并且reject所有对runtime field的查询。
(8.2)link
通过在mapping中定义一个runtime
section以及一个Painless script就可以映射到runtime fileds。这个脚本可以访问所在文档中所有的内容,包括可以通过params._source
来读取到_source
的内容以及任何字段加上字段值。在查询时,查询条件中包含了这个scripted field(下文中的day_of_week),脚本会运行并且生成结果给scripted field。
当定义了一个脚本用于runtime fields时,脚本中必须使用 emit method来输出计算的值。
下面例子的脚本中,将根据@timestamp
的字段值计算出的结果赋值给day of the week
,day of the week
是一个data
类型:
xxxxxxxxxx
161PUT my-index-000001/
2{
3 "mappings": {
4 "runtime": {
5 "day_of_week": {
6 "type": "keyword",
7 "script": {
8 "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
9 }
10 }
11 },
12 "properties": {
13 "@timestamp": {"type": "date"}
14 }
15 }
16}
runtime
中可以是以下的类型:
boolean
composite
date
double
geo_point
ip
keyword
long
date
类型的runtime fileds的字段值格式跟date
字段接受的format是一致的。
lookup
类型的runtime fileds允许从关联的索引中检索字段的信息,见retrieve fields from related indices。
如果开启了dynamic field mapping,并且dynamic
参数设置了参数值runtime
,新的字段会作为runtime自动添加到索引mapping中。
xxxxxxxxxx
111PUT my-index-000001
2{
3 "mappings": {
4 "dynamic": "runtime",
5 "properties": {
6 "@timestamp": {
7 "type": "date"
8 }
9 }
10 }
11}
Runtime fileds通常使用脚本来管理数据,然后有些情况下可以不使用脚本来使用runtime fileds。比如你想从_source
中检索出某个字段的字段值并不作任何的改变,那你不需要提供脚本,你只需要创建一个不带脚本的runtime fileds:
xxxxxxxxxx
101PUT my-index-000001/
2{
3 "mappings": {
4 "runtime": {
5 "day_of_week": {
6 "type": "keyword"
7 }
8 }
9 }
10}
当没有提供脚本时,在查询期间,Elasticsearch会从_source
中查找到跟runtime fields字段名一样的字段,如果存在的话就返回该字段值。如果不存在,那么在response中不会包含runtime fields的任何值。
在大多数情况下,会优先从doc_values中读取。由于在Lucene中不同的存储方式,相较于从_source
中检索,通过doc_values的方式读取速度更快。
但是有些场景下需要从_source
中检索字段的信息。比如说由于text
类型默认情况下不会有doc_values
,所以不得不从_source
中读取,在其他情况下,可以选择禁用特定字段上的doc_values。
NOTE:你可以在
params._source
中添加前缀来检索想要的数据(比如params._source.day_of_week
),为了简单起见,定义runtime fields时,建议尽可能在mapping定义中不使用脚本。
你可以在任何时候更新或者移除runtime fileds。添加一个相同名称的runtime files就可以覆盖现在有的runtime files,通过设置为null来移除现在有的runtime files:
xxxxxxxxxx
61PUT my-index-000001/_mapping
2{
3 "runtime": {
4 "day_of_week": null
5 }
6}
更新或者移除runtime files可能会使得正在查询的请求返回的数据不一致。由于mapping的变更可能会影响到每一个分片会访问到不同版本的脚本。
WARNING:如果你对runtime fileds进行变更或者移除,依赖runtime fileds的Existing queries or visualizations in Kibana可能会出现失败的情况,比如可视化的图表使用runtime field类型的
ip
被改为boolean
,或者被移除后会导致出错。
(8.2)link
你可以在查询请求中指定一个runtime_mappings
区域/块(section)来创建runtime fields,它只属于这次的请求。你指定了一个脚本作为runtime_mappings
的一部分跟adding a runtime field to the mappings是一样的。
在查询请求中定义一个runtime field跟在index mapping中使用的格式是一样的,所以只需要把查询请求中runtime_mappings
区域中的内容直接拷贝到index mapping的runtime
区域中。
下面的查询请求添加了day_of_week
字段到runtime_mappings
区域中,这个字段的字段值将被自动的计算出来并且只处于这次查询请求的上下文中(only within the context of this search request)。
xxxxxxxxxx
181GET my-index-000001/_search
2{
3 "runtime_mappings": {
4 "day_of_week": {
5 "type": "keyword",
6 "script": {
7 "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
8 }
9 }
10 },
11 "aggs": {
12 "day_of_week": {
13 "terms": {
14 "field": "day_of_week"
15 }
16 }
17 }
18}
你甚至可以在查询请求中定义runtime field,并且它的字段值可以从其他的runtime field中计算出来。比如你批量索引了传感器的数据:
xxxxxxxxxx
131POST my-index-000001/_bulk?refresh=true
2{"index":{}}
3{"@timestamp":1516729294000,"model_number":"QVKC92Q","measures":{"voltage":"5.2","start": "300","end":"8675309"}}
4{"index":{}}
5{"@timestamp":1516642894000,"model_number":"QVKC92Q","measures":{"voltage":"5.8","start": "300","end":"8675309"}}
6{"index":{}}
7{"@timestamp":1516556494000,"model_number":"QVKC92Q","measures":{"voltage":"5.1","start": "300","end":"8675309"}}
8{"index":{}}
9{"@timestamp":1516470094000,"model_number":"QVKC92Q","measures":{"voltage":"5.6","start": "300","end":"8675309"}}
10{"index":{}}
11{"@timestamp":1516383694000,"model_number":"HG537PU","measures":{"voltage":"4.2","start": "400","end":"8625309"}}
12{"index":{}}
13{"@timestamp":1516297294000,"model_number":"HG537PU","measures":{"voltage":"4.0","start": "400","end":"8625309"}}
你意识到你将数值类型numeric type索引成了text
类型,你想要在measures.start
以及measures.end
字段上进行聚合操作却无法实现,因为你不能在text
类型上进行聚合。Runtime fields to the rescue!。你可以添加runtime fileds,跟索引字段index field(即measures.start和measures.end)的字段名保持一致并且修改字段的类型:
xxxxxxxxxx
111PUT my-index-000001/_mapping
2{
3 "runtime": {
4 "measures.start": {
5 "type": "long"
6 },
7 "measures.end": {
8 "type": "long"
9 }
10 }
11}
Runtime field的优先级(take precedence over)比相同字段名的索引字段高。这种灵活性使得你可以投影(shadow)现有的字段(existing fields)并且计算出新值而不用修改字段本身的信息。如果你在索引数据的时候犯错了,那么你可以使用runtime field在查询阶段重新计算,将获得的值进行override values。
现在你可以很容易的在measures.start
和measures.end
字段上执行average aggregation:
xxxxxxxxxx
151GET my-index-000001/_search
2{
3 "aggs": {
4 "avg_start": {
5 "avg": {
6 "field": "measures.start"
7 }
8 },
9 "avg_end": {
10 "avg": {
11 "field": "measures.end"
12 }
13 }
14 }
15}
响应中返回了聚合结果并且没有改变 the underlying data(指的是measures.start跟measures.end):
xxxxxxxxxx
101{
2 "aggregations" : {
3 "avg_start" : {
4 "value" : 333.3333333333333
5 },
6 "avg_end" : {
7 "value" : 8658642.333333334
8 }
9 }
10}
另外你还可以定义一个runtime field作为查询请求中的一部分来计算出一个值,在相同的query(基于上文中介绍的query)中对这个runtime field进行stats aggregation。
duration
这个runtime field不在 index mapping中,当我们仍然可以使用这个字段进行查询和聚合计算。下面的请求将返回经过计算的duration
的值并且从用于聚合的文档中基于数值类型的数据执行stats aggregation来计算统计值(即count、min、max、avg、sum):
xxxxxxxxxx
201GET my-index-000001/_search
2{
3 "runtime_mappings": {
4 "duration": {
5 "type": "long",
6 "script": {
7 "source": """
8 emit(doc['measures.end'].value - doc['measures.start'].value);
9 """
10 }
11 }
12 },
13 "aggs": {
14 "duration_stats": {
15 "stats": {
16 "field": "duration"
17 }
18 }
19 }
20}
尽管 duration
这个runtime field只在这次查询请求的上下文中存在,你也可以对该字段进行搜索和聚合。灵活性十分的强大,它可以弥补(rectify)你在index mapping中犯的错误并且在单个查询请求中动态的完成计算。
xxxxxxxxxx
111{
2 "aggregations" : {
3 "duration_stats" : {
4 "count" : 6,
5 "min" : 8624909.0,
6 "max" : 8675009.0,
7 "avg" : 8658309.0,
8 "sum" : 5.1949854E7
9 }
10 }
11}
(8.2)link
如果您创建的runtime field与mapping已经存在的字段具有相同的名称,则runtime field会将对mapping filed进行投影(shadow)。在查询时,Elasticsearch计算runtime field,根据脚本计算一个值,并将该值作为查询的一部分返回。因为runtime field会对mapping field进行投影(shadow),所以你可以覆盖搜索中返回的值,而不需要修改mapping field。
例如你在索引my-index-000001
中添加了以下的文档:
xxxxxxxxxx
131POST my-index-000001/_bulk?refresh=true
2{"index":{}}
3{"@timestamp":1516729294000,"model_number":"QVKC92Q","measures":{"voltage":5.2}}
4{"index":{}}
5{"@timestamp":1516642894000,"model_number":"QVKC92Q","measures":{"voltage":5.8}}
6{"index":{}}
7{"@timestamp":1516556494000,"model_number":"QVKC92Q","measures":{"voltage":5.1}}
8{"index":{}}
9{"@timestamp":1516470094000,"model_number":"QVKC92Q","measures":{"voltage":5.6}}
10{"index":{}}
11{"@timestamp":1516383694000,"model_number":"HG537PU","measures":{"voltage":4.2}}
12{"index":{}}
13{"@timestamp":1516297294000,"model_number":"HG537PU","measures":{"voltage":4.0}}
你后来意识到HG537PU
传感器并没有报告它们的真实电压。索引值应该是报告值的1.7倍。你可以在_search
请求的runtime_mappings
部分定义一个脚本,来投影(shadow)电压场,并在查询时计算一个新值,而不是重新索引数据。
如果你想要查询 model number是HG537PU
的文档:
xxxxxxxxxx
81GET my-index-000001/_search
2{
3 "query": {
4 "match": {
5 "model_number": "HG537PU"
6 }
7 }
8}
相应中包含了model number是HG537PU
的索引值:
xxxxxxxxxx
361{
2 ...
3 "hits" : {
4 "total" : {
5 "value" : 2,
6 "relation" : "eq"
7 },
8 "max_score" : 1.0296195,
9 "hits" : [
10 {
11 "_index" : "my-index-000001",
12 "_id" : "F1BeSXYBg_szTodcYCmk",
13 "_score" : 1.0296195,
14 "_source" : {
15 "@timestamp" : 1516383694000,
16 "model_number" : "HG537PU",
17 "measures" : {
18 "voltage" : 4.2
19 }
20 }
21 },
22 {
23 "_index" : "my-index-000001",
24 "_id" : "l02aSXYBkpNf6QRDO62Q",
25 "_score" : 1.0296195,
26 "_source" : {
27 "@timestamp" : 1516297294000,
28 "model_number" : "HG537PU",
29 "measures" : {
30 "voltage" : 4.0
31 }
32 }
33 }
34 ]
35 }
36}
下面的请求中定义了一个runtime field,它的脚本中将估算字段值为HG537PU
的model_number
字段,会对于每一个满足条件的voltage
的字段值乘以1.7。
通过在_serach
API中使用field参数,你可以检索measures.voltage field
,满足查询请求中的文档会根据脚本对该字段值进行计算。
xxxxxxxxxx
201POST my-index-000001/_search
2{
3 "runtime_mappings": {
4 "measures.voltage": {
5 "type": "double",
6 "script": {
7 "source":
8 """if (doc['model_number.keyword'].value.equals('HG537PU'))
9 {emit(1.7 * params._source['measures']['voltage']);}
10 else{emit(params._source['measures']['voltage']);}"""
11 }
12 }
13 },
14 "query": {
15 "match": {
16 "model_number": "HG537PU"
17 }
18 },
19 "fields": ["measures.voltage"]
20}
我们从结果中可以看到,经过计算的measures.voltage
的字段值分别是7.14
和6.8
。That’s more like it!。runtime field作为查询请求的一部分进行字段值计算,而不修改索引值,索引值仍然在响应中返回:
xxxxxxxxxx
461{
2 ...
3 "hits" : {
4 "total" : {
5 "value" : 2,
6 "relation" : "eq"
7 },
8 "max_score" : 1.0296195,
9 "hits" : [
10 {
11 "_index" : "my-index-000001",
12 "_id" : "F1BeSXYBg_szTodcYCmk",
13 "_score" : 1.0296195,
14 "_source" : {
15 "@timestamp" : 1516383694000,
16 "model_number" : "HG537PU",
17 "measures" : {
18 "voltage" : 4.2
19 }
20 },
21 "fields" : {
22 "measures.voltage" : [
23 7.14
24 ]
25 }
26 },
27 {
28 "_index" : "my-index-000001",
29 "_id" : "l02aSXYBkpNf6QRDO62Q",
30 "_score" : 1.0296195,
31 "_source" : {
32 "@timestamp" : 1516297294000,
33 "model_number" : "HG537PU",
34 "measures" : {
35 "voltage" : 4.0
36 }
37 },
38 "fields" : {
39 "measures.voltage" : [
40 6.8
41 ]
42 }
43 }
44 ]
45 }
46}
(8.2)link
在_search
API中使用参数fields来检索runtime fields的字段值。_source
中不会展示runtime fields的信息,但是fields
可以展示所有字段的信息,即使有些字段不是_source
中的一部分。
例如下面的请求中增加了一个名为day_of_week
的runtime filed。这个runtime filed中包含了一个根据@timestamp
字段计算星期的脚本。请求中定义了"dynamic":"runtime"使得新添加的字段在mapping中将作为runtime fileds。
xxxxxxxxxx
171PUT my-index-000001/
2{
3 "mappings": {
4 "dynamic": "runtime",
5 "runtime": {
6 "day_of_week": {
7 "type": "keyword",
8 "script": {
9 "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
10 }
11 }
12 },
13 "properties": {
14 "@timestamp": {"type": "date"}
15 }
16 }
17}
我们ingest一些样本数据,有两个索引字段@timestamp
和message
:
xxxxxxxxxx
231POST /my-index-000001/_bulk?refresh
2{ "index": {}}
3{ "@timestamp": "2020-06-21T15:00:01-05:00", "message" : "211.11.9.0 - - [2020-06-21T15:00:01-05:00] \"GET /english/index.html HTTP/1.0\" 304 0"}
4{ "index": {}}
5{ "@timestamp": "2020-06-21T15:00:01-05:00", "message" : "211.11.9.0 - - [2020-06-21T15:00:01-05:00] \"GET /english/index.html HTTP/1.0\" 304 0"}
6{ "index": {}}
7{ "@timestamp": "2020-04-30T14:30:17-05:00", "message" : "40.135.0.0 - - [2020-04-30T14:30:17-05:00] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
8{ "index": {}}
9{ "@timestamp": "2020-04-30T14:30:53-05:00", "message" : "232.0.0.0 - - [2020-04-30T14:30:53-05:00] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
10{ "index": {}}
11{ "@timestamp": "2020-04-30T14:31:12-05:00", "message" : "26.1.0.0 - - [2020-04-30T14:31:12-05:00] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
12{ "index": {}}
13{ "@timestamp": "2020-04-30T14:31:19-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:19-05:00] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"}
14{ "index": {}}
15{ "@timestamp": "2020-04-30T14:31:27-05:00", "message" : "252.0.0.0 - - [2020-04-30T14:31:27-05:00] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
16{ "index": {}}
17{ "@timestamp": "2020-04-30T14:31:29-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:29-05:00] \"GET /images/hm_brdl.gif HTTP/1.0\" 304 0"}
18{ "index": {}}
19{ "@timestamp": "2020-04-30T14:31:29-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:29-05:00] \"GET /images/hm_arw.gif HTTP/1.0\" 304 0"}
20{ "index": {}}
21{ "@timestamp": "2020-04-30T14:31:32-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:32-05:00] \"GET /images/nav_bg_top.gif HTTP/1.0\" 200 929"}
22{ "index": {}}
23{ "@timestamp": "2020-04-30T14:31:43-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:43-05:00] \"GET /french/images/nav_venue_off.gif HTTP/1.0\" 304 0"}
下面的请求使用 search API检索day_of_week
字段,这个字段在mapping定义为runtime field。这个字段的字段值在查询期间动态的生成,并不需要重新对数据索引,也不需要索引这个字段。这种灵活性使得不需要更改索引字段的情况下改变映射:
xxxxxxxxxx
81GET my-index-000001/_search
2{
3 "fields": [
4 "@timestamp",
5 "day_of_week"
6 ],
7 "_source": false
8}
上述请求会为每一个匹配的文档返回一个day_of_week
字段。我们可以定义另一个名为client_ip
的runtime filed,对message
字段进行操作来获取它的字段值来完善接下来的查询。
xxxxxxxxxx
111PUT /my-index-000001/_mapping
2{
3 "runtime": {
4 "client_ip": {
5 "type": "ip",
6 "script" : {
7 "source" : "String m = doc[\"message\"].value; int end = m.indexOf(\" \"); emit(m.substring(0, end));"
8 }
9 }
10 }
11}
使用字段名为client_ip
的runtime filed,指定一个字段值来进行查询:
xxxxxxxxxx
101GET my-index-000001/_search
2{
3 "size": 1,
4 "query": {
5 "match": {
6 "client_ip": "211.11.9.0"
7 }
8 },
9 "fields" : ["*"]
10}
这次查询的响应中命中2条,day_of_week
(sunday
)的值会在查询中使用mapping中定义的脚本计算获得。结果中包含了IP address的值为211.11.9.0
的文档:
xxxxxxxxxx
351{
2 ...
3 "hits" : {
4 "total" : {
5 "value" : 2,
6 "relation" : "eq"
7 },
8 "max_score" : 1.0,
9 "hits" : [
10 {
11 "_index" : "my-index-000001",
12 "_id" : "oWs5KXYB-XyJbifr9mrz",
13 "_score" : 1.0,
14 "_source" : {
15 "@timestamp" : "2020-06-21T15:00:01-05:00",
16 "message" : "211.11.9.0 - - [2020-06-21T15:00:01-05:00] \"GET /english/index.html HTTP/1.0\" 304 0"
17 },
18 "fields" : {
19 "@timestamp" : [
20 "2020-06-21T20:00:01.000Z"
21 ],
22 "client_ip" : [
23 "211.11.9.0"
24 ],
25 "message" : [
26 "211.11.9.0 - - [2020-06-21T15:00:01-05:00] \"GET /english/index.html HTTP/1.0\" 304 0"
27 ],
28 "day_of_week" : [
29 "Sunday"
30 ]
31 }
32 }
33 ]
34 }
35}
WARNING:这个功能属于技术预览(technical preview ),可能在未来的发行版中变更或者移除。
_serach
API中的fields参数可以用来检索类型为lookup
的runtime fileds,它的字段值可以从其他索引中获取。
xxxxxxxxxx
371POST ip_location/_doc?refresh
2{
3 "ip": "192.168.1.1",
4 "country": "Canada",
5 "city": "Montreal"
6}
7
8PUT logs/_doc/1?refresh
9{
10 "host": "192.168.1.1",
11 "message": "the first message"
12}
13
14PUT logs/_doc/2?refresh
15{
16 "host": "192.168.1.2",
17 "message": "the second message"
18}
19
20POST logs/_search
21{
22 "runtime_mappings": {
23 "location": {
24 "type": "lookup",
25 "target_index": "ip_location",
26 "input_field": "host",
27 "target_field": "ip",
28 "fetch_fields": ["country", "city"]
29 }
30 },
31 "fields": [
32 "host",
33 "message",
34 "location"
35 ],
36 "_source": false
37}
第24行,定义了类型为lookup
的runtime field,从目标索引中使用term查询获得的结果作为runtime field的字段值
第25行,ip_location即目标索引
第26行,host字段的字段值将作为term查询条件中的字段值
第27行,ip字段的字段名将作为term查询条件中的字段名
第28行,要求从目标索引ip_location中返回的字段,见fields
上述的查询将从ip_location索引中为返回的搜索命中的每个ip地址返回country和city。
xxxxxxxxxx
431{
2 "took": 3,
3 "timed_out": false,
4 "_shards": {
5 "total": 1,
6 "successful": 1,
7 "skipped": 0,
8 "failed": 0
9 },
10 "hits": {
11 "total": {
12 "value": 2,
13 "relation": "eq"
14 },
15 "max_score": 1.0,
16 "hits": [
17 {
18 "_index": "logs",
19 "_id": "1",
20 "_score": 1.0,
21 "fields": {
22 "host": [ "192.168.1.1" ],
23 "location": [
24 {
25 "city": [ "Montreal" ],
26 "country": [ "Canada" ]
27 }
28 ],
29 "message": [ "the first message" ]
30 }
31 },
32 {
33 "_index": "logs",
34 "_id": "2",
35 "_score": 1.0,
36 "fields": {
37 "host": [ "192.168.1.2" ],
38 "message": [ "the second message" ]
39 }
40 }
41 ]
42 }
43}
在返回的结果中,每一个结果中单独的包含了一个从目标索引中获取的数据,这些数据分组展示(JSON中的array)。每一个结果中最多展示一条从目标索引中获取的数据,如果上文中的term查询获取了多个结果,那么就随机返回一条。
(8.2)link
Runtime fields在运行时的上下文中定义,你可以在search query 的上下文定义runtime field或者在index mapping中定义一个runtime区域(section)。如果你决定将runtime field写入到索引来获得较好的性能,你只要把完整的runtime filed的定义(包括脚本)都移动到 index mapping的上下文中。Elasticsearch会自动的使用这些索引字段来驱动查询,获得更快的响应时间。这种能力使得只需要你写一次脚本并应用到任何支持runtime field的上文中。
NOTE:目前不支持索引composite runtime field。
你可以使用runtime filed来限制Elasticsearch需要计算的字段的数量。将索引字段与runtime filed结合使用可以为你索引的数据以及如何为其他字段定义查询方式提供灵活性。
IMPORTANT:当你把runtime field写入到索引中,你不能更新其包含的脚本。如果你需要更新脚本,那么使用这个更新后的脚本并且创建一个新的字段。
比如你的公司想要替换一些旧的压力值。已经连接的传感器只能报告真实读数的一小部分。相较于使用新的传感器来得到新的压力值,你决定基于现有的读数进行计算。基于现有的报告值,你可以为在索引my-index-000001
中定义下列的mapping:
xxxxxxxxxx
191PUT my-index-000001/
2{
3 "mappings": {
4 "properties": {
5 "timestamp": {
6 "type": "date"
7 },
8 "temperature": {
9 "type": "long"
10 },
11 "voltage": {
12 "type": "double"
13 },
14 "node": {
15 "type": "keyword"
16 }
17 }
18 }
19}
你批量写入了传感器的一些样本数据,包括了从每一个传感器中的voltage的读数:
xxxxxxxxxx
131POST my-index-000001/_bulk?refresh=true
2{"index":{}}
3{"timestamp": 1516729294000, "temperature": 200, "voltage": 5.2, "node": "a"}
4{"index":{}}
5{"timestamp": 1516642894000, "temperature": 201, "voltage": 5.8, "node": "b"}
6{"index":{}}
7{"timestamp": 1516556494000, "temperature": 202, "voltage": 5.1, "node": "a"}
8{"index":{}}
9{"timestamp": 1516470094000, "temperature": 198, "voltage": 5.6, "node": "b"}
10{"index":{}}
11{"timestamp": 1516383694000, "temperature": 200, "voltage": 4.2, "node": "c"}
12{"index":{}}
13{"timestamp": 1516297294000, "temperature": 202, "voltage": 4.0, "node": "c"}
在跟一些网页工程师沟通后,你们意识到传感器应该报告2倍于现在的压力值,并且有可能更高。你创建了一个名为voltage_corrected
的runtime filed,然后检索当前的voltage
的值并且乘以2:
xxxxxxxxxx
161PUT my-index-000001/_mapping
2{
3 "runtime": {
4 "voltage_corrected": {
5 "type": "double",
6 "script": {
7 "source": """
8 emit(doc['voltage'].value * params['multiplier'])
9 """,
10 "params": {
11 "multiplier": 2
12 }
13 }
14 }
15 }
16}
在_search
API上使用fields参数来检索计算后的值:
xxxxxxxxxx
81GET my-index-000001/_search
2{
3 "fields": [
4 "voltage_corrected",
5 "node"
6 ],
7 "size": 2
8}
在审查了传感器数据以及运行了一些测试后,对于报告的传感器数据,multiplier
的值应该是4
。为了获得更高的性能,你决定把voltage_corrected
字段写入到索引中,并且使用新的multiplier
参数:
只要在一个新的名为my-index-000001
索引中,把名为voltage_corrected
的runtime field中的所有定义直接复制到新索引的mappings中,就是这么简单。你可以额外定义一个参数on_script_error
来控制在索引期间当脚本抛出异常时是否要reject整个文档。
xxxxxxxxxx
311PUT my-index-000001/
2{
3 "mappings": {
4 "properties": {
5 "timestamp": {
6 "type": "date"
7 },
8 "temperature": {
9 "type": "long"
10 },
11 "voltage": {
12 "type": "double"
13 },
14 "node": {
15 "type": "keyword"
16 },
17 "voltage_corrected": {
18 "type": "double",
19 "on_script_error": "fail",
20 "script": {
21 "source": """
22 emit(doc['voltage'].value * params['multiplier'])
23 """,
24 "params": {
25 "multiplier": 4
26 }
27 }
28 }
29 }
30 }
31}
第19行,在索引期间当脚本抛出异常时文档会被reject,将on_script_error
的值置为ignore
使得将voltage_corrected
注册到_ignored的metadata filed中然后继续索引(索引当前文档的其他字段)。
批量写入传感器的数据到my-index-000001
中:
xxxxxxxxxx
131POST my-index-000001/_bulk?refresh=true
2{ "index": {}}
3{ "timestamp": 1516729294000, "temperature": 200, "voltage": 5.2, "node": "a"}
4{ "index": {}}
5{ "timestamp": 1516642894000, "temperature": 201, "voltage": 5.8, "node": "b"}
6{ "index": {}}
7{ "timestamp": 1516556494000, "temperature": 202, "voltage": 5.1, "node": "a"}
8{ "index": {}}
9{ "timestamp": 1516470094000, "temperature": 198, "voltage": 5.6, "node": "b"}
10{ "index": {}}
11{ "timestamp": 1516383694000, "temperature": 200, "voltage": 4.2, "node": "c"}
12{ "index": {}}
13{ "timestamp": 1516297294000, "temperature": 202, "voltage": 4.0, "node": "c"}
你现在可以搜索查询中检索计算后的值,并且找到精确数值的文档。下面的范围查询返回voltage_corrected
的值大于等于16
并且小于等于20
的所有文档。这里重申一遍,在_search
API上使用fields参数来检索你想要查询的字段:
xxxxxxxxxx
131POST my-index-000001/_search
2{
3 "query": {
4 "range": {
5 "voltage_corrected": {
6 "gte": 16,
7 "lte": 20,
8 "boost": 1.0
9 }
10 }
11 },
12 "fields": ["voltage_corrected", "node"]
13}
响应中返回了满足范围查询的包含voltage_corrected
字段的文档,voltage_corrected
的值基于脚本中计算出的值:
xxxxxxxxxx
491{
2 "hits" : {
3 "total" : {
4 "value" : 2,
5 "relation" : "eq"
6 },
7 "max_score" : 1.0,
8 "hits" : [
9 {
10 "_index" : "my-index-000001",
11 "_id" : "yoSLrHgBdg9xpPrUZz_P",
12 "_score" : 1.0,
13 "_source" : {
14 "timestamp" : 1516383694000,
15 "temperature" : 200,
16 "voltage" : 4.2,
17 "node" : "c"
18 },
19 "fields" : {
20 "voltage_corrected" : [
21 16.8
22 ],
23 "node" : [
24 "c"
25 ]
26 }
27 },
28 {
29 "_index" : "my-index-000001",
30 "_id" : "y4SLrHgBdg9xpPrUZz_P",
31 "_score" : 1.0,
32 "_source" : {
33 "timestamp" : 1516297294000,
34 "temperature" : 202,
35 "voltage" : 4.0,
36 "node" : "c"
37 },
38 "fields" : {
39 "voltage_corrected" : [
40 16.0
41 ],
42 "node" : [
43 "c"
44 ]
45 }
46 }
47 ]
48 }
49}
(8.2)link
假设你从一个数据量很大的日志数据中提取字段。数据的索引非常的耗时,并且使用大量的磁盘空间,并且你想要在不提前给出一个策略(mapping)前就能探索其数据结构(Date structure)。
已知日志数据中包含了你想要提取的字段(field)。在这个例子中,我们集中关注@timestamp
和message
字段。通过使用running fields,你可以定义脚本,并在查询期间计算这些字段的值。
你可以先从一个简单的例子开始,将@timestamp
和message
添加到名为my-index-000001
的索引的mapping中,作为indexed filed。To remain flexible,使用wildcard作为message
的字段类型:
xxxxxxxxxx
141PUT /my-index-000001/
2{
3 "mappings": {
4 "properties": {
5 "@timestamp": {
6 "format": "strict_date_optional_time||epoch_second",
7 "type": "date"
8 },
9 "message": {
10 "type": "wildcard"
11 }
12 }
13 }
14}
在添加了用于检索的字段的mapping后,开始将你的日志数据索引到Elasticsearch中。下面的请求使用了bulk API将原始日志数据索引到my-index-000001
中。你可以先用一小部分样例数据来体验runtime field而不是使用所有的日志数据。
The final document is not a valid Apache log format, but we can account for that scenario in our script。
xxxxxxxxxx
151POST /my-index-000001/_bulk?refresh
2{"index":{}}
3{"timestamp":"2020-04-30T14:30:17-05:00","message":"40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
4{"index":{}}
5{"timestamp":"2020-04-30T14:30:53-05:00","message":"232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
6{"index":{}}
7{"timestamp":"2020-04-30T14:31:12-05:00","message":"26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
8{"index":{}}
9{"timestamp":"2020-04-30T14:31:19-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"}
10{"index":{}}
11{"timestamp":"2020-04-30T14:31:22-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"}
12{"index":{}}
13{"timestamp":"2020-04-30T14:31:27-05:00","message":"252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
14{"index":{}}
15{"timestamp":"2020-04-30T14:31:28-05:00","message":"not a valid apache log"}
现在你可以查看Elasticsearch是如何存储你的原始数据的。
xxxxxxxxxx
11GET /my-index-000001
下面的mapping包含了两个字段:@timestamp
和message
。
xxxxxxxxxx
201{
2 "my-index-000001" : {
3 "aliases" : { },
4 "mappings" : {
5 "properties" : {
6 "@timestamp" : {
7 "type" : "date",
8 "format" : "strict_date_optional_time||epoch_second"
9 },
10 "message" : {
11 "type" : "wildcard"
12 },
13 "timestamp" : {
14 "type" : "date"
15 }
16 }
17 },
18 ...
19 }
20}
如果你想要检索结果中包含clientip
,你可以在mapping中添加这个字段并且作为runtime field。下面的runtime script定义了一个grok pattern,它从单个文本(single text)的文档中提取出结构化的字段。grok pattern类似与正则表达式支持aliased expressions。
脚本会匹配%{COMMONAPACHELOG}
log pattern,它能处理Apache日志结构。如果pattern匹配到(clientip != null
),脚本会emit匹配到的IP地址。如果没有匹配到,脚本直接退出而不是报错。
xxxxxxxxxx
121PUT my-index-000001/_mappings
2{
3 "runtime": {
4 "http.client_ip": {
5 "type": "ip",
6 "script": """
7 String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;
8 if (clientip != null) emit(clientip);
9 """
10 }
11 }
12}
第8行,这个条件保证pattern没有匹配到也不会报错。
或者你可以在查询请求时定义相同的runtime field。runtime的定义以及脚本和之前在index mapping中的定义是完全一样。只要将定义拷贝到查询请求中的runtime_mappings
块中以及一个匹配runtime field的query。这个query返回的结果跟你在index mapping中定义http.clientip
runtime field返回的结果是一样的。前提是你在查询中指定了这个runtime field:
xxxxxxxxxx
181GET my-index-000001/_search
2{
3 "runtime_mappings": {
4 "http.clientip": {
5 "type": "ip",
6 "script": """
7 String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;
8 if (clientip != null) emit(clientip);
9 """
10 }
11 },
12 "query": {
13 "match": {
14 "http.clientip": "40.135.0.0"
15 }
16 },
17 "fields" : ["http.clientip"]
18}
你可以定义一个composite
runtime filed从单个脚本中emit多个字段。你可以定义类型化的子字段(typed subfields)集合然后emit多个值。在查询期间,每一个子字段会检索出跟它们名称相关的值。意味着你只需要指定一次grok pattern然后就能返回多个值:
xxxxxxxxxx
201PUT my-index-000001/_mappings
2{
3 "runtime": {
4 "http": {
5 "type": "composite",
6 "script": "emit(grok(\"%{COMMONAPACHELOG}\").extract(doc[\"message\"].value))",
7 "fields": {
8 "clientip": {
9 "type": "ip"
10 },
11 "verb": {
12 "type": "keyword"
13 },
14 "response": {
15 "type": "long"
16 }
17 }
18 }
19 }
20}
使用http.clientip
runtime field,你可以定义单个query用于查询指定的IP地址并返回所有相关的字段。
xxxxxxxxxx
91GET my-index-000001/_search
2{
3 "query": {
4 "match": {
5 "http.clientip": "40.135.0.0"
6 }
7 },
8 "fields" : ["*"]
9}
上面的API会返回下面的结果。由于http
是一个composite
runtime field,响应中的fields
会包括每一个子字段以及匹配到这个query相关的结果。不用提前构建你的数据结构,你就可以查询并以某种方式展示你的数据来体验以及决定索引哪些字段。
xxxxxxxxxx
411{
2 ...
3 "hits" : {
4 "total" : {
5 "value" : 1,
6 "relation" : "eq"
7 },
8 "max_score" : 1.0,
9 "hits" : [
10 {
11 "_index" : "my-index-000001",
12 "_id" : "sRVHBnwBB-qjgFni7h_O",
13 "_score" : 1.0,
14 "_source" : {
15 "timestamp" : "2020-04-30T14:30:17-05:00",
16 "message" : "40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
17 },
18 "fields" : {
19 "http.verb" : [
20 "GET"
21 ],
22 "http.clientip" : [
23 "40.135.0.0"
24 ],
25 "http.response" : [
26 200
27 ],
28 "message" : [
29 "40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
30 ],
31 "http.client_ip" : [
32 "40.135.0.0"
33 ],
34 "timestamp" : [
35 "2020-04-30T19:30:17.000Z"
36 ]
37 }
38 }
39 ]
40 }
41}
还记得脚本中的if
声明吗?
xxxxxxxxxx
11if (clientip != null) emit(clientip);
如果脚本没有包含这个条件,当某个分片上没有匹配到pattern,query会失败。在包含这个条件后,query会跳过没有匹配到grok pattern的数据。
你也可以在timestamp
字段上执行一个range query。下面的请求会返回大于等于2020-04-30T14:31:27-05:00
的文档。
xxxxxxxxxx
101GET my-index-000001/_search
2{
3 "query": {
4 "range": {
5 "timestamp": {
6 "gte": "2020-04-30T14:31:27-05:00"
7 }
8 }
9 }
10}
响应中会包含日志格式不匹配的文档,但是timestamp在定义的范围中。
xxxxxxxxxx
301{
2 ...
3 "hits" : {
4 "total" : {
5 "value" : 2,
6 "relation" : "eq"
7 },
8 "max_score" : 1.0,
9 "hits" : [
10 {
11 "_index" : "my-index-000001",
12 "_id" : "hdEhyncBRSB6iD-PoBqe",
13 "_score" : 1.0,
14 "_source" : {
15 "timestamp" : "2020-04-30T14:31:27-05:00",
16 "message" : "252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
17 }
18 },
19 {
20 "_index" : "my-index-000001",
21 "_id" : "htEhyncBRSB6iD-PoBqe",
22 "_score" : 1.0,
23 "_source" : {
24 "timestamp" : "2020-04-30T14:31:28-05:00",
25 "message" : "not a valid apache log"
26 }
27 }
28 ]
29 }
30}
如果你不需要正则表达式的强大功能,你也可以使用dissect patterns而不是grok patterns。Dissect patterns匹配固定的分隔符,但通常来说比grok快。
你可以使用dissect解析Apache log并且达到跟grok pattern一样的结果。相较于log pattern,它会包含你想要丢弃的string。特别的注意下你想要丢弃的string,能帮助你成功构建dissect patterns。
xxxxxxxxxx
121PUT my-index-000001/_mappings
2{
3 "runtime": {
4 "http.client.ip": {
5 "type": "ip",
6 "script": """
7 String clientip=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{status} %{size}').extract(doc["message"].value)?.clientip;
8 if (clientip != null) emit(clientip);
9 """
10 }
11 }
12}
同样的,你可以定义一个dissect pattern来提取HTTP response code。
xxxxxxxxxx
121PUT my-index-000001/_mappings
2{
3 "runtime": {
4 "http.responses": {
5 "type": "long",
6 "script": """
7 String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;
8 if (response != null) emit(Integer.parseInt(response));
9 """
10 }
11 }
12}
随后你可以使用http.responses
runtime field执行一个query来检索指定的HTTP response。在_search
请求中使用fields
参数指明想要检索的字段:
xxxxxxxxxx
91GET my-index-000001/_search
2{
3 "query": {
4 "match": {
5 "http.responses": "304"
6 }
7 },
8 "fields" : ["http.client_ip","timestamp","http.verb"]
9}
下面的响应包含了HTTP response为304
的文档。
xxxxxxxxxx
321{
2 ...
3 "hits" : {
4 "total" : {
5 "value" : 1,
6 "relation" : "eq"
7 },
8 "max_score" : 1.0,
9 "hits" : [
10 {
11 "_index" : "my-index-000001",
12 "_id" : "A2qDy3cBWRMvVAuI7F8M",
13 "_score" : 1.0,
14 "_source" : {
15 "timestamp" : "2020-04-30T14:31:22-05:00",
16 "message" : "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
17 },
18 "fields" : {
19 "http.verb" : [
20 "GET"
21 ],
22 "http.client_ip" : [
23 "247.37.0.0"
24 ],
25 "timestamp" : [
26 "2020-04-30T19:31:22.000Z"
27 ]
28 }
29 }
30 ]
31 }
32}
(8.2)link
每个字段都有一个field data type
或者field type
。这个类型指明(indicate)了这个字段中包含的数据种类(kind),比如一个string或者boolean值以及预期用途(intended use)。例如,你可以同时将String索引为text
和keyword
字段。然而text
字段会被analyzed用于全文检索,同时keyword
用于过滤和排序。
字段的类型(Field type)根据家族(family)分组。同一个家族的类型有完全一样的查询行为,但是在空间使用(space usage)或者性能属性(performance characteristic)上有差别。
当前有两个类型家族(type family),keyword
和text
。其他的type family只有单个字段的类型。例如boolean
类型家族由一个字段的类型: boolean
组成。
类型 | 描述 |
---|---|
binary | Binary值编码为一个Base64的字符串 |
boolean | true 以及false |
Keywords | keyword家族,包括kewword 、constant_keyword 以及wildcard |
Numbers | 数值类型,例如long 和double ,用来表示为数量 |
Dates | 时间类型,包括date和date_nanos |
alias | 为现有的字段定义一个别名 |
类型 | 描述 |
---|---|
Range | 范围类型,例如long_range , double_range , date_range , and ip_range |
ip | IPv4和IPv6地址 |
version | 软件版本。支持[Semantic Versioning precedence rules |
murmur3 | 计算以及存储哈希值 |
类型 | 描述 |
---|---|
aggregate_metric_double | 预先聚合(pre-aggregated)的指标数据 |
histogram | 预先聚合的histogram格式的数值 |
类型 | 描述 |
---|---|
text_fields | text家族,包括text 、match_only_text 。分词的,无结构的文本 |
annotated-text | Text containing special markup. Used for identifying named entities |
completion | 用于auto-complete suggestion |
search_as_you_type | text -like type for as-you-type completion |
token_count | 文本中token的统计 |
类型 | 描述 |
---|---|
dense_vector | Records dense vectors of float values |
rank_feature | Records a numeric feature to boost hits at query time. |
rank_features | Records numeric features to boost hits at query time |
类型 | 描述 |
---|---|
geo_point | 经纬度点数据(point) |
geo_shape | 复杂的形状,例如多边形(polygon) |
point | 笛卡尔坐标(Arbitrary cartesian points) |
shape | 笛卡尔几何(Arbitrary cartesian geometries) |
类型 | 描述 |
---|---|
percolator | 对Query DSL中的query进行索引 |
在Elasticsearch中,数组不要求一个专用的字段的数据类型。默认情况下每一个字段可能包含0个或多个值。然而数组中所有的值必须是相同的字段类型。见Arrays。
通常来说对同一个字段使用不同方式索引时很有用的。例如,一个字符串字段可以映射为text
字段用于全文检索,映射为keyword
用于排序或者聚合。你还可以索引一个text字段时,使用standard analyzer、english analyzer以及french analyzer。
这就是multi-fields的目的。大多数的字段类型通过fields参数来支持multi-fields。
(8.2)link
为metric aggregations存储预先聚合(pre-aggregated)的数值类型的值。aggregate_metric_double
类型的字段包含了一个或多个这些指标子字段(metric sub-field):min
、max
、sum
、value_count
。
当你在aggregate_metric_double
字段上运行指标聚合时,会使用到对应的指标字段值。例如,在aggregate_metric_double
字段执行min aggregation时会返回所有aggregate_metric_double
的子字段min
中的最小值。
IMPORTANT:
aggregate_metric_double
字段中为每一个指标子字段使用doc value存储一个数值类型的字段值。不支持Array。min
、max
、sum
的值是doulbe
类型。value_count
是long
类型的正数。
xxxxxxxxxx
121PUT my-index
2{
3 "mappings": {
4 "properties": {
5 "my-agg-metric-field": {
6 "type": "aggregate_metric_double",
7 "metrics": [ "min", "max", "sum", "value_count" ],
8 "default_metric": "max"
9 }
10 }
11 }
12}
(Required, array of strings)存储指标子字段的数组。每一个数组元素都对应于metric aggregation。合法的数组元素是min,max,sum以及value_count。你必须至少指定一个值。
(Required, string)默认用于query,script以及聚合的指标子字段,该字段必须是metrics
数组中的某个元素。
该字段只用于Elastic内部使用。将字段作为一个Time series metric。这个值是指标类型(metric type)。默认是null
(不是一个时间序列指标)。
对于aggregate_metric_double
字段,这个参数只可以是counter
、gauge
以及summary
。不能对现有的字段更新这个参数。
设计aggregate_metric_double
字段是为了用于下面的聚合:
min aggregation 返回所有min
子字段中的最小值
max aggregation 返回所有max
子字段中的最大值
sum aggregation 返回所有sum
子字段中的和
value_count aggregation 返回所有Value count
子字段中的和
avg aggregation。aggregate_metric_double
字段中并没有指标子字段avg
;avg
aggregation是通过sum
和value_count
两个指标子字段计算得来的,因此metric
数组中必须同时包含sum
和value_count
两个指标子字段。
如果将aggregate_metric_double
字段用于其他的聚合操作,那么将会使用default_metric
,相当于一个doulbe
类型的字段。default_metric
同样可以用于Script以及下面的query中:
下面的create indexAPI创建了一个名为agg_metric
的aggregate_metric_double
字段。并且max
作为default_metric
。:
xxxxxxxxxx
121PUT stats-index
2{
3 "mappings": {
4 "properties": {
5 "agg_metric": {
6 "type": "aggregate_metric_double",
7 "metrics": [ "min", "max", "sum", "value_count" ],
8 "default_metric": "max"
9 }
10 }
11 }
12}
下面的index API请求在agg_metric
字段中添加了预先聚合的数据。
xxxxxxxxxx
191PUT stats-index/_doc/1
2{
3 "agg_metric": {
4 "min": -302.50,
5 "max": 702.30,
6 "sum": 200.0,
7 "value_count": 25
8 }
9}
10
11PUT stats-index/_doc/2
12{
13 "agg_metric": {
14 "min": -93.00,
15 "max": 1702.30,
16 "sum": 300.00,
17 "value_count": 25
18 }
19}
你可以在agg_metric
字段上运行min
、max
、sum
、value_count
以及avg
聚合。
xxxxxxxxxx
101POST stats-index/_search?size=0
2{
3 "aggs": {
4 "metric_min": { "min": { "field": "agg_metric" } },
5 "metric_max": { "max": { "field": "agg_metric" } },
6 "metric_value_count": { "value_count": { "field": "agg_metric" } },
7 "metric_sum": { "sum": { "field": "agg_metric" } },
8 "metric_avg": { "avg": { "field": "agg_metric" } }
9 }
10}
聚合结果基于对应的指标子字段的字段值:
xxxxxxxxxx
201{
2...
3 "aggregations": {
4 "metric_min": {
5 "value": -302.5
6 },
7 "metric_max": {
8 "value": 1702.3
9 },
10 "metric_value_count": {
11 "value": 50
12 },
13 "metric_sum": {
14 "value": 500.0
15 },
16 "metric_avg": {
17 "value": 10.0
18 }
19 }
20}
在aggregate_metric_double
查询会默认使用default_metric
中定义的字段。
xxxxxxxxxx
101GET stats-index/_search
2{
3 "query": {
4 "term": {
5 "agg_metric": {
6 "value": 702.30
7 }
8 }
9 }
10}
上述查询返回下列的命中结果。default_metric
字段的字段值匹配请求中的值(702.30)。
xxxxxxxxxx
251{
2 ...
3 "hits": {
4 "total": {
5 "value": 1,
6 "relation": "eq"
7 },
8 "max_score": 1.0,
9 "hits": [
10 {
11 "_index": "stats-index",
12 "_id": "1",
13 "_score": 1.0,
14 "_source": {
15 "agg_metric": {
16 "min": -302.5,
17 "max": 702.3,
18 "sum": 200.0,
19 "value_count": 25
20 }
21 }
22 }
23 ]
24 }
25}
(8.2)link
alias
可以给索引中某个字段定义一个别名(alternate)。在search请求以及像 field capabilities API中可以用来替代为目标字段(target filed)。
xxxxxxxxxx
281PUT trips
2{
3 "mappings": {
4 "properties": {
5 "distance": {
6 "type": "long"
7 },
8 "route_length_miles": {
9 "type": "alias",
10 "path": "distance"
11 },
12 "transit_mode": {
13 "type": "keyword"
14 }
15 }
16 }
17}
18
19GET _search
20{
21 "query": {
22 "range" : {
23 "route_length_miles" : {
24 "gte" : 39
25 }
26 }
27 }
28}
第10行,目标字段的路径。注意的是必须是全路径,包括所有的parent objects(比如:object1.object2.field)。
几乎所有的查询请求都可以使用alias field。特别是可以用于查询、聚合、以及排序字段,同时还有docvalue_fields
, stored_fields
, suggestions, and highlights。脚本同样支持alias来访问它的字段值。见unsupported APIs了解一些不支持的情况。
有些search请求以及像 field capabilities API中可以使用field wildcard patterns,这个wildcard patterns可以匹配到具体的filed alias:
xxxxxxxxxx
11GET trips/_field_caps?fields=route_*,transit_mode
对于alias的目标字段有一些要求限制:
目标字段必须是一个具体的字段(concrete field),不能是一个对象或者另一个alias
在alias创建时目标字段必须已经存在
如果定义了nested objects,field alias必须要跟目标字段有相同的nested scope
另外,alias只能对应一个目标字段。意味着不可能使用一个alias在single clause(一个query属于一个clause)中查询多个目标字段。
可以通过更新mapping来更改alias指向(refer to)的目标字段。已知的一个限制是如果任何已经存储的percolator query包含了alias,它们仍然指向原先的目标。更多信息见percolator documentation。
不支持往alias中写入字段值:尝试在索引或者更新alias字段会产生一个错误。同样的,alias不能作为多个字段的copy_to的目标。
因为alias的名称没有在输入文档中呈现,所以不能通过用于source filtering。例如下面的请求会返回一个空结果:
xxxxxxxxxx
71GET /_search
2{
3 "query" : {
4 "match_all": {}
5 },
6 "_source": "route_length_miles"
7}
目前只有search请求以及像 field capabilities API中可以接受alias。像term vectors就不能使用alias。
Finally, some queries, such as terms, geo_shape, and more_like_this, allow for fetching query information from an indexed document. Because field aliases aren’t supported when fetching documents, the part of the query that specifies the lookup path cannot refer to a field by its alias。
(8.2)link
Elasticsearch中没有专门的array data type
。默认情况下任意一个字段可以包含零个或者多个值 。然而,在数组中的字段值必须是相同的数据类型。例如:
string数组:["one", "two"]
integer数组:[1, 2]
数组的数组:[1, [2, ,3]],这种相当于[1, ,2 ,3]
object数组:[{ "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }]
NOTE:Arrays of objects对象数组 对象数组可能无法按照你的期望工作:不能独立于数组中的其他对象来查询每个对象。如果你需要能够这样做,那么应该使用嵌套(nested)数据类型,而不是对象(object)数据类型
当动态的添加一个字段时,数组中的第一个值决定了数组元素的类型。随后所有的值都必须是相同的数据类型或者可以强转(coerce)为相同的数据类型。
数组中有不同的数据类型是不支持的: [ 10, "some string" ]。
数组中可以包含null
,可以被null_value(如果配置的话)替换或者跳过。空的数组[]
被认为是缺失字段(missing field)-某个字段没有字段值。
不需要在文档中有任何预先配置就可以使用数组,这个开箱支持的功能:
xxxxxxxxxx
341PUT my-index-000001/_doc/1
2{
3 "message": "some arrays in this document...",
4 "tags": [ "elasticsearch", "wow" ],
5 "lists": [
6 {
7 "name": "prog_list",
8 "description": "programming list"
9 },
10 {
11 "name": "cool_list",
12 "description": "cool stuff list"
13 }
14 ]
15}
16
17PUT my-index-000001/_doc/2
18{
19 "message": "no arrays in this document...",
20 "tags": "elasticsearch",
21 "lists": {
22 "name": "prog_list",
23 "description": "programming list"
24 }
25}
26
27GET my-index-000001/_search
28{
29 "query": {
30 "match": {
31 "tags": "elasticsearch"
32 }
33 }
34}
第4行,tags
字段自动添加为字符串字段(string field)。
第5行,lists
字段自动添加为object
字段。
第17行,第二篇文档没有包含数组,但是会被索引成相同的字段。
第31行,这个query在tags
字段中查找elasticsearch
,并且匹配到这两篇文档。
(8.2)link
binary
类型用于存储使用Base64编码的二进制值,默认情况下,这个字段不会存储并且无法用于搜索:
xxxxxxxxxx
191PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "name": {
6 "type": "text"
7 },
8 "blob": {
9 "type": "binary"
10 }
11 }
12 }
13}
14
15PUT my-index-000001/_doc/1
16{
17 "name": "Some binary blob",
18 "blob": "U29tZSBiaW5hcnkgYmxvYg=="
19}
第18行,二进制值中不能有换行符\n
binary
字段可以有以下参数:
doc_values : 该字段是否用基于列式存储于磁盘中,使得可以用于排序、聚合、或者Script。参数值为true
或者false
(默认值)
(8.2)link
boolean
字段的值可以是JSON种的true
和false
,也可以是可以被解释为true
或者false
的字符串。
False values:false
,"false"
, ""(空的字符串)
True values:true
,"true"
例如
xxxxxxxxxx
241PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "is_published": {
6 "type": "boolean"
7 }
8 }
9 }
10}
11
12POST my-index-000001/_doc/1?refresh
13{
14 "is_published": "true"
15}
16
17GET my-index-000001/_search
18{
19 "query": {
20 "term": {
21 "is_published": true
22 }
23 }
24}
第14行,索引了一篇文档,is_published字段的字段值是一个字符串的true,它会被解释为true
第21行,使用JSON种的true
查询文档
terms aggregation中使用1
和0
用于key
的值,并且"true"
和"false"
用于key_as_string
的值。在Script中使用Boolean字段时,会返回true
和
false
:
xxxxxxxxxx
301POST my-index-000001/_doc/1?refresh
2{
3 "is_published": true
4}
5
6POST my-index-000001/_doc/2?refresh
7{
8 "is_published": false
9}
10
11GET my-index-000001/_search
12{
13 "aggs": {
14 "publish_state": {
15 "terms": {
16 "field": "is_published"
17 }
18 }
19 },
20 "sort": [ "is_published" ],
21 "fields": [
22 {"field": "weight"}
23 ],
24 "runtime_mappings": {
25 "weight": {
26 "type": "long",
27 "script": "emit(doc['is_published'].value ? 10 : 0)"
28 }
29 }
30}
boolean
字段有以下参数:
doc_values : 该字段是否用基于列式存储于磁盘中,使得可以用于排序、聚合、或者Script。参数值为true
或者false
(默认值)
index:该字段是否需要被快速的检索?参数值为true
或者false
(默认值)。
null_value:参数值可以是上文中列出的各种true或者false的值(JSON,字符串)。默认值是null
,意味着缺失值。注意的是如果使用了script
参数,则不能设置这个参数
on_script_error:该参数定义了当Script在索引期间抛出错误后,该如何处理。默认值为fail
,会导致整个文档被reject;如果参数值为continue
,将在_ignored字段注册这个字段然后继续索引。这个参数只有在script
设置后才能被设置。
script:如果设置了这个参数,这个字段的字段值会通过script生成,而不是直接从源数据中读取。如果文档中设置了该字段的值,则会reject并且抛出错误。script跟runtime中有相同的format。
meta:字段的元数据信息
(8.2)link
JSON没有日期(dates)类型,所以在Elasticsearch中日期字段可以是:
格式化后的字符串,比如2015-01-01
或者 2015/01/01 12:10:30
数值类型:milliseconds-since-the-epoch
数值类型:seconds-since-the-epoch(配置)
NOTE:milliseconds-since-the-epoch的值必须是非负的,使用格式化后的值来表示1970前的日期
源码中,日期会被转化为UTC(如果指定了时区),存储为一个long类型的数值来表示milliseconds-since-the-epoch。
日期类型的查询会被表示为一个long类型的范围查询,聚合的结果和stored field会根据该字段对应的date format被转回到一个字符串。
NOTE:日期总是会被表示为字符串,即使在JSON中是long类型
时间格式data format可以自定义,如果format
没有指定,会使用下面默认值:
xxxxxxxxxx
11"strict_date_optional_time||epoch_millis"
上述默认值意味着接受的可选时间戳有milliseconds-since-the-epoch或者strict_date_optional_time支持的格式。
例如
xxxxxxxxxx
241PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "date": {
6 "type": "date"
7 }
8 }
9 }
10}
11
12PUT my-index-000001/_doc/1
13{ "date": "2015-01-01" }
14
15PUT my-index-000001/_doc/2
16{ "date": "2015-01-01T12:10:30Z" }
17
18PUT my-index-000001/_doc/3
19{ "date": 1420070400001 }
20
21GET my-index-000001/_search
22{
23 "sort": { "date": "asc"}
24}
第6行将会使用默认的format
第13行使用纯日期
第16行包含时间
第19行使用milliseconds-since-the-epoch
第23行,返回的sort
的值将是milliseconds-since-the-epoch
WARNING:日期将会接受类似
{"date": 1618249875.123456}
的带小数点的数值,但目前我们会丢失一定的精度,见这个issue 。
可以使用||
作为分隔符来指定multiple formats,这些format
都被依次进行匹配直到找到对应的format。下面第一个format会将milliseconds-since-the-epoch
的值转化为一个字符串。
xxxxxxxxxx
111PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "date": {
6 "type": "date",
7 "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
8 }
9 }
10 }
11}
下面列出的参数可以用于date
字段
doc_values | 是否以列式存储在磁盘上,使得可以用于排序、聚合、脚本处理,该值可以是true(默认值)或者false |
---|---|
format | 用于对日期进行解析,默认值strict_date_optional_time||epoch_millis |
locale | 解析日期时使用的区域设置,因为在所有语言中,月份没有相同的名称和/或缩写。默认是 ROOT locale |
ignore_malformed | 如果是true,格式错误的数字会被忽略。如果是false,格式错误的数字会抛出异常并且整个文档会被丢弃。注意的是如果使用了参数script ,当前参数不会被设置。 |
index | 是否需要被快速的搜索到?该值可以是true(默认值)或者false。日期字段date field只有开启doc_values才能进行查询,尽管查询速度较慢 |
null_value | 可以设置一个满足format的值,用来替代空值。默认值是null,意味这是一个缺失值。注意的是如果使用了参数script ,当前参数不会被设置。 |
on_script_error | 该值描述了通过参数script 定义的脚本在索引期间发生错误后的行为。可以设置为false(默认值),那么整个文档会被reject。或者设置为continue ,会通过ignored field来进行索引并继续进行索引。这个参数只有参数script 被设置后才能被设置 |
script | 如果设置了该值,将会索引这个脚本生成的值,而不是直接读取source(输入文档中这个字段的字段值)。如果输入的文档中已经设置了这个字段的值,那么这个脚本会被reject并报错。脚本的格式跟runtime equivalent一致,并且应该输出long类型的时间戳。 |
store | 是否要额外存储字段值并且可以被检索,不仅仅存储在_source 字段中,该值可以是true或者false(默认值) |
meta | 这个字段的Metadata |
如果你需要传输seconds-since-the-epoch
,那么保证在format
中列出epoch_second
:
xxxxxxxxxx
201PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "date": {
6 "type": "date",
7 "format": "strict_date_optional_time||epoch_second"
8 }
9 }
10 }
11}
12
13PUT my-index-000001/_doc/example?refresh
14{ "date": 1618321898 }
15
16POST my-index-000001/_search
17{
18 "fields": [ {"field": "date"}],
19 "_source": false
20}
会收到下面的日期数据:
xxxxxxxxxx
141{
2 "hits": {
3 "hits": [
4 {
5 "_id": "example",
6 "_index": "my-index-000001",
7 "_score": 1.0,
8 "fields": {
9 "date": ["2021-04-13T13:51:38.000Z"]
10 }
11 }
12 ]
13 }
14}
(8.2)link
这个时间类型是date
数据类型的补充。然而它们之间有一个很重要的不同。现有的date
数据类型存储毫秒级分辨率的时间,而date_nanos
则是纳秒级分辨率,严格限制了从1970到2262这个时间范围,因为日期仍然以代表自纪元(epoch)以来纳秒数的long类型存储。
内部处理纳秒时会将其转化为long类型的数值,聚合和存储的结果会被转回成一个字符串,字符串的值区间于Date format。
可以自定义Date format,如果不指定则会使用默认的format:
xxxxxxxxxx
11"strict_date_optional_time_nanos||epoch_millis"
例如:
xxxxxxxxxx
381PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "date": {
6 "type": "date_nanos"
7 }
8 }
9 }
10}
11
12PUT my-index-000001/_bulk?refresh
13{ "index" : { "_id" : "1" } }
14{ "date": "2015-01-01" }
15{ "index" : { "_id" : "2" } }
16{ "date": "2015-01-01T12:10:30.123456789Z" }
17{ "index" : { "_id" : "3" } }
18{ "date": 1420070400000 }
19
20GET my-index-000001/_search
21{
22 "sort": { "date": "asc"},
23 "runtime_mappings": {
24 "date_has_nanos": {
25 "type": "boolean",
26 "script": "emit(doc['date'].value.nano != 0)"
27 }
28 },
29 "fields": [
30 {
31 "field": "date",
32 "format": "strict_date_optional_time_nanos"
33 },
34 {
35 "field": "date_has_nanos"
36 }
37 ]
38}
第6行,date
字段使用默认的date format
第14行,这篇文档使用一个plain Date
第16行,这篇文档包含一个时间
第18行,这个文档使用了milliseconds-since-the-epoch
第22行,注意的是sort
的值在返回的结果中使用milliseconds-since-the-epoch
第26行,在脚本中使用了.nano
返回纳米级别的时间
第32行,在获取数据时,你可以使用fields parameter来指定Date format。如果不使用strict_date_optional_time_nanos 你会获取一个四舍五入的结果。
你也可以通过||
指定多个Date format。Date nanoseconds field type可以跟Date field type有相同的可用的参数。
WARNING:Date nanoseconds可以是一个类似
{"date": 1618249875.123456}
的带有小数点的数字。在某些情况下(#70085),我们会丢失这些日期的精度,因此应该避免使用这种格式
即使使用了date_nanos
字段,也只能在毫秒级分辨率上聚合。这个局限性同样影响transforms。
(8.2)link
默认情况下,对象中每一个子字段(sub-field)都单独映射和索引。如果子字段的类型或者名称无法提前知晓,那么它们会被自动映射(mapped dynamically)。
flattened
提供另外一种方法,它将整个对象映射为单个字段。flattened
会解析出leaf value并且将它们作为keyword索引到一个字段中。对象的内容随后可以通过query和aggregation查询。
对于很大的或者有未知数量键的对象,使用flattened
就很有用。只要创建一个字段的类型来处理整个JSON对象,有助于防止因太多不同的字段的类型导致mappings explosion。
另外,flattened
在搜索功能存在一种折衷。只允许一些基本的查询,不支持数值类型的范围查询以及高亮。更多的一些局限性见Supported operations。
NOTE:
flattened
不应该用于索引整个文档的内容,因为它会将所有的值作为keyword,不能提供全文检索功能。默认每一个子字段在mapping中有自己的entry,大多数的用例中工作都正常。
flattened
字段可以通过下面的方式创建:
xxxxxxxxxx
261PUT bug_reports
2{
3 "mappings": {
4 "properties": {
5 "title": {
6 "type": "text"
7 },
8 "labels": {
9 "type": "flattened"
10 }
11 }
12 }
13}
14
15POST bug_reports/_doc/1
16{
17 "title": "Results are not sorted correctly.",
18 "labels": {
19 "priority": "urgent",
20 "release": ["v1.2.5", "v1.3.0"],
21 "timestamp": {
22 "created": 1541458026,
23 "closed": 1541457010
24 }
25 }
26}
在索引期间,JSON对象中的每一个leaf value都会创建token。索引为keywords类型,不会分词,也不会为数值或者日期进行特别的处理。
查询最顶层flattened
字段(mapping中定义的那个字段)会搜索所有的leaf values:
xxxxxxxxxx
61POST bug_reports/_search
2{
3 "query": {
4 "term": {"labels": "urgent"}
5 }
6}
若要查询flattened object中某个key,则通过点路径:
xxxxxxxxxx
61POST bug_reports/_search
2{
3 "query": {
4 "term": {"labels.release": "v1.3.0"}
5 }
6}
由于索引的方式很类似,所以flattened
字段跟keyword字段一样有许多相同的mapping和查询功能。
目前,flattened
字段可以用于下面的query类型:
term
, terms
, and terms_set
prefix
range
match
and multi_match
query_string
and simple_query_strin
g
exists
查询时,不能使用wildcards,比如:{ "term": {"labels.time*": 1541457010}}
。注意的事,所有的查询,包括range
,都是将值当成string类型。flattened
字段不支持高亮。
可以将flattened
字段用于排序,就是类似terms
中执行简单keyword风格的聚合。与查询一样,这里没有对数字的特殊支持——JSON 对象中的所有值都被视为keyword。在排序时,这意味着值会按字典顺序进行比较。
目前不能存储flattened
字段。不能在mapping中指定store。
字段值以及固定子字段可以通过fields parameter.content检索。由于flattened
将整个对象(以及对象中的子字段)映射为一个字段,响应中的_source
包含了未更改的结构。
获取单个子字段可以在请求中显示的指定。只能对有固定路径的生效,但不能使用wildcards:
xxxxxxxxxx
231PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "flattened_field": {
6 "type": "flattened"
7 }
8 }
9 }
10}
11
12PUT my-index-000001/_doc/1?refresh=true
13{
14 "flattened_field" : {
15 "subfield" : "value"
16 }
17}
18
19POST my-index-000001/_search
20{
21 "fields": ["flattened_field.subfield"],
22 "_source": false
23}
xxxxxxxxxx
251{
2 "took": 2,
3 "timed_out": false,
4 "_shards": {
5 "total": 1,
6 "successful": 1,
7 "skipped": 0,
8 "failed": 0
9 },
10 "hits": {
11 "total": {
12 "value": 1,
13 "relation": "eq"
14 },
15 "max_score": 1.0,
16 "hits": [{
17 "_index": "my-index-000001",
18 "_id": "1",
19 "_score": 1.0,
20 "fields": {
21 "flattened_field.subfield" : [ "value" ]
22 }
23 }]
24 }
25}
你可以使用Painless script读取flattened字段的子字段的字段值。相比较doc['<field_name>'].value
。在你的Script中,使用doc['<field_name>.<sub-field_name>'].value
。例如如果你有一个名为label
的flattened字段以及一个release
的子字段。你的Painless Script中应该是doc['labels.release'].value
。
例如,比如说你的mapping中包含两个字段,其中一个是flattened
类型:
xxxxxxxxxx
131PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "title": {
6 "type": "text"
7 },
8 "labels": {
9 "type": "flattened"
10 }
11 }
12 }
13}
索引一些包含你mapping中的字段的文档。labels
字段有三个子字段:
xxxxxxxxxx
71POST /my-index-000001/_bulk?refresh
2{"index":{}}
3{"title":"Something really urgent","labels":{"priority":"urgent","release":["v1.2.5","v1.3.0"],"timestamp":{"created":1541458026,"closed":1541457010}}}
4{"index":{}}
5{"title":"Somewhat less urgent","labels":{"priority":"high","release":["v1.3.0"],"timestamp":{"created":1541458026,"closed":1541457010}}}
6{"index":{}}
7{"title":"Not urgent","labels":{"priority":"low","release":["v1.2.0"],"timestamp":{"created":1541458026,"closed":1541457010}}}
因为labels
是一个flattened
字段,所以整个对象映射到一个字段中。若要在一个Painless Script中检索子字段,那么使用doc['<field_name>.<sub-field_name>'].value
。
xxxxxxxxxx
61"script": {
2 "source": """
3 if (doc['labels.release'].value.equals('v1.3.0'))
4 {emit(doc['labels.release'].value)}
5 else{emit('Version mismatch')}
6 """
可以使用下面的参数:
depth_limit:flattened字段允许嵌套的最大深度(nested Inner object)。如果超过限制,则会抛出错误。默认值为20
。注意的是可以通过update mapping更新depth_limit
。
doc_values:- 该字段值是否在磁盘上使用列式存储,使得可以用来进行聚合、排序或者脚本。可选值true
或者false
eager_global_ordinals :是否在refresh尽快的载入global ordinals?默认是false
。如果经常用于terms aggregation,开启这个参数是很有必要的
ignore_above:leaf values的长度超过限制的话则不会被索引。默认情况喜爱没有限制并且都可以被索引。注意的是这个限制只应用与flattened的leaf values,而不是整个字段的长度
index:是否该字段需要被快速的搜索到?可选值true
或者false
。
index_options:在索引中存储哪些信息用于打分目的。默认是docs
但是可以设置为freqs
,在打分时会将词频考虑进去。
null_value:flattened字段中null
值会被替换为一个string value。默认是null
。意味着null值被认为是缺失值
similarity:使用哪一个打分算法。默认值为BM25
。
split_queries_on_whitespace:在flattened字段上执行full text queries时,是否根据空格对输入进行分割。可选值为true
或false
(default)
(8.2)link
geo_point
字段可以接受经度纬度对,可以用于:
在一个bounding box中,相对于中心点某个距离(distance)找到一个地理点(geopoints),或者从一个polygon中、或者一个geo_shape query中找到一个地理点。
根据地理位置(geographically )或者离中心点的距离(distance)对文档进行聚合
将距离信息集成到文档的相关性打分relevance score中
根据距离对文档排序(sort)
有五种方式来指定地理点,如下所示:
xxxxxxxxxx
611PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "location": {
6 "type": "geo_point"
7 }
8 }
9 }
10}
11
12PUT my-index-000001/_doc/1
13{
14 "text": "Geopoint as an object",
15 "location": {
16 "lat": 41.12,
17 "lon": -71.34
18 }
19}
20
21PUT my-index-000001/_doc/2
22{
23 "text": "Geopoint as a string",
24 "location": "41.12,-71.34"
25}
26
27PUT my-index-000001/_doc/3
28{
29 "text": "Geopoint as a geohash",
30 "location": "drm3btev3e86"
31}
32
33PUT my-index-000001/_doc/4
34{
35 "text": "Geopoint as an array",
36 "location": [ -71.34, 41.12 ]
37}
38
39PUT my-index-000001/_doc/5
40{
41 "text": "Geopoint as a WKT POINT primitive",
42 "location" : "POINT (-71.34 41.12)"
43}
44
45GET my-index-000001/_search
46{
47 "query": {
48 "geo_bounding_box": {
49 "location": {
50 "top_left": {
51 "lat": 42,
52 "lon": -72
53 },
54 "bottom_right": {
55 "lat": 40,
56 "lon": -74
57 }
58 }
59 }
60 }
61}
第15行,地理点用一个对象表示,对象中有lat
跟lon
两个key
第24行,地理点用一个字符串类型的format表示:lat,lon
第30行,地理点用GeoHash表示
第36行,地理点用数组类型的format表示:[lon
, lat
]
第42行,地理点用 Well-Known Text POINT的format表示:"POINT(lon lat)"
第48行,一个geo-bounding_box query,用来找到在地理位置范围内所有的地理点。
IMPORTANT:Geopoints expressed as an array or string 注意的使string类型的地理点的经纬度先后顺序为
lat
lon
,然而数组类型的地理点的经纬度先后顺序为lon``lat
。原先这两种类型的地理点都是
lat
lon
的先后顺序,但是数组类型的地理点为了符合GeoJSON的格式做出了更改
NOTE:一个point可以用geohash表示,是将纬度和经度的比特交错后,用 base32 编码成字符串。geohash的每一个字符增加额外的 5 个比特精度。因此,哈希越长,其精度就越高。为了索引目的,geohash被转换成纬度-经度对。在这个过程中,只使用前 12 个字符,所以在geohash指定超过 12 个字符并不会增加精度。这 12 个字符提供了 60 个比特,应该能将可能的误差减少到不到 2 厘米。
geo_point
字段可以有下面的参数:
ignore_malformed:如果为true
,格式错误的地理点会被忽略。如果为false
(default),格式错误的地理点会抛出一个异常并且reject整个文档。如果其纬度超出了 -90 至 90 的范围,或者其经度超出了 -180 至 180 的范围,则认为地理点是格式错误的。注意的使如果使用了script参数,则无法设置这个参数。
ignore_z_value:如果为true
(default),可以索引三维的point(点数据),但是只有经纬度的值会被索引;第三个维度的值被忽略。如果为false
地理点的维度如果超过了经纬度这两个维度就会抛出异常并且reject文档。注意的使如果使用了script参数,则无法设置这个参数。
index:是否该字段需要被快速的搜索到?可选值true
或者false
。只有doc_values参数的字段仍然可以被查询,尽管慢一些(albeit slower)
null_value:可是是一个地理点用来替换所有显示为null
的值。默认是null
,意味着当前字段被认为是一个缺失值。注意的是如果使用了下面的参数script
,那么不能设置当前参数
on_script_error:定义了当使用参数script
并且在索引期间脚本抛出异常后的行为。默认值为false
会导致整个文档会被reject,然后被注册到文档的 _ignored这个metadata field中并且继续索引。注意的是如果使用了下面的参数script
,那么不能设置当前参数
script:如果设置了这个参数,字段值会使用脚本生成的值而不是文档中的原始数据。如果使用输入文档(input document)中的数据作为字段值,那么这篇文档会被reject并且返回错误。Script跟runtime 的格式是一样的。并且应该抛出double类型的经纬度对值。
在script中访问地理点时,地理点是一个GeoPoint
对象,可以分别访问lat
跟lon
的值。
xxxxxxxxxx
31def geopoint = doc['location'].value;
2def lat = geopoint.lat;
3def lon = geopoint.lon;
出于性能考虑,最好直接访问经纬度值:
xxxxxxxxxx
21def lat = doc['location'].lat;
2def lon = doc['location'].lon;
(8.2)link
用来存放预先聚合、数值类型的数据来代表一个直方图的字段。使用下面的成对的两个数组:
一个double类型的值数组(value array),代表直方图的分桶。这些值在数组中必须有序并且是升序。
一个interger类型的统计数组(count array),代表每个分桶中有多少个值。这些值必须是正数或者零。
因为value array中的元素对应在count array中相同数组下标的元素,所以这两个数组的长度必须要相同。
IMPORTANT:每一篇文档中,一个
histogram
字段只能存储单个values
和count
的数组对。不支持nested arrays。histogram
字段不支持排序。
将字段作为一个Time series metric。这个值是指标类型(metric type)。默认是null
(不是一个时间序列指标)。
对于histogram
字段,这个参数接收histogram
。你不能对现有的字段更新这个参数
使用histogram
字段的主要目的是聚合。为了使其更容易被聚合访问,直方图字段数据被存储为二进制文档值并且不被索引。其以字节为单位的大小最多为 13 * numValues,其中 numValues 是提供的数组的长度。
因为没有索引数据,你可以在下面的聚合和查询中使用histogram
字段:
min aggregation
max aggregation
sum aggregation
value_count aggregation
avg aggregation
percentiles aggregation
percentile ranks aggregation
boxplot aggregation
histogram aggregation
range aggregation
exists query
当使用histogram作为聚合的一部分时,聚合结果的准确性取决于如何构建的。percentiles aggregation mode中需要重点关注:
For the T-Digest mode, the values array represents the mean centroid positions and the counts array represents the number of values that are attributed to each centroid. If the algorithm has already started to approximate the percentiles, this inaccuracy is carried over in the histogram.
For the High Dynamic Range (HDR) histogram mode, the values array represents fixed upper limits of each bucket interval, and the counts array represents the number of values that are attributed to each interval. This implementation maintains a fixed worse-case percentage error (specified as a number of significant digits), therefore the value used when generating the histogram would be the maximum accuracy you can achieve at aggregation time.
The histogram field is "algorithm agnostic" and does not store data specific to either T-Digest or HDRHistogram. While this means the field can technically be aggregated with either algorithm, in practice the user should chose one algorithm and index data in that manner (e.g. centroids for T-Digest or intervals for HDRHistogram) to ensure best accuracy.
下面的create index API请求创建了一个新的索引,mapping中有两个字段:
my_histogram
是histogram
字段用于存储百分值
my_text
是keyword
字段用于存储直方图的标题
xxxxxxxxxx
131PUT my-index-000001
2{
3 "mappings" : {
4 "properties" : {
5 "my_histogram" : {
6 "type" : "histogram"
7 },
8 "my_text" : {
9 "type" : "keyword"
10 }
11 }
12 }
13}
下面的index API请求为两个直方图:histogram_1
和histogram_2
存储预先聚合的数据。
xxxxxxxxxx
171PUT my-index-000001/_doc/1
2{
3 "my_text" : "histogram_1",
4 "my_histogram" : {
5 "values" : [0.1, 0.2, 0.3, 0.4, 0.5],
6 "counts" : [3, 7, 23, 12, 6]
7 }
8}
9
10PUT my-index-000001/_doc/2
11{
12 "my_text" : "histogram_2",
13 "my_histogram" : {
14 "values" : [0.1, 0.25, 0.35, 0.4, 0.45, 0.5],
15 "counts" : [8, 17, 8, 7, 6, 2]
16 }
17}
第5、14行:Values for each bucket. Values in the array are treated as doubles and must be given in increasing order. For T-Digest histograms this value represents the mean value. In case of HDR histograms this represents the value iterated to. 第6、15行:Count for each bucket. Values in the arrays are treated as integers and must be positive or zero. Negative values will be rejected. The relation between a bucket and a count is given by the position in the array.
(8.2)link
xxxxxxxxxx
241PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "ip_addr": {
6 "type": "ip"
7 }
8 }
9 }
10}
11
12PUT my-index-000001/_doc/1
13{
14 "ip_addr": "192.168.1.1"
15}
16
17GET my-index-000001/_search
18{
19 "query": {
20 "term": {
21 "ip_addr": "192.168.0.0/16"
22 }
23 }
24}
NOTE:你可以使用ip_range data type在单个字段中存储ip ranges。
ip
字段接受下列的参数:
该字段值是否在磁盘上使用列式存储,使得可以用来进行聚合、排序或者脚本。可选值true
或者false
如果设置为true
,数据格式错误的IP地址被忽略。如果设置为false
(默认值),数据格式错误的IP地址会导致抛出异常并reject整个文档。注意的是如果使用了下面的参数script
,那么不能设置当前参数
是否该字段需要被快速的搜索到?可选值true
或者false
。只有doc_values参数的字段仍然可以使用term or range-based的查询,尽管慢一些(albeit slower)
ip
字段接受一个IPV4或者IPV6的值,同时也接受显示(explicit)的null
值,意味着当前字段被认为是一个缺失值。注意的是如果使用了下面的参数script
,那么不能设置当前参数
on_script_error
定义了当使用参数script
并且在索引期间脚本抛出异常后的行为。默认值为reject
会导致整个文档会被reject,设置为ignore
后,会被注册到文档的 _ignored这个metadata field中并且继续索引。注意的是如果使用了下面的参数script
,那么不能设置当前参数
script
如果设置了这个参数,字段值会使用脚本生成的值而不是文档中的原始数据。如果使用输入文档(input document)中的数据作为字段值,那么这篇文档会被reject并且返回错误。脚本跟runtime equivalent采用一样的格式,并且应该返回IPV4或者IPV6格式的值
当前字段是否要存储使得可以从_source
检索该字段。可选值true
或者false
time_series_dimension
预览功能, 可能会被更改或者移除,目前只在Elasticsearch代码内部使用,暂不介绍。
最常用查询IP的方式就是使用 CIDR:
xxxxxxxxxx
81GET my-index-000001/_search
2{
3 "query": {
4 "term": {
5 "ip_addr": "192.168.0.0/16"
6 }
7 }
8}
或者
xxxxxxxxxx
81GET my-index-000001/_search
2{
3 "query": {
4 "term": {
5 "ip_addr": "2001:db8::/48"
6 }
7 }
8}
注意的是,冒号是query_string查询的特殊字符,因此ipv6地址需要转义。最简单的方法是在搜索值两边加双引号:
xxxxxxxxxx
81GET my-index-000001/_search
2{
3 "query": {
4 "query_string" : {
5 "query": "ip_addr:\"2001:db8::/48\""
6 }
7 }
8}
(8.2)link
join
数据类型是一个特殊的字段,用来创建同一个索引中文档间的父子关系。relations
定义了文档之间的关系,它也定义了父文档的名称以及子文档的名称。
WARNING:不要使用多级父子关系来模拟关系型数据库模型。每增加一级关系,都会在查询时增加内存和计算的开销。为了获得更好的搜索性能,建议denormalize你的数据。
通过以下方式定义父子关系:
xxxxxxxxxx
161PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "my_id": {
6 "type": "keyword"
7 },
8 "my_join_field": {
9 "type": "join",
10 "relations": {
11 "question": "answer"
12 }
13 }
14 }
15 }
16}
第8行,字段的名字
第11行,定义了单个关系,question
是answer
的父级(parent)
若要使用join索引一篇文档,需要在_source
中提供关系的名称和父级文档(可选)。例如下面的例子创建了在question
的上下文中定义了两个parent
文档:
xxxxxxxxxx
171PUT my-index-000001/_doc/1?refresh
2{
3 "my_id": "1",
4 "text": "This is a question",
5 "my_join_field": {
6 "name": "question"
7 }
8}
9
10PUT my-index-000001/_doc/2?refresh
11{
12 "my_id": "2",
13 "text": "This is another question",
14 "my_join_field": {
15 "name": "question"
16 }
17}
第6行,这篇文档时question
文档。
当索引父级文档(parent document)时,你可以选择快捷的只指定关系的名称,而不是使用对象进行封装:
xxxxxxxxxx
131PUT my-index-000001/_doc/1?refresh
2{
3 "my_id": "1",
4 "text": "This is a question",
5 "my_join_field": "question"
6}
7
8PUT my-index-000001/_doc/2?refresh
9{
10 "my_id": "2",
11 "text": "This is another question",
12 "my_join_field": "question"
13}
第5行,直接使用关系的名称。
当索引一篇子文档(child parent)时,必须在_source
中添加关系的名称以及父文档的文档ID(_id
).
WARNING:要求将父子关系索引到同一个分片上,因此你必须使用父级文档ID来路由
例如下面的例子中展示了如何索引两篇child
文档:
xxxxxxxxxx
191PUT my-index-000001/_doc/3?routing=1&refresh
2{
3 "my_id": "3",
4 "text": "This is an answer",
5 "my_join_field": {
6 "name": "answer",
7 "parent": "1"
8 }
9}
10
11PUT my-index-000001/_doc/4?routing=1&refresh
12{
13 "my_id": "4",
14 "text": "This is another answer",
15 "my_join_field": {
16 "name": "answer",
17 "parent": "1"
18 }
19}
第1行,路由值是强制要有的,因为父/子文档必须索引到同一个分片上
第6行,answer
是这个文档的join 名称
第7行,这个子文档的父级文档的文档ID
在Elasticsearch中,不应将join字段用作关系数据库中的连接操作。为了保持良好的性能,关键是将数据反规范化(de-normalize)成文档。每个join字段、has_child
或has_parent
查询都会显著增加查询性能的负担,并可能触发global ordinals的构建。
只有在数据包含一对多关系,其中一个实体数量显著多于另一个实体的情况下,使用join字段才有意义。例如,产品和这些产品的优惠就是这样的用例,如果优惠的数量远多于产品的数量,那么将产品模型化为父文档,将优惠模型化为子文档是有意义的。
每一个索引的mapping中只能有一个join
类型的字段
父/子文档必须索引到相同的分片上。意味着在getting、deleting、updating子文档时必须提供相同的route
值
一个元素可以有多个子节点但只能有一个父节点
可以像现有的join
字段中添加新的关系
如果元素已经是父节点,也可以向现有元素添加子节点
在Elasticsearch中使用父子连接(parent-join)搜索时,该功能创建一个字段来索引文档内关系的名称(例如my_parent, my_child等)。
对于每个父/子关系,它还会创建一个字段,该字段名称由join字段的名称加上#
和关系中父项的名称组成。例如,对于my_parent
→ [my_child, another_child]
关系,join字段会创建一个额外的名为my_join_field#my_parent
的字段。
如果文档是子文档(my_child或another_child),此字段包含文档链接到的父_id
;如果是父文档(my_parent),则包含文档的_id
。搜索包含join字段的索引时,这两个字段总是返回在搜索响应中。
当查询一个包含join
于的索引时,这两个字段总是会返回:
xxxxxxxxxx
71GET my-index-000001/_search
2{
3 "query": {
4 "match_all": {}
5 },
6 "sort": ["my_id"]
7}
返回:
xxxxxxxxxx
721{
2 ...,
3 "hits": {
4 "total": {
5 "value": 4,
6 "relation": "eq"
7 },
8 "max_score": null,
9 "hits": [
10 {
11 "_index": "my-index-000001",
12 "_id": "1",
13 "_score": null,
14 "_source": {
15 "my_id": "1",
16 "text": "This is a question",
17 "my_join_field": "question"
18 },
19 "sort": [
20 "1"
21 ]
22 },
23 {
24 "_index": "my-index-000001",
25 "_id": "2",
26 "_score": null,
27 "_source": {
28 "my_id": "2",
29 "text": "This is another question",
30 "my_join_field": "question"
31 },
32 "sort": [
33 "2"
34 ]
35 },
36 {
37 "_index": "my-index-000001",
38 "_id": "3",
39 "_score": null,
40 "_routing": "1",
41 "_source": {
42 "my_id": "3",
43 "text": "This is an answer",
44 "my_join_field": {
45 "name": "answer",
46 "parent": "1"
47 }
48 },
49 "sort": [
50 "3"
51 ]
52 },
53 {
54 "_index": "my-index-000001",
55 "_id": "4",
56 "_score": null,
57 "_routing": "1",
58 "_source": {
59 "my_id": "4",
60 "text": "This is another answer",
61 "my_join_field": {
62 "name": "answer",
63 "parent": "1"
64 }
65 },
66 "sort": [
67 "4"
68 ]
69 }
70 ]
71 }
72}
第17行,这个文档是question
第30行,这个文档是question
第45行,这个文档是answer
第46行,子文档链接的父文档ID
查看has_child、has_parent Query、child aggregation以及inner hits了解更多信息。
&emspjoin
字段的字段值可以在聚合根脚本中访问,可以使用parent_id query查询。
xxxxxxxxxx
281GET my-index-000001/_search
2{
3 "query": {
4 "parent_id": {
5 "type": "answer",
6 "id": "1"
7 }
8 },
9 "aggs": {
10 "parents": {
11 "terms": {
12 "field": "my_join_field#question",
13 "size": 10
14 }
15 }
16 },
17 "runtime_mappings": {
18 "parent": {
19 "type": "long",
20 "script": """
21 emit(Integer.parseInt(doc['my_join_field#question'].value))
22 """
23 }
24 },
25 "fields": [
26 { "field": "parent" }
27 ]
28}
第4行,查询parent_id
字段(见has_parent query 和has_child query)
第12行,在parent_id
上聚合(见children aggregation)
第21行,在脚本中访问parent_id
join
字段使用全局序号(global ordinals)来加速连接操作。每当分片发生变化后,全局序号需要重建。分片中存储的父ID值越多,重建join字段的全局序号所需时间就越长;
默认情况下,全局序号会积极(eagerly)构建:如果索引发生变化,join
字段的全局序号会作为刷新的一部分被重建。这可能会显著增加刷新时间。然而,大多数情况下这是正确的权衡,否则在首次使用父子连接查询或聚合时,全局序号会被重建,这可能会为用户引入显著的延迟峰值。
当join
字段使用不频繁且频繁写入时,可能有必要禁用积极加载。
xxxxxxxxxx
151PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "my_join_field": {
6 "type": "join",
7 "relations": {
8 "question": "answer"
9 },
10 "eager_global_ordinals": false
11 }
12 }
13 }
14}
15
可以通过下面的方式查看每一个parent relation对应的全局序号的内存使用量:
xxxxxxxxxx
51# Per-index
2GET _stats/fielddata?human&fields=my_join_field#question
3
4# Per-node per-index
5GET _nodes/stats/indices/fielddata?human&fields=my_join_field#question
也可以为单个parent定义多个child:
xxxxxxxxxx
131PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "my_join_field": {
6 "type": "join",
7 "relations": {
8 "question": ["answer", "comment"]
9 }
10 }
11 }
12 }
13}
第8行,question
是answer
和comment
的parent
WARNING:不要使用多级父子关系来模拟关系型数据库模型。每增加一级关系,都会在查询时增加内存和计算的开销。为了获得更好的搜索性能,建议denormalize你的数据。
Multiple levels of parent/child:
xxxxxxxxxx
141PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "my_join_field": {
6 "type": "join",
7 "relations": {
8 "question": ["answer", "comment"],
9 "answer": "vote"
10 }
11 }
12 }
13 }
14}
第7行,question
是answer
和comment
的parent
第8行,vote
是answer
的parent
上面的mapping意思就是:
xxxxxxxxxx
71 question
2 / \
3 / \
4comment answer
5 |
6 |
7 vote
索引一个grandchild文档需要跟grand-parent一样的路由值(the greater parent of the lineage)
xxxxxxxxxx
81PUT my-index-000001/_doc/3?routing=1&refresh
2{
3 "text": "This is a vote",
4 "my_join_field": {
5 "name": "vote",
6 "parent": "2"
7 }
8}
第1行,必须跟parent、grand-parent在相同的分片上
第6行,这个文档的父级文档的文档ID(必须指向answer
文档)
(8.2)link
keyword家族包含下面的字段类型:
keyword:用于ID,邮件地址,hostname,状态码,zip code,或者标签
constant_keyword:keyword类型,并且所有文档中该字段的字段值都是相同的
wildcard:用于非结构化的机器自动生成的内容。wildcard
类型对于large value或者high cardinality的字段值进行了优化
keyword常用于sorting,aggregation以及term-level queries,例如term。
TIP:若要全文检索,请不要使用keyword。而是使用text类型。
下面是keyword
字段的mapping:
xxxxxxxxxx
101PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "tags": {
6 "type": "keyword"
7 }
8 }
9 }
10}
TIP:Mapping numeric identifiers 不是所有的数值类型的数据都应该映射为一个numeric数据类型。Elasticsearch会为数值类型的字段,比如
integer
、long
针对range查询优化。然而,keyword字段更适合于term以及其他term-level Query。 比如一些标识符,ISBN或者产品ID,很少用于range
query,但是常用于term-level query。 如果有以下的场景,可以考虑将数值类型的标识符映射为keyword
:
你不打算将标识符类型的数据用于range query
快速的检索对你来说很重要的话,那么相较于数值类型的字段,在
keyword
字段上进行term
query通常更快。如果你不确定该使用哪种类型,那么可以通过multi-field来同时使用
keyword
跟数值类型的字段。
keyword
字段可以使用以下的参数:
doc_values : 该字段是否用基于列式存储于磁盘中,使得可以用于排序、聚合、或者Script。参数值为true
或者false
(默认值)
eager_global_ordinals :是否在refresh尽快的载入global ordinals?默认是false
。如果经常用于terms aggregation,开启这个参数是很有必要的
fields: Multi-fields允许同一个将string value基于不同的目的用多种方式进行索引。比如用于查询或者用于排序聚合
ignore_above:不会索引长于该参数的值。默认是2147483647
。然而注意的是默认的动态映射创建的子字段keyword
会覆盖这个默认的值,并且将ignore_above
设置为256
index:该字段是否需要被快速的检索?参数值为true
或者false
(默认值)。
index_options:在索引中存储哪些信息用于打分目的。默认是docs
但是可以设置为freqs
,在打分时会将词频考虑进去。
meta:字段的元数据信息
norms:打分时是否考虑字段值的长度。默认false,可以设置为true
null_value:参数值可以是一个该字段的相同类型的数值。默认值是null
,意味着缺失值。注意的是如果使用了script
参数,则不能设置这个参数
on_script_error:该参数定义了当Script在索引期间抛出错误后,该如何处理。默认值为fail
,会导致整个文档被reject;如果参数值为continue
,将在_ignored字段注册这个字段然后继续索引。这个参数只有在script
设置后才能被设置。
script:如果设置了这个参数,这个字段的字段值会通过script生成,而不是直接从源数据中读取。如果文档中设置了该字段的值,则会reject并且抛出错误。script跟runtime中有相同的format。
similarity:使用哪一个打分算法。默认值为BM25
。
normalizer:如何在索引之前预先处理keyword的值,默认为false
,即使用keyword原有的字段值。
split_queries_on_whitespace:执行full text queries时,是否根据空格对输入进行分割。可选值为true
或false
(default)
time_series_dimension
预览功能, 可能会被更改或者移除,目前只在Elasticsearch代码内部使用,暂不介绍。
Constant keyword是keyword
的一种特例用于所有的文档中会索引相同的值。
xxxxxxxxxx
171PUT logs-debug
2{
3 "mappings": {
4 "properties": {
5 "@timestamp": {
6 "type": "date"
7 },
8 "message": {
9 "type": "text"
10 },
11 "level": {
12 "type": "constant_keyword",
13 "value": "debug"
14 }
15 }
16 }
17}
constant_keyword
支持跟keyword
有相同的查询跟聚合,但是会利用所有文档拥有相同的值这个特点来提高查询效率。
同时还允许文档中不包含这个字段或者这个字段的字段值跟mapping中的值相同。下面两个索引请求是等价的:
xxxxxxxxxx
121POST logs-debug/_doc
2{
3 "date": "2019-12-12",
4 "message": "Starting up Elasticsearch",
5 "level": "debug"
6}
7
8POST logs-debug/_doc
9{
10 "date": "2019-12-12",
11 "message": "Starting up Elasticsearch"
12}
然而如果文档中该字段的字段值跟mapping中的不一致是不允许。
如果mapping中没有配置value
,那么该字段会基于第一篇文档中该字段的值自动的配置。注意的是这意味着如果第一篇文档的值是有问题的,会导致其他的合法的文档被reject。
在提供value
之前(通过mapping或者第一篇文档),任何对该字段的查询都不会匹配任何文档。包括exists query。
该字段的value
在设置后就不能变更。
该类型可以有以下的参数:
meta:字段的元数据信息
value:索引中所有文档的constant keyword fields的字段值。如果没有设置该参数,则是基于第一篇被索引的文档中该字段的字段值
wildcard
字段是一种特殊的keyword类型,用于无结构的机器自动生成的内容,并且你计划使用wildcard跟regexp这类Query进行查询。wildcard
类型对于large value和high cardinality有专门的优化。
Mapping unstructured content 你可以将一个无结构的内容使用
text
或者keyword家族字段。使用哪一种取决于内容的特点以及你如何查询这个字段 如果有以下的场景,则使用text
字段:
内容是可读的(human-readable),比如邮件正文或者产品描述
你想要使用独立的单词或者短语进行查询,例如
the brown fox jumped
,使用full text queries,Elasticsearch会对text
字段进行分词并且返回相关性最高的结果。如果有以下的场景,则使用keyword家族字段:
内容是机器自动生成的,比如日志消息或者HTTP请求信息
你想要使用精确完整的值进行查询,比如
org.foo.bar
,或者使用部分字符序列,比如使用term-level queires查询org.foo.*
。Choosing a keyword family field type 如果你选择了一个keyword家族字段,使用
keyword
还是wildcard
取决于字段值的长度和字段值的种类(cardinality)。如果你经常使用wildcard或者regexp查询并且满足下面类别的一种,则使用wildcard
:
字段值种类包含超过one million个并且经常使用通配符在关键字开头的查询,例如
*foo
和*baz
。字段值大小超过32KB并且经常使用wildcard pattern查询
否则就使用
keyword
类型,这样查询速度更快,索引更快并且占用更少的存储开销。对于深入的比较以及讨论,见related blog post。Switching from a text field to a keyword field 如果你之前使用
text
字段处理无结构的机器自动生成的内容。你可以reindex to update the mapping到keyword
或者wildcard
字段中。我们还建议,将你们应用或者工作流中在这个字段上的full text queries替换为等价的term-level queries。
在内部,通配符字段使用 ngram 对整个字段值进行索引,并存储完整字符串。这个索引被用作rough filter,以减少随后通过检索和检查完整值来检查的值数量。这个字段特别适合用于在日志行上运行类似 grep 的查询。存储成本通常低于keyword
字段,但对完整词项的精确匹配的搜索速度较慢。如果字段值共享许多前缀,如同一个网站的 URL,wildcard
字段的存储成本可能高于等效的keyword
字段。
你可以通过下面的方式索引以及查询一个wildcard
字段:
xxxxxxxxxx
261PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "my_wildcard": {
6 "type": "wildcard"
7 }
8 }
9 }
10}
11
12PUT my-index-000001/_doc/1
13{
14 "my_wildcard" : "This string can be quite lengthy"
15}
16
17GET my-index-000001/_search
18{
19 "query": {
20 "wildcard": {
21 "my_wildcard": {
22 "value": "*quite*lengthy"
23 }
24 }
25 }
26}
wildcard
可以使用下面的参数:
null_value:参数值可以是一个字符串。默认值是null
,意味着缺失值。注意的是如果使用了script
参数,则不能设置这个参数
ignore_above:不会索引长于该参数的值。默认是2147483647
。
wildcard
字段跟keyword一样不会被分词。因此不支持依赖位置的查询,比如短语查询
在执行通配符查询时,任何设置的重写参数将被忽略。返回的文档打分总是一个常数值。
(8.2)link
nested
类型是object类型的特殊版本,它允许将对象数组以一种可以独立查询每个对象的方式进行索引。
TIP:当你需要处理包含大量任意键的键值对时,你可能会考虑将每一对键值作为一个独立的嵌套文档进行建模,其中每个文档包含两个字段:一个用于键(key),另一个用于值(value)。
例如产品A:color: red size: medium 产品B: color: blueweight: 1kg。希望通过color: red size: medium这两个条件才会找到产品A。
若考虑使用flattened数据类型,则将整个对象作为一个字段的字段值,允许对整个对象的内容做简单的搜索。Nested document和query通常开销昂贵,所以在这种例子中使用
flattened
数据类型是个不错的选择。
Elasticsearch没有inner object的概念。因此,它是将其平铺为的字段名字段值列表。例如索引以下的文档:
xxxxxxxxxx
141PUT my-index-000001/_doc/1
2{
3 "group" : "fans",
4 "user" : [
5 {
6 "first" : "John",
7 "last" : "Smith"
8 },
9 {
10 "first" : "Alice",
11 "last" : "White"
12 }
13 ]
14}
第4行,user
字段会动态的添加为object
类型。
上面的文档在内部被转化为以下的内容:
xxxxxxxxxx
51{
2 "group" : "fans",
3 "user.first" : [ "alice", "john" ],
4 "user.last" : [ "smith", "white" ]
5}
user.first
和user.last
平铺为多值的字段,同时alice
和white
的关联信息也丢失了(关联信息指的是:它们两个属于同一个对象)。下面alice AND smith
的这种查询会错误的匹配到这篇文档:
xxxxxxxxxx
111GET my-index-000001/_search
2{
3 "query": {
4 "bool": {
5 "must": [
6 { "match": { "user.first": "Alice" }},
7 { "match": { "user.last": "Smith" }}
8 ]
9 }
10 }
11}
如果你需要索引对象数组并且还要维护数组中每一个对象相对独立,那么使用nested
数据类型而不是object数据类型。
在内部,nested object会将数组中每一个对象分别索引到一个hidden document(即在Lucene层每一个对象对应一个文档),意思是每一个nested object可以使用nested query独立于其他对象进行查询
xxxxxxxxxx
661PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "user": {
6 "type": "nested"
7 }
8 }
9 }
10}
11
12PUT my-index-000001/_doc/1
13{
14 "group" : "fans",
15 "user" : [
16 {
17 "first" : "John",
18 "last" : "Smith"
19 },
20 {
21 "first" : "Alice",
22 "last" : "White"
23 }
24 ]
25}
26
27GET my-index-000001/_search
28{
29 "query": {
30 "nested": {
31 "path": "user",
32 "query": {
33 "bool": {
34 "must": [
35 { "match": { "user.first": "Alice" }},
36 { "match": { "user.last": "Smith" }}
37 ]
38 }
39 }
40 }
41 }
42}
43
44GET my-index-000001/_search
45{
46 "query": {
47 "nested": {
48 "path": "user",
49 "query": {
50 "bool": {
51 "must": [
52 { "match": { "user.first": "Alice" }},
53 { "match": { "user.last": "White" }}
54 ]
55 }
56 },
57 "inner_hits": {
58 "highlight": {
59 "fields": {
60 "user.first": {}
61 }
62 }
63 }
64 }
65 }
66}
第6行,user
字段使用nested
而不是object
。
第36行,因为Alice
和Smith
不是同一个nested object,所以这个query不会匹配到文档
第53行,因为Alice
和White
是同一个nested object,所以这个query会匹配到文档
第57行,inner_hits
允许我们高亮匹配到的文档。
使用nested query查询
用nested聚合以及reverse_nested aggregations.
用nested inner hits检索以及高亮
IMPORTANT:因为nested documents索引为单独的文档(在lucene中是单独的文档,数组中的每一个对象都是lucene中的一篇文档,并且包含父文档的父文档ID,以及在主文档中的位置信息)。因此它们只能通过
nested
query,nested/reverse_nested
aggregation,或者nested inner hits访问。例如,如果nested document内的字符串字段的index_options设置为
offsets
以便在高亮显示时使用倒排信息,那么在主要高亮(main highlighting)显示阶段这些偏移量将不可用。相反,需要通过nested inner hits来执行高亮显示。在通过docvalue_fields或stored_fields进行搜索时加载字段时,同样的考虑也适用。
可以有下面的参数:
dynamic:(Optional, string)是否将新的properties
写入到已存在的nested object中,可选值为true
(default), runtime
, false
and strict
properties:nested object中的所有字段(这些字段被称为properties),这些字段可以是任何数据类型data type,新的字段可能被添加现在有的nested object字段中
include_in_parent:(Optional, Boolean) 如果为true
,nested object中的所有字段同样添加到parent document 作为标准(flat)字段。默认是false
。
如果有多层嵌套,那么上一层就是parent。就可以使用普通的query,比如match
根据上一层父级的字段进行搜索
include_in_root:(Optional, Boolean)如果为true
,nested object中的所有字段同样添加到root document 作为标准(flat)字段。默认是false
。
这两个参数为true的好处相同点就是可以通过普通query就可以进行查询,而不需要专用的nested query。
(8.2)link
支持下列的数值类型:
long:有符号的64位整数,最小值2^63
,最大值2^63 -1
integer:有符号32位整数,最小值2^31
,最大值2^31 -1
short:有符号的16位整数,最小值-32,768
,最大值32,767
byte:有符号的8位整数,最小值-128
,最大值127
double:一个双精度的64位IEEE 754浮点数,限制为有限值(不包括infinity和NaN,Not a Number)
float:一个单精度的32位IEEE 754浮点数,限制为有限值
half_float:一个半精度的16位IEEE 754浮点数,限制为有限值。
scaled_float:一个由长整数(long)支持,通过固定双精度缩放因子缩放的浮点数
unsigned_long:无符号的64位整数,最小值为0,最大值为2^64-1
。
下面是在mapping中配置数值字段的例子:
xxxxxxxxxx
171PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "number_of_bytes": {
6 "type": "integer"
7 },
8 "time_in_seconds": {
9 "type": "float"
10 },
11 "price": {
12 "type": "scaled_float",
13 "scaling_factor": 100
14 }
15 }
16 }
17}
NOTE:
double
,float
和half_float
类型中-0.0
和+0.0
是不一样的值。因此,term
query-0.0
不会匹配到+0.0
,反之亦然。对于range query也是一样的:如果上界是-0.0
那么+0.0
不会匹配,同样的,如果下界是+0.0
那么-0.0
不会匹配到。
对于整数类型(byte
, short
, integer
以及long
),对于你的用例,你应该选择最小类型(smallest Type)。这将让索引和搜索更加高效。注意的是尽管存储时会基于实际的值进行优化,因此选择一种类型而不是另一种类型对存储需求没有影响。
对于浮点型,通常使用scaling factor
将浮点数据存储为整数会更有效率,这正是scaled_float
类型在底层所做的。例如,一个price字段可以使用scaling factor
为100的scaled_float
来存储。API中像是将字段值作为一个double存储,但在Elasticsearch内部以"分"为单位存储,price*100
,就可以将double变成一个整数。相较于浮点数,整数更易于压缩来节省磁盘开销。scaled_float
属于精度跟磁盘开销之间的折中方式。例如,假设你在跟踪 CPU利用率,这是一个介于 0 和 1 之间的数字。CPU 利用率是 12.7% 还是 13% 通常并不重要,所以你可以使用一个 scaling_factor 为 100 的 scaled_float,以将 CPU 利用率四舍五入到最接近的百分比,以节省空间。(如果字段值是固定小数位,用scaled_float是个不错的选择)
如果scaled_float
不适合你的使用场景,你应该在浮点型之间选择一个够用且最小的类型:double
、float
、half_float
。这里提供了一个类型之间差异的表,帮助你选择适当的浮点型类型。
Type | Minimum value | Maximum value | Significant bits / digits | Example precision loss |
---|---|---|---|---|
double | 2^-1074 | (2-2^-52)·2^1023 | 53 / 15.95 | 1.2345678912345678 → 1.234567891234568 |
float | 2^-149 | (2-2^-23)·2^127 | 24 / 7.22 | 1.23456789 → 1.2345679 |
half_float | 2^-24 | 65504 | 11 / 3.31 | 1.2345 → 1.234375 |
TIP:Mapping numeric identifiers 不是所有的数值类型的数据都应该映射为一个numeric数据类型。Elasticsearch会为数值类型的字段,比如
integer
、long
针对range查询优化。然而,keyword字段更适合于term以及其他term-level Query。 比如一些标识符,ISBN或者产品ID,很少用于range
query,但是常用于term-level query。 如果有以下的场景,可以考虑将数值类型的标识符映射为keyword
:
你不打算将标识符类型的数据用于range query
快速的检索对你来说很重要的话,那么相较于数值类型的字段,在
keyword
字段上进行term
query通常更快。如果你不确定该使用哪种类型,那么可以通过multi-field来同时使用
keyword
跟数值类型的字段。
数值类型的字段可以使用以下的参数:
doc_values : 该字段是否用基于列式存储于磁盘中,使得可以用于排序、聚合、或者Script。参数值为true
或者false
(默认值)
ignore_malformed:如果为true
,忽略格式错误的数值。如果为false
(default),遇到格式错误的数值会抛出一个异常并且reject整个文档。注意的是如果使用了script
参数,则不能设置这个参数
index:该字段是否需要被快速的检索?参数值为true
或者false
(默认值)。
meta:字段的元数据信息
null_value:参数值可以是一个该字段的相同类型的数值。默认值是null
,意味着缺失值。注意的是如果使用了script
参数,则不能设置这个参数
on_script_error:该参数定义了当Script在索引期间抛出错误后,该如何处理。默认值为fail
,会导致整个文档被reject;如果参数值为continue
,将在_ignored字段注册这个字段然后继续索引。这个参数只有在script
设置后才能被设置。
script:如果设置了这个参数,这个字段的字段值会通过script生成,而不是直接从源数据中读取。如果文档中设置了该字段的值,则会reject并且抛出错误。script跟runtime中有相同的format。
time_series_dimension
预览功能, 可能会被更改或者移除,目前只在Elasticsearch代码内部使用,暂不介绍。
time_series_metric
预览功能。。该字段只用于Elastic内部使用。将字段作为一个Time series metric。这个值是指标类型(metric type)。默认是null
(不是一个时间序列指标)
scaled_float
可以使用下面额外的参数
scaling_factor:该参数用来对数值进行编码。在索引期间会将数值于这个参数进行乘积运算,然后四舍五入到最近的一个long值。比如说scaled_float
的值为10
,那么在Elasticsearch内部会将2.34
存储为23
并且查询期间的操作(query、aggregation、sorting)会将这篇文档的该值视为2.3
。scaled_float
值越高代表更高的精度但增加磁盘使用。必须设置这个参数。
(8.2)link
事实上JSON documents是有层次的。文档中可能包含inner objects,并且它们自身还包含inner objects:
xxxxxxxxxx
111PUT my-index-000001/_doc/1
2{
3 "region": "US",
4 "manager": {
5 "age": 30,
6 "name": {
7 "first": "John",
8 "last": "Smith"
9 }
10 }
11}
第2行,文档外部同样是JSON object
第4行,包含一个名为manager
的inner object
第6行,manager
接着还包含一个名为name
的inner object
内部的实现是一个简单的,扁平的key-value链,如下所示:
xxxxxxxxxx
61{
2 "region": "US",
3 "manager.age": 30,
4 "manager.name.first": "John",
5 "manager.name.last": "Smith"
6}
上述文档对应的显示的mapping如下所示:
xxxxxxxxxx
211PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "region": {
6 "type": "keyword"
7 },
8 "manager": {
9 "properties": {
10 "age": { "type": "integer" },
11 "name": {
12 "properties": {
13 "first": { "type": "text" },
14 "last": { "type": "text" }
15 }
16 }
17 }
18 }
19 }
20 }
21}
第4行,mapping中最顶层的properties的定义
第8行,manager
字段是一个inner object
第11行,manager.name
字段是manager
字段中的inner object
你不需要显示的指定object
指定type
下面的参数可以应用于object
字段
dynamic:是否将新的字段写入到已存在的object字段中,可选值为true
(default), runtime
, false
and strict
enabled:object字段的字段值是否要进行解析并且写入到索引中(true
),或者完全忽略(false
)
properties:object中的所有字段(这些字段被称为properties),这些字段可以是任何数据类型data type,新的字段可能被添加现在有的object字段中(比如dynamic参数置为true的情况下)
IMPORTANT:如果不是要索引一个object,而是object数组,请先阅读Nested。
(8.2)link
text family包含的字段类型(field type)如下所示:
text,用于全文内容 的传统的字段类型,比如email的body或者产品的描述
match_only_text,经过space-optimized优化的text
字段的变种,它关闭了打分并且对于需要position的查询执行较慢。这个字段最适合索引log messages。
索引full-text values的字段,例如email的body或者一个产品的描述。字段值会被分词,字段值会被analyzer分词,将一个string值转化为一个一个独立的term,并将这些term进行索引。analysis process允许Elasticsearch在每一个full text filed中查询独立的词(individual Word)。text字段不用于排序或者很少用于聚合(尽管significant text aggregation是一个明显的例外)。
text
字段最适合用于无结构的并且是human-readable的内容。如果你需要索引无结构的machine-generated的内容,见Mapping unstructured content。
如果你需要索引例如email地址,hostnames,状态码,或者tags,你应该选择keyword字段。
下面是text字段的mapping例子:
xxxxxxxxxx
101PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "full_name": {
6 "type": "text"
7 }
8 }
9 }
10}
有时候同一个字段需要同时有full text(text
)和keyword(keyword
)。一个用来全文检索,另一个用于聚合和排序。可以通过 multi-fields实现。
text
字段接受下面的参数
analyzer | 分词器analyzery应该同时用于index-time和search-time(除非被search_analyzer重写(override)),默认是index analyzer或者standard analyzer. |
---|---|
eager_global_ordinals | 是否在refresh尽快的载入global ordinals?默认是false 。如果经常用于terms aggregation,开启这个参数是很有必要的 |
fielddata | 这个字段是否用于排序、聚合、或在script中使用?默认是false |
fielddata_frequency_filter | (Expert Setting)在开启fielddata 后,将哪些值载入到内存中,默认值是false |
fields | Multi-fields允许同一个将string value基于不同的目的索引用多种方式进行索引。比如用于查询或者用于排序聚合,或者使用不同的分词器对string value进行分词 |
index | 这个字段是否可以被搜索?默认是true |
index_options | 哪些信息需要被存储到索引中用于查询和高亮,默认是positions |
index_prefixes | 开启这个参数后,term的2到5个前缀字符会被索引到额外的字段(separate filed)。这使得前缀搜索更高效,代价就是索引大小会增加 |
index_phrases | 开启这个参数后,2个term组成的字段值会被索引到额外的字段(separate filed)中。这使得exact phrase(no slop) 查询可以更高效,代价就是索引大小会增加。注意的是只有移除了停用词后才能达到最佳性能,因为包含了停用词的短语将不会使用subsidiary field并且会回滚到标准的短语查询。默认是false |
norms | 执行打分的查询时是否要考虑字段值的长度 |
position_increment_gap | The number of fake term position which should be inserted between each element of an array of strings. Defaults to the position_increment_gap configured on the analyzer which defaults to 100 . 100 was chosen because it prevents phrase queries with reasonably large slops (less than 100) from matching terms across field values. |
store | 是否存储字段值并且可以通过_source 字段查询。默认是false |
search_analyzer | 是否在search-time时使用上文中第一个参数中指定的分词器(默认值)? |
search_quote_analyzer | 当遇到短语时,是否在search-time使用上文中第一个参数中指定的分词。默认值是上文中search_analyzer 中指定的分词器 |
similarity | 使用哪种打分算法?默认值是BM25 |
term_vector | 是否为这个字段存储term vectors?默认值是false |
meta | 这个字段的Metadata |
text
字段默认是可以用于搜索的,但是默认不能用于聚合、排序、或者scripting。如果你想要排序,聚合,或者从script中访问text
字段,你将会遇到这个异常:Fielddata is disabled on text fields by default
。 设置your_field_name
为fielddata=true
,通过uninverting倒排索引(inverted index)将fielddata导入到内存中。注意的是这将会使用大量的内存。
Field data是在排序、聚合、或者scripting中访问fulll text field的分词后的token的唯一方法。例如,比如说New York
这个full text field将会分词为new
和york
两个token,对这些token进行聚合需要field data。
在text field上开启fielddata通常是没有意义的。Field data使用field data cache并存储在堆上,因为计算开销很大。计算field data会导致latency spikes并且增加堆内存的使用,这也是导致集群性能问题的原因之一。
大多数的用户会使用multi-field mappings来同时获得text
字段用于全文检索以及不分词的keyword
字段用于聚合,如下所示:
xxxxxxxxxx
151PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "my_field": {
6 "type": "text",
7 "fields": {
8 "keyword": {
9 "type": "keyword"
10 }
11 }
12 }
13 }
14 }
15}
第5行,my_field
字段用于查询
第8行,my_field.keyword
用于聚合、排序或者在script中使用
你可以使用update mapping API对现有的text
字段开启fielddata,如下所示:
xxxxxxxxxx
91PUT my-index-000001/_mapping
2{
3 "properties": {
4 "my_field": {
5 "type": "text",
6 "fielddata": true
7 }
8 }
9}
第4行,my_field
指定的mapping应该由这个字段现有的mapping加上fielddata
参数组成。
FieldData filtering可以用于减少载入到内存的term的数量,因此能降低内存使用量。可以通过frequency来过滤term:
frequency filter能让你只将frequency在min
和max
之间的term载入到内存,min
和max
可以是一个确切的数值(absolute number, 数值大于1)或者是一个百分值(比如0.01
就是1%
并且1.0
就是100%
)。frequency在每个段中进行计算。百分值基于一个段中拥有这个字段的文档数量相对于所有文档的数量。
可以通过指定min_segment_size
,即段中包含的文档数量最小值来排除一些小段:
xxxxxxxxxx
161PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "tag": {
6 "type": "text",
7 "fielddata": true,
8 "fielddata_frequency_filter": {
9 "min": 0.001,
10 "max": 0.1,
11 "min_segment_size": 500
12 }
13 }
14 }
15 }
16}
text类型的一个变体是match_only_text
。它是打分和与位置相关的query的一种折中类型。这个类型的字段跟只索引文档号(index_options: docs)的text
字段有相同存储数据效率并且关闭了norms(norms: false)。在这个字段上执行term query跟在text
类型的字段上更快,至少也是相等。然而如果在match_only_text
上执行需要位置信息的query,例如match_phrase,那性能会降低,因为需要从_source
中验证短语是否匹配。所有的query返回固定的打分值:1.0。
不能配置分词器:文本总是用默认的分词器分词(默认是standard)。
span queries无法在这个类型的字段上使用,可以使用interval queries,或者你真的需要使用span queries,那么使用text类型。
除此之外,match_only_text
支持与文本(text
)相同的查询。就像text
一样,它不支持排序,并且对聚合的支持有限。
xxxxxxxxxx
131PUT logs
2{
3 "mappings": {
4 "properties": {
5 "@timestamp": {
6 "type": "date"
7 },
8 "message": {
9 "type": "match_only_text"
10 }
11 }
12 }
13}
match_only_text
接受下面的参数:
fields: Multi-fields允许同一个将string value基于不同的目的索引用多种方式进行索引。比如用于查询或者用于排序聚合,或者使用不同的分词器对string value进行分词
meta:字段的元数据信息
(8.2)link
token_count
字段是一个integer类型的字段,它用来接收字符串的值,然后分词最后记录字符串中token的数量。
例如:
xxxxxxxxxx
311PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "name": {
6 "type": "text",
7 "fields": {
8 "length": {
9 "type": "token_count",
10 "analyzer": "standard"
11 }
12 }
13 }
14 }
15 }
16}
17
18PUT my-index-000001/_doc/1
19{ "name": "John Smith" }
20
21PUT my-index-000001/_doc/2
22{ "name": "Rachel Alice Williams" }
23
24GET my-index-000001/_search
25{
26 "query": {
27 "term": {
28 "name.length": 3
29 }
30 }
31}
第5行,名为name
的字段是一个text字段,使用了默认的standard
分词器。
第7行,name.length
字段是一个multi-field的token_count
字段,它会记录name
字段中token的数量,
第28行,这个query只会匹配包含Rachel Alice Williams
的文档,因为它会被分为三个token。
token_count
字段接收下列的参数:
analyzer:analyzer用来对字符串的值分词,必须要指定。处于性能考虑,使用不带token filter的分词器。
enable_position_increments:是否要统计position increments。如果你不想要统计被分词器过滤掉(比如stop)的token的position increments,那么设置为false
。默认为true
。
index:该字段是否需要被快速的检索?参数值为true
或者false
(默认值)。
doc_values : 该字段是否用基于列式存储于磁盘中,使得可以用于排序、聚合、或者Script。参数值为true
或者false
(默认值)
index:该字段是否需要被快速的检索?参数值为true
或者false
(默认值)。
null_value:参数值是具有相同类型的数值用来替换为null
的字段值。默认值是null
,意味着缺失值。注意的是如果使用了script
参数,则不能设置这个参数
(8.2)link
Unsigned long是一个数值类型的字段,代表一个无符号的64bit的整数,最小值为0并且最大值为2^64 - 1
([0, 18446744073709551615])
xxxxxxxxxx
101PUT my_index
2{
3 "mappings": {
4 "properties": {
5 "my_counter": {
6 "type": "unsigned_long"
7 }
8 }
9 }
10}
unsigned long可以用数值或者字符串的格式进行索引,代表了[0, 18446744073709551615]范围的整数。不能有小数部分。
xxxxxxxxxx
91POST /my_index/_bulk?refresh
2{"index":{"_id":1}}
3{"my_counter": 0}
4{"index":{"_id":2}}
5{"my_counter": 9223372036854775808}
6{"index":{"_id":3}}
7{"my_counter": 18446744073709551614}
8{"index":{"_id":4}}
9{"my_counter": 18446744073709551615}
Term query可以使用数值或者字符串格式。
xxxxxxxxxx
81GET /my_index/_search
2{
3 "query": {
4 "term" : {
5 "my_counter" : 18446744073709551615
6 }
7 }
8}
range query可以包含带有小数的值。在这种情况下,Elasticsearch会将它们转化成整数:gte
和gt
会转化成最近的向上的整数(inclusive);lt
和lte
会转化成最近的向下的整数(inclusive)。
建议使用字符串格式的值,就不会再解析时丢失精度。
xxxxxxxxxx
111GET /my_index/_search
2{
3 "query": {
4 "range" : {
5 "my_counter" : {
6 "gte" : "9223372036854775808",
7 "lte" : "18446744073709551615"
8 }
9 }
10 }
11}
对于使用unsigned_long
字段排序的query,对于某个文档,如果字段值在long的范围内,则返回long
类型的排序值,如果字段值超过了这个范围,则返回BigInteger
。
NOTE:客户端需要能够正确的处理JSON种的big integer
xxxxxxxxxx
71GET /my_index/_search
2{
3 "query": {
4 "match_all" : {}
5 },
6 "sort" : {"my_counter" : "desc"}
7}
如果unsigned_long
被设置为stored
,那么会以字符串存储,并且返回的结果也是字符串。
对于term aggregattion,跟sort values一样,会使用Long
或者BigInteger
。对于其他聚合,会被转化成double
类型。
默认情况下,脚本中unsigned_long
字段返回了一个signed long,意味着大于Long.MAX_VALUE
的值会显示为负数。你可以使用Long.compareUnsigned(long, long)
, Long.divideUnsigned(long, long)
以及 Long.remainderUnsigned(long, long)
来正确的处理这些值。
比如说,下面的脚本返回一个counter/10的值:
xxxxxxxxxx
131GET /my_index/_search
2{
3 "query": {
4 "match_all" : {}
5 },
6 "script_fields": {
7 "count10" : {
8 "script": {
9 "source": "Long.divideUnsigned(doc['my_counter'].value, 10)"
10 }
11 }
12 }
13}
或者在脚本中你可以使用field API将unsigned long类型视为BigInteger
。下面的脚本中将my_counter
视为BigInteger
,并且默认值为BigInteger.ZERO
。
xxxxxxxxxx
31"script": {
2 "source": "field('my_counter').asBigInteger(BigInteger.ZERO)"
3}
如果脚本中需要返回float或者double类型,你可以进一步将BigInteger
转化为一个double或者float:
xxxxxxxxxx
111GET /my_index/_search
2{
3 "query": {
4 "script_score": {
5 "query": {"match_all": {}},
6 "script": {
7 "source": "field('my_counter').asBigInteger(BigInteger.ZERO).floatValue()"
8 }
9 }
10 }
11}
支持包含unsigned_long
在内的混合数值类型的搜索查询,但排序查询除外。因此,在两个索引中进行排序查询时,如果相同的字段名在一个索引中是unsigned_long
类型,在另一个索引中是long
类型,这将无法产生正确的结果,应该避免这种情况。如果确实需要这种排序,可以使用基于脚本的排序。
支持包含unsigned_long
在内的多种数值类型的聚合操作。在这种情况下,值会被转换为double
类型。
(8.2)link
version
字段是keyword
字段的特例类型,用于处理软件版本号以及支持特殊的优先级规则(specialized precedence rule)。优先级遵循Semantic Versioning。例如按照主要(major)、次要(minor)、补丁(patch)的数值排序(比如 "2.1.0" < 2.4.1"" < "2.11.2")。预发布版本排在正式版之前(比如"1.0.0-alpha" < 1.0.0"")。
你可以按照下面的内容索引一个version
字段:
xxxxxxxxxx
101PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "my_version": {
6 "type": "version"
7 }
8 }
9 }
10}
该字段提供了跟常规的keyword
字段相同的搜索能力。比如可以使用match
或者term
query来精确匹配并且支持前缀(prefix)和通配符(wildcard)查询。最主要的好处就是range
query遵循Semver排序,所以"1.0.0"到"1.5.0"的range
查询会包含"1.2.3",但是不会包含"1.11.2"。注意的是如果使用keyword存储版本号则排序是不同的,因为排序规则是字典序。
软件版本号应该遵循[Semantic Versioning rules]的策略和优先级规则,不过有一个显著的例外就是允许主要的版本标识多于或少于三个(例如"1.2"或"1.2.3.4"是有效的(valid)版本号,但没有严格遵循Semver的规则)不满足Semver规则的版本号(例如,“1.2.alpha.4”)仍然会被索引并且可以精确匹配。但是这些版本号会按照字典序排序,并且排在有效版本号之后。空的version
字段值被认为是无效的,并且排在所有有效版本号之后,其他无效版本号之前。
meta:字段的元数据信息
(8.2)link
每一篇文档都有关联的metadata,例如_index
和_id
metadata field。这些metadata fields的行为可以在创建mapping时自定义。
_index:文档所属索引。
_id:文档的ID。
_source:代表文档内容的原始JSON。
_size:_srouce
字段的大小(单位:字节),由mapper-size插件提供。
_doc_count:当文档作为pre-aggregation 数据时的一个自定义的用于存储文档计数的字段。
_field_names:文档中所有字段值不为null的字段。
_ignored:因为ignore_malformed,在索引期间文档中所有被ignore的字段、
_routing:自定义的routing value用于将一篇文档路由到指定的分片上。
_meta:应用指定的metadata。
(8.2)link
Bucket aggregation总是返回一个名为doc_count
的字段,它表示每一个bucket中参与集合的文档数量。计算doc_count
的值是很简单的。在每一个bucket中,每收集到一篇文档,doc_count
的值就加一。
在独立的文档中进行聚合计算时这种简单的方式是很高效的。但是统计那些存储pre-aggregation的文档数量就会丢失精度(比如说histogram或者aggregate_metric_double
),因为一个summary field中代表了多篇文档(见Histogram fields)。
为了允许纠正统计pre-aggregation数据时的文档数量。可以使用一种名为_doc_count
的metadata field类型。_doc_count
必须总是用一个正整数来表示单个summary field对应的文档数量。
当文档中添加了_doc_count
,所有的bucket aggregation必须遵照这个_doc_count
的值,按照这个值来累加bucket中doc_count
的值。如果一篇文档中不包含任何_doc_count
,默认存在一个_doc_count = 1
的信息。
IMPORTANT:
_doc_count
字段的字段值在一篇文档中只能是一个正整数,Nested arrays是不允许的如果一篇文档中不包含_doc_count
,按照
_doc_count = 1
这种默认行为进行聚合统计
下面的create index API使用下面的mapping创建了一个新的索引:
字段名my_histogram
是一个histogram类型的字段存储了百分比数据(percentile data)
字段名my_text
是一个keyword类型的字段为这个Histogram存储了标题信息
xxxxxxxxxx
131PUT my_index
2{
3 "mappings" : {
4 "properties" : {
5 "my_histogram" : {
6 "type" : "histogram"
7 },
8 "my_text" : {
9 "type" : "keyword"
10 }
11 }
12 }
13}
下面的index API请求存储2个histogram的pre-aggregated data:histogram_1
and histogram_2
xxxxxxxxxx
191PUT my_index/_doc/1
2{
3 "my_text" : "histogram_1",
4 "my_histogram" : {
5 "values" : [0.1, 0.2, 0.3, 0.4, 0.5],
6 "counts" : [3, 7, 23, 12, 6]
7 },
8 "_doc_count": 45
9}
10
11PUT my_index/_doc/2
12{
13 "my_text" : "histogram_2",
14 "my_histogram" : {
15 "values" : [0.1, 0.25, 0.35, 0.4, 0.45, 0.5],
16 "counts" : [8, 17, 8, 7, 6, 2]
17 },
18 "_doc_count": 62
19}
第18行,_doc_count
必须是一个正整数表示每一个histogram中的文档数量。
如果我们在my_index
上运行下面的terms aggregation:
xxxxxxxxxx
81GET /_search
2{
3 "aggs" : {
4 "histogram_titles" : {
5 "terms" : { "field" : "my_text" }
6 }
7 }
8}
我们会得到下面的response:
xxxxxxxxxx
191{
2 ...
3 "aggregations" : {
4 "histogram_titles" : {
5 "doc_count_error_upper_bound": 0,
6 "sum_other_doc_count": 0,
7 "buckets" : [
8 {
9 "key" : "histogram_2",
10 "doc_count" : 62
11 },
12 {
13 "key" : "histogram_1",
14 "doc_count" : 45
15 }
16 ]
17 }
18 }
19}
(8.2)link
_field_names
字段用来对一篇文档中每一个字段值不是null的字段的字段名进行索引。exists query使用这个字段来查找具有或不具有特定字段的任何非空值的文档。
现在_field_names
只会对有doc_values
以及关闭norms
的字段的字段名进行索引。对于有些只开启doc_values
或者只开启norms
的字段也可以使用exists query,但是不是通过_field_names
字段实现的。
不允许关闭_field_names
。现在默认开启_field_names
是因为它不再有曾经的索引开销。
NOTE:关闭
_field_names
的功能已经被移除了。在新的索引上使用它会抛出一个错误。在8.0前的索引上关闭_field_names
仍然是允许的,但是会报告出一个deprecation的警告。
(8.2)link
_ignored
field 用来索引并存储在索引期间被忽略的文档的所有字段的名称。例如字段值是格式错误的并且开启了ignore_malformed或者keyword字段值超过了ignore_above中的设置。
这个字段可以使用 term, terms, exists进行查询。
下面的例子描述了匹配的文档中包含一个或者多个被忽略的字段。
xxxxxxxxxx
81GET _search
2{
3 "query": {
4 "exists": {
5 "field": "_ignored"
6 }
7 }
8}
下面的例子描述了将匹配在索引期间,@timestamp
字段被忽略的文档。
xxxxxxxxxx
81GET _search
2{
3 "query": {
4 "term": {
5 "_ignored": "@timestamp"
6 }
7 }
8}
(8.2)link
每个文档都有一个_id
用于唯一识别(unique identification)。该值会被索引使得可以通过GET API或者ids query用于查找。这个_id
要么在索引期间被赋值(assigned)、要么通过Elasticsearch生成一个唯一的值。这个字段在mappings中是不可以配置的。
_id
字段的值可以在term
、terms
、match
、query_string
查询中可用(accessible)。
xxxxxxxxxx
201# Example documents
2PUT my-index-000001/_doc/1
3{
4 "text": "Document with ID 1"
5}
6
7PUT my-index-000001/_doc/2?refresh=true
8{
9 "text": "Document with ID 2"
10}
11
12GET my-index-000001/_search
13{
14 "query": {
15 "terms": {
16 "_id": [ "1", "2" ]
17 }
18 }
19}
20
第16行即根据_id
进行查询,见ids query。
_id
字段在聚合(aggregation)、排序、脚本中是被限制使用的。万一(in case)需要使用_id
字段来进行排序或者聚合,建议将_id
字段的值复制(duplicate)到其他字段并且这个字段开启doc_valies
。
_id字段的值的大小被限制为512个字节并且larger values会被reject。
(8.2)link
当在多个索引上执行查询时,有时候希望增加一个关联相关索引的子query(query clause)。_index
字段允许你只在相关的索引上匹配文档。_index
的值可以用于某些查询、聚合或者排序、Script:
xxxxxxxxxx
411PUT index_1/_doc/1
2{
3 "text": "Document in index 1"
4}
5
6PUT index_2/_doc/2?refresh=true
7{
8 "text": "Document in index 2"
9}
10
11GET index_1,index_2/_search
12{
13 "query": {
14 "terms": {
15 "_index": ["index_1", "index_2"]
16 }
17 },
18 "aggs": {
19 "indices": {
20 "terms": {
21 "field": "_index",
22 "size": 10
23 }
24 }
25 },
26 "sort": [
27 {
28 "_index": {
29 "order": "asc"
30 }
31 }
32 ],
33 "script_fields": {
34 "index_name": {
35 "script": {
36 "lang": "painless",
37 "source": "doc['_index']"
38 }
39 }
40 }
41}
第15行,根据_index
的字段值查询
第21行,在_index
上执行聚合
第28行,基于_index
字段排序
第37行,在脚本中访问_index
的值
_index
field exposed virtually,它不会作为一个真实的字段添加到Lucene的索引中。这意味着你可以在term
或 terms
查询中使用_index
字段(或任何被重写为 term
查询的查询,如 match
、query_string
或 simple_query_string
查询),以及 prefix
和 wildcard
查询。但是,它不支持regexp
和 fuzzy
查询。
对_index
字段的查询除了接受具体的索引名称外,还接受索引别名。
NOTE:在指定远程索引名称(如
cluster_1:index_3
)时,查询必须包含分隔符 :。例如,对cluster_*:index_3
的wildcard
查询将匹配远程索引中的文档。然而,对cluster*index_1
的查询只会匹配本地索引,因为没有分隔符。这种行为与远程索引名称的通常解析规则一致。
(8.2)link
一种可以使用自定义元数据(meta data)跟mapping进行关联的类型。Elasticsearch完全不会去使用它,可以用来存储指定应用(application-specific)的元数据,比如说这篇文档所属类别:
xxxxxxxxxx
121PUT my-index-000001
2{
3 "mappings": {
4 "_meta": {
5 "class": "MyApp::User",
6 "version": {
7 "min": "1.0",
8 "max": "1.3"
9 }
10 }
11 }
12}
第4行,可以通过 GET mapping API检索_meta
的信息
_meta
字段可以通过update mapping API对已存在的类型进行变更。
xxxxxxxxxx
101PUT my-index-000001/_mapping
2{
3 "_meta": {
4 "class": "MyApp2::User3",
5 "version": {
6 "min": "1.3",
7 "max": "1.5"
8 }
9 }
10}
(8.2)link
使用下面的公式让索引中的一篇文档被路由到指定的分片中:
xxxxxxxxxx
21routing_factor = num_routing_shards / num_primary_shards
2shard_num = (hash(_routing) % num_routing_shards) / routing_factor
num_routing_shards即索引设置中的index.number_of_routing_shards。
num_primary_shards即索引设置中的index.number_of_shards。
_routing的默认值是文档的_id。自定义的路由模板(routing pattern)可以通过对某个文档指定一个自定义的routing
值实现。例如:
xxxxxxxxxx
61PUT my-index-000001/_doc/1?routing=user1&refresh=true
2{
3 "title": "This is a document"
4}
5
6GET my-index-000001/_doc/1?routing=user1
第1行中,这篇文档使用user1
作为路由值替代ID。
第6行中,当getting、deleting、updating这篇文档时需要提供路由值。
_routing
的值可以在查询中使用(accessible):
xxxxxxxxxx
81GET my-index-000001/_search
2{
3 "query": {
4 "terms": {
5 "_routing": [ "user1" ]
6 }
7 }
8}
第5行中,在_routing
字段上进行查询,见ids query。
Data streams不支持自定义routing除非创建时,在模板中开启allow_custom_routing。
自定义的路由能减少(reduce)查询的压力。不需要将搜索请求分散(fan out)到索引的所有分片,而是只发送到匹配特定路由值的分片。
xxxxxxxxxx
81GET my-index-000001/_search?routing=user1,user2
2{
3 "query": {
4 "match": {
5 "title": "document"
6 }
7 }
8}
第1行中,这次查询只会在路由值user1、user2相关的分片中执行。
当使用自定义的路由时,无论是Indexing、getting、deleting、updating一篇文档时都是需要提供路由值的。
忘掉路由值能将一篇文档索引到一个或多个分片上。作为一个安全措施(as a safeguard),_routing
字段可以被配置使得在所有的CRUD操作时都需要routing
值。
xxxxxxxxxx
131PUT my-index-000002
2{
3 "mappings": {
4 "_routing": {
5 "required": true
6 }
7 }
8}
9
10PUT my-index-000002/_doc/1
11{
12 "text": "No routing value provided"
13}
第5行中,所有文档的操作都需要提供路由值。
第10行中,这个请求会抛出routing_missing_exception
的异常。
使用自定义的路由值来配置索引时,可以路由到一个分片集合中而不是单个分片上。这可以降低集群不平衡导致关闭的风险,同时能降低查询压力。
在索引创建期间可以通过索引层的设置index.routing_partition_size来实现。随着分区(partition)的大小的增长,数据将分布的越均匀(evenly),那么每次查询都需要搜索更多的分片。
当提供了这个设置后,计算分片的公式将变成:
xxxxxxxxxx
21routing_value = hash(_routing) + hash(_id) % routing_partition_size
2shard_num = (routing_value % num_routing_shards) / routing_factor
也就是说(that is)routing
字段将用来计算出分片集合,然后_id
用来从分片集合中选出一个分片。
要开启这个功能,index.routing_partition_size
的值要大于1并且小于index.number_of_shards
。
一旦开启这个功能,被分区(partitioned)的索引将有下列的限制:
不能创建Mapping中的join filed的关系
这个索引的所有mappings信息都需要指定_routing
的require
(8.2)link
_source
字段包含了在索引期间传给Elasticsearch的原始的JSON格式的文档内容。_source
自身不会被索引(因此这个字段没法用于搜索),又因为它被存储了,所以可以通过fetch requests
例如get或者search方式来获得。
_source
字段会带来索引的存储成本,基于这个原因可以通过下面的方式关闭:
xxxxxxxxxx
81PUT my-index-000001
2{
3 "mappings": {
4 "_source": {
5 "enabled": false
6 }
7 }
8}
WARNING:Think before disabling the _source field 用户经常不考虑关闭
_source
字段的后果然后会后悔这么做。如果_source
字段不可见那么下面的功能就没法支持:
update、update_by_query以及reindex这些API
从一个索引重新索引到另一个的能力,或者是修改mapping、分词器或者升级一个索引到一个新的主要版本
索引期间通过观察原始文档调试查询或者聚合的能力
在未来的版本中可能可以自动修复受损(corruption)索引的能力
TIP:如果需要关心磁盘空间,可以提高 compression level而不是关闭
_source
。
这是expert-only的功能使得在_source
的内容在索引之后、存储之前进行字段的剪枝。
WARNING:从
_source
中移除一些字段跟关闭_source
有着类似的负面问题(downside),特别是你不能从一个Elasticsearch索引重新索引到另一个。可以考虑使用source filtering 来替代这种方式。
参数includes/excludes
(支持wildcards)可以按照以下方式使用:
xxxxxxxxxx
401PUT logs
2{
3 "mappings": {
4 "_source": {
5 "includes": [
6 "*.count",
7 "meta.*"
8 ],
9 "excludes": [
10 "meta.description",
11 "meta.other.*"
12 ]
13 }
14 }
15}
16
17PUT logs/_doc/1
18{
19 "requests": {
20 "count": 10,
21 "foo": "bar"
22 },
23 "meta": {
24 "name": "Some metric",
25 "description": "Some metric description",
26 "other": {
27 "foo": "one",
28 "baz": "two"
29 }
30 }
31}
32
33GET logs/_search
34{
35 "query": {
36 "match": {
37 "meta.other.foo": "one"
38 }
39 }
40}
第21、25、27、28行,_source
字段存储之前会移除这几个字段
第37行,我们仍然可以通过这个字段进行检索,尽管它没有被存储
(8.2)link
在多个索引上执行查询时,有时候希望在包含指定数据层(data tie)(data_hot
, data_warm
, data_cold
or data_frozen
)的索引上查询。_tier
字段用于匹配索引设置tier_preference
:
xxxxxxxxxx
181PUT index_1/_doc/1
2{
3 "text": "Document in index 1"
4}
5
6PUT index_2/_doc/2?refresh=true
7{
8 "text": "Document in index 2"
9}
10
11GET index_1,index_2/_search
12{
13 "query": {
14 "terms": {
15 "_tier": ["data_hot", "data_warm"]
16 }
17 }
18}
第15行,根据_tier
的值查询
通常,查询会使用 terms 查询来列出感兴趣的层级,但你可以在任何被重写为 term 查询的查询中使用_tier
字段,比如 match
、query_string
、term
、terms
或 simple_query_string
查询,以及 prefix
和 wildcard
查询。然而,它不支持 regexp
和 fuzzy
查询。
索引的 tier_preference 设置是一个逗号分隔的层级名称列表,按优先顺序排列,即首选托管索引的层级排在最前,其后可能有许多备选选项。查询匹配只考虑第一优先级(列表的第一个值)。
(8.2)link
这一章节提供了不同的mapping 参数(mapping parameters)的详细的介绍,mapping参数用于filed mappings:
下面的mapping参数对于某些或者所有的字段的数据类型是通用的:
(8.2)link
IMPORTANT:只有text类型支持
analyzer
参数。
analyzer
参数用来指定分词器analyzer 并且用于在索引和查询text字段时进行文本分词text analysis。
除非用mapping参数search_analyzer进行覆盖,分词器将同时用于index and search analysis,见Specify an analyzer
TIP:建议在生产中使用前先试用下分词器,见Test an analyzer。
TIP:
analyzer
参数不能通过update mapping API对已存在的字段进行变更。
search_quote_analyzer
允许你指定一个分词器用于match_phrase和query_string这类phrase query,处理这类query并且不使用停用词时特别有用。
若要为phrase query不启动停用词可以利用三个分词器:
使用设置analyzer
用于索引所有的term,包括停用词
使用设置search_analyzer
用于non-phrase query,它将移除停用词
使用设置search_quote_analyzer
用于phrase query,它将不会移除停用词
xxxxxxxxxx
591PUT my-index-000001
2{
3 "settings":{
4 "analysis":{
5 "analyzer":{
6 "my_analyzer":{
7 "type":"custom",
8 "tokenizer":"standard",
9 "filter":[
10 "lowercase"
11 ]
12 },
13 "my_stop_analyzer":{
14 "type":"custom",
15 "tokenizer":"standard",
16 "filter":[
17 "lowercase",
18 "english_stop"
19 ]
20 }
21 },
22 "filter":{
23 "english_stop":{
24 "type":"stop",
25 "stopwords":"_english_"
26 }
27 }
28 }
29 },
30 "mappings":{
31 "properties":{
32 "title": {
33 "type":"text",
34 "analyzer":"my_analyzer",
35 "search_analyzer":"my_stop_analyzer",
36 "search_quote_analyzer":"my_analyzer"
37 }
38 }
39 }
40}
41
42PUT my-index-000001/_doc/1
43{
44 "title":"The Quick Brown Fox"
45}
46
47PUT my-index-000001/_doc/2
48{
49 "title":"A Quick Brown Fox"
50}
51
52GET my-index-000001/_search
53{
54 "query":{
55 "query_string":{
56 "query":"\"the quick brown fox\""
57 }
58 }
59}
TIP:可以通过update mapping API对现有的字段更新
search_quote_analyzer
设置。
第1行,my_analyzer
对所有的字符串进行分词,包括停用词
第2行,my_stop_analyzer
会移除停用词
第3行,analyzer
会在索引期间使用my_analyzer
这个分词器
第4行,search_analyzer
会为non-phrase query使用my_stop_analyzer
这个分词器并且移除停用词
第5行,search_quote_analyzer
会使用my_analyzer
分词器并且确保使用phrase query时停用词不会被移除
第6行,由于query语句作为一个phrase query使用引号包裹,因此触发search_quote_analyzer
并且保证停用词不会从这个query中移除。my_analyzer
分词器会返回下面的token: [the, quick, brown, fox] 用来匹配文档。同时term query会使用my_stop_analyzer
分词器并且过滤掉停用词。因此查询语句quick brown fox
或者A quick brown fox
都会返回两篇文档(the是停用词),因为这两篇文档都包含下面的token: [quick, brown, fox]。没有search_quote_analyzer
的话,就无法为phrase query执行精确匹配(exactly match),是因为停用词会被移除,使得两篇文档都会被匹配。
(8.2)link
Data is not always clean,在JSON中,5这个数字可以用字符串"5"表示,也可以用数值表示,或者应该是整数5,但是在JSON中是一个浮点数,比如5.0,甚至是字符串"5.0"。
coerce
会尝试将dirty value转化为某个合适的字段的数据类型。比如:
字符串会被强制转化为数值
浮点型会被截断为整数
例如:
xxxxxxxxxx
241PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "number_one": {
6 "type": "integer"
7 },
8 "number_two": {
9 "type": "integer",
10 "coerce": false
11 }
12 }
13 }
14}
15
16PUT my-index-000001/_doc/1
17{
18 "number_one": "10"
19}
20
21PUT my-index-000001/_doc/2
22{
23 "number_two": "10"
24}
第18行,number_one这个字段将包含数值类型的10
第23行,由于coerce
被禁用了,所以这篇文档会被reject
TIP:
coerce
参数可以通过update mapping API更新。
index.mapping.coerce
这个设置能够在索引层全局的关闭所有字段的coerce
的设置
xxxxxxxxxx
231PUT my-index-000001
2{
3 "settings": {
4 "index.mapping.coerce": false
5 },
6 "mappings": {
7 "properties": {
8 "number_one": {
9 "type": "integer",
10 "coerce": true
11 },
12 "number_two": {
13 "type": "integer"
14 }
15 }
16 }
17}
18
19PUT my-index-000001/_doc/1
20{ "number_one": "10" }
21
22PUT my-index-000001/_doc/2
23{ "number_two": "10" }
第20行,number_one
这个字段中的设置覆盖了索引层的设置,即开启了coerce
第23行,这个文档会被reject,因为number_two
继承了索引层的设置,即关闭了coerce
(8.2)link
copy_to
参数允许你将多个字段的字段值写入到group field中,这个group filed可以被当成一个字段进行查询。
TIP:如果你经常使用多个字段进行查询,你可以使用copy_to写入到较少的字段来提高查询速度。
下图中,first_name
和last_name
的字段值能被拷贝到full_name
中:
xxxxxxxxxx
361PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "first_name": {
6 "type": "text",
7 "copy_to": "full_name"
8 },
9 "last_name": {
10 "type": "text",
11 "copy_to": "full_name"
12 },
13 "full_name": {
14 "type": "text"
15 }
16 }
17 }
18}
19
20PUT my-index-000001/_doc/1
21{
22 "first_name": "John",
23 "last_name": "Smith"
24}
25
26GET my-index-000001/_search
27{
28 "query": {
29 "match": {
30 "full_name": {
31 "query": "John Smith",
32 "operator": "and"
33 }
34 }
35 }
36}
第7、11行,first_name
和last_name
的字段值被拷贝到full_name
。
第30行,first_name
和last_name
这两个字段依然能分别用来查询frist_name以及last_name,而full_name
可以用来同时查询frist_name以及last_name。
一些重要点:
字段值被拷贝,而不是terms被拷贝(字段值在进行分词处理后生成的一个或多个最小分词单元成为term)
_source
字段中的值不会显示被拷贝的字段值
同一个字段值可以被拷贝到多个字段中:"copy_to": [ "field_1", "field_2" ]
你不能递归的方式进行字段值拷贝,比如先field_1拷贝到field_2,然后field_2拷贝到field_3,并且期望通过这种方式将field_1的字段值拷贝到field_3中,这是不可行的。你应该使用将一个字段值直接拷贝到多个字段的方式
NOTE:字段值类型是objects的字段不支持copy_to,例如date_range。
(8.2)link
大部分的字段默认情况下都会被索引,使得可以被用于搜索。倒排索引使得可以在有序的term集合中找到该term,并且能马上获取到包含 term的文档列表。
排序、聚合以及脚本中访问字段值需要一种不同的数据访问模式(data access pattern)。我们需要能够根据文档(文档号)找到这个文档中包含的某个字段的所有字段值,而不是根据字段值找到文档。
DocValues一种 on-disk的数据结构。这种基于文档的索引,使得上述的数据访问模式的成为可能。DocValues使用列式存储column-oriented 存储了跟_source字段相同的值,使得排序跟聚合更高效。DocValues几乎支持所有的字段类型,除了text跟annotated_text这两类字段。
Numeric types, date types, boolean type, ip type, geo_point type 和keyword type这些字段只开启了docValues(在Lucene中只用正排的数据结构存储)而没有用倒排索引存储,这些字段仍旧能够实现搜索,但是查询性能较差。这种索引方式是一种disk usage和查询性能的折中(tradeoff),适合用于那些不用于查询或者查询性能不重要的字段。这使得只包含docValues的字段非常适合通常不用于过滤的字段,例如metric data上的计量表(gauges)或计数器。
Doc-value-only字段可以通过下面方式配置:
xxxxxxxxxx
141PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "status_code": {
6 "type": "long"
7 },
8 "session_id": {
9 "type": "long",
10 "index": false
11 }
12 }
13 }
14}
第6行,status_code
是一个普通的long
类型的字段。
第10行,session_id
关闭了index
,使得该字段是Doc-value-only
字段,并且只开启DocValues。
所有支持DocValues的字段都会默认开启DocValues,如果你确定这个字段不需要进行聚合、排序,或者在脚本中访问这个字段的字段值,那么你可以关闭DocValues来节省磁盘空间:
xxxxxxxxxx
141PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "status_code": {
6 "type": "keyword"
7 },
8 "session_id": {
9 "type": "keyword",
10 "doc_values": false
11 }
12 }
13 }
14}
status_code
默认开启doc_values
status_code
关闭了doc_values
,但是仍然能被用于查询。
NOTE:你不能关闭wildcard 字段的
doc_values
。
(8.2)link
当你索引的一篇文档中包含一个新的字段时,Elasticsearch会动态的添加字段,添加到文档中,或者文档中某个对象字段(object field)中,下面的例子在文档中添加了一个String类型的字段username
,以及对象字段name
,在这个对象字段中添加了两个字段:
xxxxxxxxxx
101PUT my-index-000001/_doc/1
2{
3 "username": "johnsmith",
4 "name": {
5 "first": "John",
6 "last": "Smith"
7 }
8}
9
10GET my-index-000001/_mapping
第4行,name
对象字段下的两个字段是name.first
和name.last
第10行,查看这个索引的mapping
下面这篇文档增加两个字符串字段: email
和name.middle
:
xxxxxxxxxx
121PUT my-index-000001/_doc/2
2{
3 "username": "marywhite",
4 "email": "mary@white.com",
5 "name": {
6 "first": "Mary",
7 "middle": "Alice",
8 "last": "White"
9 }
10}
11
12GET my-index-000001/_mapping
inner object会继承parent object或者mapping type中的dynamic
参数,在下面的例子,在mapping顶层中关闭了dynamic,所以顶层中新的字段不会被动态添加。
然而,user.social_networks
这个object字段开启了动态mapping,所以这一层的新的字段会被动态添加:
xxxxxxxxxx
191PUT my-index-000001
2{
3 "mappings": {
4 "dynamic": false,
5 "properties": {
6 "user": {
7 "properties": {
8 "name": {
9 "type": "text"
10 },
11 "social_networks": {
12 "dynamic": true,
13 "properties": {}
14 }
15 }
16 }
17 }
18 }
19}
第4行,mapping类型那一层关闭了动态mapping
第6行,user
这个object字段继承了mapping类型一层的dynamic
的参数
第12行,social_networks
这个inner object开启了动态mapping
dynamic
的不同值控制了是否动态的添加新的字段,可选值如下:
true:新的字段会被自动添加(默认值)
runtime:新的字段会被作为runtime fields进行动态添加,这些字段不会被索引,查询阶段可以从_source
中加载出来
false:新的字段会被忽略,这些字段不会被索引也无法用于搜索,但仍然存在与_source
中,这些字段不会被添加到mapping中,必须要显示添加
strict:如果发现有新的字段出现,那么异常会被抛出并且这篇文档会被reject,新的字段必须在mapping中显示添加
(8.2)link
为了能支持在聚合和其他操作中可以从文档中找到字段值,Elasticsearch使用了一个成为doc values的数据结构。例如keyword
这种Term-based的字段类型(field type)会使用ordinal mapping这种更紧凑的数据结构来存储doc values。ordinal mapping的工作原理是给每一个term分配一个基于字典序,递增有序的整数。会为每篇文档存储这个字段的doc values的ordinal(数字),而不是字段值对应的原始term,通过一个lookup Structure实现ordinal和term之间的转化。
在聚合中使用ordinal能极大的提高性能。例如在分片级别(shard-level)terms aggregation
中使用ordinal来收集文档号并放入到分桶中,并且在跨分片对查询结果合并(combining)前将ordinal转化为对应的原始的term值。
每一个index segment定义了自己的ordinal mapping。但是聚合是需要收集分片上的所有数据。所以为了能在分片级别使用ordinal用于聚合,Elasticsearch创建了一个名为global ordinals
的统一映射(ordinal mapping)。这个global ordinal mapping是一个segment original,为每一个段构建并维护了global ordinal 到local ordinal的映射关系。
如果查询中包含了下列的component就会使用Global ordinals:
一些使用keyword
,ip
, flattened
字段的bucket aggregation。包含了上文中提到的terms aggregation
,同样的还有composite
,diversified_sampler
以及significant_terms
。
使用text
字段的bucket aggregation需要这个字段开启fielddata。
Operations on parent and child documents from a join
field, including has_child
queries and parent
aggregations.
NOTE: global ordinal mapping会占用heap memory,属于field data cache的一部分。 对high cardinality fields(不同的字段值数量很多)进行聚合会使用很多的内存并且会触发field data circuit breaker。
查询期间,在ordinals可以被使用前必须先构建global ordinal mapping。默认情况下,在需要使用global ordinal时并且只在这类查询第一次发起后才会构建这个mapping。如果你想要优化索引速度(Indexing speed),这是一种正确的方式。但如果查询性能的优先级最高,建议尽快生成将要执行聚合的字段的global ordinals:
xxxxxxxxxx
91PUT my-index-000001/_mapping
2{
3 "properties": {
4 "tags": {
5 "type": "keyword",
6 "eager_global_ordinals": true
7 }
8 }
9}
开启eager_global_ordinals
后,在分片refresh后就会构建global ordinals。Elasticsearch总是在展示(expose)变更的索引内容前先载入它们,将构建global ordinal的开销从查询期间转移到索引期间。Elasticsearch将总是在创建一个新的副本分片时急切的(eagerly)构建global ordinal,同样的会发生在增加副本数量或者在新的节点重新分配一个分片时。
可以通过更新eager_global_ordinals
这个设置来关闭Eager loading:
xxxxxxxxxx
91PUT my-index-000001/_mapping
2{
3 "properties": {
4 "tags": {
5 "type": "keyword",
6 "eager_global_ordinals": false
7 }
8 }
9}
通常来说,global ordinal不会在载入时间跟内存占用方面有很大的开销。然而在大的分片上或者字段中包含了很多唯一term上载入global ordinal的开销会比较昂贵。因为global ordinal提供了分片上所有段的统一的mapping,当出现一个新段后需要重新完整的构建global ordinal:
在某些情况下,可以完全避免加载global ordinal:
terms
,sampler
,significant_terms
支持execution_hint参数来控制分桶的收集。默认是值global_ordinals
,但可以设置为map
直接使用term值。
如果一个分片被force-merged到单个段中,那么它的segment ordinal就已经相当于global ordinal。在这种情况下,Elasticsearch不需要构建一个global ordinal mapping并且不会有因为使用global ordinals产生的开销。注意的是,处于性能原因,你应该只force-merge那些你不再往里写入的索引。
(8.2)link
Elasticsearch会尝试索引你提供的所有的字段,但有时候你只是想保存某些字段而不想索引它们。例如你想要使用Elasticsearch来存储web session,你也许要索引session ID和 last update time,但是你不想对session data进行查询或者聚合操作。
enabled
参数只能在mapping的顶层top-level定义并且只能应用object类型的字段,这会使得Elasticsearch不再解析object字段下所有内容,JSON内容仍然可以从_source中检索到,无法通过其他方式查询或者进行存储:
xxxxxxxxxx
351UT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "user_id": {
6 "type": "keyword"
7 },
8 "last_updated": {
9 "type": "date"
10 },
11 "session_data": {
12 "type": "object",
13 "enabled": false
14 }
15 }
16 }
17}
18
19PUT my-index-000001/_doc/session_1
20{
21 "user_id": "kimchy",
22 "session_data": {
23 "arbitrary_object": {
24 "some_array": [ "foo", "bar", { "baz": 2 } ]
25 }
26 },
27 "last_updated": "2015-12-06T18:20:22"
28}
29
30PUT my-index-000001/_doc/session_2
31{
32 "user_id": "jpountz",
33 "session_data": "none",
34 "last_updated": "2015-12-06T18:22:13"
35}
第11行,session_data
字段的enable
参数为false
第22行,任意类型的数据都可以赋值给session_data
字段,Elasticsearch不会解析这些数据(不解析意味着可以给一个object字段赋非JSON格式的数据)
第33行,session_data
字段同样会忽略不是JSON类型的数据
可以在mapping中直接定义enable
参数为false,这样使得所有的内容可以在_source
字段中检索到但是不会被索引。
xxxxxxxxxx
211PUT my-index-000001
2{
3 "mappings": {
4 "enabled": false
5 }
6}
7
8PUT my-index-000001/_doc/session_1
9{
10 "user_id": "kimchy",
11 "session_data": {
12 "arbitrary_object": {
13 "some_array": [ "foo", "bar", { "baz": 2 } ]
14 }
15 },
16 "last_updated": "2015-12-06T18:20:22"
17}
18
19GET my-index-000001/_doc/session_1
20
21GET my-index-000001/_mapping
第4行,enable
参数将应用mapping中的所有字段
第19行,文档可以被检索到
第21行,这个索引的mapping中没有任何的字段的信息
The enabled
setting for existing fields and the top-level mapping definition cannot be updated。
由于Elasticsearch完全不会去解析字段值,所以可以把非object格式(JSON格式)的值赋值给object
字段:
xxxxxxxxxx
161PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "session_data": {
6 "type": "object",
7 "enabled": false
8 }
9 }
10 }
11}
12
13PUT my-index-000001/_doc/session_1
14{
15 "session_data": "foo bar"
16}
成功添加了文档,并且session_data
字段的字段值不是JSON类型。
(8.2)link
在JSON类型的文档中,日期用字符串表示。Elasticsearch使用预先配置好的一些format集合来识别并且将这些字符串解析为long类型的milliseconds-since-the-epoch UTC。
除了build-in formats,你可以指定custom formats,例如使用熟悉的yyyy/MM/dd
:
xxxxxxxxxx
111PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "date": {
6 "type": "date",
7 "format": "yyyy-MM-dd"
8 }
9 }
10 }
11}
许多API支持date value,也支持date math expression,例如now-1m/d
,即当前时间减去一个月,四舍五入到最近的那天。
支持自定义的date format。参考DateTimeFormatter docs。
大多数下面提到的格式都有一个严格对应的格式,这意味着年、月和日的部分必须分别严格使用4位、2位和2位数字,可能需要前面补零。例如,像 5/11/1 这样的日期将被视为无效,需要重写为 2005/11/01 才能被日期解析器(Date format)接受。
要使用它们,您需要在日期格式的名称前加上 strict_ ,例如使用 strict_date_optional_time 而不是 date_optional_time。
这些严格的日期格式在动态映射日期(date fields are dynamically mapped)字段时特别有用,以确保不会意外地将无关的字符串映射为日期。
下表列出了所有支持的默认 ISO 格式(国际标准化组织(International Organization for Standardization, ISO)制定的标准格式):
一个用于自epoch以来毫秒数的format。请注意,这个时间戳受限于 Java 的 Long.MIN_VALUE 和 Long.MAX_VALUE。
一个用于自epoch以来毫秒数的format。请注意,这个时间戳受限于 Java 的 Long.MIN_VALUE 和 Long.MAX_VALUE。epoch_millis除以1000即epoch_second。
一个通用的 ISO 日期时间format,其中日期至少必须包括年份,而时间(由 T 分隔)是可选的。示例:yyyy-MM-dd'T'HH:mm:ss.SSSZ
或yyyy-MM-dd
。.sss
表示毫秒,Z
表示时区,它等同于+0000
。
一个通用的 ISO 日期时间format,其中日期至少必须包括年份,而时间(由 T 分隔)是可选的。秒的小数部分具有纳秒级的分辨率。示例:yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ
或yyyy-MM-dd
。
一个基本的完整的日期format:4位数的年,2位数的月以及2位数的天。示例:yyyyMMdd
。
一个基本的format由日期和时间组成,用T
分隔:yyyyMMdd'T'HHmmssZ
。
一个基本的format由日期和时间组成,用T
分隔,没有毫秒:yyyyMMdd'T'HHmmssZ
。
完整的序数日期(ordinal date)的format,使用4位数的年以及3位数的天:yyyyDDD
。比如2023年的1月1日表示为 2023001,而12月31日表示为 2023365(或在闰年中为 2023366)。。
一个用于完整序数日期和时间的格式化器,使用4位数年份和3位数一年中的天数:yyyyDDD'T'HHmmss.SSSZ
。比如2023301'T'153045.678+0100
2023 代表年份。
301 表示一年中的第301天,这通常对应于10月28日。
'T' 是日期和时间之间的分隔符。
153045.678 表示时间为15点30分45秒和678毫秒。
+0100 表示时区偏移量为UTC+1小时。
一个用于完整序数日期和不带毫秒的时间的格式化器,使用4位数年份和3位数一年中的天数:yyyyDDD'T'HHmmssZ
。
一个基本的format,使用2位数的小时,2位数的分钟,2位数的秒,3位数的毫秒以及时区偏移:HHmmss.SSSZ
。这里的Z表示UTC时区,例如153045.678Z
表示0时区,而153045.678+0800
表示东八区,同样的153045.678Z
跟153045.678+0000
是一样的。
一个基本的format,使用2位数的小时,2位数的分钟,2位数的秒以及时区偏移:HHmmssZ
。
一个基本的format,使用2位数的小时,2位数的分钟,2位数的秒,3位数的毫秒以及时区偏移,T
作为前缀:'T'HHmmss.SSSZ
。
一个基本的format,使用2位数的小时,2位数的分钟,2位数的秒以及时区偏移,T
作为前缀:'T'HHmmssZ
。
一个基本完整日期,使用4位数的年,2位数的周以及1位数的天:xxxx'W'wwe
。例如2023年的第一周的周三可以表示为 2023W013
xxxx 表示周年(weekyear),即包含该周的年份,通常是四位数字。
'W' 是一个字面字符,用作周数前的标识符。
ww 表示周年中的周数(01 到 53)。
e 表示周中的天数(1 到 7),其中 1 代表周一,7 代表周日。
相较于basic_week_date or strict_basic_week_date,额外增加了时间,用T
分隔:xxxx'W'wwe'T'HHmmss.SSSZ
。
相较于basic_week_date_time or strict_basic_week_date_time,少了毫秒:xxxx'W'wwe'T'HHmmssZ
。
一个完整的日期,使用4位数的年,2位数的月,2位数的天:yyyy-MM-dd
。
相较于date or strict_date,额外增加了时间,用T
分隔:yyyy-MM-dd'T'HH
。
相较于date_hour or strict_date_hour,额外增加了分钟:yyyy-MM-dd'T'HH:mm
。
相较于date_hour_minute or strict_date_hour_minute,额外增加了秒:yyyy-MM-dd'T'HH:mm:ss
。
相较于date_hour_minute_second or strict_date_hour_minute_second,额外增加了秒的小数部分(也即就是毫秒),描述000~999范围:yyyy-MM-dd'T'HH:mm:ss.SSS
。
相较于date_hour_minute_second or strict_date_hour_minute_second,额外增加了毫秒,描述000~999范围:yyyy-MM-dd'T'HH:mm:ss.SSS
。
date_hour_minute_second_fraction or strict_date_hour_minute_second_fraction跟date_hour_minute_second_millis or strict_date_hour_minute_second_millis是相同的。
一种基本的完整的日期跟时间的format,用T
分隔日期跟时间:yyyy-MM-dd'T'HH:mm:ss.SSSZ
。
相较于date_time or strict_date_time,没有毫秒:yyyy-MM-dd'T'HH:mm:ssZ
。
2位数的小时:HH
。
相较于hour or strict_hour,额外增加了分钟:HH:mm
。
相较于hour_minute or strict_hour_minute,额外增加了秒:HH:mm:ss
。
相较于hour_minute_second or strict_hour_minute_second,额外增加了秒的小数(也就是毫秒):HH:mm:ss.SSS
。
相较于hour_minute_second or strict_hour_minute_second,额外增加了毫秒:HH:mm:ss.SSS
。
一个完整的序数日期,使用4位数的年和3位数的天:yyyy-DDD
。例如,2023-001 表示 2023 年的第一天,即 1 月 1 日。而 2023-365 表示 2023 年的最后一天,假设它不是闰年。
相比较ordinal_date or strict_ordinal_date,额外增加了时间:yyyy-DDD'T'HH:mm:ss.SSSZ
。
相比较ordinal_date_time or strict_ordinal_date_time,没有毫秒:yyyy-DDD'T'HH:mm:ssZ
。
一个使用2位数的小时,2位数的分钟,2位数的秒以及3位数的秒的小数(也即是毫秒)和时区偏移:HH:mm:ss.SSSZ
。
相比较 time or strict_time,没有毫秒:HH:mm:ssZ
。
相比较 time or strict_time,额外使用了T
作为前缀:'T'HH:mm:ss.SSSZ
。
相比较t_time or strict_t_time,没有毫秒:'T'HH:mm:ssZ
。
一个完整的日期,使用4位数的年,2位数的周以及1位数的天:xxxx-'W'ww-e
。 例如2023年的第一周的周三可以表示为 2023-W01-3。
xxxx 表示周年(weekyear),即包含该周的年份,通常是四位数字。
'W' 是一个字面字符,表示周数的前缀。
ww 表示周年中的周数,是两位数字(01 到 53)。
e 表示周中的天数,是一位数字(1 到 7),其中 1 代表周一,7 代表周日。
相较于week_date or strict_week_date,额外增加了时间,使用T
分隔:xxxx-'W'ww-e'T'HH:mm:ss.SSSZ
。
相较于week_date_time or strict_week_date_time,没有毫秒:xxxx-'W'ww-e'T'HH:mm:ssZ
。
一个使用4位数的年的format:xxxx
。
相较于weekyear or strict_weekyear,额外增加了2位数的周:xxxx-'W'ww
。
相较于weekyear_week or strict_weekyear_week,额外增加了1位数的天:xxxx-'W'ww-e
。
一个使用4位数的年的format:yyyy
。
相较于year or strict_year,额外增加了2位数的月:yyyy-MM
。
相较于year_month or strict_year_month,额外增加了2位数的天:yyyy-MM-dd
。
(8.2)link
字符串长度超过ignore_above
中设置的值不会被索引或者存储,对于字符串数组,ignore_above
会应用(apply)到数组中每一个字符串,并且超过ignore_above
中设置的值不会被索引或者存储。
NOTE:All strings/array elements will still be present in the
_source
field, if the latter is enabled which is the default in Elasticsearch.
xxxxxxxxxx
321PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "message": {
6 "type": "keyword",
7 "ignore_above": 20
8 }
9 }
10 }
11}
12
13PUT my-index-000001/_doc/1
14{
15 "message": "Syntax error"
16}
17
18PUT my-index-000001/_doc/2
19{
20 "message": "Syntax error with some long stacktrace"
21}
22
23GET my-index-000001/_search
24{
25 "aggs": {
26 "messages": {
27 "terms": {
28 "field": "message"
29 }
30 }
31 }
32}
第7行,这个字段会忽略字符串长度超过20的字段值
第13行,这篇文档能成功索引
第18行,这篇文档会被索引,但是message
字段不会被索引
第23行,两篇文档都会被搜索到,但聚合中只有第一篇文档的message的值会被呈现present
TIP:可以通过update mapping API更新
ignore_above
的值。
这个选项也可以用来处理Lucene中单个term的最大长度只支持到32766的问题。
NOTE:
ignore_above
计算的字符的数量,但Lucene是按照字节计算的,如果你使用UTF-8那么长度应该限制到 32766/4 =8191,因为UTF-8最多用4个字节来描述一个字符。
(8.2)link
有时候你不用过度控制待写入的数据,用户可能发送了date类型的login
字段,但是又发送了一个email地址的login
字段。
尝试索引一个数据类型错误的字段默认情况下会抛出异常,并且reject这个文档,ignore_malformed
参数如果设置为true,允许忽略异常,数据格式异常的字段不会被索引,其他的字段会被正常处理。
xxxxxxxxxx
261PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "number_one": {
6 "type": "integer",
7 "ignore_malformed": true
8 },
9 "number_two": {
10 "type": "integer"
11 }
12 }
13 }
14}
15
16PUT my-index-000001/_doc/1
17{
18 "text": "Some text value",
19 "number_one": "foo"
20}
21
22PUT my-index-000001/_doc/2
23{
24 "text": "Some text value",
25 "number_two": "foo"
26}
第19行,这篇文档的text
字段会被索引,number_one
字段不会被索引
第25行,这篇文档会被reject因为number_two
字段不允许数据格式错误的字段值
下面的mapping类型允许设置ignore_malformed
参数:
Numeric | long , integer , short , byte , double , float , half_float , scaled_float |
---|---|
Date | date |
Date nanoseconds | date_nanos |
Geopoint | geo_point for lat/lon points |
Geoshape | geo_shape for complex shapes like polygons |
IP | ip for IPv4 and IPv6 addresses |
TIP:可以通过update mapping API更新
ignore_malformed
的值。
在索引层index level设置index.mapping.ignore_malformed
会对mapping中所有字段生效,如果mapping中的某些类型不支持这个参数会忽略该设置。
xxxxxxxxxx
171PUT my-index-000001
2{
3 "settings": {
4 "index.mapping.ignore_malformed": true
5 },
6 "mappings": {
7 "properties": {
8 "number_one": {
9 "type": "byte"
10 },
11 "number_two": {
12 "type": "integer",
13 "ignore_malformed": false
14 }
15 }
16 }
17}
第8行,number_one
字段会继承索引层的设置
第13行,number_two
字段的ignore_malformed
会覆盖索引层的设置
ignore_malformed
开启后,数据格式错误的字段在索引期间会被忽略,无论如何建议保留那些包含数据格式错误字段的文档的数量,否则查询_ignored字段就没有意义了。Elasticsearch可以使用_ignored
字段结合exists
,term
or terms
来查询哪些文档中有数据格式错误的字段。
下面的数据类型不能使用ignore_malformed
:
ignore_malformed
参数不能用于JSON objects。JSON object是使用{}
包住的类型,它包含的数据映射了nested, object, and range数据类型。
如果你提交了数据格式错误的JSON object,Elasticsearch会无视ignore_malformed
参数的设置并返回错误并且reject这个文档。
(8.2)link
index
这个参数用来控制字段值是否要索引,该值可以是true或者false,默认值为true,没有进行索引的字段通常是无法用于查询的。
(8.2)link
index_options
参数用来控制哪些信息被添加到倒排索引inverted index中,用于查询或者高亮。只有term-based的字段比如text、keyword才支持该配置。
index_options
参数的可选值如下所示,注意的是每个参数值包含上一个参数值中添加的参数,比如freq
包含docs
;positions
同时包含freq
和docs
:
docs:只有文档号被添加到倒排索引中,可以解决这个问题:term是否在这个字段中?
freqs:文档号跟词频会被添加到倒排索引中,词频会用于打分,词频大意味着文档能获得较高的分数
positions(默认值):文档号跟词频、位置会被添加到倒排索引中,位置信息可以用于proximity or phrase queries
offsets:文档号跟词频、位置。term的开始跟结束的偏移信息(可以用于定位term在字符串中的位置)会被添加到倒排索引中,offsets可以用于提高highlighting的性能。
xxxxxxxxxx
301PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "text": {
6 "type": "text",
7 "index_options": "offsets"
8 }
9 }
10 }
11}
12
13PUT my-index-000001/_doc/1
14{
15 "text": "Quick brown fox"
16}
17
18GET my-index-000001/_search
19{
20 "query": {
21 "match": {
22 "text": "brown fox"
23 }
24 },
25 "highlight": {
26 "fields": {
27 "text": {}
28 }
29 }
30}
第27行,因为第7行设置了offsets
参数,text
字段将默认使用倒排信息(posting)来实现高亮。
(8.2)link
开启这个参数后,2个term组成的字段值会被索引到额外的字段(separate filed)中。这使得exact phrase(no slop) 查询可以更高效,代价就是索引大小会增加。注意的是只有移除了停用词后才能达到最佳性能,因为包含了停用词的短语将不会使用subsidiary field并且会回滚到标准的短语查询。默认是false。
(8.2)link
index_prefixes
参数对term的前缀进行索引来加速前缀查询。接收下面的可选设置:
min_chars
用于索引的前缀长度最小值。该值必须大于0,默认值是2(inclusive)
max_chars
用于索引的前缀长度最大值。该值必须小于20,默认值是5(inclusive)
下面的例子使用默认的前缀长度设置创建了一个text字段:
xxxxxxxxxx
111PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "body_text": {
6 "type": "text",
7 "index_prefixes": { }
8 }
9 }
10 }
11}
第7行,空的object即使用默认的min_chars
和max_chars
设置。
这个例子使用了自定义的前缀长度设置:
xxxxxxxxxx
141PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "full_name": {
6 "type": "text",
7 "index_prefixes": {
8 "min_chars" : 1,
9 "max_chars" : 10
10 }
11 }
12 }
13 }
14}
(8.2)link
字段的附加元信息(metadata)(key-value键值对信息),元数据对Elasticsearch是不透明的,他只是用来在多个应用间分享在同一个索引中某个字段的元信息,比如字段值的单位unit:
xxxxxxxxxx
131PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "latency": {
6 "type": "long",
7 "meta": {
8 "unit": "ms"
9 }
10 }
11 }
12 }
13}
NOTE:字段的元数据最多可以设置5个,key的长度必须小于等于20,value的长度必须小于等于50
NOTE:字段的元数据可以通过mapping更新进行变更,新的元信息会覆盖旧的元信息
Elastic产品会使用下面2个标准的字段的元数据,你可以遵循这些元数据约定来获得更好的数据开箱即用体验
数值类型的单位,例如percent
, byte
或者 time unit,默认情况字段是没有单位的,除了数值类型的字段,百分比单位约定 1
即100%
。
metric_type同样用于数值类型的字段,可选值是gauge
跟counter
,gauge是一种单值测量,可以随着时间的推移而上升或下降,例如温度。counter是一个单值累积计数器,它只上升,比如web服务器处理的请求数。默认情况下,字段不会使用metric_type参数。仅对数值类型的字段有效。
(8.2)link
经常出于某些目的需要对同一个字段执行不同的索引方式,这就是multi-fields
的设计初衷。例如,一个字符串的字段值可以作为text
字段使得可以全文检索,也可以作为keyword
字段使得可以用于排序、聚合:
xxxxxxxxxx
441PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "city": {
6 "type": "text",
7 "fields": {
8 "raw": {
9 "type": "keyword"
10 }
11 }
12 }
13 }
14 }
15}
16
17PUT my-index-000001/_doc/1
18{
19 "city": "New York"
20}
21
22PUT my-index-000001/_doc/2
23{
24 "city": "York"
25}
26
27GET my-index-000001/_search
28{
29 "query": {
30 "match": {
31 "city": "york"
32 }
33 },
34 "sort": {
35 "city.raw": "asc"
36 },
37 "aggs": {
38 "Cities": {
39 "terms": {
40 "field": "city.raw"
41 }
42 }
43 }
44}
第8行,city.raw
字段是city
字段的keyword
版本
第31行,city
字段可以用于全文检索
第35、40行,city.raw
可以用于排序跟聚合
你可以通过update mapping API对现有的字段添加multi-filed
multi-field
字段完全独立与parent filed(上面的例子中,city.raw
的parent filed就是city
),意味着它不会继承parent field的任何参数。当然了,Multi-fields不会更改_source
中的内容。
另一个multi-fields的用例就是对同一个字段的字段值采用不同的分词器来得到更好的关联(better relevance)。比如我们可以对字段值使用standard analyzer,将这个字段值分为多个词(word),或者对这个字段值使用english analyzer ,将英文单词转为root format,并且写入到索引:
xxxxxxxxxx
361PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "text": {
6 "type": "text",
7 "fields": {
8 "english": {
9 "type": "text",
10 "analyzer": "english"
11 }
12 }
13 }
14 }
15 }
16}
17
18PUT my-index-000001/_doc/1
19{ "text": "quick brown fox" }
20
21PUT my-index-000001/_doc/2
22{ "text": "quick brown foxes" }
23
24GET my-index-000001/_search
25{
26 "query": {
27 "multi_match": {
28 "query": "quick brown foxes",
29 "fields": [
30 "text",
31 "text.english"
32 ],
33 "type": "most_fields"
34 }
35 }
36}
第5行,text
字段使用standard
(默认)分词器
第8行,text.english
字段使用english
分词器
第19、22行,索引了两篇文档,一篇文档中包含了fox
而另一篇则包含了foxes
第33行,同时查询text
跟text.english
字段,这两个字段都会影响文档的打分
text
字段在第一篇文档中包含了fox
并且在第二篇文档中包含了foxes
。text.english
字段在两篇文档中都包含了fox
,因为foxes
被词根化stemmed为成了fox
。
查询中的字符串同样会对于text
字段使用standard
分词器,并且对于text.english
字段使用english
分词器。词根化stemmed的字段允许如果是查询foxes
,会去匹配包含fox
的文档,使得我们可以尽可能的匹配更多的文档。当查询没有词根化unstemmed的text
字段时,我们会提高那些同时匹配foxes
的文档的分数。
(8.2)link
keyword的属性normalizer
类似与analyzer,只是它能保证分析链(analysis chain)只会生成单个token。
normalizer
先应用到keyword然后再进行索引,同样的在查询期间,查询kewword
字段时会执行一个query parser,例如match query或者term-level query例如term query。
可以使用一个简单的elasticsearch自带的(ship with)名为lowercase
的normalizer,自定义的normalizer可以在analysis中定义:
xxxxxxxxxx
571PUT index
2{
3 "settings": {
4 "analysis": {
5 "normalizer": {
6 "my_normalizer": {
7 "type": "custom",
8 "char_filter": [],
9 "filter": ["lowercase", "asciifolding"]
10 }
11 }
12 }
13 },
14 "mappings": {
15 "properties": {
16 "foo": {
17 "type": "keyword",
18 "normalizer": "my_normalizer"
19 }
20 }
21 }
22}
23
24PUT index/_doc/1
25{
26 "foo": "BÀR"
27}
28
29PUT index/_doc/2
30{
31 "foo": "bar"
32}
33
34PUT index/_doc/3
35{
36 "foo": "baz"
37}
38
39POST index/_refresh
40
41GET index/_search
42{
43 "query": {
44 "term": {
45 "foo": "BAR"
46 }
47 }
48}
49
50GET index/_search
51{
52 "query": {
53 "match": {
54 "foo": "BAR"
55 }
56 }
57}
上面的查询会匹配到1和2这两篇文档,因为BAR
同时在索引和查询期间被转化为bar
。
xxxxxxxxxx
351{
2 "took": $body.took,
3 "timed_out": false,
4 "_shards": {
5 "total": 1,
6 "successful": 1,
7 "skipped" : 0,
8 "failed": 0
9 },
10 "hits": {
11 "total" : {
12 "value": 2,
13 "relation": "eq"
14 },
15 "max_score": 0.4700036,
16 "hits": [
17 {
18 "_index": "index",
19 "_id": "1",
20 "_score": 0.4700036,
21 "_source": {
22 "foo": "BÀR"
23 }
24 },
25 {
26 "_index": "index",
27 "_id": "2",
28 "_score": 0.4700036,
29 "_source": {
30 "foo": "bar"
31 }
32 }
33 ]
34 }
35}
同样的,由于keyword的值在索引前就先被转化了,意味着聚合操作返回的是normalized values:
xxxxxxxxxx
111GET index/_search
2{
3 "size": 0,
4 "aggs": {
5 "foo_terms": {
6 "terms": {
7 "field": "foo"
8 }
9 }
10 }
11}
返回:
xxxxxxxxxx
341{
2 "took": 43,
3 "timed_out": false,
4 "_shards": {
5 "total": 1,
6 "successful": 1,
7 "skipped" : 0,
8 "failed": 0
9 },
10 "hits": {
11 "total" : {
12 "value": 3,
13 "relation": "eq"
14 },
15 "max_score": null,
16 "hits": []
17 },
18 "aggregations": {
19 "foo_terms": {
20 "doc_count_error_upper_bound": 0,
21 "sum_other_doc_count": 0,
22 "buckets": [
23 {
24 "key": "bar",
25 "doc_count": 2
26 },
27 {
28 "key": "baz",
29 "doc_count": 1
30 }
31 ]
32 }
33 }
34}
(8.2)link
norms
存储各种标准化因子(normalization),随后在查询期间用于文档的打分 。
norms
对于打分是很有用的,但是它同时需要很多的磁盘空间(通常在每个文档每个字段上约占一个字节,即使是索引中没有这个特定字段的文档也是如此)。因此,如果你不需要在某个特定字段上进行得分计算,你应该禁用该字段上的规范。特别是对于那些仅用于过滤或聚合的字段。
TIP:可以通过update mapping API对关闭现有的字段的
norm
。
Norms可以通过update mapping API按照下面的方式关闭:
xxxxxxxxxx
91PUT my-index-000001/_mapping
2{
3 "properties": {
4 "title": {
5 "type": "text",
6 "norms": false
7 }
8 }
9}
NOTE:Norms不会马上被移除,而是在你继续索引新的文档后,并且在旧的段合并到新段后才会移除。被移除了norm的字段将返回不一致的结果,因为有些文档不在有norm,而其他的文档仍然有norm。
(8.2)link
null
不能被索引或者搜索,当一个字段被设置为null
(空的数组或者数组元素都是null
),该字段被认为是没有值的。
null_value
允许将那些为null的字段值替换为你指定的值,使得可以被索引和搜索,例如:
xxxxxxxxxx
301PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "status_code": {
6 "type": "keyword",
7 "null_value": "NULL"
8 }
9 }
10 }
11}
12
13PUT my-index-000001/_doc/1
14{
15 "status_code": null
16}
17
18PUT my-index-000001/_doc/2
19{
20 "status_code": []
21}
22
23GET my-index-000001/_search
24{
25 "query": {
26 "term": {
27 "status_code": "NULL"
28 }
29 }
30}
第7行,使用字符串NULL
替换那些为null的字段值。
第20行,空的数组不包含显示的null值,该字段值不会被替换为NULL
第27行,字段名status_code、字段值NULL的查询可以匹配文档1,不会匹配文档2
IMPORTANT:
null_value
的字段值类型必须跟这个字段的类型一致,long类型的字段不能设置为字符串类型的null_value
NOTE:
null_value
只会影响数据的索引和查询,它不会修改_source中文档的数据
(8.2)link
为了能支持proximity or phrase queries,text类型的字段被分词(Analyzed)后会统计term的positions信息。当索引多值(Arrays)的text字段时,Elasticsearch会在这些值之间添加一个假的距离,来阻止phrase query能跨相邻两个值进行查询。这个假的距离使用position_increment_gap
配置并且默认值是100。
例如
xxxxxxxxxx
271PUT my-index-000001/_doc/1
2{
3 "names": [ "John Abraham", "Lincoln Smith"]
4}
5
6GET my-index-000001/_search
7{
8 "query": {
9 "match_phrase": {
10 "names": {
11 "query": "Abraham Lincoln"
12 }
13 }
14 }
15}
16
17GET my-index-000001/_search
18{
19 "query": {
20 "match_phrase": {
21 "names": {
22 "query": "Abraham Lincoln",
23 "slop": 101
24 }
25 }
26 }
27}
第11行,这个phrase query 不会匹配到文档,与我们的期望不符。
第23行,这个phrase query会匹配到文档,因为slop
大于position_increment_gap
,尽管Abraham
和Lincoln
是两个数组元素的值。
position_increment_gap
可以通过下面的方式在mapping中指定:
xxxxxxxxxx
251PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "names": {
6 "type": "text",
7 "position_increment_gap": 0
8 }
9 }
10 }
11}
12
13PUT my-index-000001/_doc/1
14{
15 "names": [ "John Abraham", "Lincoln Smith"]
16}
17
18GET my-index-000001/_search
19{
20 "query": {
21 "match_phrase": {
22 "names": "Abraham Lincoln"
23 }
24 }
25}
第7行,若position_increment_gap
设置为0,那么数组中相邻的两个term的距离为0
例如[ "John Abraham", "Lincoln Smith"],Abraham跟Lincoln之间的距离为0。
第22行,这个phrase query会匹配到文档看起来是有点奇怪的,但原因是我们在mapping要求这么做的。
在es中定义了"names": [ "John Abraham", "Lincoln Smith"],如果position_increment_gap的值为0,那么相当于定义了"names": "John Abraham Lincoln Smith"。在Lucene层,es的arrays类型会将每一个数组元素作为Lucene中同一篇文档的相同字段名的多个字段信息。以name为例,在Lucene中,会生成两个字段名为name,字段值分别为John Abraham和Lincoln Smith的字段。由于这两个字段有相同的字段名,那么在倒排索引中,Abraham和Lincoln的位置是相邻的,当然前提是position_increment_gap的值为0。
(8.2)link
类型映射,object fields和包含子字段(sub-field)nested fiedlds都称为properties
。这些properties可能是任意的数据类型(date type),包括object
和nested
。可以通过下面的方式添加properties
:
在creating an index时显示的定义
通过update mapping API显示的增加或更新mapping时显示的定义
对包含新的字段的文档动态的添加
下面是一个添加properties
的例子,添加了一个object
和nested
字段:
xxxxxxxxxx
391PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "manager": {
6 "properties": {
7 "age": { "type": "integer" },
8 "name": { "type": "text" }
9 }
10 },
11 "employees": {
12 "type": "nested",
13 "properties": {
14 "age": { "type": "integer" },
15 "name": { "type": "text" }
16 }
17 }
18 }
19 }
20}
21
22PUT my-index-000001/_doc/1
23{
24 "region": "US",
25 "manager": {
26 "name": "Alice White",
27 "age": 30
28 },
29 "employees": [
30 {
31 "name": "John Smith",
32 "age": 34
33 },
34 {
35 "name": "Peter Brown",
36 "age": 26
37 }
38 ]
39}
第4行,在顶层mapping中定义
第6行,在manager
下定义
第13行,在employees
下定义
第22行,索引一篇对应上面定义的mapping的文档
TIP:
properties
这个配置运行在同一个索引中对同一个字段名有不同的设置。可以通过update mapping API添加或者更新现有字段的properties。
Inner field可以通过dot notation获取并在query、aggregation中使用:
xxxxxxxxxx
231GET my-index-000001/_search
2{
3 "query": {
4 "match": {
5 "manager.name": "Alice White"
6 }
7 },
8 "aggs": {
9 "Employees": {
10 "nested": {
11 "path": "employees"
12 },
13 "aggs": {
14 "Employee Ages": {
15 "histogram": {
16 "field": "employees.age",
17 "interval": 5
18 }
19 }
20 }
21 }
22 }
23}
IMPORTANT:必须要指定Inner field的完整路径
(8.2)link
通常来说,在索引和查询期间应该应用(apply)相同的analyzer来保证query中的term跟倒排索引中的term有相同的格式(in the same format)。
但有的时候在查询期间使用不同的分词器是有意义的,比如使用edge_ngram tokenizer 用于autocomplete或者查询期间的同义词查询。
默认情况下,查询会使用mapping中定义的analyzer
,但是可以用search_analyzer
设置覆盖:
xxxxxxxxxx
501PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "filter": {
6 "autocomplete_filter": {
7 "type": "edge_ngram",
8 "min_gram": 1,
9 "max_gram": 20
10 }
11 },
12 "analyzer": {
13 "autocomplete": {
14 "type": "custom",
15 "tokenizer": "standard",
16 "filter": [
17 "lowercase",
18 "autocomplete_filter"
19 ]
20 }
21 }
22 }
23 },
24 "mappings": {
25 "properties": {
26 "text": {
27 "type": "text",
28 "analyzer": "autocomplete",
29 "search_analyzer": "standard"
30 }
31 }
32 }
33}
34
35PUT my-index-000001/_doc/1
36{
37 "text": "Quick Brown Fox"
38}
39
40GET my-index-000001/_search
41{
42 "query": {
43 "match": {
44 "text": {
45 "query": "Quick Br",
46 "operator": "and"
47 }
48 }
49 }
50}
第13行,Analysis设置中自定义的分词器autocomplete
第28,29行,text
字段使用在索引期间使用autocomplete
分词器,而在查询期间使用standard
分词器
第37行,这个字段对应的这些term会被索引:[q, qu, qui, quic, quick, b, br, bro, brow, brown, f, fo, fox]
第45行,query中查询的term为:[quick, br]
见Index time search-as-you- type 查看这个例子的详细介绍。
TIP:可以使用update mapping API对现有的字段上更新
search_analyzer
设置。注意的是,Note, that in order to do so, any existing "analyzer" setting and "type" need to be repeated in the updated field definition。
(8.2)link
Elasticsearch允许你为每一个字段配置一个文本打分算法(text scoring algorithm)或者similarity
。参数similarity
允许你提供一个简单文本打分算法而不是使用默认的BM25
,例如boolean
。
BM25 | 即Okapi BM25 algorithm,Elasticsearch以及Lucene默认使用这个算法 |
---|---|
boolean | 一个简单的布尔算法,不需要全文本ranking并且打分值基于是否匹配到了查询中的term。Boolean算法给term打出的分数跟它们的query boost一样 |
参数similarity
可以在字段最开始创建时设置在这个字段的层级:
xxxxxxxxxx
141PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "default_field": {
6 "type": "text"
7 },
8 "boolean_sim_field": {
9 "type": "text",
10 "similarity": "boolean"
11 }
12 }
13 }
14}
第5行,这个字段名为default_field
的字段使用BM25
算法
第10行,这个字段名为boolean_sim_field
的字段使用boolean
算法
(8.2)link
默认情况下,字段值被indexed使得可以被搜索到,但是它们没有存储(store)。意味着该字段能用于查询,但是原始的字段值(origin source value)不能被检索到。
通常来说没有关系,因为字段值早已经是_source字段的一部分,默认被存储了。如果你想检索单个字段或者一些字段的字段值而不是整个_source
, 你可以使用source filtering来达到你的目的。
在有些情况下store
一个字段的字段值是合理的(make sense)。比如说你有一篇文档有title
、date
以及一个字段值很大的content
字段。你可能只想获取title
、date
的字段值而不用从字段值很大(因为_source中包含所有字段的字段值)的_source
字段中提取出来:
xxxxxxxxxx
301PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "title": {
6 "type": "text",
7 "store": true
8 },
9 "date": {
10 "type": "date",
11 "store": true
12 },
13 "content": {
14 "type": "text"
15 }
16 }
17 }
18}
19
20PUT my-index-000001/_doc/1
21{
22 "title": "Some short title",
23 "date": "2015-01-01",
24 "content": "A very long content field..."
25}
26
27GET my-index-000001/_search
28{
29 "stored_fields": [ "title", "date" ]
30}
第7、11行,title
跟date
字段的字段值被存储了
第29行,请求将检索title
跟date
的字段值(不是从_source
中提取)
NOTE:
Stored fields returned as arrays
为了一致性,stored fields总是以数组方式返回,因为没法知道原始的字段值是单值,多值还是一个空的数组。如果你想获取这个字段原始的字段值(输入文档中的值),你应该从
_source
中获取。
(8.2)link
Term vectors包含了经过analysis处理后生成的terms的信息,包括:
terms的列表
每个term的位置(先后顺序)
term在原始的字符串中的开始跟结束的偏移位置
负载(payload,不是所有的term都会有payload)每个不同位置(在原始字符串中的位置)的term关联了一个二进制数据的payload
这些term vector会被存储所以可以针对某一篇文档进行检索。
term_vector
可以接受下面的参数:
no | 不会存储term vectors(默认) |
---|---|
yes | 只有term(位置、偏移、负载不会存储)会被存储 |
with_positions | 存储term跟位置 |
with_offsets | 存储term跟偏移 |
with_positions_offsets | 存储term、位置以及偏移 |
with_positions_payloads | 存储term、位置以及负载 |
with_positions_offsets_payloads | 存储term、位置、偏移以及负载 |
fast vector highlighter需要参数with_positions_offsets
。term vectors API用来检索存储的信息。
WARNING:参数
with_positions_offsets
设置后会使得这个字段的索引大小翻倍。
xxxxxxxxxx
301PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "text": {
6 "type": "text",
7 "term_vector": "with_positions_offsets"
8 }
9 }
10 }
11}
12
13PUT my-index-000001/_doc/1
14{
15 "text": "Quick brown fox"
16}
17
18GET my-index-000001/_search
19{
20 "query": {
21 "match": {
22 "text": "brown fox"
23 }
24 },
25 "highlight": {
26 "fields": {
27 "text": {}
28 }
29 }
30}
term vectors开启后,text
字段会默认使用fast vector highlighter。
(8.2)link 使用下面的设置限制mapping(手动或者动态创建)中字段的数量,防止因为文档(比如每一篇文档都会增加一些字段)导致mapping explosion。
索引中字段的数量最大值。Field and object mappings,以及field aliases都会纳入到统计中并受到限制。默认值为1000
。
IMPORTANT:这个限制是为了防止mappings和查询变的很大(too large)。数量越高会导致性能下降以及内存问题,特别是集群处于高负载或者低资源时。 如果你提高这个设置,我们建议你同时提高indices.query.bool.max_clause_count,这个设置限制了一个Query中clause数量的最大值。
TIP:如果你mapping中包含 a large,arbitrary set of keys,考虑使用flattened 数据类型。
某个字段的最大深度,用来估量inner object的数量。例如,如果所有的字段都在root object level,那么深度为1
。如果某个字段有一个object mapping,那么深度就是2
。默认值为20
。
索引中nested类型的字段的数量最大值。nested
类型应该只用于特殊的用例中,数组中的object需要各自独立的进行查询。为了保护mapping的不当设计,这个设置会限制每一个索引中nested
类型的字段的数量。默认值是50
。
一篇文档中所有nested
类型的字段中JSON object的数量总和最大值。当一篇文档中包含太多的nested object时,防止出现OOM的错误。默认值是10000
。
字段的名称长度最大值。这个设置不能解决mappings explosion的问题,但是如果你想要限制filed length时仍然是有用的。通常不需要设置它。除非用户增加了大量的字段,并且每个字段的字段名长度非常的长,否则使用默认值就可以了。默认值是Long.MAX_VALUE (no limit)
。
预览功能(Dynamic,integer)
Elastic内部使用。
索引中时序维度(time series dimension)数量的最大值。默认值为16
。
你可以使用time_series_dimension mapping参数将某个字段标记为一个dimension。
(8.2)link
Elasticsearch 8.0.0不再支持mapping types。见7.x中的removal of types了解如何不使用mapping Type并迁移你的集群。
(8.2)link
Text analysis是将无结构的文本(unstructured text),例如email的body或者产品描述,转化为优化查询的有结构的格式(structured format)的过程。
Elasticsearch在索引和查询text字段时会执行text analysis。
如果你的索引中没有text
字段,那你不需要任何设置,可以跳过本章节内容。
然而,如果你使用了text
字段或者你的文本查询(text analysis)没有返回期望的结果,那configuring text analysis可以提供一定的帮助。如果你正在使用Elasticsearch实现下面的功能,那你也应该要查看下analysis configuration:
构建一个搜索引擎
挖掘无结构的数据
对特定语言进行微调(Fine-tune)搜索
进行词典或语言研究(Perform lexicographic or linguistic research)
(8.2)link
文本分析(Text analysis)使得Elasticsearch可以执行全文检索(full-text search),查询结果将返回所有相关的结果而不是返回精确的匹配结果。
如果你查询Quick fox jumps
,你可能希望返回的文档中包含A quick brown fox jumps over the lazy dog
,并且你可能还想要文档中包含像fast fox
或者foxes leap
相关的词。
Analysis通过tokenization
使得全文检索成为可能。将一个文本break down成较小的多个块(smaller chunks),称为tokens。大部分情况下,token是一个个独立的单词(individual word)。
如果你将一个短语the quick brown fox jumps
作为单个string,然后用户查询quick fox
,则不会匹配到这个短语。如果对这个短语进行tokenize,并且分别索引每一个词,这个query string中的term会被分别的查询。意味着这个短语会被quick fox
,fox brown
或者其他variations匹配到。
Tokenization使得可以匹配到独立的term(individual term),但只是字符匹配(match literally),意思是:
查询Quick
不会匹配到quick
,尽管你想要查询它们中的一个且能匹配到另一个
尽管fox
和foxes
有相同的词根(root word),但查询foxes
不会匹配到fox
,反之亦然(vice versa)
查询jums
不会匹配到leaps
。它们没有相同的词根,它们是同义词(synonyms)并且有相同的意思
为了解决上述的问题,text analysis可以将这些token normalize到一个标准格式。这使得你可以匹配到一些token,尽管这些token跟查询的term不是完全一样,但是它们足够的相似使得可以产生关联,例如:
Quick
可以小写为quick
foxes
可以被stemmed,或者reduce成它的词根:fox
jump
和leap
是同义词,可以索引为单个词:jump
为了查询时能如期(intend)的匹配到这些词,你可以对query string应用相同的Tokenization和Normalization。例如Foxes leap
的查询可以被normalized 成fox jump
的查询。
Text analysis通过分词器analyzer实现,一系列的规则来管理整个的处理过程。
Elasticsearch包含一个默认的分词器(analyzer),名为standard analyzer,这个分词器在大部分的开箱即用用例中都表现不错。
如果你想自定(tailor)查询的体验,你可以选择不同的built-in analyzer,甚至configure a custom one。自定义的分词器可以控制分词处理过程中的一些步骤,包括:
在Tokenization前更改文本(change text)
text如何转化为token
在索引和搜索前,Normalization对token进行更改
(8.2)link
这一章节介绍Elasticsearch中text analysis的一些基本内容:
(8.2)link
分词器(analyzer)可以是内置或者自定义的,它是包含了三个lower-level building blocks的包:character filters, tokenizers, 和 token filters。
原始的文本作为一个字符流(a stream of characters),character filter接受这个字符流,通过adding,removing,或者changing character的方式对字符流进行转化。例如character filter可以用来将Hindu-Arabic numerals (٠١٢٣٤٥٦٧٨٩) 转化为 Arabic-Latin equivalents (0123456789)或者从流中移除(strip)HTML的element,比如<b>
。
一个分词器可以没有,或者多个character filter。有序的依次应用(apply)这些character filter。
Tokenizer接受字符流,将其拆分(break up)为独立的token(individual token),token通常是独立的一个词(individual word),然后输出一个token流(a stream of tokens)。例如whitespace会将文本按照空格进行token的划分,它会将Quick brown fox!
划分成[Quick, brown, fox!]
。
tokenizer同样负责记录每一个term的先后顺序,位置,以及字符的开始跟结束的偏移信息。
一个分词器只能有一个Tokenizer。
token filters接受token流,并且add,remove,或者更改token。例如,lowercase这个token filters会将所有的token转成小写,stop token filter会从token流中移除常见的词(common word,停用词),比如the
。synonym token filter会将同义词放到token流中。
Token filters不允许更改token的位置以及偏移信息。
一个分词器可以没有,或者多个Token filters,有序的依次应用(apply)这些token filters。
(8.2)link
Text analysis会在下面两个时间点发生:
当索引一篇文档时,所有的text的字段值都会被分词(analyzed)。
当在text
字段上执行full-text search时,query string(用户查询的内容)会被分词。
Search time也被称为query time。
analyzer或者一系列的analysis rules在不同的时间点分别称为index analyzer
和search analyzer
。
在大多数情况下,Index time和search time应该使用相同的analyzer。这样能保证字段值跟query string都被更改(change)成相同的tokens。这样的话在搜索时就能达到期望的token匹配。
下面的值作为text
字段的字段值进行索引:
xxxxxxxxxx
11The QUICK brown foxes jumped over the dog!
如果Index analyzer将上文的字段值转化为成token并进行了normalizes,在这个例子中,每一个token代表了一个词:
xxxxxxxxxx
11[ quick, brown, fox, jump, over, dog ]
这些token都会被索引。
随后,一位用户会在相同的text
字段上进行以下的查询:
xxxxxxxxxx
11"Quick fox"
用户想要匹配刚刚上文中索引的句子:The QUICK brown foxes jumped over the dog!
。
但是用户提供的query string中不存在精确的词(exact word)来匹配文档中的内容:
Quick
vs QUICK
fox
vs foxes
为了能正确的查询,query string要使用相同的analyzer。这个analyzer会产生下面的tokens:
xxxxxxxxxx
11[ quick, fox ]
执行查询时,Elasticsearch会比较query string中tokens 和text
字段索引的tokens:
Token | Query string | text field |
---|---|---|
quick | X | X |
brown | X | |
fox | X | X |
jump | X | |
over | X | |
dog | X |
由于字段值跟query string使用相同方式进行分词,因此它们会生成相同的token。quick
和fox
是精确匹配,意味着查询会匹配到包含"The QUICK brown foxes jumped over the dog!"
的文档,满足用户期望。
有时候index time和search time需要使用不同的analyzer,不过这种场景不是很常见(while less common)。Elasticsearch允许你specify a separate search analyzer来处理这种场景。
通常来说,当字段值和query string使用相同格式的token(same form of tokens)后得到不在期望内或者不相关的匹配时才会考虑使用不同的(separate)search analyzer。
Elasticsearch用来创建一个搜索引擎来匹配那些以查询词为前缀的结果。例如,查询词tr
应该匹配tram
或者trope
,而不会匹配taxi
或者bat
。
一篇文档被添加到搜索引擎的索引中,这篇文档包含一个text
类型的字段的值:
xxxxxxxxxx
11"Apple"
index analyzer会将这个字段值转化为tokens并且执行normalizes。在这个例子中,每一个token是这个词的各种前缀。
xxxxxxxxxx
11[ a, ap, app, appl, apple]
这些token都会被索引。
随后,用户在相同的text
字段上进行搜索:
xxxxxxxxxx
11"appli"
用户期望匹配以appli
为前缀的结果,比如appliance
或者application
。这个查询不应该匹配到apply
。
然而如果使用index analyzer来处理这个query string,它会生成下面的tokens:
xxxxxxxxxx
11[ a, ap, app, appl, appli ]
当Elasticsearch拿着query string中的tokens去匹配apply
的tokens时,它会找到好几个匹配的结果:
Token | appli | apple |
---|---|---|
a | X | X |
ap | X | X |
app | X | X |
appl | X | X |
appli | X |
这意味着错误的(erroneous)匹配到了apple
,而且不仅仅会匹配到apply
,还会匹配到所有以a
为前缀的结果。
要修复这种情况,可以为query string指定一个不同的search analyzer来处理text
字段。
在这个例子中,你可以指定一个search analyzer来生成单个token而不是生成前缀集合:
xxxxxxxxxx
11[ appli]
这个query string token只会匹配以appli
为前缀的结果,更接近用户期望的查询结果。
(8.2)link
Stemming(词根化)将一个词reduce到这个词的root form(词根)。使得这个词的各种变形都能在查询时匹配到。
例如,walking
和walked
都可以被stemmed到相同的root word:walk
。一旦进行stemming,walking
、walked
、walk
任意一个出现在查询中都会匹配到另外两个。
Stemming属于language-dependent,但通常就是移除词的前缀跟后缀。
有些情况下,经过stemming后的词可能就不是一个真实的词。例如jumping
和jumpiness
可以stemmed为jumpi
。jumpi
不是一个真实存在的词,但是这不会影响查询。如果所有词的变形都可以reduce为相同的词根,就可以正确的匹配到。
在Elasticsearch中,在stemmer token filters中处理stemming。这些token filter根据它们不同词根化的方式可以分类为:
Algorithmic stemmers,基于一系列的规则执行词根化
Dictionary stemmers,基于查找词典执行词根化
由于词根化会更改token,我们建议在index and search analysis中使用相同的stemmer token filters。
Algorithmic stemmers通过应用一系列的规则将每一个词reduce到它的词根(root form)。例如,一个用于英文的algorithmic stemmers会移除复数(plural)的-s
跟-es
后缀。
Algorithmic stemmers有以下的一些优点:
它们要求更少的步骤并且开箱即用
它们占用少量的内存
通常比Dictionary stemmers的处理速度快
然而大多数的algorithmic stemmers只会改变词的文字(text of a word)。这意味着一些irregular word,它们本身不包含词根的内容,这类词就不能很好的词根化:
be
,are
和am
mouse
和mice
foot
和feet
下面的token filters使用了algorithmic stemming:
stemmer,为一些语言提供了algorithmic stemming,带有一些额外的变形(variants)
kstem,用于英语的词根化,它结合了内建字典(built-in dictionary)的algorithmic stemming
porter_stem,推荐使用的用于英语的词根化
snowball,使用Snowball-based,用于多种语言
Dictionary stemmers从提供的字典中查找词。将未词根化的word variants替换为字典中词根化的词。
理论上,dictionary stemmers适用于:
irregular words的词根化
辨别出那些拼写类似但含义毫无相关的单词,比如:
organ
和organization
broker
和broken
实践中,algorithmic stemmers通常优于(outperform)dictionary stemmers。因为dictionary stemmers有以下的缺点:
Dictionary quality
dictionary stemmer的效果取决于字典。为了能很好的工作,这些字典必须包含大量的词,需要定期更新,并且根据不同的语言进行变更。通常来说,当一个词典在开始使用后,它的不完整性以及一些条目就已经过时了。
Size and performance
Dictionary stemmers必须将所有的词,前缀,后缀从字典中载入到内存。这会带来大量的RAM开销。Low-quality的字典在去除前缀和后缀方面的效率也可能较低,这会显著减缓词根提取过程
你可以使用hunspell token filter来执行 dictionary stemming。
TIPs:如果可以的话,我们建议在使用 hunspell token filter前先使用algorithmic stemmer。
有时候stemming会将一些词共享同一个词根但是这些词之间只是拼写相似但是含义是没有任何关联的。例如,skies
和skiing
会被词根化为ski
。
为了防止出现上述问题以及更好的控制stemming,你可以使用下面的token filters:
stemmer_override,可以定义指定tokens的词根化规则
keyword_marker,可以指定token作为一个keyword,随后的stemmer token filters不会对keyword token词根化
conditional,可以标记token为keyword word,类似keyword_marker
对于内置(built-in)的language analyzers,你可以使用stem_exclusion参数来指定不需要词根化的词。
(8.2)link
当tokenizer 将文本转化为tokens后,它同时会记录下面的信息:
流中每一个token的position
positionLength
,一个token跨越(span)的位置数量(number of position)
使用上述信息可以创建一个directed acyclic graph,称为token graph
。在一个token graph中,每一个位置代表了一个节点(node)。每一个token代表了一个edge或者arc,指向下一个位置。
一些token filters会增加新的token到现有的token stream中,例如同义词。这些同义词通常跟现有的tokens拥有相同的position。
在下面的图中,quick
和它的同义词fast
有相同的位置:0
。它们跨越相同的位置:
有些token filters会增加跨越多个位置的token。这个token是multi-word的同义词,例如使用atm
作为automatic teller machine
的同义词。
然而只有一些token filters,比如graph token filters
,会为multi-position的token记录准确的positionLength
。这类token filter包括:
Some tokenizers, such as the nori_tokenizer, also accurately decompose compound tokens into multi-position tokens。
下图中,domain name system
和它的同义词dns
,它们的position都是0
。然而dns
的positionLength
为3
。其他的token在图中的positionLength
的值默认就是1
。
Indexing会忽略positionLength
的属性并且不支持包含multi-position tokens的token graph。
然而match和match_phrase query会使用这些图从单个query string中生成多个sub-queries。
某个用户使用match_phrase
查询下面的短语:
xxxxxxxxxx
11domain name system is fragile
在search analysis中,dns
是domain name system
的同义词,会被添加到query string对应的token stream中。dns
这个token的positionLength
的值为3
。
match_phrase
使用这个图来生成下面的sub-queries:
xxxxxxxxxx
21dns is fragile
2domain name system is fragile
意味着查询会匹配包含dns is fragile
或者domain name system is fragile
的文档。
下面的token filter会添加token,这些token会跨越多个位置,但是只将positionLength
记录为默认值:1
。
这意味着这些filters会为包含这些token的流生产出错误的(invalid)token graph。
在下图中,dns
是multi-position,它是domain name system
的同义词。然而dns
的positionLength
为1
,导致一个错误的图。
避免使用错误的token图用于搜索。错误的图会导致不满足期望的查询结果。
(8.2)link
默认情况下,Elasticsearch会使用默认的standard analyzer用于所有的文本分析(text analysis)。standard
分词器(analyzer)作为开箱即用能满足大多数的语言和用例。如果你选择standard
,那么不需要其他的任何配置。
如果standard
满足不了你的需求,你可以查看以及测试Elasticsearch提供的其他内置的built-in analyzers。内置的分词器也不需要进行配置,但是可以使用一些参数来调节分词器的行为。例如你可以对standard
配置一个自定义的停用词列表。
如果内置的的分词器都不能满足你的需求,你可以创建并测试你自定义的分词器。自定义的分词器包括选择和组合不同的analyzer components,让你能控制分词的过程。
(8.2)link
analyze API 是一个非常有用(invaluable)的工具来查看分词器的分词效果。请求中可以指定内置的分词器:
xxxxxxxxxx
51POST _analyze
2{
3 "analyzer": "whitespace",
4 "text": "The quick brown fox."
5}
API会返回如下的结果:
xxxxxxxxxx
321{
2 "tokens": [
3 {
4 "token": "The",
5 "start_offset": 0,
6 "end_offset": 3,
7 "type": "word",
8 "position": 0
9 },
10 {
11 "token": "quick",
12 "start_offset": 4,
13 "end_offset": 9,
14 "type": "word",
15 "position": 1
16 },
17 {
18 "token": "brown",
19 "start_offset": 10,
20 "end_offset": 15,
21 "type": "word",
22 "position": 2
23 },
24 {
25 "token": "fox.",
26 "start_offset": 16,
27 "end_offset": 20,
28 "type": "word",
29 "position": 3
30 }
31 ]
32}
你也可以测试下面的组合:
一个tokenizer
没有或者多个token filters
没有或者多个character filters
xxxxxxxxxx
61POST _analyze
2{
3 "tokenizer": "standard",
4 "filter": [ "lowercase", "asciifolding" ],
5 "text": "Is this déja vu?"
6}
API会返回如下的结果:
xxxxxxxxxx
321{
2 "tokens": [
3 {
4 "token": "is",
5 "start_offset": 0,
6 "end_offset": 2,
7 "type": "<ALPHANUM>",
8 "position": 0
9 },
10 {
11 "token": "this",
12 "start_offset": 3,
13 "end_offset": 7,
14 "type": "<ALPHANUM>",
15 "position": 1
16 },
17 {
18 "token": "deja",
19 "start_offset": 8,
20 "end_offset": 12,
21 "type": "<ALPHANUM>",
22 "position": 2
23 },
24 {
25 "token": "vu",
26 "start_offset": 13,
27 "end_offset": 15,
28 "type": "<ALPHANUM>",
29 "position": 3
30 }
31 ]
32}
Positions and character offsets 从上文中的
analyze
API可以看出,分词器不仅仅会将词转化为term,并且还会有序的记录每一个term的相对位置(用于短语查询或者word proximity查询),以及每一个term在原始文本中的开始跟结束的偏移位置(用于高亮查询)
或者在指定的索引上运行analyze
API时指定一个custom analyzer:
xxxxxxxxxx
371PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "std_folded": {
7 "type": "custom",
8 "tokenizer": "standard",
9 "filter": [
10 "lowercase",
11 "asciifolding"
12 ]
13 }
14 }
15 }
16 },
17 "mappings": {
18 "properties": {
19 "my_text": {
20 "type": "text",
21 "analyzer": "std_folded"
22 }
23 }
24 }
25}
26
27GET my-index-000001/_analyze
28{
29 "analyzer": "std_folded",
30 "text": "Is this déjà vu?"
31}
32
33GET my-index-000001/_analyze
34{
35 "field": "my_text",
36 "text": "Is this déjà vu?"
37}
第6行,定义一个名为std_folded
的分词器
第21行,my_text
字段使用std_folded
分词器进行分词
第27行,要想引用这个分词器,analyze
API必须要指定索引名
第29行,通过分词器的名称来指定一个分词器
第35行,对my_text
字段进行分词
API会返回如下的结果:
xxxxxxxxxx
321{
2 "tokens": [
3 {
4 "token": "is",
5 "start_offset": 0,
6 "end_offset": 2,
7 "type": "<ALPHANUM>",
8 "position": 0
9 },
10 {
11 "token": "this",
12 "start_offset": 3,
13 "end_offset": 7,
14 "type": "<ALPHANUM>",
15 "position": 1
16 },
17 {
18 "token": "deja",
19 "start_offset": 8,
20 "end_offset": 12,
21 "type": "<ALPHANUM>",
22 "position": 2
23 },
24 {
25 "token": "vu",
26 "start_offset": 13,
27 "end_offset": 15,
28 "type": "<ALPHANUM>",
29 "position": 3
30 }
31 ]
32}
(8.2)link
内置的分词器(analyzer)可以直接使用而不需要进行任何的配置。而一些内置的分词器支持参数配置来改变分词器的行为。例如,standard analyzer可以配置自定义的停用词集合:
xxxxxxxxxx
391PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "std_english": {
7 "type": "standard",
8 "stopwords": "_english_"
9 }
10 }
11 }
12 },
13 "mappings": {
14 "properties": {
15 "my_text": {
16 "type": "text",
17 "analyzer": "standard",
18 "fields": {
19 "english": {
20 "type": "text",
21 "analyzer": "std_english"
22 }
23 }
24 }
25 }
26 }
27}
28
29POST my-index-000001/_analyze
30{
31 "field": "my_text",
32 "text": "The old brown cow"
33}
34
35POST my-index-000001/_analyze
36{
37 "field": "my_text.english",
38 "text": "The old brown cow"
39}
第6行,我们定义了一个基于standard
的分词器std_english
,但是使用了英语的停用词
第17行,my_text
字段直接使用了standard
分词器,没有进行任何的配置。
第31行,不会移除停用词,所以分词结果为:[the, old, brown, cow
]
第21行,my_text.english
字段使用了std_english
,所以英语停用词会被移除,所以第37行的分词结果为:[old, brown, cow
]
(8.2)link
当内置的分词器(analyzer)无法满足你的需求时,你可以创建一个custom
分词器,并使用合适的组合:
没有或者多个character filters
没有或者多个token filters
custom
分词器接受以下的参数:
type | 分词器类型,可以是built-in analyzer类型。对于custom analyzer,使用custom 或者omit这个参数 |
---|---|
tokenizer | 内置或者自定义的tokenizer |
char_filter | 可选参数,一个或者多个内置或者自定义character filters |
filter | 可选参数,一个或者多个内置或者自定义token filters |
position_increment_gap | When indexing an array of text values, Elasticsearch inserts a fake "gap" between the last term of one value and the first term of the next value to ensure that a phrase query doesn’t match two terms from different array elements. Defaults to 100 . See position_increment_gap for more. |
这个例子使用了以下的组合:
xxxxxxxxxx
261PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_custom_analyzer": {
7 "type": "custom",
8 "tokenizer": "standard",
9 "char_filter": [
10 "html_strip"
11 ],
12 "filter": [
13 "lowercase",
14 "asciifolding"
15 ]
16 }
17 }
18 }
19 }
20}
21
22POST my-index-000001/_analyze
23{
24 "analyzer": "my_custom_analyzer",
25 "text": "Is this <b>déjà vu</b>?"
26}
第7行,对于custom
分词器,使用参数type
的值为custom
或者omittype
这个参数。
例子:
xxxxxxxxxx
461PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_custom_analyzer": {
7 "char_filter": [
8 "emoticons"
9 ],
10 "tokenizer": "punctuation",
11 "filter": [
12 "lowercase",
13 "english_stop"
14 ]
15 }
16 },
17 "tokenizer": {
18 "punctuation": {
19 "type": "pattern",
20 "pattern": "[ .,!?]"
21 }
22 },
23 "char_filter": {
24 "emoticons": {
25 "type": "mapping",
26 "mappings": [
27 ":) => _happy_",
28 ":( => _sad_"
29 ]
30 }
31 },
32 "filter": {
33 "english_stop": {
34 "type": "stop",
35 "stopwords": "_english_"
36 }
37 }
38 }
39 }
40}
41
42POST my-index-000001/_analyze
43{
44 "analyzer": "my_custom_analyzer",
45 "text": "I'm a :) person, and you?"
46}
第6行,分配给这个索引一个默认自定义的分词器,my_custom_analyzer
。这个分词器使用了一个自定义的tokenizer,character filter以及token filter。这个分词器omit了type
参数。
第18行,定义了一个自定义的名为punctuation
的tokenizer。
第24行,定义了一个自定义的名为emoticons
的character filter。
第33行,定义了一个自定义的名为english_stop
的token filter。
上面的例子会生成下面的term:
xxxxxxxxxx
11[ i'm, _happy_, person, you ]
(8.2)link
Elasticsearch提供了多种方法(a variety of ways)来指定一个内置的或者自定义的分词器(analyzer)。
By text field, index, or query
TIP: Keep it simple 在不同的level(filed-level,index-level)和不同的时间点(索引或查询期间)能够灵活的指定分词器is good,但只有必要时才这么做。
大多数情况下,简单的方式可以work well:为每一个
text
字段指定分词器。见Specify the analyzer for a field。这个方式会按Elasticsearch的默认行为工作,让你在索引和查询阶段使用相同的分词器。也能让你快速的通过get mapping API了解哪个字段使用了哪个分词器。
如果你不为你的索引创建mapping,你可以使用index templates来达到相同的效果。
Elasticsearch会有序的检查下面的参数来检测使用了哪种index analyzer:
索引设置:analysis.analyzer.default
。见Specify the default analyzer for an index
如果没有指定这些参数,那么默认使用standard analyzer。
你可以使用mapping参数analyzer为每一个text
字段指定分词器。
下面的create index API要求为title
字段指定whitespace
分词器:
xxxxxxxxxx
111PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "title": {
6 "type": "text",
7 "analyzer": "whitespace"
8 }
9 }
10 }
11}
除了在field-level指定分词器,你可以使用analysis.analyzer.default
设置一个fallback analyzer。
下面的create index API为索引my-index-000001
设置了一个名为simple
的fallback analyzer。
xxxxxxxxxx
121PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "default": {
7 "type": "simple"
8 }
9 }
10 }
11 }
12}
在执行full-text query时,你可以使用analyzer
参数来指定 search analyzer,它会覆盖任何其他的search analyzer。
下面的search API在match query中设置了一个stop
的分词器。
xxxxxxxxxx
111GET my-index-000001/_search
2{
3 "query": {
4 "match": {
5 "message": {
6 "query": "Quick foxes",
7 "analyzer": "stop"
8 }
9 }
10 }
11}
你可以使用search_analyzer参数为每一个text
字段指定一个search analyzer。
如果提供了search analyzer,必须使用analyzer
参数来指定index analyzer。
下面的create index API为title
字段指定了一个simply
分词器作为search analyzer。
xxxxxxxxxx
121PUT my-index-000001
2{
3 "mappings": {
4 "properties": {
5 "title": {
6 "type": "text",
7 "analyzer": "whitespace",
8 "search_analyzer": "simple"
9 }
10 }
11 }
12}
当creating an index时,你可以使用analysis.analyzer.default_search
设置一个默认的search analyzer。
如果设置了默认的search analyzer,你必须要使用analysis.analyzer.default
设置默认的index analyzer。
下面的creating an index中,为名为my-index-000001
的索引设置了一个默认的名为whitespace
分词器作为默认的search analyzer。
xxxxxxxxxx
151PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "default": {
7 "type": "simple"
8 },
9 "default_search": {
10 "type": "whitespace"
11 }
12 }
13 }
14 }
15}
(8.2)link
Elasticsearch自带多种内置分析器,这些分析器可以在任何索引中使用,无需进一步配置:
Standard Analyzer:standard
根据Unicode文本分割算法的定义,在单词边界处将文本划分为term。它移除大部分标点符号,将词项转换为小写,并支持移除停用词
Simple Analyzer:simple
在遇到非字母字符时将文本划分为term。它将所有词项转换为小写
Whitespace Analyzer:whitespace
在遇到任何空白字符时将文本划分为term。它不会将词项转换为小写
Stop Analyzer:stop
类似于simple
分词器,但也支持移除停用词
Keyword Analyzer:keyword
是一种“无操作”分析器,接受任何给定的文本,并将相同的文本作为单个term输出
Pattern Analyzer:pattern
使用正则表达式将文本分割为term。它支持小写转换和停用词。
Language Analyzers:Elasticsearch提供了许多特定语言的分析器,如英语或法语。
Fingerprint Analyzer:fingerprint
是一种专门的分词器,用于创建可用于重复检测的指纹。
如果你没有找到你想要的分词器,那你可以custom一个分词器,让这个分词器包含合适的character filter、tokenizer以及token filters。
(8.2)link
fingerprint
分词器实现了fingerprinting algorithm 用于在OpenRefine项目中帮助进行聚类。
输入的文本会被小写化、标准化来移除扩展的字符,排序、去重并且最后拼接成单个token。如果配置了stopword列表,停用词会被移除。
xxxxxxxxxx
51POST _analyze
2{
3 "analyzer": "fingerprint",
4 "text": "Yes yes, Gödel said this sentence is consistent and."
5}
上面的句子生成下面的单个term:
xxxxxxxxxx
11[ and consistent godel is said sentence this yes ]
fingerprint
分词器接受以下的参数:
separator:拼接时使用的字符。默认是空格
max_output_size:token数量的最大值。默认是255
。超过这个大小会被丢弃
stopwords:预先定义的类似_english_
的停用词列表或者包含通用词的数组。默认是_none_
stopwords_path:包含的通用词的文件路径
见Stop Token Filter了解更多关于停用词配置的信息。
下面的例子中,我们为fingerprint
分词器配置了一个预先定义好的English停用词:
xxxxxxxxxx
191PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_fingerprint_analyzer": {
7 "type": "fingerprint",
8 "stopwords": "_english_"
9 }
10 }
11 }
12 }
13}
14
15POST my-index-000001/_analyze
16{
17 "analyzer": "my_fingerprint_analyzer",
18 "text": "Yes yes, Gödel said this sentence is consistent and."
19}
上面的例子中生成以下的term:
xxxxxxxxxx
11[ consistent godel said sentence yes ]
fingerprint
包含下面的内容:
Stop Token Filter (disabled by default)
如果你需要自定义fingerprint
分词器,你可以重新创建一个名为custom
分词器然后修改它,通常添加token filter。这将重建内置的fingerprint
分词器,你可以使用它,将其作为进一步的自定义作为一个起始点:
xxxxxxxxxx
171PUT /fingerprint_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "rebuilt_fingerprint": {
7 "tokenizer": "standard",
8 "filter": [
9 "lowercase",
10 "asciifolding",
11 "fingerprint"
12 ]
13 }
14 }
15 }
16 }
17}
(8.2)link
keyword
分词器是一个"noop"(没有操作)的分词器,它将整个输入的字符串作为单个token。
xxxxxxxxxx
51POST _analyze
2{
3 "analyzer": "keyword",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的句子将会生成以下单个term:
xxxxxxxxxx
11[ The 2 QUICK Brown-Foxes jumped over the lazy dog's bone. ]
keyword
分词器没有配置项。
keyword
分词器包含以下内容:
如果你需要自定义keyword
分词器,你可以重新创建一个名为custom
分词器然后修改它,通常添加token filter。通常如果你不需要将字符串切分为token时应该选择Keyword type 类型,但如果你一定要这么勇,这将重建内置的fingerprint
分词器,你可以使用它,将其作为进一步的自定义作为一个起始点:
xxxxxxxxxx
141PUT /keyword_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "rebuilt_keyword": {
7 "tokenizer": "keyword",
8 "filter": [
9 ]
10 }
11 }
12 }
13 }
14}
第8行,你可以在这里添加任意的token filter。
(8.2)link
simple
分词器根据任意非字母(non-letter)的字符对文本进行划分。比如数字、空格、连字符和撇号,丢弃非字母字符,并将大写字母转换为小写字母。
xxxxxxxxxx
51POST _analyze
2{
3 "analyzer": "simple",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
simple
分词器解析上面的句子并且生成下面的token:
xxxxxxxxxx
11[ the, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]
simple
分词器包含以下内容:
如果你需要自定义simple
分词器,你可以重新创建一个名为custom
分词器然后修改它,通常添加token filter:
xxxxxxxxxx
141PUT /my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_custom_simple_analyzer": {
7 "tokenizer": "lowercase",
8 "filter": [
9 ]
10 }
11 }
12 }
13 }
14}
第8行,在这里添加token filter。
(8.2)link
standard
分词器(analyzer)是默认的分词器,如果没有指定分词器则使用standard
。standard
提供了基于语法的分词(基于Unicode Text Segmentation algorithm,见 Unicode Standard Annex #29),它能很好的对大多数语言进行分词。
xxxxxxxxxx
51POST _analyze
2{
3 "analyzer": "standard",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的句子会生成下面的term:
xxxxxxxxxx
11[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog's, bone ]
standard
分词器接收下面的参数:
max_token_length
:token的最大长度。如果一个token的长度超过了max_token_length
,会按照max_token_length
长度进行分割。默认值是255
。
stopwords:预设的停用词,例如_english_
或者包含了停用词的数组。默认值为_none_
。
stopwords_path:包含停用词的文件的路径
见Stop Token Filter查看停用词的配置。
在这个例子中,我们将standard
分词器的参数max_token_length
设置为5
(只是为了展示参数效果),然后使用预先定义的英语停用词:
xxxxxxxxxx
201PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_english_analyzer": {
7 "type": "standard",
8 "max_token_length": 5,
9 "stopwords": "_english_"
10 }
11 }
12 }
13 }
14}
15
16POST my-index-000001/_analyze
17{
18 "analyzer": "my_english_analyzer",
19 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
20}
上面的例子能生成下面的term:
xxxxxxxxxx
11[ 2, quick, brown, foxes, jumpe, d, over, lazy, dog's, bone ]
standard
分词器包括下面的内容:
如果你需要自定义配置参数外的standard
分词器,你需要重建一个custom
的分词器然后修改它。通常是添加token filter。这样将重建一个内置的standard
分词器。
xxxxxxxxxx
151PUT /standard_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "rebuilt_standard": {
7 "tokenizer": "standard",
8 "filter": [
9 "lowercase"
10 ]
11 }
12 }
13 }
14 }
15}
第9行,你可以在lowercase
后添加其他的token filter。
(8.2)link
stop
分词器和simple analyzer是一样的,但是增加了支持移除停用词。默认使用_english_
停用词。
xxxxxxxxxx
51POST _analyze
2{
3 "analyzer": "stop",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的句子将生成以下term:
xxxxxxxxxx
11[ quick, brown, foxes, jumped, over, lazy, dog, s, bone ]
stop
分词器接受以下的参数:
stopwords:预先定义的类似_english_
的停用词列表或者包含通用词的数组。默认是_english_
stopwords_path:包含的通用词的文件路径
见Stop Token Filter了解更多关于停用词配置的信息。
在这个例子中,我们配置stop
分词器来使用一个指定的停用词列表:
xxxxxxxxxx
191PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_stop_analyzer": {
7 "type": "stop",
8 "stopwords": ["the", "over"]
9 }
10 }
11 }
12 }
13}
14
15POST my-index-000001/_analyze
16{
17 "analyzer": "my_stop_analyzer",
18 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
19}
上面的例子生成下面的term:
xxxxxxxxxx
11[ quick, brown, foxes, jumped, lazy, dog, s, bone ]
包含以下内容:
如果你需要自定义stop
分词器,你可以重新创建一个名为custom
分词器然后修改它,通常添加token filter,这将重建内置的stop
分词器,你可以使用它,将其作为进一步的自定义作为一个起始点:
xxxxxxxxxx
211PUT /stop_example
2{
3 "settings": {
4 "analysis": {
5 "filter": {
6 "english_stop": {
7 "type": "stop",
8 "stopwords": "_english_"
9 }
10 },
11 "analyzer": {
12 "rebuilt_stop": {
13 "tokenizer": "lowercase",
14 "filter": [
15 "english_stop"
16 ]
17 }
18 }
19 }
20 }
21}
第8行,默认的停用出可以用stopwords
或者stopwords_path
参数覆盖
第15行,你可以在english_stop
后面添加任意的token filter
(8.2)link
whitespace
分词器(analyzer)会在根据空格将文本划分出term。
xxxxxxxxxx
51POST _analyze
2{
3 "analyzer": "whitespace",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的句子会生成下面的term:
xxxxxxxxxx
11[ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog's, bone. ]
whitespace
分词器不可以配置。
whitespace
分词器包含下面的内容:
如果你需要自定义whitespace
分词器,你可以重新创建一个名为custom
分词器然后修改它,通常添加token filter。这将重建内置的whitespace
分词器:
xxxxxxxxxx
131PUT /whitespace_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "rebuilt_whitespace": {
7 "tokenizer": "whitespace",
8 "filter": [
9 ]
10 }
11 }
12 }
13 }
第8行,你可以在这里添加token filter。
char_group
tokenizer定义了一组集合,集合中的元素作为文本切分标识。希望自定义一个简单的分词器或者或者无法接受pattern tokenizer 的开销时,使用这个tokenizer非常有用。
char_group
tokenizer 接受以下参数:
tokenize_on_chars
:包含字符的列表。列表中的字符作为切分的标识。这个参数可以包含单个字符,比如-
,也可以是字符组,比如whitespace
, letter
, digit
, punctuation
, symbol
max_token_length
:token的最大长度。如果一个token的长度超过了max_token_length
,会按照max_token_length
长度进行划分。默认值是255
。
xxxxxxxxxx
121POST _analyze
2{
3 "tokenizer": {
4 "type": "char_group",
5 "tokenize_on_chars": [
6 "whitespace",
7 "-",
8 "\n"
9 ]
10 },
11 "text": "The QUICK brown-fox"
12}
输出:
xxxxxxxxxx
321{
2 "tokens": [
3 {
4 "token": "The",
5 "start_offset": 0,
6 "end_offset": 3,
7 "type": "word",
8 "position": 0
9 },
10 {
11 "token": "QUICK",
12 "start_offset": 4,
13 "end_offset": 9,
14 "type": "word",
15 "position": 1
16 },
17 {
18 "token": "brown",
19 "start_offset": 10,
20 "end_offset": 15,
21 "type": "word",
22 "position": 2
23 },
24 {
25 "token": "fox",
26 "start_offset": 16,
27 "end_offset": 19,
28 "type": "word",
29 "position": 3
30 }
31 ]
32}
(8.2)link
classic
tokenizer是一个基于语法的分词器,适用于英文文档。这个分词器对首字母缩写词、公司名称、电子邮件地址和互联网主机名有特殊处理的启发式(tokenizer)规则。然而,这些规则并非总是有效,而且这个分词器对于英语以外的大多数语言效果不佳:
它在大多数标点符号处分割单词,移除标点。但是,如果点号后面没有空白,则认为它是令token的一部分。
它在连字符(-
)处分割单词,除非token中有数字,在这种情况下,整个token被解释为产品号,不进行分割。
它将电子邮件地址和互联网主机名识别为一个token。
xxxxxxxxxx
51POST _analyze
2{
3 "tokenizer": "classic",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的句子将会生成下面的term:
xxxxxxxxxx
11[ The, 2, QUICK, Brown, Foxes, jumped, over, the, lazy, dog's, bone ]
classic
tokenizer 接受以下参数:
max_token_length
:token的最大长度。如果一个token的长度超过了max_token_length
,会按照max_token_length
长度进行划分。默认值是255
。
在这个例子中,我们将max_token_length
的值设置为5(出于演示目的):
xxxxxxxxxx
181PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_analyzer": {
7 "tokenizer": "my_tokenizer"
8 }
9 },
10 "tokenizer": {
11 "my_tokenizer": {
12 "type": "classic",
13 "max_token_length": 5
14 }
15 }
16 }
17 }
18}
上面的例子生成以下的term:
xxxxxxxxxx
11[ The, 2, QUICK, Brown, Foxes, jumpe, d, over, the, lazy, dog's, bone ]
keyword
tokenizer是一个noop
(没有任何操作) tokenizer。无论给定的文本是什么样子,总是将其作为单个term。他可以跟token filter结合来标准化输出,比如将email地址小写。
(8.2)link
letter
tokenizer在遇到一个非字母时开始切分。对于大多数的欧洲语言,这种切分方式是合理的,但是对于一些亚洲语言就不行了,因为单词之间不是用空格分隔的。
xxxxxxxxxx
51POST _analyze
2{
3 "tokenizer": "letter",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的例子生成以下的term:
xxxxxxxxxx
11[ The, QUICK, Brown, Foxes, jumped, over, the, lazy, dog, s, bone ]
letter
tokenizer没有配置项。
lowercase
tokenizer跟letter tokenizer是一样的,也就是遇到一个非字母时开始切分,区别就是同时将所有的字母小写化。这个tokenizer的功能就相当于组合了letter tokenizer和lowercase token filter,但是性能更好,因为它将这两个步骤同时处理了。
xxxxxxxxxx
51POST _analyze
2{
3 "tokenizer": "lowercase",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的例子生成以下的term:
xxxxxxxxxx
11[ the, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]
lowercase
tokenizer没有配置项。
path_hierarchy
tokenizer 用来处理有层级的值,比如说文件系统路径,根据路径符号划分,然后输出不同层的term。
Pattern
tokenizer使用正则表达式将文本进行分词,将匹配到的词,或者匹配到一段文本作为term。
(8.2)link
standard
tokenizer提供了基于语法的分词(基于Unicode Text Segmentation algorithm,见 Unicode Standard Annex #29),它能很好的对大多数语言进行分词。
xxxxxxxxxx
51POST _analyze
2{
3 "tokenizer": "standard",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的句子会生成下面的term:
xxxxxxxxxx
11[ The, 2, QUICK, Brown, Foxes, jumped, over, the, lazy, dog's, bone ]
standard
tokenizer接收下面的参数:
max_token_length:token的最大长度。如果一个token的长度超过了max_token_length
,会按照max_token_length
长度进行划分。默认值是255
。
在这个例子中,我们将standard
tokenizer的参数max_token_length
配置为5
(至少为了展示参数效果):
xxxxxxxxxx
241PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_analyzer": {
7 "tokenizer": "my_tokenizer"
8 }
9 },
10 "tokenizer": {
11 "my_tokenizer": {
12 "type": "standard",
13 "max_token_length": 5
14 }
15 }
16 }
17 }
18}
19
20POST my-index-000001/_analyze
21{
22 "analyzer": "my_analyzer",
23 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
24}
上面的例子会生成下面的term:
xxxxxxxxxx
11[ The, 2, QUICK, Brown, Foxes, jumpe, d, over, the, lazy, dog's, bone ]
(8.2)link
uax_url_email
tokenizer跟standard tokenizer很像,差别是它能识别URLs以及email,并且他们作为单个term。
xxxxxxxxxx
51POST _analyze
2{
3 "tokenizer": "uax_url_email",
4 "text": "Email me at john.smith@global-international.com"
5}
上面的例子会生成下面的term:
xxxxxxxxxx
11[ Email, me, at, john.smith@global-international.com ]
如果是standard
tokenizer会生成:
xxxxxxxxxx
11[ Email, me, at, john.smith, global, international.com ]
uax_url_email
tokenizer接受以下参数:
max_token_length
:token的最大长度。如果一个token的长度超过了max_token_length
,会按照max_token_length
长度进行划分。默认值是255
。
在这个例子中,我们将max_token_length
设置为5(仅演示目的):
xxxxxxxxxx
251PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_analyzer": {
7 "tokenizer": "my_tokenizer"
8 }
9 },
10 "tokenizer": {
11 "my_tokenizer": {
12 "type": "uax_url_email",
13 "max_token_length": 5
14 }
15 }
16 }
17 }
18}
19
20POST my-index-000001/_analyze
21{
22 "analyzer": "my_analyzer",
23 "text": "john.smith@global-international.com"
24}
25
上面的例子会生成下面的term:
xxxxxxxxxx
11[ john, smith, globa, l, inter, natio, nal.c, om ]
(8.2)link
whitespace
tokenizer会在根据空格将文本划分出term。
xxxxxxxxxx
51 POST _analyze
2{
3 "tokenizer": "whitespace",
4 "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
5}
上面的句子将生成下面的term:
xxxxxxxxxx
11[ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog's, bone. ]
whitespace
tokenizer接收下面的参数:
max_token_length
:token的最大长度。如果一个token的长度超过了max_token_length
,会按照max_token_length
长度进行划分。默认值是255
。
(8.2)link
移除撇号后所有的字符,包括撇号自身。
这个filter被包含在Elasticsearch内置的Turkish language analyzer。它使用的是Lucene中的ApostropheFilter,为Turkish语言构建的。
下面的analyze API请求中展示了apostrophe token filter是如何工作的。
xxxxxxxxxx
61GET /_analyze
2{
3 "tokenizer" : "standard",
4 "filter" : ["apostrophe"],
5 "text" : "Istanbul'a veya Istanbul'dan"
6}
这个filter生成下面的tokens:
xxxxxxxxxx
11[ Istanbul, veya, Istanbul ]
下面的create index API请求中使用这个filter来配置一个全新的custom analyzer。
xxxxxxxxxx
131PUT /apostrophe_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "standard_apostrophe": {
7 "tokenizer": "standard",
8 "filter": [ "apostrophe" ]
9 }
10 }
11 }
12 }
13}
ASCII folding token filter将不在Basic Latin Unicode block(基本拉丁字母Unicode块)(前127个ASCII字符)中的字母、数字和符号字符转换为它们的等价的ASCII(如果存在的话)。例如,这个过滤器会将à
转换为a
。
这个filter使用的是Lucene中的ASCIIFoldingFilter。
定义token filter集合,应用到满足脚本中条件的token上。
这个filter使用的是Lucene中的ConditionalTokenFilter。
对一个token stream进行排序和去重,让将这个stream中的token进行拼接,最后作为单个token输出。
例如,这个filter会对[ the, fox, was, very, very, quick ]
这个token stream进行如下更改:
根据字母排序:[ fox, quick, the, very, very, was ]
移除重复的very
这个token
将这个token stream拼接成单个token:[fox quick the very was ]
保留或移除指定的类型。比如你可以使用这个filter通过只保留<ALPHANUM>
将3 quick foxes
修改为quick foxes
。
指定一个word list,只保留在这个列表中的word。
指定token中字符数量的min跟max,小于min或者大于max的token会被移除。比如你可以使用这个filter过滤掉小于2个字符以及大于5个字符的token。
限制输出的token数量。这个filter通常用于根据token的数量来限制文档字段值的大小。
默认情况下,这个filter只保留token stream中的第一个token。比如,这个filter可以将[ one, two, three ]
这个token filter改为[ one ]
。
(8.2)link
将token text都转为小写的token。例如你可以使用lowercase
filter将THE Lazy DoG
转化为the lazy dog
。
除了默认的filter,lowercase
token filter还提供了访问Lucene中为Greek, Irish, and Turkish特别定制的lowercase filter。
下面的analyze API使用默认的Lowercase
filter 将THE Quick FoX JUMPs
转化为小写。
xxxxxxxxxx
61GET _analyze
2{
3 "tokenizer" : "standard",
4 "filter" : ["lowercase"],
5 "text" : "THE Quick FoX JUMPs"
6}
这个filter产生下面的token:
xxxxxxxxxx
11[ the, quick, fox, jumps ]
下面的create index API 配置了一个custom analyzer,它使用了lowercase
filter:
xxxxxxxxxx
131PUT lowercase_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "whitespace_lowercase": {
7 "tokenizer": "whitespace",
8 "filter": [ "lowercase" ]
9 }
10 }
11 }
12 }
13}
(optional)特定的一些语言对应的lowercase token filter。可选值为:
greek:使用Lucene中的GreekLowerCaseFilter
irish:使用Lucene中的IrishLowerCaseFilter
turkish:使用Lucene中的TurkishLowerCaseFilter
如果不指定该参数,则默认使用LowerCaseFilter。
若要自定义lowercase
filter,为新的自定义的token filter创建所需要的基本要素(basis)。
下面的例子中自定义了用于Greek语言的lowercase
filter:
xxxxxxxxxx
201PUT custom_lowercase_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "greek_lowercase_example": {
7 "type": "custom",
8 "tokenizer": "standard",
9 "filter": ["greek_lowercase"]
10 }
11 },
12 "filter": {
13 "greek_lowercase": {
14 "type": "lowercase",
15 "language": "greek"
16 }
17 }
18 }
19 }
20}
移除不满足脚本条件的token。这个filter只支持内联的Painless脚本。脚本在analysis predicate context中计算。
(8.2)link
将stream中的每一个token进行颠倒(反转)。比如,你可以使用这个filter将cat
改成tac
。
基于后缀查询,比如找到以-ion
为后缀的词或者根据扩展名查询的场景使用这个filter就非常有用。
这个filter使用了Lucene中的ReverseStringFilter。
下面的analyze API请求中使用了reverse
filter将quick for jumps
中的每一个token进行了颠倒:
xxxxxxxxxx
61GET _analyze
2{
3 "tokenizer" : "standard",
4 "filter" : ["reverse"],
5 "text" : "quick fox jumps"
6}
下面的filter生成了以下的token:
xxxxxxxxxx
11[ kciuq, xof, spmuj ]
下面的create index API请求使用了这个filter来配置一个全新的custom analyzer。
xxxxxxxxxx
131PUT reverse_example
2{
3 "settings" : {
4 "analysis" : {
5 "analyzer" : {
6 "whitespace_reverse" : {
7 "tokenizer" : "whitespace",
8 "filter" : ["reverse"]
9 }
10 }
11 }
12 }
13}
(8.2)link
从token stream中移除stop words。
如果没有自定义停用词,会默认移除下列的英文单词:
xxxxxxxxxx
11a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with
除了英语的停用词,stop
filter支持预先定义好的stop word lists for several languages。你也可以用数组或者文件来定义属于你的停用词。
stop
filter使用的是Lucene的StopFilter。
下面的analyzer API请求中使用stop
filter将移除a quick fox jumps over the lazy dog
中的a
和the
:
xxxxxxxxxx
61GET /_analyze
2{
3 "tokenizer": "standard",
4 "filter": [ "stop" ],
5 "text": "a quick fox jumps over the lazy dog"
6}
这个filter会生成下面的token:
xxxxxxxxxx
11[ quick, fox, jumps, over, lazy, dog ]
下面的create index API请求使用stop
filter来配置一个新的自定义的custom analyzer:
xxxxxxxxxx
131PUT /my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_analyzer": {
7 "tokenizer": "whitespace",
8 "filter": [ "stop" ]
9 }
10 }
11 }
12 }
13}
stopwords:(Optional, string or array of strings)。用于指定语言类型,例如_arabic_
或者_thai_
。默认值是_english_。
每一种语言对应一个预设的停用词列表,在Lucene中提供。见Stop words by language查看支持的语言以及其停用词。
也接受停用词数组。
若要使用空的停用词,使用_none_
。
stopwords_path:(Optional, string) 文件路径,该文件中包含待移除的停用词。
这个路径必须是绝对路径或者是config
下的相对路径。文件的字符编码必须是UTF-8。文件中每一个停用词必须用换行符分隔。
ignore_case:(Optional, Boolean) 如果为true
,停用词的匹配是大小写不敏感的。例如,如果该参数为true
,那么the
这个停用词会会移除the
,THE
,或者The
。默认值是false
。
remove_trailing:(Optional, Boolean) 如果为true
,并且token stream的最后一个token是停用词,则移除这个token。默认值是true
。
当使用completion suggester时,这个参数应该要设置为false
。这样能保证green a
这个query能匹配并且建议出green apple
并且仍然能够移除其他的停用词。
要自定义stop
filter,需要创建一个新的custom token filter所需要的基本要素。你可以使用可配置的参数来更改filter。
例如,下面的请求中创建了一个自定义的大小写不敏感的stop
filter。这个filter会移除_english_中的停用词。
xxxxxxxxxx
191PUT /my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "default": {
7 "tokenizer": "whitespace",
8 "filter": [ "my_custom_stop_words_filter" ]
9 }
10 },
11 "filter": {
12 "my_custom_stop_words_filter": {
13 "type": "stop",
14 "ignore_case": true
15 }
16 }
17 }
18 }
19}
你也可以指定自定义的停用词。例如下面的请求中创建了一个自定义的大小写敏感的stop
filter,它只移除and
,is
和the
:
xxxxxxxxxx
201PUT /my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "default": {
7 "tokenizer": "whitespace",
8 "filter": [ "my_custom_stop_words_filter" ]
9 }
10 },
11 "filter": {
12 "my_custom_stop_words_filter": {
13 "type": "stop",
14 "ignore_case": true,
15 "stopwords": [ "and", "is", "the" ]
16 }
17 }
18 }
19 }
20}
下列是参数stopwords支持的参数值。并且链接指向了Lucene中每一种语言对应的停用词:
Brazilian Portuguese stop words
synonym
token filter允许在解析过程(analysis process)中处理同义词。通过一个配置文件来配置同义词。例子:
xxxxxxxxxx
211PUT /test_index
2{
3 "settings": {
4 "index": {
5 "analysis": {
6 "analyzer": {
7 "synonym": {
8 "tokenizer": "whitespace",
9 "filter": [ "synonym" ]
10 }
11 },
12 "filter": {
13 "synonym": {
14 "type": "synonym",
15 "synonyms_path": "analysis/synonym.txt"
16 }
17 }
18 }
19 }
20 }
21}
上面的例子中配置了一个带有analysis/synonym.txt
路径的synonym
filter(相对于config
目录)。synonym
分词器然后使用这个filter。
(未完成)
(8.2)link
对超过字符数量限制的token进行截断。这个限制值默认是10
,可以通过参数length
参数配置。
例如,你可以使用这个filter将所有的token中的字符数量限制到3或者更少的值。jumping fox
修改为jum fox
。
这个filter使用的是Lucene中的TruncateTokenFilter。
下面的analyze API请求使用这个filter将he quinquennial extravaganza carried on
中超过10个字符的token进行了截断:
xxxxxxxxxx
61GET _analyze
2{
3 "tokenizer" : "whitespace",
4 "filter" : ["truncate"],
5 "text" : "the quinquennial extravaganza carried on"
6}
下面的filter生成以下的tokens:
xxxxxxxxxx
11[ the, quinquenni, extravagan, carried, on ]
下面的create index API请求使用这个filter用来配置一个全新的custom analyzer。
xxxxxxxxxx
131PUT custom_truncate_example
2{
3 "settings" : {
4 "analysis" : {
5 "analyzer" : {
6 "standard_truncate" : {
7 "tokenizer" : "standard",
8 "filter" : ["truncate"]
9 }
10 }
11 }
12 }
13}
length:(Optional, integer)每一个token包含的字符数量限制。超过这个值会被截断。默认为10
要自定义这个filter,需要创建一个新的custom token filter所需要的基本要素。你可以使用可配置的参数来更改filter。
例如,下面的请求创建了一个名为5_char_trunc
的truncate
filter,会将token中的字符限制到5个或者5个以下。
xxxxxxxxxx
191PUT 5_char_words_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "lowercase_5_char": {
7 "tokenizer": "lowercase",
8 "filter": [ "5_char_trunc" ]
9 }
10 },
11 "filter": {
12 "5_char_trunc": {
13 "type": "truncate",
14 "length": 5
15 }
16 }
17 }
18 }
19}
(8.2)link
将token转化为大写。比如你可以使用这个filter将the Lazy DoG
修改为THE LAZY DOG
。
这个filter使用的是Lucene中的UpperCaseFilter。
WARNING:某些语言中大写字符能映射多个小写字符。因此使用这个filter可能导致失去小写字符的信息。 若要避免这种损失但仍保持一致的字母大小写,可以改用lowercase filter。
下面的analyze API请求使用这个filter将the Quick FoX JUMPs
修改为大写。
xxxxxxxxxx
61GET _analyze
2{
3 "tokenizer" : "standard",
4 "filter" : ["uppercase"],
5 "text" : "the Quick FoX JUMPs"
6}
这个filter生成以下的tokens:
xxxxxxxxxx
11[ THE, QUICK, FOX, JUMPS ]
下面的create index API请求使用这个filter来配置一个全新的customer analyzer。
xxxxxxxxxx
131PUT uppercase_example
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "whitespace_uppercase": {
7 "tokenizer": "whitespace",
8 "filter": [ "uppercase" ]
9 }
10 }
11 }
12 }
13}
(8.2)link
将HTML元素从文本中移除并且将HTML实体替换成解码后的值(比如&
替换为&
)。
html_strip
filter使用的是Lucene中的HTMLStripCharFilter。
下面的analyzer API使用html_strip
filter 将文本<p>I'm so <b>happy</b>!</p>
修改为\nI'm so happy!\n
。
xxxxxxxxxx
81GET /_analyze
2{
3 "tokenizer": "keyword",
4 "char_filter": [
5 "html_strip"
6 ],
7 "text": "<p>I'm so <b>happy</b>!</p>"
8}
这个filter生成下面的文本:
xxxxxxxxxx
11[ \nI'm so happy!\n ]
下面的create index API请求使用html_strip
filter来配置一个全新的custom analyzer。
xxxxxxxxxx
151PUT /my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_analyzer": {
7 "tokenizer": "keyword",
8 "char_filter": [
9 "html_strip"
10 ]
11 }
12 }
13 }
14 }
15}
escaped_tags:(Optional, array of strings)没有尖括号(< >
)修饰的HTML元素列表。这个filter从文本中移除HTML元素的过程中会跳过这些。比如说配置了["p"]
则会跳过<p>
这个HTML元素
若要自定义html_strip
filter,则将其作为一个全新自定义的filter的组成部分,你可以使用可配置的参数来修改filter。
下面的create index API使用了一个名为my_custom_html_strip_char_filter
的自定义html_strip
filter,并且配置到custom analyzer 中。
my_custom_html_strip_char_filter
跳过了<b>
这个HTML 元素。
xxxxxxxxxx
231PUT my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_analyzer": {
7 "tokenizer": "keyword",
8 "char_filter": [
9 "my_custom_html_strip_char_filter"
10 ]
11 }
12 },
13 "char_filter": {
14 "my_custom_html_strip_char_filter": {
15 "type": "html_strip",
16 "escaped_tags": [
17 "b"
18 ]
19 }
20 }
21 }
22 }
23}
(8.2)link
mapping
character filter接收一个key,value的map。在character stream中遇到map中的key时,则将其替换为map中对应的value。
这是一种greedy的匹配。按照最长模板进行匹配。替换值(replacement)可以是一个空的string。
mapping
filter 使用了 Lucene中的MappingCharFilter。
下面的analyze API使用了mapping
filter 将Hindu-Arabic numerals (٠١٢٣٤٥٦٧٨٩) 转化为Arabic-Latin equivalents (0123456789)。将文本内容My license plate is ٢٥٠١٥
转化为了My license plate is 25015
。
xxxxxxxxxx
221GET /_analyze
2{
3 "tokenizer": "keyword",
4 "char_filter": [
5 {
6 "type": "mapping",
7 "mappings": [
8 "٠ => 0",
9 "١ => 1",
10 "٢ => 2",
11 "٣ => 3",
12 "٤ => 4",
13 "٥ => 5",
14 "٦ => 6",
15 "٧ => 7",
16 "٨ => 8",
17 "٩ => 9"
18 ]
19 }
20 ],
21 "text": "My license plate is ٢٥٠١٥"
22}
上面的filter会生成下面的文本:
xxxxxxxxxx
11[ My license plate is 25015 ]
mappings:(Required*, array of strings)数组类型的mappings,包含了key到value的映射。
必须指定这个参数或者mappings_path
中的一个。
mappings_path:(Required*, string)文件的路径,该文件中包含了key到value的映射。
该路径必须是绝对路径或者是相对于config
的相对路径。文件中的字符编码必须是UTF-8。文件中每一个key到value必须用换行符区分。
必须指定这个参数或者mappings
中的一个。
要自定义mappings
filter,创建一个自定义的character filter所需要的基本要素,你可以使用可配置的参数来修改它。
下面的create index API请求配置了一个新的custom analyzer,它使用了一个自定义的mappings
filter,my_mappings_char_filter
。
my_mappings_char_filter
filter将表情符号(emoticons):),:(
替换成相同意思的文本:
xxxxxxxxxx
241PUT /my-index-000001
2{
3 "settings": {
4 "analysis": {
5 "analyzer": {
6 "my_analyzer": {
7 "tokenizer": "standard",
8 "char_filter": [
9 "my_mappings_char_filter"
10 ]
11 }
12 },
13 "char_filter": {
14 "my_mappings_char_filter": {
15 "type": "mapping",
16 "mappings": [
17 ":) => _happy_",
18 ":( => _sad_"
19 ]
20 }
21 }
22 }
23 }
24}
上面的analyze API请求使用自定义的my_mappings_char_filter
将文本I'm delighted about it :(
中的:(
替换成了_sad_
。
xxxxxxxxxx
61GET /my-index-000001/_analyze
2{
3 "tokenizer": "keyword",
4 "char_filter": [ "my_mappings_char_filter" ],
5 "text": "I'm delighted about it :("
6}
这个filter会生成下面的文本:
xxxxxxxxxx
11[ I'm delighted about it _sad_ ]
(8.2)link
Normalizers跟分词器类似,区别在于它只输出单个token。因此,它没有tokenizer并且只接收一些有效的character filter和token filter。只有对单个字符处理的filter是允许在Normalizers中使用的。例如lowercase filter是允许的,而stemming filter则不允许,因为它需要将关键字作为一个整体来处理。下面的filter可以在Normalizers中使用:arabic_normalization
, asciifolding
, bengali_normalization
, cjk_width
, decimal_digit
, elision
, german_normalization
, hindi_normalization
, indic_normalization
, lowercase
, persian_normalization
, scandinavian_folding
, serbian_normalization
, sorani_normalization
, uppercase
。
Elasticsearch使用lowercase
用于内置的normalizers。其他形式的normalizers要求进行配置。
自定义的normalizers可以定义character filter和token filter的集合。
xxxxxxxxxx
311PUT index
2{
3 "settings": {
4 "analysis": {
5 "char_filter": {
6 "quote": {
7 "type": "mapping",
8 "mappings": [
9 "« => \"",
10 "» => \""
11 ]
12 }
13 },
14 "normalizer": {
15 "my_normalizer": {
16 "type": "custom",
17 "char_filter": ["quote"],
18 "filter": ["lowercase", "asciifolding"]
19 }
20 }
21 }
22 },
23 "mappings": {
24 "properties": {
25 "foo": {
26 "type": "keyword",
27 "normalizer": "my_normalizer"
28 }
29 }
30 }
31}
(8.2)link
这个主题介绍了在Elasticsearch 7.8引入的composable index template。关于之前版本的index template是如何工作的见legacy template documentation。
在创建一个索引时,index template告诉Elasticsearch如何进行创建。对于data streams,index template用于创建流的backing索引。Template先于索引的创建。手动或者通过索引一篇文档创建一个索引后,template setting会作为创建索引的一个基本要素( basis)。
一共有索引模板index template和组件模版component template两种类型的模板。component template构建配置了mapping,settings,alias这几块并可以复用。你可以使用一个或多个component template来构造index template,但不能直接应用到(apply)索引集合(a set of indices)。index templates 可以包含一个component template集合,直接指定settings,mappings和aliases。
应用(apply)index template的几个条件:
composable template比legacy template优先级高。如果composable template没有匹配到索引,legacy template可能会匹配到并且应用。
如果使用显示的设置(explicit settings)来创建索引并且同时匹配到了一个index template。那么在create index请求中的settings比index template和component template中的优先级高。
如果一个新的data stream或者索引匹配到了多个index template,则使用优先级最高的index template
Elasticsearch有内置的index template,每一个的优先级都是100
,如下:
logs--
metrics--
synthetics--
Elastic Agent使用这些模板来创建data streams。Fleet integration创建的index template使用类似的overlapping index patterns并且优先级是200
。
如果你使用Fleet或者Elastic Agent, 那么你的index template的优先级要小于100
来防止覆盖掉这些模板。若要避免意外的应用了这些内置模板,采取下面一个或者多个方法:
若要关闭内置的index template和component template,使用cluster update settings API将stack.templates.enabled设置为false
。
使用一个不会被 覆盖的index pattern
给overlapping pattern的模板一个priority
大于200
的值。例如如果你不想要使用Fleet或者Elastic Agent并且想要为logs-*
这个index pattern创建一个模板,那么将你的模板的priority的值设置为500
。这能保证你的模板能被应用于logs-*
而不是使用内置的模板。
使用index template 和put component templateAPIs来创建和更新index templates。你也可以在Kibana用意Stack Management来manage index templates。
下面的请求创建了两个component templates。
xxxxxxxxxx
281PUT _component_template/component_template1
2{
3 "template": {
4 "mappings": {
5 "properties": {
6 "@timestamp": {
7 "type": "date"
8 }
9 }
10 }
11 }
12}
13
14PUT _component_template/runtime_component_template
15{
16 "template": {
17 "mappings": {
18 "runtime": {
19 "day_of_week": {
20 "type": "keyword",
21 "script": {
22 "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
23 }
24 }
25 }
26 }
27 }
28}
第18行,这个component template添加了一个名为day_of_week
的runtime field的字段,用于一个新的索引匹配到模板时。
下面的请求创建了一个由上面的component template组成的index template。
xxxxxxxxxx
321PUT _index_template/template_1
2{
3 "index_patterns": ["te*", "bar*"],
4 "template": {
5 "settings": {
6 "number_of_shards": 1
7 },
8 "mappings": {
9 "_source": {
10 "enabled": true
11 },
12 "properties": {
13 "host_name": {
14 "type": "keyword"
15 },
16 "created_at": {
17 "type": "date",
18 "format": "EEE MMM dd HH:mm:ss Z yyyy"
19 }
20 }
21 },
22 "aliases": {
23 "mydata": { }
24 }
25 },
26 "priority": 500,
27 "composed_of": ["component_template1", "runtime_component_template"],
28 "version": 3,
29 "_meta": {
30 "description": "my custom"
31 }
32}
(8.2)link
模板不仅仅由多个component template进行组合,也可以由index template自身。有两个simulation APIs用来查看最终的索引设置(index settings)。(当你指定了一个新的模板后,由于现有的模板可能跟这个新的模板有相同的index patter,由于优先级问题导致新的模板无法应用到新创建的索引,那么可以通过simulate来查看某个索引对应的index template,或者查看某个模板设置,见Simulate index template API和 Simulate index API)
若要模拟(simulate)出将要应用到某个指定的索引的索引设置,可以通过以下请求获取。
xxxxxxxxxx
11POST /_index_template/_simulate_index/my-index-000001
若要模拟(simulate)出某个模板的模板设置,可以通过以下请求获取。
xxxxxxxxxx
11POST /_index_template/_simulate/template_1
你也可以在simulate 请求body中指定一个模板定义(新的模板设置)。这可以让你在添加一个新的模板前先校验下settings。(校验的目的之一是查看新的模板会不会被现有的模板覆盖或者覆盖现有的模板)
xxxxxxxxxx
351PUT /_component_template/ct1
2{
3 "template": {
4 "settings": {
5 "index.number_of_shards": 2
6 }
7 }
8}
9
10PUT /_component_template/ct2
11{
12 "template": {
13 "settings": {
14 "index.number_of_replicas": 0
15 },
16 "mappings": {
17 "properties": {
18 "@timestamp": {
19 "type": "date"
20 }
21 }
22 }
23 }
24}
25
26POST /_index_template/_simulate
27{
28 "index_patterns": ["my*"],
29 "template": {
30 "settings" : {
31 "index.number_of_shards" : 3
32 }
33 },
34 "composed_of": ["ct1", "ct2"]
35}
下面的响应中展示了将会应用到匹配到的索引配置中的settings,mappings,以及aliases。任何被覆盖的模板中的配置会被simulate 请求中指定模板定义或者优先级更高的现有的模板取代。
xxxxxxxxxx
331{
2 "template" : {
3 "settings" : {
4 "index" : {
5 "number_of_shards" : "3",
6 "number_of_replicas" : "0",
7 "routing" : {
8 "allocation" : {
9 "include" : {
10 "_tier_preference" : "data_content"
11 }
12 }
13 }
14 }
15 },
16 "mappings" : {
17 "properties" : {
18 "@timestamp" : {
19 "type" : "date"
20 }
21 }
22 },
23 "aliases" : { }
24 },
25 "overlapping" : [
26 {
27 "name" : "template_1",
28 "index_patterns" : [
29 "my*"
30 ]
31 }
32 ]
33}
第5行,分片的数量来自simulate请求中的值,替代了index templatect1
中的值
第19行,@timestap
字段的定义来自index templatect2
第 27行,被覆盖的模板信息,它们有较低的优先级
(8.2)link
数据流(data stream)允许你跨多个索引append-only方式存储时间序列数据,同时为你提供单个命名资源用于请求。Data stream非常适合用于logs、events、metrics以及其他源源不断生成的数据。
你可以直接向一个数据流提交索引和查询请求。数据流可以自动的将请求路由到存储流数据(stream's data)的backing indices中。你可以使用index lifecycle management(ILM)实现backing indeices的自动化管理。例如,你可以使用ILM自动将较老的backing indices移到成本更低的硬件上以及删除不再需要的数据。ILM可以帮助你降低随着不断增长的数据带来的开销和成本。
一个数据流由一个或者多个hidden、自动生成的backing indices组成。
一个数据流要求有一个匹配的index template(index template)。这个模板包含了用于配置流的backing indices的mappings和settings。
每一个索引到数据流中的文档必须包含一个@timestamp
字段,映射为date或者date_nanos字段类型。如果index template没有指定@timestamp
字段。Elasticsearch会根据默认选项映射一个date
类型的@timestmap
字段。
同一个index template可以用于多个数据流。你不能删除一个正在被某个数据流使用的index template。
当你向一个数据流提交一个读请求时,数据流会将这个请求路由到所有的backing indice上。
最近创建的backing index是数据流的write index。数据流只向这个索引中增加文档。
你不能往其他backing indices中添加新的文档,甚至不能直接向这些索引中发送请求。
你同样不能在write index上执行下面的操作,免得阻碍(hinder)索引写入:
rollover操作会创建一个新的backing index,这个索引成为数据流中新的write index。
我们建议使用ILM,它能在write index达到一个指定的寿命(age)或者大小后自动的滚动(roll over)数据流。如果有必要的话,你也可以manually roll over一个数据流。
每一个数据流会追踪它的generation:一个六位数字,0作为填充值的整数,用来累计(cumulative)数据流滚动次数,从000001
开始。
当创建一个backing index后,这个索引按下面的方式约定命名
xxxxxxxxxx
11.ds-<data-stream>-<yyyy.MM.dd>-<generation>
<yyyy.MM.dd>
是backing index的创建时间。generation数值越大的backing index包含更多最近的数据。例如,web-server-logs
这个数据流的generation的值为34
。这个流在2099年3月7日创建,那么就命名为.ds-web-server-logs-2099.03.07-000034
。
有些shink或者restore操作会更改backing index的名称。这些名称的变更不会数据流中移除backing index。
数据流被设计为用于现有数据很少或者从不更新的场景。你不能向数据流中现有的文档发送更新或者删除的请求。与之替换使用update by query和delete by query APIs。
如果有必要的话,你可以使用update or delete documents 向文档的backing index中提交请求。
TIP:如果你要频繁的更新或删除时序数据,使用write index的index alias来替换数据流。见Manage time series data without data streams。
(8.2)link
按照下面几个步骤来设置一个数据流
你也可以convert an index alias to a data stream。
IMPORTANT:如果你使用Fleet或者Elastic Agent,可以跳过这个教程。Fleet和Elastic Agent会为你设置数据流。见Fleet的data streams文档。
尽管是可选的,我们建议你使用ILM来实现数据流的backing indice的管理自动化。ILM需要一个索引生命周期策略。
在Kibana中创建一个索引生命周期策略,打开主菜单然后选择Stack Management > Index Lifecycle Policies,点击Create policy。
你也可以通过create lifecycle policy API创建。
xxxxxxxxxx
471PUT _ilm/policy/my-lifecycle-policy
2{
3 "policy": {
4 "phases": {
5 "hot": {
6 "actions": {
7 "rollover": {
8 "max_primary_shard_size": "50gb"
9 }
10 }
11 },
12 "warm": {
13 "min_age": "30d",
14 "actions": {
15 "shrink": {
16 "number_of_shards": 1
17 },
18 "forcemerge": {
19 "max_num_segments": 1
20 }
21 }
22 },
23 "cold": {
24 "min_age": "60d",
25 "actions": {
26 "searchable_snapshot": {
27 "snapshot_repository": "found-snapshots"
28 }
29 }
30 },
31 "frozen": {
32 "min_age": "90d",
33 "actions": {
34 "searchable_snapshot": {
35 "snapshot_repository": "found-snapshots"
36 }
37 }
38 },
39 "delete": {
40 "min_age": "735d",
41 "actions": {
42 "delete": {}
43 }
44 }
45 }
46 }
47}
data stream需要匹配一个索引模板(Index template)。在大多数情况下,你会使用一个或者多个组件模版component templates来组合成一个索引模板。你通常会为mappings跟Index settingss使用各自的component templates。这可以让你在多个索引模板中复用component templates。
当你创建组件模版时,包括:
为@timestamp
字段定义mapping类型,date或者date_nanos。如果你不指定,Elasticsearch会默认将这个字段作为date
类型的字段
索引设置index.lifecycle.name
中给定生命周期策略的名称
TIP:使用Elastic Common Schema (ECS)来映射你的字段类型。ECS字段默认跟一些Elastic Stack features 集成 如果你不确定如何映射你的字段类型,你可以在查询期间使用runtime fields从非结构化的内容unstructured content中提取字段。例如你可以将log message索引到一个
wildcard
字段,随后在查询期间从这个字段中提取IP地址和其他数据。
若要创建在kibana中创建一个组件模版,打开主菜单然后跳转到Stack Management > Index Management。在Index Templates视图中,点击Create component template。
你也可以使用create component template API创建组件模版。
xxxxxxxxxx
351# Creates a component template for mappings
2PUT _component_template/my-mappings
3{
4 "template": {
5 "mappings": {
6 "properties": {
7 "@timestamp": {
8 "type": "date",
9 "format": "date_optional_time||epoch_millis"
10 },
11 "message": {
12 "type": "wildcard"
13 }
14 }
15 }
16 },
17 "_meta": {
18 "description": "Mappings for @timestamp and message fields",
19 "my-custom-meta-field": "More arbitrary metadata"
20 }
21}
22
23# Creates a component template for index settings
24PUT _component_template/my-settings
25{
26 "template": {
27 "settings": {
28 "index.lifecycle.name": "my-lifecycle-policy"
29 }
30 },
31 "_meta": {
32 "description": "Settings for ILM",
33 "my-custom-meta-field": "More arbitrary metadata"
34 }
35}
若要使用你的组件模版来创建一个索引模板,需要指定:
一个或者多个index_patterns
来匹配data stream的名称。我们建议使用data stream naming scheme
这个模版是data stream可以使用的(用data_stream
字段表示该模版可以用于数据流)
包含你mapping跟索引设置Index settings的组件模版
优先级大于200
,以避免与内置模板发生冲突。见Avoid index pattern collisions)
若要在Kibana中创建一个index template,那么打开主菜单然后跳转到Stack Management > Index Management。在Index Templates视图中,点击Create template。
你也可以使用create index template API。包含data_stream
object使得模版可以用于data streams。
xxxxxxxxxx
111PUT _index_template/my-index-template
2{
3 "index_patterns": ["my-data-stream*"],
4 "data_stream": { },
5 "composed_of": [ "my-mappings", "my-settings" ],
6 "priority": 500,
7 "_meta": {
8 "description": "Template for my time series data",
9 "my-custom-meta-field": "More arbitrary metadata"
10 }
11}
Indxing requests将文档添加到data stream中。这些请求必须使用create
类型中的op_type
。文档中必须包含@timestamp
字段。
若要自动创建你的data stream,提交一个索引请求并指定目标data stream的名称。名称必须能匹配到索引模板中的index_patterns
。
xxxxxxxxxx
111PUT my-data-stream/_bulk
2{ "create":{ } }
3{ "@timestamp": "2099-05-06T16:21:15.000Z", "message": "192.0.2.42 - - [06/May/2099:16:21:15 +0000] \"GET /images/bg.jpg HTTP/1.0\" 200 24736" }
4{ "create":{ } }
5{ "@timestamp": "2099-05-06T16:25:42.000Z", "message": "192.0.2.255 - - [06/May/2099:16:25:42 +0000] \"GET /favicon.ico HTTP/1.0\" 200 3638" }
6
7POST my-data-stream/_doc
8{
9 "@timestamp": "2099-05-06T16:21:15.000Z",
10 "message": "192.0.2.42 - - [06/May/2099:16:21:15 +0000] \"GET /images/bg.jpg HTTP/1.0\" 200 24736"
11}
你也可以使用create data stream API手动创建时stream。这个stream的名称仍然必须能匹配到索引模板中的index_patterns
。
xxxxxxxxxx
11PUT _data_stream/my-data-stream
使用index privilege来控制对data stream的访问。保证data stream跟它的backing indices有相同的权限。
见Data stream privileges中的例子。
在Elasticsearch7.9之前,你通常使用index alias with a write index来管理时序数据。Data stream替代了这个功能,更低的维护成本以及自动跟data tiers集成。
若要将带有writer Index的索引别名转化为一个data stream,并且使用相同的名称,请使用migrate to data stream API。在传化期间,别名的索引变成了stream中的隐藏的backing indices。索引别名中的writer Index变成了stream的writer Index。这个stream依然要求一个能匹配的索引模板,并且这个索引模板能适用于data stream(模版中要有data_stream
字段)。
xxxxxxxxxx
11POST _data_stream/_migrate/my-time-series-data
若要在Kibana中获取data stream的信息,打开主菜单然后跳转到Stack Management > Index Management,在Data Streams视图中,点击data stream的名称。
你可以使用get data stream API。
xxxxxxxxxx
11GET _data_stream/my-data-stream
若要在kibana中删除一个data stream和它的backing indices。打开主菜单然后跳转到Stack Management > Index Management,在Data Streams视图中,点击trash icon。当你有这个data stream的delete_index
的security privilege 才能看到这个图标。
你也可以使用delete data stream API实现
xxxxxxxxxx
11DELETE _data_stream/my-data-stream
(8.2)link
在set up a data stream之后,你可以:
若要添加一篇独立的文档,使用index API。支持Ingest pipeline。
xxxxxxxxxx
81POST /my-data-stream/_doc/
2{
3 "@timestamp": "2099-03-08T11:06:07.000Z",
4 "user": {
5 "id": "8a4f500d"
6 },
7 "message": "Login successful"
8}
你不能使用index API中的PUT/<target>/_doc/<_id>
这种请求格式将一篇文档添加到data stream中。若要指定文档ID,使用PUT/<target>/_create/<_id>
这种格式刷。只支持create
类型中的op_type。
若要一次请求添加多篇文档,使用bulk API。只支持create
选项。
xxxxxxxxxx
71PUT /my-data-stream/_bulk?refresh
2{"create":{ }}
3{ "@timestamp": "2099-03-08T11:04:05.000Z", "user": { "id": "vlb44hny" }, "message": "Login attempt failed" }
4{"create":{ }}
5{ "@timestamp": "2099-03-08T11:06:07.000Z", "user": { "id": "8a4f500d" }, "message": "Login successful" }
6{"create":{ }}
7{ "@timestamp": "2099-03-09T11:07:08.000Z", "user": { "id": "l7gk7f82" }, "message": "Logout successful" }
data stream支持以下查询:
使用data stream stats API获取一个或多个data stream的统计数据。
xxxxxxxxxx
11GET /_data_stream/my-data-stream/_stats?human=true
使用rollover API手动roll over一个data stream:
xxxxxxxxxx
11POST /my-data-stream/_rollover/
你不能搜索一个closed backing index,即使是搜索对应的data stream也不行。你不能update或delete关闭的索引中的文档。
若要重新打开一个关闭的backing index,向对应的索引直接提交一个open index API request:
xxxxxxxxxx
11POST /.ds-my-data-stream-2099.03.07-000001/_open/
若要重新打开data stream中所有已关闭的索引,向这个stream提交一个open index API 请求:
xxxxxxxxxx
11POST /my-data-stream/_open/
使用reindex API从一个现有的索引、别名。或者data stream中拷贝文档到一个data stream中。因为data stream是append-only,因此reindex到一个data stream时必须使用create
类型为op_type
的值。reindex不能更新现有文档到一个data stream中。
xxxxxxxxxx
101POST /_reindex
2{
3 "source": {
4 "index": "archive"
5 },
6 "dest": {
7 "index": "my-data-stream",
8 "op_type": "create"
9 }
10}
使用update by query API将满足Query的文档更新到一个data stream中。
xxxxxxxxxx
141POST /my-data-stream/_update_by_query
2{
3 "query": {
4 "match": {
5 "user.id": "l7gk7f82"
6 }
7 },
8 "script": {
9 "source": "ctx._source.user.id = params.new_id",
10 "params": {
11 "new_id": "XgdX0NoX"
12 }
13 }
14}
使用delete by query API将满足Query的文档从data stream中删除。
如果有需要的话,你可以通过往包含文档的backing index中发送请求来更新/删除data stream中的文档。你需要:
包含文档的backing index的名称
如果是更新文档,需要sequence number and primary term
若要获取这些信息,使用一个search request:
xxxxxxxxxx
91GET /my-data-stream/_search
2{
3 "seq_no_primary_term": true,
4 "query": {
5 "match": {
6 "user.id": "yWIumJd7"
7 }
8 }
9}
响应:
xxxxxxxxxx
331{
2 "took": 20,
3 "timed_out": false,
4 "_shards": {
5 "total": 3,
6 "successful": 3,
7 "skipped": 0,
8 "failed": 0
9 },
10 "hits": {
11 "total": {
12 "value": 1,
13 "relation": "eq"
14 },
15 "max_score": 0.2876821,
16 "hits": [
17 {
18 "_index": ".ds-my-data-stream-2099.03.08-000003",
19 "_id": "bfspvnIBr7VVZlfp2lqX",
20 "_seq_no": 0,
21 "_primary_term": 1,
22 "_score": 0.2876821,
23 "_source": {
24 "@timestamp": "2099-03-08T11:06:07.000Z",
25 "user": {
26 "id": "yWIumJd7"
27 },
28 "message": "Login successful"
29 }
30 }
31 ]
32 }
33}
第18行,包含文档的backing index 第19行,文档的Document ID 第20行,文档目前的序号 第21行,文档的primary term
若要更新文档,使用index API并且携带合法的if_seq_no
和if_primary_term
参数 :
xxxxxxxxxx
81PUT /.ds-my-data-stream-2099-03-08-000003/_doc/bfspvnIBr7VVZlfp2lqX?if_seq_no=0&if_primary_term=1
2{
3 "@timestamp": "2099-03-08T11:06:07.000Z",
4 "user": {
5 "id": "8a4f500d"
6 },
7 "message": "Login successful"
8}
若要删除文档,使用delete API:
xxxxxxxxxx
11DELETE /.ds-my-data-stream-2099.03.08-000003/_doc/bfspvnIBr7VVZlfp2lqX
如要使用一个请求来删除/更新多篇文档,使用buli API中的delete
、index
以及update
动作。对于index
,需要包含合法的if_seq_no and if_primary_term 参数。
xxxxxxxxxx
31PUT /_bulk?refresh
2{ "index": { "_index": ".ds-my-data-stream-2099.03.08-000003", "_id": "bfspvnIBr7VVZlfp2lqX", "if_seq_no": 0, "if_primary_term": 1 } }
3{ "@timestamp": "2099-03-08T11:06:07.000Z", "user": { "id": "8a4f500d" }, "message": "Login successful" }
(8.2)link
每一个data steam有一个matching index template。模板中的mappings和index settings会应用到这个stream创建出来的新的backing index。也同时包括stream自动创建出来的的第一个backing index。
在创建一个data stream之前,我们建议你考虑好模板中的mappings和settings。
如果你随后需要更改某个data stream的mappings或settings,你有以下的选项:
TIP:如果你的更改中包含对现有的field mappings或static index settings做更改,通常需要reindex来应用到data stream的backing index 中。如果你已经执行了reindex,你可以使用相同的过程来添加新的field mappings以及更改dynamic index settings。见Use reindex to change mappings or settings。
若要向data stream中为一个新的字段添加一个mapping,按照下面的步骤执行:
更新data steam使用的index template。保证新的field mapping添加到这个stream以后创建的backing index中。
例如,名为my-data-stream
的data stream现在使用的是名为my-data-steram-template
的模板。
下面的create or update index template请求向模板中为新的字段message
添加了一个mapping。
xxxxxxxxxx
151PUT /_index_template/my-data-stream-template
2{
3 "index_patterns": [ "my-data-stream*" ],
4 "data_stream": { },
5 "priority": 500,
6 "template": {
7 "mappings": {
8 "properties": {
9 "message": {
10 "type": "text"
11 }
12 }
13 }
14 }
15}
第7行,为新的字段message
添加一个mapping。
使用update mapping API添加新的字段的mapping到data stream中。默认情况下会添加到stream中现有的backing index中,包括write index。
下面的更新mapping API请求将新的字段message
添加到名为my-data-stream
的data stream中。
xxxxxxxxxx
81PUT /my-data-stream/_mapping
2{
3 "properties": {
4 "message": {
5 "type": "text"
6 }
7 }
8}
若只向stream中的write index添加mapping,在更新mapping API中添加请求参数write_index_only
为true
。
下面的更新mapping请求中将新的字段message
的mapping只添加到write index中。没有添加到这个stream的其他backing index中。
xxxxxxxxxx
81PUT /my-data-stream/_mapping?write_index_only=true
2{
3 "properties": {
4 "message": {
5 "type": "text"
6 }
7 }
8}
每一个mapping parameter的文档中都会告知是否可以使用update mapping API更新现有的字段。若要为现有的字段更新这些参数,按照下面的步骤执行:
更新data steam使用的index template。保证新的field mapping添加到这个stream以后创建的backing index中。
例如,名为my-data-stream
的data stream现在使用的是名为my-data-steram-template
的模板。
下面的create or update index template请求为host.ip
修改了参数。字段的ignore_malformed的值设置为true
。
xxxxxxxxxx
201PUT /_index_template/my-data-stream-template
2{
3 "index_patterns": [ "my-data-stream*" ],
4 "data_stream": { },
5 "priority": 500,
6 "template": {
7 "mappings": {
8 "properties": {
9 "host": {
10 "properties": {
11 "ip": {
12 "type": "ip",
13 "ignore_malformed": true
14 }
15 }
16 }
17 }
18 }
19 }
20}
第13行,修改host.ip
的ignore_malformed
的值为true
。
使用update mapping API添加新的字段的mapping到data stream中。默认情况下会添加到stream中现有的backing index中,包括write index。
下面的update mapping API请求目标是my-data-stream
。该请求将host.ip
字段的ignore_malformed
的值设置为true
。
xxxxxxxxxx
131PUT /my-data-stream/_mapping
2{
3 "properties": {
4 "host": {
5 "properties": {
6 "ip": {
7 "type": "ip",
8 "ignore_malformed": true
9 }
10 }
11 }
12 }
13}
若只向stream中的write index添加mapping,在更新mapping API中添加请求参数write_index_only
为true
。
下面的更新mapping请求只对my-data-stream
的write index中的host.ip
字段变更。这个更改不会应用到stream的其他backing index。
xxxxxxxxxx
131PUT /my-data-stream/_mapping?write_index_only=true
2{
3 "properties": {
4 "host": {
5 "properties": {
6 "ip": {
7 "type": "ip",
8 "ignore_malformed": true
9 }
10 }
11 }
12 }
13}
除了支持更新mapping参数,我们不建议更改现有字段的mapping或者类型,即使是在data stream匹配到的index template或者它的backing index中。更改现有字段的mapping会invalidate已经索引的数据。
如果你需要修改现有字段的mapping,那么创建一个新的data stream然后reindex。见Use reindex to change mappings or settings。
若要为data stream更改dynamic index template,按照下面的步骤执行:
更新data steam使用的index template。保证setting能应用到到这个stream以后创建的backing index中。
例如,名为my-data-stream
的data stream现在使用的是名为my-data-steram-template
的模板。
下面的create or update index template请求将模板的index.refresh_interval
设置为30s
(30秒)。
xxxxxxxxxx
111PUT /_index_template/my-data-stream-template
2{
3 "index_patterns": [ "my-data-stream*" ],
4 "data_stream": { },
5 "priority": 500,
6 "template": {
7 "settings": {
8 "index.refresh_interval": "30s"
9 }
10 }
11}
第8行,将index.refresh_interval
设置为30s
。
使用update mapping API更新data stream的index setting。默认情况下会添加到stream中现有的backing index中,包括write index。
下面的更新index settings API请求为my-data-stream
更新设置index.refresh_interval
。
xxxxxxxxxx
61PUT /my-data-stream/_settings
2{
3 "index": {
4 "refresh_interval": "30s"
5 }
6}
IMPORTANT:若要修改
index.lifecycle.name
,首先使用remove policy API移除现有的ILM策略。见Switch lifecycle policies。
只有在backing index创建时才能设置Static index settings。你不能使用update index settings API更新static index settings。
若要将新的static setting应用到以后的backing index上,那么更新data steam使用的index template。更新之后会自动的应用到新创建的backing index上。
例如,my-data-stream-template
是一个my-data-stream
使用的现有的index template。
下面的create or update index template API请求向模板中添加了新的sort.field
和sort.order
两个设置。
xxxxxxxxxx
121PUT /_index_template/my-data-stream-template
2{
3 "index_patterns": [ "my-data-stream*" ],
4 "data_stream": { },
5 "priority": 500,
6 "template": {
7 "settings": {
8 "sort.field": [ "@timestamp"],
9 "sort.order": [ "desc"]
10 }
11 }
12}
第8行, 增加index setting sort.field
第9行,增加index setting sort.order
如果需要的话,你可以roll over the data stream来马上将设置应用到data stream的write index上。应用到在rollover之后新添加到stream的数据上。然而不会影响data stream中现有的索引和现有的数据上。
若要应用static settings changes到现有的backing index上,你必须创建一个新的data stream然后reindex。见Use reindex to change mappings or settings。
你可以使用reindex修改data stream的mapping或settings。这么做通常是因为要对现有的字段的类型做变更或者要为backing index更新static index settings。
若要reindex某个data stream。首先创建或者更新index template,这样才能包含你想要的修改后的mapping或settings。你随后就可以将现有的数据reindex到一个新的匹配到模板的data stream中。模板中变更的mapping和setting都会应用到添加到新的data stream中的每一篇文档和backing index。
按照下面的步骤执行:
为新的data stream选择一个名称或者index pattern。这个新的data stream会包含你现有的stream中的数据。
你可以使用 resolve index API检查下 名称或者pattern会不会匹配到现有的索引,aliases或者data stream。如果有,你应该考虑使用其他的名称或者pattern。
下面的resolve index API请求会检查现有的索引,aliases或者data stream有没有以new-data-stream
开头的。如果没有,index pattern new-data-stream*
可以用于创建新的data stream。
xxxxxxxxxx
11GET /_resolve/index/new-data-stream*
API返回了下面的响应,指出现有的目标(existing target)中不匹配这个pattern。
xxxxxxxxxx
51{
2 "indices": [ ],
3 "aliases": [ ],
4 "data_streams": [ ]
5}
创建或者更新一个index template。这个模板引应该包含你想要应用到新的data stream中的backing index的mappings和settings。
这个index template必须满足requirements for a data stream template。他应该在index_patterns
属性中同样包含之前选择的名称或者index patter。
TIP:如果你只增加或者修改小部分东西,我们建议你通过拷贝现有的模板,根据你的需要进行修改,然后用于创建一个新的模板。
例如,名为my-data-stream
的data stream现在使用的是名为my-data-steram-template
的模板。
下面的create or update index template API 请求创建了一个新的index template,名为new-data-stream-template
,new-data-stream-template
将my-data-stream-template
作为基准,作出了下面的改动:
index_patterns
中匹配以new-data-stream
开头 的索引或data stream
@timestamp
字段使用date_nanos
字段而不是使用date
模板中包含了sort.field
和sort.order
的index settings,原来的my-data-stream-template
没有这两个setting
xxxxxxxxxx
191PUT /_index_template/new-data-stream-template
2{
3 "index_patterns": [ "new-data-stream*" ],
4 "data_stream": { },
5 "priority": 500,
6 "template": {
7 "mappings": {
8 "properties": {
9 "@timestamp": {
10 "type": "date_nanos"
11 }
12 }
13 },
14 "settings": {
15 "sort.field": [ "@timestamp"],
16 "sort.order": [ "desc"]
17 }
18 }
19}
第10行,@timestamp
字段的类型修改为date_nanos
第15行,增加index setting sort.field
第16行,增加index setting sort.order
使用create data stream API 手动创建新的data stream。data stream的名称必须匹配定义在index template中的index_patterns
属性。
我们不建议indexing new data to create this data stream。因为随后你将从现有的data stream中的旧数据reindex到这个data stream中。这会导致一个或者多个backing index会同时包含新旧数据。
IMPORTANT:Mixing new and old data in a data stream 尽管新旧数据的混合是安全,但它会影响data retention。如果你要删除旧的索引,你可能会意外的删除一个同时包含新旧数据的索引。为了防止过早的(premature)数据丢失,你需要保留这样的backing index直到里面最新的数据可以被删除。
下面的create data stream API请求的目标是new-data-stream
,它会匹配到index template中的new-data-stream-template
。因为现有的索引或data stream没有使用这个名称,所以这个请求会创建名为new-data-stream
的data stream。
xxxxxxxxxx
11PUT /_data_stream/new-data-stream
如果你不想在新的data stream中混合新旧数据,那么先暂停新文档的索引。尽管新旧数据的混合是安全,但它会影响data retention。如果你要删除旧的索引,你可能会意外的删除一个同时包含新旧数据的索引。为了防止过早的(premature)数据丢失,你需要保留这样的backing index直到里面最新的数据可以被删除。
如果你使用ILM来automate rollover,那么降低ILM的poll interval。这能保证在下一次rollover检查之前,当前的write index不会增长的过大。默认情况下,ILM每10分钟检查下rollover的条件。
下面cluster update settings API 请求将indices.lifecycle.poll_interval
的值降低为1m
。
xxxxxxxxxx
61PUT /_cluster/settings
2{
3 "persistent": {
4 "indices.lifecycle.poll_interval": "1m"
5 }
6}
使用op_type
中的create
将你的数据reindex到新的data stream中
如果你想要按照原来索引的顺序来对数据进行划分,你可以允许多个reindex请求。这些reindex请求可以使用独立的backing index作为输入源。你可以使用get data stream API查看backing index列表。
例如,你计划将my-data-stream
reindex到new-data-steram
中,然而你想要为my-data-steram
中的每一个backing index分别提交一个reindex请求,从最旧的backing index开始。这样就能保留原来索引的顺序。
下面的get data stream API请求查看了my-data-stream
的信息,包含了一个backing index的列表。
xxxxxxxxxx
11GET /_data_stream/my-data-stream
响应中的indices
包含了stream中当前的backing index。数组中第一个元素就是最旧的backing index的信息。
xxxxxxxxxx
271{
2 "data_streams": [
3 {
4 "name": "my-data-stream",
5 "timestamp_field": {
6 "name": "@timestamp"
7 },
8 "indices": [
9 {
10 "index_name": ".ds-my-data-stream-2099.03.07-000001",
11 "index_uuid": "Gpdiyq8sRuK9WuthvAdFbw"
12 },
13 {
14 "index_name": ".ds-my-data-stream-2099.03.08-000002",
15 "index_uuid": "_eEfRrFHS9OyhqWntkgHAQ"
16 }
17 ],
18 "generation": 2,
19 "status": "GREEN",
20 "template": "my-data-stream-template",
21 "hidden": false,
22 "system": false,
23 "allow_custom_routing": false,
24 "replicated": false
25 }
26 ]
27}
第10行,my-data-steram
的indices
数组中第一个条目中包含了这个stream中最旧的名为.ds-my-data-stream-2099.03.07-000001
的backing index信息。
下面的reindex API请求将.ds-my-data-stream-2099.03.07-000001
中的文档拷贝到new-data-steam
中。这个请求的op_type
是create
。
xxxxxxxxxx
101POST /_reindex
2{
3 "source": {
4 "index": ".ds-my-data-stream-2099.03.07-000001"
5 },
6 "dest": {
7 "index": "new-data-stream",
8 "op_type": "create"
9 }
10}
你也可以在每次的请求中使用一个query对一个文档子集进行reindex。
下面的reindex API请求将my-data-steram
中的文档拷贝到new-data-stream
中,请求中使用了range query,只reindex了上周的文档。注意的是这个请求的op_type
是craete
。
xxxxxxxxxx
181POST /_reindex
2{
3 "source": {
4 "index": "my-data-stream",
5 "query": {
6 "range": {
7 "@timestamp": {
8 "gte": "now-7d/d",
9 "lte": "now/d"
10 }
11 }
12 }
13 },
14 "dest": {
15 "index": "new-data-stream",
16 "op_type": "create"
17 }
18}
如果你之前修改了ILM的poll interval,那么在reindex完成后改回到原来的值。防止master node上没必要的负载。
下面的cluster update settings API请求将indices.lifecycle.poll_interval
设置为默认值。
xxxxxxxxxx
61PUT /_cluster/settings
2{
3 "persistent": {
4 "indices.lifecycle.poll_interval": null
5 }
6}
使用新的data stream恢复索引。在这个stream上的查询会使用新的数据以及reindex的数据。
一旦你验证完所有的reindex数据在新的data stream是可见的,你就可以安全的移除旧的stream。
下面的delete data stream API 请求删除了my-data-steam
,这个请求同样会删除它包含的所有的backing index和其他任何数据。
xxxxxxxxxx
11DELETE /_data_stream/my-data-stream
(8.2)link
Ingest pipelines能让你在索引之前对你的数据执行常用的转换(common transformation)。例如你可以使用pipeline来移除字段,从文本中提取值,并且丰富你的数据(enrich your data)。
一个pipeline由一系列称为processors的可配置的任务组成。每一个processor顺序的运行,对incoming document做对应的更改。在运行完processor后,Elasticsearch将转换后的document添加到data stream或者索引中。
你可以使用Kibana的Ingest Pipelines 功能或者ingest APIS来管理ingest pipeline。Elasticsearch将pipeline存储在cluster state中。
节点角色(node role)为Ingest node负责pipeline的处理。若要使用ingest pipeline,你的集群中必须至少有一个节点角色为ingest
的节点。对于繁重的ingest负载,我们建议你创建一个dedicated ingest nodes
如果开启了Elasticsearch security feature,你必须有manage_pipeline
的cluster privilege才能管理ingest pipeline。若要使用Kibana的Ingest Pipeline 功能,你也需要有cluster:monitor/nodes/info
的cluster privilege。
在Kibana中,打开主菜单并且点击Stack Management > Ingest Pipelines。从下拉菜单中,你可以:
查看你的pipeline列表以及drill down后查看详情
编辑或者克隆现有的pipeline
删除pipeline
若要创建一个pipeline,点击Create pipeline > New pipeline。可以查看Example: Parse logs这个示例教程。
NOTE:New pipeline from CSV可以让你使用一个CSV来创建一个ingest pipeline,它将自定义的数据映射到Elastic Common Schema (ECS)。将你的数据映射到ECS使得数据更易于查询并且能让你从其他数据集中复用(reuse)可视化。见Map custom data to ECS。
你也可以使用ingest APIs来创建和管理pipeline。下面的create pipeline API请求中创建了一个包含2个set processor ,以及一个lowercase processor的pipeline。这三个processor会根据指定的顺序有序执行。
xxxxxxxxxx
251PUT _ingest/pipeline/my-pipeline
2{
3 "description": "My optional pipeline description",
4 "processors": [
5 {
6 "set": {
7 "description": "My optional processor description",
8 "field": "my-long-field",
9 "value": 10
10 }
11 },
12 {
13 "set": {
14 "description": "Set 'my-boolean-field' to true",
15 "field": "my-boolean-field",
16 "value": true
17 }
18 },
19 {
20 "lowercase": {
21 "field": "my-keyword-field"
22 }
23 }
24 ]
25}
当你创建或者更新一个pipeline时,你可以指定一个可选的version
的整数值。你可以使用version以及参数if_version来有条件的更新pipeline。一次成功的更新操作会提高pipeline的版本号。
xxxxxxxxxx
51PUT _ingest/pipeline/my-pipeline-id
2{
3 "version": 1,
4 "processors": [ ... ]
5}
若要通过API不设置version
,可以在替换或者更新pipeline时不指定versopm
参数。
在生产中使用pipeline前,我们建议你先用样例文档(sample document)进行测试。当在Kibana中创建或者编辑了一个pipeline,点击Document tab页的Add documents,提供样例文档并点击Run the pipeline。
你也可以使用simulate pipeline API来测试pipeline。你可以在请求路径中指定一个配置好的pipeline。例如下面的请求中测试了my-pipeline
。
xxxxxxxxxx
151POST _ingest/pipeline/my-pipeline/_simulate
2{
3 "docs": [
4 {
5 "_source": {
6 "my-keyword-field": "FOO"
7 }
8 },
9 {
10 "_source": {
11 "my-keyword-field": "BAR"
12 }
13 }
14 ]
15}s
或者你可以指定一个在请求body中指定pipeline和它的processor。
xxxxxxxxxx
241POST _ingest/pipeline/_simulate
2{
3 "pipeline": {
4 "processors": [
5 {
6 "lowercase": {
7 "field": "my-keyword-field"
8 }
9 }
10 ]
11 },
12 "docs": [
13 {
14 "_source": {
15 "my-keyword-field": "FOO"
16 }
17 },
18 {
19 "_source": {
20 "my-keyword-field": "BAR"
21 }
22 }
23 ]
24}
这个API会返回转换后的文档:
xxxxxxxxxx
281{
2 "docs": [
3 {
4 "doc": {
5 "_index": "_index",
6 "_id": "_id",
7 "_source": {
8 "my-keyword-field": "foo"
9 },
10 "_ingest": {
11 "timestamp": "2099-03-07T11:04:03.000Z"
12 }
13 }
14 },
15 {
16 "doc": {
17 "_index": "_index",
18 "_id": "_id",
19 "_source": {
20 "my-keyword-field": "bar"
21 },
22 "_ingest": {
23 "timestamp": "2099-03-07T11:04:04.000Z"
24 }
25 }
26 }
27 ]
28}
在请求参数中使用pipeline
对单篇或者多篇文档应用一个pipeline
xxxxxxxxxx
111POST my-data-stream/_doc?pipeline=my-pipeline
2{
3 "@timestamp": "2099-03-07T11:04:05.000Z",
4 "my-keyword-field": "foo"
5}
6
7PUT my-data-stream/_bulk?pipeline=my-pipeline
8{ "create":{ } }
9{ "@timestamp": "2099-03-07T11:04:06.000Z", "my-keyword-field": "foo" }
10{ "create":{ } }
11{ "@timestamp": "2099-03-07T11:04:07.000Z", "my-keyword-field": "bar" }
你也可以在update by query或者 reindex API中使用pipeline
参数:
xxxxxxxxxx
131POST my-data-stream/_update_by_query?pipeline=my-pipeline
2
3POST _reindex
4{
5 "source": {
6 "index": "my-data-stream"
7 },
8 "dest": {
9 "index": "my-new-data-stream",
10 "op_type": "create",
11 "pipeline": "my-pipeline"
12 }
13}
使用索引设置index.default_pipeline来设置一个默认的pipeline。如果没有指定pipeline
参数,Elasticsearch则会应用(apply )这个默认的pipeline。
如果为Elastic Beat添加一个ingest pipeline,可以在<BEAT_NAME>.yml
中的output.elasticsearch
下指定参数pipeline
。
xxxxxxxxxx
31output.elasticsearch:
2 hosts: ["localhost:9200"]
3 pipeline: my-pipeline
Fleet会为integration自动的添加ingest pipeline。Fleet会使用包含了pipeline index settings的index templates来 应用pipeline。Elasticsearch会基于stream’s naming scheme匹配到这些模板到你的Fleet data streams中。
WARNING:不要修改Fleet的ingest pipeline或者对你的Fleet integration使用自定义的pipeline。这么做会破坏你的Fleet data streams。
Fleet不会为Custom logs integration提供一个ingest pipeline。你可以使用两种方法中的一种来为你的integration安全的指定一个pipeline:index template 或者一个custom configuration。
Create并且test你的ingest pipeline。将你的pipeline命名为logs-<dataset-name>-default
。这样方便为你的integration进行追踪(track)。
xxxxxxxxxx
51PUT _ingest/pipeline/logs-my_app-default
2{
3 "description": "Pipeline for `my_app` dataset",
4 "processors": [ ... ]
5}
创建一个index template,包含在index setting中设置的pipeline的index.default_pipeline和index.final_pipeline。保证模板中开启了data stream。这个模板的index pattern应该匹配logs-<dataset-name>-*
。
你可以使用Kibana的Index Management功能来create index template API。
例如,下面的请求创建了一个模板来匹配logs-my_app-*
。这个模板使用了component template,它包含了index.default_pipeline
这个index setting。
xxxxxxxxxx
191# Creates a component template for index settings
2PUT _component_template/logs-my_app-settings
3{
4 "template": {
5 "settings": {
6 "index.default_pipeline": "logs-my_app-default",
7 "index.lifecycle.name": "logs"
8 }
9 }
10}
11
12# Creates an index template matching `logs-my_app-*`
13PUT _index_template/logs-my_app-template
14{
15 "index_patterns": ["logs-my_app-*"],
16 "data_stream": { },
17 "priority": 500,
18 "composed_of": ["logs-my_app-settings", "logs-my_app-mappings"]
19}
在Fleet中增加或者编辑Custom logs integration时,点击Configure integration > Custom log file > Advanced options。
在Dataset name中,指定你的数据集名称,Fleet会为integration添加新的数据并输出(resulting)logs-<dataset-name>-default
data stream。
例如,如果你的数据集名称是my_app
,Fleet将新的数据添加到logs-my_app-default
数据流。
使用rollover API来roll over你的数据流。这将保证Elasticsearch为integration将index template和pipeline设置应用到新的数据上。
xxxxxxxxxx
11POST logs-my_app-default/_rollover/
Create和test你的ingest pipeline。默认命名pipeline的名称为logs-<dataset-name>-default
。这使得让你的integration更易于追踪(track)。
例如,下面的请求中为数据集my-app
创建了一个pipeline。这个pipeline的名称是logs-my_app-default
。
xxxxxxxxxx
51PUT _ingest/pipeline/logs-my_app-default
2{
3 "description": "Pipeline for `my_app` dataset",
4 "processors": [ ... ]
5}
当你在Fleet中添加或者编辑你的Custom logs integration。点击Configure integration > Custom log file > Advanced options。
在Dataset name,指定你的数据集名称。Fleet将为integration添加新的数据并且输出到logs-<dataset-name>-default
数据流中。
例如,如果你的数据集名称是my_app
,Fleet将添加新的数据到logs-my_app-default
数据流中。
在Custom Configurations,在pipeline
策略设置中指定你的pipeline。
如果你用standalone方式启动Elastic Agent。你可以使用包含index.default_pipeline和index.final_pipeline index setting的index template来应用pipeline。或者你可以在elastic-agent.yml
中指定pipeline
策略。见Install standalone Elastic Agents。
Processor对incoming document的source field有读写权限。若要在一个processor中访问一个field,使用其字段名。下面的process set
访问了my-long-field
。
xxxxxxxxxx
111PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "set": {
6 "field": "my-long-field",
7 "value": 10
8 }
9 }
10 ]
11}
你也可以前置(prepend)_source
前缀。
xxxxxxxxxx
111PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "set": {
6 "field": "_source.my-long-field",
7 "value": 10
8 }
9 }
10 ]
11}
使用.
(dot notation)来访问object fields。
IMPORTANT:如果你的文档中包含flattened object,使用dot_expander先进行expand。其他的ingest processor不能访问flattened object。
xxxxxxxxxx
181PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "dot_expander": {
6 "description": "Expand 'my-object-field.my-property'",
7 "field": "my-object-field.my-property"
8 }
9 },
10 {
11 "set": {
12 "description": "Set 'my-object-field.my-property' to 10",
13 "field": "my-object-field.my-property",
14 "value": 10
15 }
16 }
17 ]
18}
多个processor参数支持Mustache template snippets。为了能在template snippet中访问字段值,使用三个大括号(curly brackets)包住字段名。你可以使用template snippets动态的设置字段名。
xxxxxxxxxx
121PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "set": {
6 "description": "Set dynamic '<service>' field to 'code' value",
7 "field": "{{{service}}}",
8 "value": "{{{code}}}"
9 }
10 }
11 ]
12}
Processor能通过名称访问下面的metadata filed:
_index
_id
_routing
_dynamic_templates
xxxxxxxxxx
121PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "set": {
6 "description": "Set '_routing' to 'geoip.country_iso_code' value",
7 "field": "_routing",
8 "value": "{{{geoip.country_iso_code}}}"
9 }
10 }
11 ]
12}
使用一个Mustache template snippet来访问metadata filed的值。例如{{{_routing}}}
将retrieve一篇文档的 routing value。
xxxxxxxxxx
141PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "set": {
6 "description": "Use geo_point dynamic template for address field",
7 "field": "_dynamic_templates",
8 "value": {
9 "address": "geo_point"
10 }
11 }
12 }
13 ]
14}
上面的set processor
告诉ES如果字段address在索引的映射中还未定义,则使用名为geo_point的动态模板。如果在批量请求中已经定义了address字段的动态模板,这个处理器会覆盖它,但对批量请求中定义的其他动态模板没有影响。
WARNING: 如果你automatically generate document id,你不能在processor中使用
{{{_id}}}
,因为Elasticsearch在ingest之后才会自动分配_id
值。
Ingest processors可以使用key _ingest
添加并且访问ingest metadata。
不同于source和metadata field,Elasticsearch默认不会索引ingest metadata。Elasticsearch同样允许以_ingest
开头的source fields,如果你的数据中包含了这种source fields,使用_source._ingest
来访问它们。
pipeline默认只创建名为_ingest.timestamp
的ingest metadata字段。这个字段包含的是Elasticsearch收到文档索引请求时的timestamp。若要索引_ingest.timestamp
或者其他ingest metadata字段,使用set
processor。
xxxxxxxxxx
121PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "set": {
6 "description": "Index the ingest timestamp as 'event.ingested'",
7 "field": "event.ingested",
8 "value": "{{{_ingest.timestamp}}}"
9 }
10 }
11 ]
12}
pipeline的processor有序运行的。默认情况下,当其中一个processor允许失败或者遇到一个错误时,pipeline的处理就会停止下来。
若要忽略某个processor的失败并且运行剩余的 processor。将ignore_failure
为true
。
xxxxxxxxxx
131PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "rename": {
6 "description": "Rename 'provider' to 'cloud.provider'",
7 "field": "provider",
8 "target_field": "cloud.provider",
9 "ignore_failure": true
10 }
11 }
12 ]
13}
使用on_failure
参数指定processor list,在一个processor失败后马上就运行它们。如果指定了on_failure
,即使没有配置任何processor,Elasticsearch之后会运行pipeline中剩余的processor。
xxxxxxxxxx
221PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "rename": {
6 "description": "Rename 'provider' to 'cloud.provider'",
7 "field": "provider",
8 "target_field": "cloud.provider",
9 "on_failure": [
10 {
11 "set": {
12 "description": "Set 'error.message'",
13 "field": "error.message",
14 "value": "Field 'provider' does not exist. Cannot rename to 'cloud.provider'",
15 "override": false
16 }
17 }
18 ]
19 }
20 }
21 ]
22}
on_failure
中嵌套processor用于嵌套错误的处理
xxxxxxxxxx
321PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "rename": {
6 "description": "Rename 'provider' to 'cloud.provider'",
7 "field": "provider",
8 "target_field": "cloud.provider",
9 "on_failure": [
10 {
11 "set": {
12 "description": "Set 'error.message'",
13 "field": "error.message",
14 "value": "Field 'provider' does not exist. Cannot rename to 'cloud.provider'",
15 "override": false,
16 "on_failure": [
17 {
18 "set": {
19 "description": "Set 'error.message.multi'",
20 "field": "error.message.multi",
21 "value": "Document encountered multiple ingest errors",
22 "override": true
23 }
24 }
25 ]
26 }
27 }
28 ]
29 }
30 }
31 ]
32}
你也可以为一个pipeline指定on_failure
。如果某个没有设置on_failure
的processor失败后,Elasticsearch会使用这个pipeline-level的参数作为一个fallback。Elasticsearch不会尝试运行pipeline中剩余的processor。
xxxxxxxxxx
131PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [ ... ],
4 "on_failure": [
5 {
6 "set": {
7 "description": "Index document to 'failed-<index>'",
8 "field": "_index",
9 "value": "failed-{{{ _index }}}"
10 }
11 }
12 ]
13}
pipeline失败后的额外信息可以在document的metadata filed中查看:on_failure_message
, on_failure_processor_type
, on_failure_processor_tag
, and on_failure_pipeline
。这些字段的信息只能在on_failure
中才能被访问。
下面的例子中使用了document的metadata fields,它包含了pipeline 失败后的信息:
xxxxxxxxxx
131PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [ ... ],
4 "on_failure": [
5 {
6 "set": {
7 "description": "Record error information",
8 "field": "error_information",
9 "value": "Processor {{ _ingest.on_failure_processor_type }} with tag {{ _ingest.on_failure_processor_tag }} in pipeline {{ _ingest.on_failure_pipeline }} failed with message {{ _ingest.on_failure_message }}"
10 }
11 }
12 ]
13}
每一个processor都支持可选的if
条件,使用Painless script编写。如果提供了这个配置,processor只有在if
条件为ture
的情况下才会运行。
IMPORTANT:
if
条件运行于Painless的ingest processor context。在if
条件中,ctx
的值是只读的。
xxxxxxxxxx
111PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "drop": {
6 "description": "Drop documents with 'network.name' of 'Guest'",
7 "if": "ctx?.network?.name == 'Guest'"
8 }
9 }
10 ]
11}
如果开启了集群设置script.painless.regex.enabled,你可以在if
条件脚本中使用正则表达式。见Painless regular expressions了解支持的语法。
TIP:如果可以的话,避免在
if
条件中使用复杂或者开销大的脚本。然而你可以使用Kibana consloe的 triple quote syntax来编写以及调试larger script。
xxxxxxxxxx
211PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "drop": {
6 "description": "Drop documents that don't contain 'prod' tag",
7 "if": """
8 Collection tags = ctx.tags;
9 if(tags != null){
10 for (String tag : tags) {
11 if (tag.toLowerCase().contains('prod')) {
12 return false;
13 }
14 }
15 }
16 return true;
17 """
18 }
19 }
20 ]
21}
你可以使用stored script作为if
条件中的脚本。
xxxxxxxxxx
291PUT _scripts/my-prod-tag-script
2{
3 "script": {
4 "lang": "painless",
5 "source": """
6 Collection tags = ctx.tags;
7 if(tags != null){
8 for (String tag : tags) {
9 if (tag.toLowerCase().contains('prod')) {
10 return false;
11 }
12 }
13 }
14 return true;
15 """
16 }
17}
18
19PUT _ingest/pipeline/my-pipeline
20{
21 "processors": [
22 {
23 "drop": {
24 "description": "Drop documents that don't contain 'prod' tag",
25 "if": { "id": "my-prod-tag-script" }
26 }
27 }
28 ]
29}
Incoming document通常包含object field。如果一个processor script尝试访问一个字段,但是parent object不存在,Elasticsearch会返回一个NullPointerException
。若要避免这些异常,使用null safe operators,例如?.
,你的script就可以变的null safe。
例如,ctx.network?.name.equalsIgnoreCase('Guest')
不是null safe。ctx.network?.name
可能返回null,将脚本重写为'Guest'.equalsIgnoreCase(ctx.network?.name)
的话就是null safe,因为Guest
总是non-null。
如果无法确保是null safe,就添加一个显示的null检查。
xxxxxxxxxx
111PUT _ingest/pipeline/my-pipeline
2{
3 "processors": [
4 {
5 "drop": {
6 "description": "Drop documents that contain 'network.name' of 'Guest'",
7 "if": "ctx.network?.name != null && ctx.network.name.contains('Guest')"
8 }
9 }
10 ]
11}
基于你的规则,在pipeline中使用if
条件来为你的文档应用pipeline。你可以在index template中使用这个pipeline作为default pipeline。
xxxxxxxxxx
261PUT _ingest/pipeline/one-pipeline-to-rule-them-all
2{
3 "processors": [
4 {
5 "pipeline": {
6 "description": "If 'service.name' is 'apache_httpd', use 'httpd_pipeline'",
7 "if": "ctx.service?.name == 'apache_httpd'",
8 "name": "httpd_pipeline"
9 }
10 },
11 {
12 "pipeline": {
13 "description": "If 'service.name' is 'syslog', use 'syslog_pipeline'",
14 "if": "ctx.service?.name == 'syslog'",
15 "name": "syslog_pipeline"
16 }
17 },
18 {
19 "fail": {
20 "description": "If 'service.name' is not 'apache_httpd' or 'syslog', return a failure message",
21 "if": "ctx.service?.name != 'apache_httpd' && ctx.service?.name != 'syslog'",
22 "message": "This pipeline requires service.name to be either `syslog` or `apache_httpd`"
23 }
24 }
25 ]
26}
使用node stats API来获取全局的以及per-pipeline的ingest 统计数据。来检测出哪些pipeline运行频次最多或者处理时间耗时最多。
(8.2)link
在这个教程中,你将使用ingest pipeline以及common log format,在索引前对服务器日志进行解析。在开始之前,先查看使用ingest pipeline前的prerequisite。
你要解析的日志如下所示:
xxxxxxxxxx
31212.87.37.154 - - [30/May/2099:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\"
2200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)
3AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\"
这些日志中包含了timestamp,IP address,以及user agent。你要让这三个信息在Elasticsearch有自己的字段,使得可以用于快速的查询以及可视化。你同样想知道这些请求的出处。
在Kibana中,打开主菜单并且点击Stack Management > Ingest Pipelines。
点击Create pipeline > New pipeline
给这个pipeline提供一个名称以及描述
添加一个grok processor来解析日志消息。
点击Add a processor并且选择Grok processor类型
设置Field为message
并且Patterns设置为下面的grok pattern
点击Add保存processor
设置processor的描述信息为Extract fields from 'message'
xxxxxxxxxx
11%{IPORHOST:source.ip} %{USER:user.id} %{USER:user.name} \\[%{HTTPDATE:@timestamp}\\] \"%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}\" %{NUMBER:http.response.status_code:int} (?:-|%{NUMBER:http.response.body.bytes:int}) %{QS:http.request.referrer} %{QS:user_agent}
为timestamp,IP address,和user agent增加processor。如下配置processor:
Processor type | Field | Additional options | Description |
---|---|---|---|
Date | @timestamp | Formats: dd/MMM/yyyy:HH:mm:ss Z | Format '@timestamp' as 'dd/MMM/yyyy:HH:mm:ss Z' |
GeoIP | source.ip | Target field: source.geo | Add 'source.geo' GeoIP data for 'source.ip' |
User agent | user_agent | Extract fields from 'user_agent' |
如下所示:
下面四个processor将会顺序执行:
Grok > Date > GeoIP > User agent
你也可以使用箭头图标重新排序
或者,你可以点击Import processors链接然后定义如下的JSON:
xxxxxxxxxx
321{
2 "processors": [
3 {
4 "grok": {
5 "description": "Extract fields from 'message'",
6 "field": "message",
7 "patterns": ["%{IPORHOST:source.ip} %{USER:user.id} %{USER:user.name} \\[%{HTTPDATE:@timestamp}\\] \"%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}\" %{NUMBER:http.response.status_code:int} (?:-|%{NUMBER:http.response.body.bytes:int}) %{QS:http.request.referrer} %{QS:user_agent}"]
8 }
9 },
10 {
11 "date": {
12 "description": "Format '@timestamp' as 'dd/MMM/yyyy:HH:mm:ss Z'",
13 "field": "@timestamp",
14 "formats": [ "dd/MMM/yyyy:HH:mm:ss Z" ]
15 }
16 },
17 {
18 "geoip": {
19 "description": "Add 'source.geo' GeoIP data for 'source.ip'",
20 "field": "source.ip",
21 "target_field": "source.geo"
22 }
23 },
24 {
25 "user_agent": {
26 "description": "Extract fields from 'user_agent'",
27 "field": "user_agent"
28 }
29 }
30 ]
31
32}
若要测试pipeline,点击Add documents。
在Document tab页,提供了一个文档文档样例用于测试:
xxxxxxxxxx
71[
2 {
3 "_source": {
4 "message": "212.87.37.154 - - [05/May/2099:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" 200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\""
5 }
6 }
7]
点击Run the pipeline然后验证pipeline是否按预期运行
如果一切看着都可以,关闭面板,然后点击Create pipeline。
现在你已经准备就绪将日志数据索引到data stream中。
创建一个index template并开启data stream。
xxxxxxxxxx
61PUT _index_template/my-data-stream-template
2{
3 "index_patterns": [ "my-data-stream*" ],
4 "data_stream": { },
5 "priority": 500
6}
使用你创建的pipeline索引文档。
xxxxxxxxxx
41POST my-data-stream/_doc?pipeline=my-pipeline
2{
3 "message": "89.160.20.128 - - [05/May/2099:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" 200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\""
4}
若要进行校验,查询data stream来检索文档。下面的查询使用filter_path来只返回document source。
xxxxxxxxxx
11GET my-data-stream/_search?filter_path=hits.hits._source
这个API返回如下:
xxxxxxxxxx
601{
2 "hits": {
3 "hits": [
4 {
5 "_source": {
6 "@timestamp": "2099-05-05T16:21:15.000Z",
7 "http": {
8 "request": {
9 "referrer": "\"-\"",
10 "method": "GET"
11 },
12 "response": {
13 "status_code": 200,
14 "body": {
15 "bytes": 3638
16 }
17 },
18 "version": "1.1"
19 },
20 "source": {
21 "ip": "89.160.20.128",
22 "geo": {
23 "continent_name" : "Europe",
24 "country_name" : "Sweden",
25 "country_iso_code" : "SE",
26 "city_name" : "Linköping",
27 "region_iso_code" : "SE-E",
28 "region_name" : "Östergötland County",
29 "location" : {
30 "lon" : 15.6167,
31 "lat" : 58.4167
32 }
33 }
34 },
35 "message": "89.160.20.128 - - [05/May/2099:16:21:15 +0000] \"GET /favicon.ico HTTP/1.1\" 200 3638 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\"",
36 "url": {
37 "original": "/favicon.ico"
38 },
39 "user": {
40 "name": "-",
41 "id": "-"
42 },
43 "user_agent": {
44 "original": "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\"",
45 "os": {
46 "name": "Mac OS X",
47 "version": "10.11.6",
48 "full": "Mac OS X 10.11.6"
49 },
50 "name": "Chrome",
51 "device": {
52 "name": "Mac"
53 },
54 "version": "52.0.2743.116"
55 }
56 }
57 }
58 ]
59 }
60}
(8.2)link
你可以在ingest过程中使用enrich processor将现有的索引中的数据添加到incoming document(传入文档)中。
例如,你可以使用enrich processor来实现以下目标:
根据已知的IP地址识别网络服务或供应商
根据产品ID添加零售订单的产品信息
根据电子邮件地址补充联系信息
根据用户坐标添加邮政编码
大多数processor是自包含的(self-contained,也就是processor都是独立的,它们不依赖于外部数据或其他processor来执行其任务),仅改变incoming document中的现有数据。
enrich processor则是添加新的数据到incoming document中并且要求一些特殊的组件:
使用一些配置选项将正确的enrich data添加到正确的incoming document中。
一个enrich policy中包含以下内如:
一个或多个source index的列表,他们以文档形式存储enrich data
policy type决定了processor如何匹配enrich data到incoming document中
source index中的match field用来与incoming document匹配
enrich field包含了source index中的enrich data,将被添加到incoming document中
enrich policy可以被processor使用之前需要先被executed。执行后,enrich policy使用策略中source index中的enrich data来创建一个名为enrich index的streamlined(简化) system index。processor使用这个索引匹配以及丰富incoming document。
source index中包含了你想要添加到incoming document中的enrich data。你可以跟Elasticsearch中其他常规索引(regular index,系统索引就不属于常规索引)一样创建并且管理这些索引 。你可以在一个enrich policy中指定多个source index。你可以在多个enrich policy中使用相同的source index。
一种特殊的系统索引跟一个特定的enrich policy关联
直接通过文档(source index中的document)到文档(incoming document)的方式会很慢,并且属于资源密集型的操作。为了提高速度,enrich processor使用了enrich index。
enrich index中包含了来自source index中的丰富数据,但是有一些特殊的属性来帮助简化(streamline)处理过程:
他们都是系统索引,意味着由Elasticsearch管理并且只被enrich processor使用
他们都是以.enrich-*
开头
他们是只读的,意味着你不能直接修改他们
他们都force merged,使得能被快速检索
(8.2)link
按照以下步骤创建一个enrich processor:
Check the prerequisites.
一旦你设置好了一个enrich processor,你可以update your enrich data和update your enrich policies。
IMPORTANT:enrich processor会执行很多操作,可能会影响ingest Pipeline的速度 强烈建议在生产中部署进行测试和基准测试 我们不建议使用enrich processor处理实时数据。enrich processor最合适处理很少更改的数据
如果开启了Elasticsearch security features,你必须要有:
被使用到的索引的read
index privilege
built-in role enrich_user
首先,添加文档到一个或多个source index中。这些文档应该包含你最终想要添加到incoming document中的丰富数据
你可以使用document和index API,跟Elasticsearch中其他常规索引一样,管理source index。
你也可以设置Beats,例如Filebeat,来自动的发送/索引你的source Index。见Getting started with Beats。
source index中添加完丰富数据后,然后使用create enrich policy API创建一个enrich policy。
WARNING:enrich policy一旦创建结束,你不能更新/更改。见Update an enrich policy。
enrich policy创建后,你可以使用execute enrich policy API执行该策略来创建enrich index。
enrich index中包含了策略中的source index。enrich index 的名称总是以.enrich-*
开头,只读索引,并且force merged。
WARNING:enrich index应该只被enrich processor使用,避免使用enrich index用于其他目的。
一旦你拥有了source Index、enrich policy以及相关的enrich Index,你就可以设置一个包含带有策略的enrich processor的ingest pipeline。
定义一个enrich processor,然后使用create or update pipeline API将它添加到一个ingest pipeline中。
在定义enrich processor时,你必须至少包含下面的内容:
需要使用的enrich policy
用来enrich Index与incoming document匹配的字段
添加到incoming document的target Field。enrich policy中指定的match_field
和enrich_fields
中所有的字段将作为target_field
的字段
你可以使用max_matches
选项设置一个incoming document可以匹配的丰富文档的数量。如果设置为默认值的1
。那么target_field
字段变成json object否则就是json array。
见Enrich了解全部的可配置的选项。
你也可以在ingest pipeline中添加其他的processor。
你现在可以使用ingest pipeline丰富并索引文档了。
在生产中使用pipeline之前,我们建议你先写入一些测试文档然后使用get API验证enrich data是否被正确的添加。
创建之后,你不能向enrich Index中更新或者添加文档。而是更新你的source index然后再次execute enrich policy。这样会从更新后的source Index中创建新的enrich index。之前的enrich index会使用一个maintenance job稍后删除。默认是每15分钟。
如果有必要的话,你可以使用ingest pipelinereindex或者update已经提取的文档(ingested document)。
enrich policy一旦创建完毕,你就不能更新或者修改,不过你可以:
创建并且execute一个新的enrich policy
在使用中的enrich processor中用新的enrich policy替换之前的
使用delete enrich policy删除之前的enrich policy
enrich coordinator是一个组件,负责管理和执行每个ingest node上所需的搜索以丰富文档。它将所有pipeline中所有enrich processor的搜索合并成批量multi-searches。
enrich policy executor是一个组件,负责管理所有enrich policies的执行。当执行一个enrich policies时,此组件将创建一个新的enrich index并移除之前的enrich index。enrich policies的执行由elected master node管理。这些策略的执行发生在不同的节点上。
enrich
processor有enrich coordinator和enrich policy executor相关的节点设置。
enrich coordinator支持下面的节点设置:
用于enriching documents的搜索的最大缓存数量。默认值为1000。整个集群的所有enrich processors共用一个缓存。此设置确定该缓存的大小。
enriching documents时运行的最大并发multi-search requests请求数量。默认值为8。
enriching documents时在一个multi-search requests请求中包含的最大搜索数量。默认值为128。
enrich policy executor支持下面的节点设置:
将source index reindex到enrich index时的最大批量大小。默认值为10000。
允许在enrich index上进行的最大force merge 尝试次数。默认值为3。
Elasticsearch检查是否可以删除未使用的enrich index的频率。默认值为15分钟。
最大并发执行的enrich policy数量。默认值为50。
(8.2)link
geo_match
类型的enrich policies基于一个geographic location,使用gep_shape query,将匹配到的丰富数据添加到incoming document中。
下面的例子中创建了一个geo_match
类型的enrich policy,基于坐标集(a set of coordinates)将postal code
的信息添加到incoming document中。然后将geo_match
类型的enrich policy添加到一个ingest pipeline中。
使用create index API创建一个source index,索引中至少包含一个geo_shape
字段。
xxxxxxxxxx
131PUT /postal_codes
2{
3 "mappings": {
4 "properties": {
5 "location": {
6 "type": "geo_shape"
7 },
8 "postal_code": {
9 "type": "keyword"
10 }
11 }
12 }
13}
使用index API将enrich data添加到source index中
xxxxxxxxxx
81PUT /postal_codes/_doc/1?refresh=wait_for
2{
3 "location": {
4 "type": "envelope",
5 "coordinates": [ [ 13.0, 53.0 ], [ 14.0, 52.0 ] ]
6 },
7 "postal_code": "96598"
8}
使用create enrich policy API创建一个geo_match
类型的enrich policy。这个策略包含:
一个或多个source index
一个source index中的match_field
以及geo_shape
,用来跟incoming document进行匹配
source Index中的enrich field,将追加到incoming document中。
xxxxxxxxxx
81PUT /_enrich/policy/postal_policy
2{
3 "geo_match": {
4 "indices": "postal_codes",
5 "match_field": "location",
6 "enrich_fields": [ "location", "postal_code" ]
7 }
8}
使用create or update pipeline API创建一个ingest pipeline。在这个pipeline中,添加一个包含以下内容的enrich processor:
enrich policy
incoming document的field
用来匹配enrich Index的文档中的geoshape
target_field
用来为incoming document存储追加的enrich data。这个字段中包含了enrich policy中指定的match_field
和enrich_fields
信息
target_field可以是一个新的字段名(如果是incoming document中已有的字段,会被覆盖)
shape_relation
用来告知processor如何匹配incoming document和enrich index中的geoshapes。见Spatial Relations 了解更多可选参数以及介绍。
xxxxxxxxxx
141PUT /_ingest/pipeline/postal_lookup
2{
3 "processors": [
4 {
5 "enrich": {
6 "description": "Add 'geo_data' based on 'geo_location'",
7 "policy_name": "postal_policy",
8 "field": "geo_location",
9 "target_field": "geo_data",
10 "shape_relation": "INTERSECTS"
11 }
12 }
13 ]
14}
使用ingest pipeline索引一篇文档。incoming document中应该包含enrich processor中指定的field
:
xxxxxxxxxx
61PUT /users/_doc/0?pipeline=postal_lookup
2{
3 "first_name": "Mardy",
4 "last_name": "Brown",
5 "geo_location": "POINT (13.5 52.5)"
6}
若要验证enrich processor匹配到的、以及追加的字段的信息。可以使用get API查看索引后的文档:
xxxxxxxxxx
11GET /users/_doc/0
该接口返回以下响应:
xxxxxxxxxx
211{
2 "found": true,
3 "_index": "users",
4 "_id": "0",
5 "_version": 1,
6 "_seq_no": 55,
7 "_primary_term": 1,
8 "_source": {
9 "geo_data": {
10 "location": {
11 "type": "envelope",
12 "coordinates": [[13.0, 53.0], [14.0, 52.0]]
13 },
14 "postal_code": "96598"
15 },
16 "first_name": "Mardy",
17 "last_name": "Brown",
18 "geo_location": "POINT (13.5 52.5)"
19 }
20}
21
(8.2)link
匹配enrich policies,将匹配到的enrich data基于精确值添加到incoming document中,比如email地址或者ID,使用的是term query。
下面的例子中创建了一个match
类型的enrich policy,基于一个email地址将用户名(user name)和联系方式(contact)添加到incoming document中。然后将这个match
policy添加到ingest pipeline的processor中。
使用create index API和index API创建一个source index。
下面的index API创建了一个source index,然后向这个索引中索引了一个新的文档。
xxxxxxxxxx
111PUT /users/_doc/1?refresh=wait_for
2{
3 "email": "mardy.brown@asciidocsmith.com",
4 "first_name": "Mardy",
5 "last_name": "Brown",
6 "city": "New Orleans",
7 "county": "Orleans",
8 "state": "LA",
9 "zip": 70116,
10 "web": "mardy.asciidocsmith.com"
11}
使用create enrich policy API创建一个match
类型的enrich policy。这个策略包含:
一个或多个source index
一个source index中的match_field
,用来跟incoming document进行匹配
source Index中的enrich field,将追加到incoming document中。
xxxxxxxxxx
81PUT /_enrich/policy/users-policy
2{
3 "match": {
4 "indices": "users",
5 "match_field": "email",
6 "enrich_fields": ["first_name", "last_name", "city", "zip", "state"]
7 }
8}
使用execute enrich policy API为这个策略创建一个enrich Index。
xxxxxxxxxx
11POST /_enrich/policy/users-policy/_execute
使用create or update pipeline API创建一个ingest pipeline。在这个pipeline中,添加一个包含以下内容的enrich processor:
enrich polic
incoming document的field
用来匹配enrich Index中的文档
target_field
用来为incoming document存储追加的enrich data。这个字段中包含了enrich policy中指定的match_field
和enrich_fields
信息
xxxxxxxxxx
141PUT /_ingest/pipeline/user_lookup
2{
3 "processors" : [
4 {
5 "enrich" : {
6 "description": "Add 'user' data based on 'email'",
7 "policy_name": "users-policy",
8 "field" : "email",
9 "target_field": "user",
10 "max_matches": "1"
11 }
12 }
13 ]
14}
使用ingest pipeline索引一篇文档。incoming document中应该包含enrich processor中指定的field
:
xxxxxxxxxx
41PUT /my-index-000001/_doc/my_id?pipeline=user_lookup
2{
3 "email": "mardy.brown@asciidocsmith.com"
4}
若要验证enrich processor匹配到的、以及追加的字段的信息。可以使用get API查看索引后的文档:
xxxxxxxxxx
11GET /my-index-000001/_doc/my_id
该接口返回以下响应:
xxxxxxxxxx
191{
2 "found": true,
3 "_index": "my-index-000001",
4 "_id": "my_id",
5 "_version": 1,
6 "_seq_no": 55,
7 "_primary_term": 1,
8 "_source": {
9 "user": {
10 "email": "mardy.brown@asciidocsmith.com",
11 "first_name": "Mardy",
12 "last_name": "Brown",
13 "zip": 70116,
14 "city": "New Orleans",
15 "state": "LA"
16 },
17 "email": "mardy.brown@asciidocsmith.com"
18 }
19}
(8.2)link
一个range
类型的enrich policy使用一个term query匹配incoming document中的数值、日期或者IP地址类型的字段,使用匹配到的字段值去enrich Index中执行同一个字段的范围查询。不支持range到range的匹配。
下面的例子中创建了一个range
类型的enrich policy,该策略基于IP地址向incoming document中添加描述性网络名称(descriptive network name)和负责部门(responsible department)。然后,它将enrich policy添加到ingest pipeline的一个processor中。
使用create index API以及合适的mapping创建一个source index。
xxxxxxxxxx
101PUT /networks
2{
3 "mappings": {
4 "properties": {
5 "range": { "type": "ip_range" },
6 "name": { "type": "keyword" },
7 "department": { "type": "keyword" }
8 }
9 }
10}
下面的请求index API的请求向source Index中添加了一篇新文档。
xxxxxxxxxx
61PUT /networks/_doc/1?refresh=wait_for
2{
3 "range": "10.100.0.0/16",
4 "name": "production",
5 "department": "OPS"
6}
使用create enrich policy API创建一个range
类型的enrich policy。这个策略必须包含:
一个或多个source index
一个source index中的match_field
,用来跟incoming document进行匹配
source Index中的enrich field,将追加到incoming document中。
由于我们计划基于IP地址来丰富文档,因此策略中的match_field
必须是ip_range
字段
xxxxxxxxxx
81PUT /_enrich/policy/networks-policy
2{
3 "range": {
4 "indices": "networks",
5 "match_field": "range",
6 "enrich_fields": ["name", "department"]
7 }
8}
使用execute enrich policy API 创建一个enrich Index。
xxxxxxxxxx
11POST /_enrich/policy/networks-policy/_execute
使用create or update pipeline API创建一个ingest pipeline。在这个pipeline中,添加一个包含以下内容的enrich processor:
enrich policy
incoming document的field
用来匹配enrich Index中的文档
target_field
用来为incoming document存储追加的enrich data。这个字段中包含了enrich policy中指定的match_field
和enrich_fields
信息
xxxxxxxxxx
141PUT /_ingest/pipeline/networks_lookup
2{
3 "processors" : [
4 {
5 "enrich" : {
6 "description": "Add 'network' data based on 'ip'",
7 "policy_name": "networks-policy",
8 "field" : "ip",
9 "target_field": "network",
10 "max_matches": "10"
11 }
12 }
13 ]
14}
使用ingest pipeline索引一篇文档。incoming document中应该包含enrich processor中指定的field
:
xxxxxxxxxx
41PUT /my-index-000001/_doc/my_id?pipeline=networks_lookup
2{
3 "ip": "10.100.34.1"
4}
若要验证enrich processor匹配到的、以及追加的字段的信息。可以使用get API查看索引后的文档:
xxxxxxxxxx
11GET /my-index-000001/_doc/my_id
该接口返回以下响应:
xxxxxxxxxx
181{
2 "_index" : "my-index-000001",
3 "_id" : "my_id",
4 "_version" : 1,
5 "_seq_no" : 0,
6 "_primary_term" : 1,
7 "found" : true,
8 "_source" : {
9 "ip" : "10.100.34.1",
10 "network" : [
11 {
12 "name" : "production",
13 "range" : "10.100.0.0/16",
14 "department" : "OPS"
15 }
16 ]
17 }
18}
如果待追加的字段存在,并且是字段值是数组类型,那么追加一个或多个值到现有的数组中。如果待追加的字段存在,并且字段值是标量(scalar)类型,那么将其转化为数组类型,然后追加到数组中。如果待追加的字段不存在,那么创建一个数组类型的字段值并且追加到这个数组中。可以追加单个值或者数组。
将易于理解(human readable)的字节值(比如 1kb)转化为其字节值(比如 1024)。如果字段是string数组,那么数组中所有元素都会被转化。
支持的易于理解的单位有b
、kb
、mb
、gb
、tb
、pb
。不区分大小写。如果有不支持的格式或者转化后的值超过2^63则会发生错误。
将圆形定义转化为近似的正多边形。
根据社区ID(Community ID Specification)规范计算网络流数据的社区ID。您可以使用社区ID来关联与单个流相关的网络事件。
社区ID处理器默认从相关的Elastic Common Schema(ECS)字段读取网络流数据。如果您使用ECS,则无需配置。
将当前被提取的文档中的字段转化为不同的类型。比如将string转化为Integer。如果字段值是个数组,那么所有数组元素都被转化。
支持的类型有:integer
, long
, float
, double
, string
, boolean
, ip
以及auto
。
转化为boolean
时,如果字符串的值为true
(不区分大小)则转化为true,如果是false
则转化为false。如果是其他值则抛出异常。
如果包含了一个有效的IPv4或者IPv6地址可以转化为ip
类型,并且使用IP的mapping类型进行索引。
如果使用了auto
,那么会将字符串类型的字段值转化为最接近的non-string、non-IP类型。比如,字段值为true
时会转化为boolean
类型。注意的转化为float的优先级高于double。比如242.15
会自动转化为float类型。如果无法正确的自动转化,这个processor仍然算作处理成功并且字段值保持原样。这样,target_field
将被更新为转化前的值。
从文档中的单个文本字段提取CSV行的字段。CSV中的任何空字段都将被跳过。
解析字段中的日期。然后使用日期或者时间戳作为文档的timestamp。默认情况下,date processor
将解析出的日期作为一个名为@timestamp
的新字段。你可以通过target_field
指定一个不同的字段名。可以在同一个date processor中定义多个format。它们将在处理过程中按照定义中的顺序依次用于尝试解析日期。
这个processor的目的是使用date math index name support.,基于文档中日期或者时间字段,将文档指向正确的基于时间的索引。
processor根据提供的索引名称前缀、正在处理的文档中的日期或时间戳字段以及提供的日期舍入,设置带有日期数学索引名称表达式的_index
元数据字段。
首先processor获取处理中的文档中的日期或者时间戳字段的信息。然后(可选)将字段中的值根据format进行解析。然后,将此日期、提供的索引名称前缀和提供的日期舍入格式化为一个日期数学索引名称表达式(date math index name expression)。此处也可以可选地指定日期格式化,以指定日期应如何格式化为日期数学索引名称表达式。
跟Grok processor类型,dissect
同样从文档中单个文本字段中提取出结构化的字段。跟Grok processor不同的是,它不使用Regular Expressions。这使得dissect的语法更简单并且有些情况下性能快于Grok processor。
Dissect根据定义的模式去匹配某个单文本的字段。
比如有下面的模式:
xxxxxxxxxx
11%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}
将会匹配下面的一行日志:
xxxxxxxxxx
111.2.3.4 - - [30/Apr/1998:22:00:52 +0000] \"GET /english/venues/cities/images/montpellier/18.gif HTTP/1.0\" 200 3171
将会生成下面的字段:
xxxxxxxxxx
161"doc": {
2 "_index": "_index",
3 "_type": "_type",
4 "_id": "_id",
5 "_source": {
6 "request": "/english/venues/cities/images/montpellier/18.gif",
7 "auth": "-",
8 "ident": "-",
9 "verb": "GET",
10 "@timestamp": "30/Apr/1998:22:00:52 +0000",
11 "size": "3171",
12 "clientip": "1.2.3.4",
13 "httpversion": "1.0",
14 "status": "200"
15 }
16}
dissect pattern由字符串中部分被丢弃的值定义,比如上面的例子中,第一个被丢弃的部分就是单个空格。Dissect会从头开始找到这个空格,然后将这个空前的所有值设置为clientio
。后面dissect又匹配了[
以及]
,然后将[
跟]
中所有的值设置为@timestamp
。特别注意要丢弃的字符串部分将帮助构建成功的解析模式。
成功的匹配要求模式中的所有key都必须有一个value。如果模式中定义的任何%{keyname}
没有值,则会抛出异常,并且可能通过on_failure指令进行处理。可以使用空键%{}
或named skip key 匹配值,但从最终文档中排除该值。所有匹配的值都表示为字符串数据类型。可以使用convert processor将其转换为预期的数据类型。
Dissect还支持可以改变dissect默认行为的key modifiers。例如,你可以指示dissect忽略某些字段、追加字段、跳过填充等。更多信息请见下文
(8.2)link
将用点.
表示的字段名扩展成一个对象字段。这个processor使得可以让点字段(field with dot)能在pipeline中被其他processor访问。否则这些点字段无法被任何processor访问。
field:(Required)待扩展的点字段。如果设置为*
,那么top-level上所有的字段都会被扩展。
path:(Optional)点字段中包含了需要展开的点字段。如果你需要展开的点字段是另一个对象字段的key需要将path指定为对象字段的key,因为field
字段无法理解leaf fields
xxxxxxxxxx
71{
2"address": {
3 "street": {
4 "name": "Main St"
5 }
6}
7}
override:(Optional)(默认值:false)如果扩展后跟现有的字段冲突,是否进行覆盖。如果为true
,则覆盖,否则生成一个数组同时保留这两个值
xxxxxxxxxx
61{
2 "foo.bar" : "value2",
3 "foo" : {
4 "bar" : "value1"
5 }
6}
description:(Optional)processor的描述信息。用来描述配置或这个processor的目的
if:(Optional)有条件的运行processor。见Conditionally run a processor
ignore_failure:(Optional)(默认值:false)忽略processor的报错。见Handling pipeline failures
on_failure:(Optional)处理processor的报错。Handling pipeline failures
tag:(Optional)processor的标识符。对debugging或作为指标有用
xxxxxxxxxx
51{
2 "dot_expander": {
3 "field": "foo.bar"
4 }
5}
该processor会将下面的内容:
xxxxxxxxxx
31{
2 "foo.bar" : "value"
3}
扩展为:
xxxxxxxxxx
51{
2 "foo" : {
3 "bar" : "value"
4 }
5}
如果bar
已经是foo
的子字段,那么processor合并这两个值。如果这个字段属于标量字段(scalar field,即只包含单个值的字段),那么就将这两个字段值用数组表示
比如有以下的文档:
xxxxxxxxxx
61{
2 "foo.bar" : "value2",
3 "foo" : {
4 "bar" : "value1"
5 }
6}
会被dot_expander
扩展为:
xxxxxxxxxx
51
2 "foo" : {
3 "bar" : ["value1", "value2"]
4 }
5}
如果将override
选项设置为true
:
xxxxxxxxxx
61{
2 "dot_expander": {
3 "field": "foo.bar",
4 "override": true
5 }
6}
这种情况下就进行覆盖:
xxxxxxxxxx
51{
2 "foo" : {
3 "bar" : "value2"
4 }
5}
field
选项可以设置为*
将top-level的字段都进行扩展:
xxxxxxxxxx
51{
2 "dot_expander": {
3 "field": "*"
4 }
5}
该processor会将下面这篇文档:
xxxxxxxxxx
41{
2 "foo.bar" : "value",
3 "baz.qux" : "value"
<