Elasticsearch之索引管理
删除索引
用以下的请求来 删除索引:
DELETE /my_index
DELETE /index_one,index_two
DELETE /index_*
DELETE /_all
DELETE /*
对一些人来说,能够用单个命令来删除所有数据可能会导致可怕的后果。如果你想要避免意外的大量删除, 你可以在你的 elasticsearch.yml 做如下配置:
action.destructive_requires_name: true
这个设置使删除只限于特定名称指向的数据, 而不允许通过指定 _all 或通配符来删除指定索引库。你同样可以通过 Cluster State API 动态的更新这个设置。
索引设置
下面是两个 最重要的设置:
-
number_of_shards
每个索引的主分片数,默认值是 5 。这个配置在索引创建后不能修改。
-
number_of_replicas
每个主分片的副本数,默认值是 1 。对于活动的索引库,这个配置可以随时修改。
例如,我们可以创建只有 一个主分片,没有副本的小索引:
PUT /my_temp_index
{
"settings": {
"number_of_shards" : 1,
"number_of_replicas" : 0
}
}
然后,我们可以用 update-index-settings API 动态修改副本数:
PUT /my_temp_index/_settings
{
"number_of_replicas": 1
}
配置分析器
第三个重要的索引设置是 analysis 部分, 用来配置已存在的分析器或针对你的索引创建新的自定义分析器。
在下面的例子中,我们创建了一个新的分析器,叫做 es_std , 并使用预定义的 西班牙语停用词列表:
PUT /spanish_docs
{
"settings": {
"analysis": {
"analyzer": {
"es_std": {
"type": "standard",
"stopwords": "_spanish_"
}
}
}
}
}
es_std 分析器不是全局的--它仅仅存在于我们定义的 spanish_docs 索引中。 为了使用 analyze API来对它进行测试,我们必须使用特定的索引名:
GET /spanish_docs/_analyze
{
"analyzer":"my_analyzer",
"text":"El veloz zorro marrón"
}
自定义分析器
虽然Elasticsearch带有一些现成的分析器,然而在分析器上Elasticsearch真正的强大之处在于,你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单元过滤器来创建自定义的分析器。
PUT /my_index
{
"settings": {
"analysis": {
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [ "&=> and "]
}},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": [ "the", "a" ]
}},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": [ "html_strip", "&_to_and" ],
"tokenizer": "standard",
"filter": [ "lowercase", "my_stopwords" ]
}}
}}}
测试自定义分析器
GET /my_index/_analyze
{
"analyzer":"my_analyzer",
"text":"The quick & brown fox"
}
类型(type)和映射
Elasticsearch 类型是 以 Lucene 处理文档的这个方式为基础来实现的。一个索引可以有多个类型,这些类型的文档可以存储在相同的索引中。
Lucene 没有文档类型的概念,每个文档的类型名被存储在一个叫 _type 的元数据字段上。 当我们要检索某个类型的文档时, Elasticsearch 通过在 _type 字段上使用过滤器限制只返回这个类型的文档。
Lucene 也没有映射的概念。 映射是 Elasticsearch 将复杂 JSON 文档 映射 成 Lucene 需要的扁平化数据的方式。
-
避免类型陷阱
这导致了一个有趣的思想实验: 如果有两个不同的类型,每个类型都有同名的字段,但映射不同(例如:一个是字符串一个是数字),将会出现什么情况?
简单回答是,Elasticsearch 不会允许你定义这个映射。当你配置这个映射时,将会出现异常。
详细回答是,每个 Lucene 索引中的所有字段都包含一个单一的、扁平的模式。一个特定字段可以映射成 string 类型也可以是 number 类型,但是不能两者兼具。因为类型是 Elasticsearch 添加的 优于 Lucene 的额外机制(以元数据 _type 字段的形式),在 Elasticsearch 中的所有类型最终都共享相同的映射。
以 data 索引中两种类型的映射为例:
{
"data": {
"mappings": {
"people": {
"properties": {
"name": {
"type": "string",
},
"address": {
"type": "string"
}
}
},
"transactions": {
"properties": {
"timestamp": {
"type": "date",
"format": "strict_date_optional_time"
},
"message": {
"type": "string"
}
}
}
}
}
}
每个类型定义两个字段 (分别是 "name"/"address" 和 "timestamp"/"message" )。它们看起来是相互独立的,但在后台 Lucene 将创建一个映射,如:
{
"data": {
"mappings": {
"_type": {
"type": "string",
"index": "not_analyzed"
},
"name": {
"type": "string"
}
"address": {
"type": "string"
}
"timestamp": {
"type": "long"
}
"message": {
"type": "string"
}
}
}
}
注: 这不是真实有效的映射语法,只是用于演示
对于整个索引,映射在本质上被 扁平化 成一个单一的、全局的模式。这就是为什么两个类型不能定义冲突的字段:当映射被扁平化时,Lucene 不知道如何去处理。
-
类型讨论
那么,这个讨论的结论是什么?技术上讲,多个类型可以在相同的索引中存在,只要它们的字段不冲突(要么因为字段是互为独占模式,要么因为它们共享相同的字段)。
重要的一点是: 类型可以很好的区分同一个集合中的不同细分。在不同的细分中数据的整体模式是相同的(或相似的)。
类型不适合 完全不同类型的数据 。如果两个类型的字段集是互不相同的,这就意味着索引中将有一半的数据是空的(字段将是 稀疏的 ),最终将导致性能问题。在这种情况下,最好是使用两个单独的索引。
根对象
映射的最高一层被称为 根对象 ,它可能包含下面几项:
- 一个 properties 节点,列出了文档中可能包含的每个字段的映射
- 各种元数据字段,它们都以一个下划线开头,例如 _type 、 _id 和 _source
- 设置项,控制如何动态处理新的字段,例如 analyzer 、 dynamic_date_formats 和 dynamic_templates
- 其他设置,可以同时应用在根对象和其他 object 类型的字段上,例如 enabled 、 dynamic 和 include_in_all
动态映射
当 Elasticsearch 遇到文档中以前 未遇到的字段,它用 dynamic mapping 来确定字段的数据类型并自动把新的字段添加到类型映射。
有时这是想要的行为有时又不希望这样。通常没有人知道以后会有什么新字段加到文档,但是又希望这些字段被自动的索引。也许你只想忽略它们。如果Elasticsearch是作为重要的数据存储,可能就会期望遇到新字段就会抛出异常,这样能及时发现问题。
幸运的是可以用 dynamic 配置来控制这种行为 ,可接受的选项如下:
- true,动态添加新的字段--缺省
- false,忽略新的字段
- strict,如果遇到新字段抛出异常
配置参数 dynamic 可以用在根 object 或任何 object 类型的字段上。你可以将 dynamic 的默认值设置为 strict , 而只在指定的内部对象中开启它, 例如:
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic": "strict",
"properties": {
"title": { "type": "string"},
"stash": {
"type": "object",
"dynamic": true
}
}
}
}
}
使用上述动态映射, 你可以给 stash 对象添加新的可检索的字段:
PUT /my_index/my_type/1
{
"title": "This doc adds a new field",
"stash": { "new_field": "Success!" }
}
但是对根节点对象 my_type 进行同样的操作会失败:
PUT /my_index/my_type/1
{
"title": "This throws a StrictDynamicMappingException",
"new_field": "Fail!"
}
自定义动态映射
使用 dynamic_templates ,你可以完全控制 新检测生成字段的映射。你甚至可以通过字段名称或数据类型来应用不同的映射。
每个模板都有一个名称, 你可以用来描述这个模板的用途, 一个 mapping 来指定映射应该怎样使用,以及至少一个参数 (如 match) 来定义这个模板适用于哪个字段。
模板按照顺序来检测;第一个匹配的模板会被启用。例如,我们给 string 类型字段定义两个模板:
- es :以 _es 结尾的字段名需要使用 spanish 分词器。
- en :所有其他字段使用 english 分词器。
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{ "es": {
"match": "*_es",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "spanish"
}
}},
{ "en": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "english"
}
}}
]
}}}
缺省映射
通常,一个索引中的所有类型(type)共享相同的字段和设置。 default 映射更加方便地指定通用设置,而不是每次创建新类型时都要重复设置。 default 映射是新类型的模板。在设置 default 映射之后创建的所有类型都将应用这些缺省的设置,除非类型在自己的映射中明确覆盖这些设置。
例如,我们可以使用 default 映射为所有的类型禁用 _all 字段, 而只在 blog 类型启用:
PUT /my_index
{
"mappings": {
"_default_": {
"_all": { "enabled": false }
},
"blog": {
"_all": { "enabled": true }
}
}
}
重新索引数据
尽管可以增加新的类型到索引中,或者增加新的字段到类型中,但是不能添加新的分析器或者对现有的字段做改动。 如果你那么做的话,结果就是那些已经被索引的数据就不正确, 搜索也不能正常工作。
对现有数据的这类改变最简单的办法就是重新索引:用新的设置创建新的索引并把文档从旧的索引复制到新的索引。
字段 _source 的一个优点是在Elasticsearch中已经有整个文档。你不必从源数据中重建索引,而且那样通常比较慢。
为了有效的重新索引所有在旧的索引中的文档,用 scroll 从旧的索引检索批量文档 , 然后用 bulk API 把文档推送到新的索引中。
从Elasticsearch v2.3.0开始, Reindex API 被引入。它能够对文档重建索引而不需要任何插件或外部工具。
索引别名和零停机
在前面提到的,重建索引的问题是必须更新应用中的索引名称。 索引别名就是用来解决这个问题的!
索引 别名 就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何一个需要索引名的API来使用。别名 带给我们极大的灵活性。
有两种方式管理别名:
- _alias 用于单个操作
- _aliases 用于执行多个原子级操作
在本章中,我们假设你的应用有一个叫 my_index 的索引。事实上, my_index 是一个指向当前真实索引的别名。真实索引包含一个版本号: my_index_v1 , my_index_v2 等等。
首先,创建索引 my_index_v1 ,然后将别名 my_index 指向它:
# 创建索引 my_index_v1
PUT /my_index_v1
# 设置别名 my_index 指向 my_index_v1
PUT /my_index_v1/_alias/my_index
你可以检测这个别名指向哪一个索引:
GET /*/_alias/my_index
或哪些别名指向这个索引:
GET /my_index_v1/_alias/*
两者都会返回下面的结果:
{
"my_index_v1" : {
"aliases" : {
"my_index" : { }
}
}
}
然后,我们决定修改索引中一个字段的映射。当然,我们不能修改现存的映射,所以我们必须重新索引数据。 首先, 我们用新映射创建索引 my_index_v2 :
PUT /my_index_v2
{
"mappings": {
"my_type": {
"properties": {
"tags": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
然后我们将数据从 my_index_v1 索引到 my_index_v2 ,下面的过程在 重新索引你的数据 中已经描述过。一旦我们确定文档已经被正确地重索引了,我们就将别名指向新的索引。
一个别名可以指向多个索引,所以我们在添加别名到新索引的同时必须从旧的索引中删除它。这个操作需要原子化,这意味着我们需要使用 _aliases 操作:
POST /_aliases
{
"actions": [
{ "remove": { "index": "my_index_v1", "alias": "my_index" }},
{ "add": { "index": "my_index_v2", "alias": "my_index" }}
]
}
你的应用已经在零停机的情况下从旧索引迁移到新索引了。
即使你认为现在的索引设计已经很完美了,在生产环境中,还是有可能需要做一些修改的。做好准备:在你的应用中使用别名而不是索引名。然后你就可以在任何时候重建索引。别名的开销很小,应该广泛使用。