索引管理
本文翻译自Elasticsearch官方指南的索引管理(Index Management)一章
我们已经了解了ES是如何在不需要任何复杂的计划和安装就能让我们很容易地开始开发一个新的应用的。但是,用不了多久你就会想要仔细调整索引和搜索过程来更好的适配你的用例。
几乎所有的定制都和索引(Index)以及其中的类型(Type)相关。本章我们就来讨论用于管理索引和类型映射的API,以及最重要的设置。
创建索引
到现在为止,我们已经通过索引一份文档来完成了新索引的创建。这个索引是使用默认的设置创建的,新的域通过动态映射(Dynamic Mapping)的方式被添加到了类型映射(Type Mapping)中。
现在我们对这个过程拥有更多的控制:我们需要确保索引被创建时拥有合适数量的主分片(Primary Shard),并且在索引任何数据之前,我们需要设置好解析器(Analyzers)以及映射(Mappings)。
因此我们需要手动地去创建索引,将任何需要的设置和类型映射传入到请求正文中,就像下面这样:
PUT /my_index { "settings": { ... }, "mappings": { "type_one": { ... }, "type_two": { ... }, ... } }
事实上,如果你想阻止索引被自动创建,可以通过添加下面的设置到每个节点的config/elasticsearch.yml
文件中:
action.auto_create_index: false
将来,我们会讨论如何使用索引模板(Index Template)来预先定义自动生成的索引。这个功能在索引日志数据的时候有用武之地:索引的名字中会包含日期,每天都有一个有着合适配置的索引被自动地生成。
删除索引
使用下面的请求完成索引的删除:
DELETE /my_index
你也可以删除多个索引:
DELETE /index_one,index_two
DELETE /index_*
你甚至还可以删除所有的索引:
DELETE /_all
索引设置
虽然索引的种种行为可以通过索引模块的参考文档介绍的那样进行配置,但是……
TIP
ES中提供了一些很好的默认值,只有当你知道它是干什么的,以及为什么要去修改它的时候再去修改。
两个最重要的设置:
number_of_shards
一个索引中含有的主分片(Primary Shard)的数量,默认值是5。在索引创建后这个值是不能被更改的。
number_of_replicas
每一个主分片关联的副本分片(Replica Shard)的数量,默认值是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),可以利用已经存在的解析器(Analyzer)进行配置,或者是为你的索引定制新的解析器。
在解析和解析器中,我们介绍了一些内置的解析器,它们用来将全文字符串转换成适合搜索的倒排索引(Inverted Index)。
对于全文字符串字段默认使用的是standard
解析器,它对于多数西方语言而言是一个不错的选择。它包括:
standard
分词器。它根据词语的边界进行分词。standard
token过滤器。用来整理上一步分词器得到的tokens,但是目前是一个空操作(no-op)。lowercase
token过滤器。将所有tokens转换为小写。stop
token过滤器。移除所有的stopwords,比如a,the,and,is等
默认下stopwords过滤器没有被使用。可以通过创建一个基于standard
解析器的解析器并设置stopwords
参数来启用。要么提供一个stopwords的列表或者告诉它使用针对某种语言预先定义的stopwords列表。
在下面的例子中,我们创建了一个名为es_std
的解析器,它使用了预先定义的西班牙语中的stopwords列表:
PUT /spanish_docs { "settings": { "analysis": { "analyzer": { "es_std": { "type": "standard", "stopwords": "_spanish_" } } } } }
es_std
解析器不是全局的 - 它只作用于spanish_docs
索引。可以通过制定索引名,使用analyze
API进行测试:
GET /spanish_docs/_analyze?analyzer=es_std { El veloz zorro marrón }
下面的部分结果显示了西班牙语中的stopword El
已经被正确地移除了:
{ "tokens" : [ { "token" : "veloz", "position" : 2 }, { "token" : "zorro", "position" : 3 }, { "token" : "marrón", "position" : 4 } ] }
自定义解析器(Custom Analyzers)
虽然ES本身已经提供了一些解析器,但是通过组合字符过滤器(Character Filter),分词器(Tokenizer)以及词条过滤器(Token Filter)来创建你自己的解析器才会显示出其威力。
在解析和解析器中,我们提到过解析器(Analyzer)就是将3种功能打包得到的,它会按照下面的顺序执行:
-
字符过滤器(Character Filter) 字符过滤器用来在分词前将字符串进行"整理"。比如,如果文本是HTML格式,那么它会含有类似
<p>
或者<div>
这样的HTML标签,但是这些标签我们是不需要索引的。我们可以使用html_strip
字符过滤器移除所有的HTML标签,并将所有的像Á这样的HTML实体(HTML Entity)转换为对应的Unicode字符:Á。 -
分词器(Tokenizers) 一个解析器必须有一个分词器。分词器将字符串分解成一个个单独的词条(Term or Token)。在
standard
解析器中使用的standard
分词器,通过单词边界对字符串进行划分来得到词条,同时会移除大部分的标点符号。另外还有其他的分词器拥有着不同的行为。比如
keyword
分词器,它不会进行任何分词,直接原样输出。whitespace
分词器则只通过对空白字符进行划分来得到词条。而pattern
分词器则根据正则表达式来进行分词。 -
词条过滤器(Token Filter) 在分词后,得到的词条流(Token Stream)会按照顺序被传入到指定的词条过滤器中。
词条过滤器能够修改,增加或者删除词条。我们已经提到了
lowercase
词条过滤器和stop
词条过滤器,但是ES中还有许多其它可用的词条过滤器。stemming
词条过滤器会对单词进行词干提取来得到其词根形态(Root Form)。ascii_folding
词条过滤器则会移除变音符号(Diacritics),将类似于très
的词条转换成tres
。ngram
词条过滤器和edge_ngram
词条过滤器会产生适用于部分匹配(Partial Matching)或者自动完成(Autocomplete)的词条。
在深入搜索中,我们会通过例子来讨论这些分词器和过滤器的使用场景和使用方法。但是首先,我们需要解释如何来创建一个自定义的解析器。
创建一个自定义的解析器
和上面我们配置es_std
解析器的方式相同,我们可以在analysis
下对字符过滤器,分词器和词条过滤器进行配置:
PUT /my_index
{
"settings": {
"analysis": {
"char_filter": { ... custom character filters ... },
"tokenizer": { ... custom tokenizers ... },
"filter": { ... custom token filters ... },
"analyzer": { ... custom analyzers ... }
}
}
}
比如,要创建拥有如下功能的解析器:
- 使用
html_strip
字符过滤器完成HTML标签的移除。 - 将&字符替换成" and ",使用一个自定义的
mapping
字符过滤器。
"char_filter": {
"&_to_and": {
"type": "mapping",
"mappings": [ "&=> and "]
}
}
- 使用
standard
分词器对文本进行分词。 - 使用
lowercase
词条过滤器将所有词条转换为小写。 - 使用一个自定义的stopword列表,并通过自定义的stop词条过滤器将它们移除:
"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" ]
}
}
因此,整个create-index
请求就像下面这样:
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" ]
}}
}}}
创建索引之后,使用analyze
API对新的解析器进行测试:
GET /my_index/_analyze?analyzer=my_analyzer
The quick & brown fox
得到的部分结果如下,表明我们的解析器能够正常工作:
{
"tokens" : [
{ "token" : "quick", "position" : 2 },
{ "token" : "and", "position" : 3 },
{ "token" : "brown", "position" : 4 },
{ "token" : "fox", "position" : 5 }
]
}
我们需要告诉ES这个解析器应该在什么地方使用。我们可以将它应用在string
字段的映射中:
PUT /my_index/_mapping/my_type { "properties": { "title": { "type": "string", "analyzer": "my_analyzer" } } }
类型和映射(Types and Mappings)
在ES中的类型(Type)代表的是一类相似的文档。一个类型包含了一个名字(Name) - 比如user
或者blogpost
- 以及一个映射(Mapping)。映射就像数据库的模式那样,描述了文档中的字段或者属性,和每个字段的数据类型 -string
,integer
,date
等 - 这些字段是如何被Lucene索引和存储的。
在什么是文档中,我们说一个类型就好比关系数据库中的一张表。尽管一开始这样思考有助于理解,但是对类型本身进行更细致的解释 - 它们到底是什么,它们是如何在Lucene的基础之上实现的 - 仍然是有价值的。
Lucene是如何看待文档的
Lucene中的文档包含的是一个简单field-value对的列表。一个字段至少要有一个值,但是任何字段都可以拥有多个值。类似的,一个字符串值也可以通过解析阶段而被转换为多个值。Lucene不管值是字符串类型,还是数值类型或者什么别的类型 - 所有的值都会被同等看做一些不透明的字节(Opaque bytes)。
当我们使用Lucene对文档进行索引时,每个字段的值都会被添加到倒排索引(Inverted Index)的对应字段中。原始值也可以被选择是否会不作修改的被保存到索引中,以此来方便将来的获取。
类型是如何实现的
ES中的type是基于以下简单的基础进行实现的。一个索引中可以有若干个类型,每个类型又有它自己的mapping,然后类型下的任何文档可以存储在同一个索引中。
可是Lucene中并没有文档类型这一概念。所以在具体实现中,类型信息通过一个元数据字段_type
记录在文档中。当我们需要搜索某个特定类型的文档时,ES会自动地加上一个针对_type字段的过滤器来保证返回的结果都是目标类型上的文档。
同时,Lucene中也没有映射的概念。映射是ES为了对复杂JSON文档进行扁平化(可以被Lucene索引)而设计的一个中间层。
比如,user
类型的name
字段可以定义成一个string
类型的字段,而它的值则应该被whitespace
解析器进行解析,然后再被索引到名为name
的倒排索引中。
"name": {
"type": "string",
"analyzer": "whitespace"
}
避免类型中的陷阱
由于不同类型的文档能够被添加到相同的索引中,产生了一些意想不到的问题。
比如在我们的索引中,存在两个类型:blog_en
用来保存英文的博文,blog_es
用来保存西班牙文的博文。这两种类型中都有一个title
字段,只不过它们使用的解析器分别是english
和spanish
。
问题可以通过下面的查询反映:
GET /_search
{
"query": {
"match": {
"title": "The quick brown fox"
}
}
}
我们在两个类型中搜索title
字段。查询字符串(Query String)需要被解析,但是应该使用哪个解析器:是spanish
还是english
?答案是会利用首先找到的title
字段对应的解析器,因此对于部分文档这样做是正确的,对于另一部分则不然。
我们可以通过将字段命名地不同 - 比如title_en
和title_es
- 或者通过显式地将类型名包含在字段名中,然后对每个字段独立查询来避免这个问题:
GET /_search
{
"query": {
"multi_match": {
"query": "The quick brown fox",
"fields": [ "blog_en.title", "blog_es.title" ]
}
}
}
multi_match
查询会对指定的多个字段运行match
查询,然后合并它们的结果。
以上的查询中对blog_en.title
字段使用english
解析器,对blog_es.title
字段使用spanish
解析器,然后对两个字段的搜索结果按照相关度分值进行合并。
这个解决方案能够在两个域是相同数据类型时起作用,但是考虑下面的场景,当向相同索引中添加两份文档时会发生什么:
类型user
{ "login": "john_smith" }
类型event
{ "login": "2014-06-01" }
Lucene本身不在意类型一个字段是字符串类型,而另一个字段是日期类型 - 它只是愉快地将它们当做字节数据进行索引。
但是当我们试图去针对event.login
字段进行排序的时候,ES需要将login
字段的值读入到内存中。根据Fielddata提到的,ES会将索引中的所有文档都读入,无论其类型是什么。
取决于ES首先发现的login
字段的类型,它会试图将这些值当做字符串或者日期类型读入。因此,这会产生意料外的结果或者直接失败。
Tip 为了避免发生这些冲突,建议索引中,每个类型的同名字段都使用相同的映射方式。
根对象(Root Object)
映射的最顶层被称为根对象。它包含了:
- 属性区域(Properties Section),列举了文档中包含的每个字段的映射信息。
- 各种元数据(Metadata)字段,它们都以_开头,比如
_type
,_id
,_source
。 - 控制用于新字段的动态探测(Dynamic Detection)的设置,如
analyzer
,dynamic_date_formats
和dynamic_templates
。 - 其它的可以用在根对象和
object
类型中的字段上的设置,如enabled
,dynamic
和include_in_all
。
属性(Properties)
我们已经在核心简单字段类型(Core Simple Field Type)和复杂核心字段类型(Complex Core Field Type)中讨论了对于文档字段或属性最为重要的三个设置:
type
:字段的数据类型,比如string
或者date
。index
:一个字段是否需要被当做全文(Full text)进行搜索(analyzed
),被当做精确值(Exact value)进行搜索('not_analyzed'),或者不能被搜索(no
)。analyzer
:全文字段在索引时(Index time)和搜索时(Search time)使用的analyzer
。
我们会在后续章节中合适的地方讨论诸如ip
,geo_point
和geo_shape
等其它字段类型。
元数据:_source
字段
默认,ES会将表示文档正文的JSON字符串保存为_source
字段。和其它存储的字段一样,_source
字段也会在保存到磁盘上之前被压缩。
这个功能几乎是总被需要的,因为它意味着:
- 完整的文档在搜索结果中直接就是可用的 - 不需要额外的请求来得到完整文档
_source
字段让部分更新请求(Partial Update Request)成为可能- 当映射发生变化而需要对数据进行重索引(Reindex)时,你可以直接在ES中完成,而不需要从另外一个数据存储(Datastore)(通常较慢)中获取所有文档
- 在你不需要查看整个文档时,可以从
_source
直接抽取出个别字段,通过get
或者search
请求返回 - 调试查询更容易,因为可以清楚地看到每个文档包含的内容,而不需要根据一个ID列表来对它们的内容进行猜测
即便如此,存储_store
字段确实会占用磁盘空间。如果以上的任何好处对你都不重要,你可以使用以下的映射来禁用_source
字段:
PUT /my_index
{
"mappings": {
"my_type": {
"_source": {
"enabled": false
}
}
}
}
在一个搜索请求中,你可以只要求返回部分字段,通过在请求正文(Request body)中指定_source
参数:
GET /_search
{
"query": { "match_all": {}},
"_source": [ "title", "created" ]
}
这些字段的值会从_source
字段中被抽取出来并返回,而不是完整的_source
。
存储字段(Stored fields)
除了将一个字段的值索引外,你还可以选择将字段的原始值(Original field value)进行
store
来方便将来的获取。有过使用Lucene经验的用户会使用存储字段来选择在搜索结果中能够被返回的字段。实际上,_source
字段就是一个存储字段。在ES中,设置个别的文档字段为存储字段通常都是一个错误的优化。整个文档已经通过
_source
字段被保存了。使用_source
参数来指定需要抽取的字段几乎总是更好的方案。
元数据:_all
字段
在简化搜索(Search Lite)中我们介绍了_all
字段:它是一个特殊的字段,将其它所有字段的值当做一个大的字符串进行索引。query_string
查询语句(以及?q=john
这种形式的查询)在没有指定具体字段的时候,默认搜索的就是_all
字段。
_all
字段在一个新应用的探索阶段有用处,此时你对文档的最终结构还不太确定。你可以直接使用任何搜索字符串,并且也能够得到需要的结果:
GET /_search
{
"match": {
"_all": "john smith marketing"
}
}
随着你的应用逐渐成熟,对搜索要求也变的更加精确,你就会越来越少地使用_all
字段。 _all
字段是一种搜索的霰弹枪策略(Shotgun approach)。通过查询个别字段,你可以对搜索结果有更灵活,强大和细粒度的控制,来保证结果是最相关的。
在相关度算法(Relevance Algorithm)中一个重要的考量因素是字段的长度:字段越短,那么它就越重要。一个出现在较短的
title
字段中的词条会比它出现在较长的content
字段中时要更重要。而这个关于字段长度的差别在_all
字段中时不存在的。
如果你决定不再需要_all
字段了,那么可以通过下面的映射设置来禁用它:
PUT /my_index/_mapping/my_type
{
"my_type": {
"_all": { "enabled": false }
}
}
可以使用include_in_all
设置来对每个字段进行设置,是否需要将它包含到_all
字段中,默认值是true
。在一个对象(或者在根对象上)设置include_in_all
会改变其中所有字段的默认设置。
如果你只需要将部分字段添加到_all
字段中,比如title
,overview
,summary
,tags
等,用来方便地进行全文搜索。那么相比完全禁用_all
,你可以将include_in_all
默认设置为对所有字段禁用,然后对你选择的字段启用:
PUT /my_index/my_type/_mapping
{
"my_type": {
"include_in_all": false,
"properties": {
"title": {
"type": "string",
"include_in_all": true
},
...
}
}
}
需要记住的是,_all
字段也只不过是一个被解析过的string
字段。它使用默认的解析器来解析其值,无论来源字段中设置的是什么解析器。和任何string
字段一样,你也可以配置_all
字段应该使用的解析器:
PUT /my_index/my_type/_mapping
{
"my_type": {
"_all": { "analyzer": "whitespace" }
}
}
元数据:文档ID(Document Identity)
和文档ID相关的有四个元数据字段:
_id
:文档的字符串ID_type
:文档的类型_index
:文档属于的索引_uid
:_type
和_id
的结合,type#id
默认情况下,_uid
字段会被保存和索引。意味着它可以被获取,也可以被搜索。_type
字段会被索引但不会被保存。_id
和_index
既不会被索引也不会被保存,也就是说它们实际上是不存在的。
尽管如此,你还是能够查询_id
字段,就好像它是一个实实在在的字段一样。ES使用_uid
字段来得到_id
。尽管你可以为这些字段修改index
和store
设置,但是你几乎不需要这么做。
_id
字段有一个你也许会用到的设置:path
,它用来告诉ES:文档应该从某个字段中抽取一个值来作为它自身的_id
。
PUT /my_index
{
"mappings": {
"my_type": {
"_id": {
"path": "doc_id"
},
"properties": {
"doc_id": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
以上请求设置_id
来源于doc_id
字段。
然后,当你索引一份文档:
POST /my_index/my_type
{
"doc_id": "123"
}
得到的结果是这样的:
{
"_index": "my_index",
"_type": "my_type",
"_id": "123",
"_version": 1,
"created": true
}
警告
这样做虽然很方便,但是它对于
bulk
请求(参考为什么选择有趣的格式)有一些性能影响。处理请求的节点不能够利用优化的批处理格式:仅通过解析元数据行来得知哪个分片(Shard)应该接受该请求。相反,它需要解析文档正文部分。
动态映射(Dynamic Mapping)
当ES在文档中碰到一个以前没见过的字段时,它会利用动态映射来决定该字段的类型,并自动地对该字段添加映射。
有时这正是需要的行为,但有时不是。你或许不知道在以后你的文档中会添加哪些字段,但是你想要它们能够被自动地索引。或许你只是想要忽略它们。或者 - 尤其当你将ES当做主要的数据存储使用时 - 大概你会希望这些未知的字段会抛出异常来提醒你注意这一问题。
幸运的是,你可以通过dynamic
设置来控制这一行为,它能够接受以下的选项:
true
:默认值。动态添加字段false
:忽略新字段strict
:如果碰到陌生字段,抛出异常
dynamic
设置可以适用在根对象上或者object
类型的任意字段上。你应该默认地将dynamic
设置为strict
,但是为某个特定的内部对象启用它:
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic": "strict",
"properties": {
"title": { "type": "string"},
"stash": {
"type": "object",
"dynamic": true
}
}
}
}
}
在my_type
对象上如果碰到了未知字段则会抛出一个异常。 在stash
对象上会动态添加新字段。
通过以上的映射,你可以向stash
添加新的可搜索的字段:
PUT /my_index/my_type/1
{
"title": "This doc adds a new field",
"stash": {
"new_field": "Success!"
}
}
但是,如果在顶层对象上试图添加新字段则会失败:
PUT /my_index/my_type/1
{
"title": "This throws a StrictDynamicMappingException",
"new_field": "Fail!"
}
NOTE
将
dynamic
设置为false
并不会改变_source
字段的内容 -_source
字段仍然会保存你索引的整个JSON文档。只不过是陌生的字段将不会被添加到映射中,以至于它不能被搜索到。
自定义动态映射
如果你知道你需要动态的添加的新字段,那么你也许会启用动态映射。然而有时动态映射的规则又有些不够灵活。幸运的是,你可以调整某些设置来让动态映射的规则更加适合你的数据。
date_detection
当ES碰到一个新的字符串字段时,它会检查该字串是否含有一个可被识别的日期,比如2014-01-01
。如果存在,那么它会被识别为一个date
类型的字段。否则会将它作为string
进行添加。
有时这种行为会导致一些问题。如果你想要索引一份这样的文档:
{ "note": "2014-01-01" }
假设这是note
字段第一次被发现,那么根据规则它会被作为date
字段添加。但是如果下一份文档是这样的:
{ "note": "Logged out" }
这时该字段显然不是日期,但是已经太迟了。该字段的类型已经是日期类型的字段了,因此这会导致一个异常被抛出。
可以通过在根对象上将date_detection
设置为false
来关闭日期检测:
PUT /my_index
{
"mappings": {
"my_type": {
"date_detection": false
}
}
}
有了以上的映射,一个字符串总是会被当做string
类型。如果你需要一个date
字段,你需要手动地添加它。
NOTE
ES中识别日期的方法可以通过
dynamic_date_formats
设置改变。
dynamic_templates
通过dynamic_templates
,你可以拥有对新字段的动态映射规则拥有完全的控制。你设置可以根据字段名称或者类型来使用一个不同的映射规则。
每个模板都有一个名字,可以用来描述这个模板做了什么。同时它有一个mapping
用来指定具体的映射信息,和至少一个参数(比如match
)用来规定对于什么字段需要使用该模板。
模板的匹配是有顺序的 - 第一个匹配的模板会被使用。比如我们可以为string
字段指定两个模板:
es
:以_es
结尾的字段应该使用spanish
解析器en
:其它所有字段使用english
解析器
我们需要将es
模板放在第一个,因为它相比能够匹配所有字符串字段的en
模板更加具体:
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"
}
}}
]
}}}
match_mapping_type
允许你只对特定类型的字段使用模板,正如标准动态映射规则那样,比如string
,long
等。
match
参数只会匹配字段名,path_match
参数用于匹配对象中字段的完整路径,比如address.*.name
可以匹配如下字段:
{
"address":
"city":
"name": "New York"
}
}
}
unmatch
和path_unmatch
模式能够用来排除某些字段,没有被排除的字段则会被匹配。
更多的配置选项可以在根对象的参考文档中找到。
默认映射(Default Mapping)
一般情况下,索引中的所有类型都会有相似的字段和设置。因此将这些常用设置在_default
映射中指定会更加方便,这样就不需要在每次创建新类型的时候都重复设置。_default
映射的角色是新类型的模板。所有在_default
映射之后创建的类型都会包含所有的默认设置,除非显式地在类型映射中进行覆盖。
比如,我们使用_default
映射对所有类型禁用_all
字段,唯独对blog
类型启用它。可以这样实现:
PUT /my_index
{
"mappings": {
"_default_": {
"_all": { "enabled": false }
},
"blog": {
"_all": { "enabled": true }
}
}
}
_default_
映射同时也是一个声明作用于整个索引的动态模板(Dynamic Templates)的好地方。
数据重索引
虽然你可以向索引中添加新的类型,或者像类型中添加新的字段,但是你不能添加新的解析器或者对现有字段进行修改。如果你这么做了,就会让已经索引的数据变的不正确,导致搜索不能正常的进行。
为已经存在的数据适用这些更改的最简单的方法就是重索引(Reindex):新建一个拥有最新配置的索引,然后将所有旧索引中的数据拷贝到新的索引中。
_source
字段的一个优势是在ES中你已经拥有了整个文档。你不需要通过数据库来重建你的索引,这种方法通常会更慢。
为了从旧索引中高效地对所有文档进行重索引,可以使用scan和scroll来批量地从旧索引中获取文档,然后使用bulk API将它们添加到新索引中。
批量重索引
你可以同时执行多个重索引任务,但是你显然不想要它们的结果有任何重叠。可以根据
date
或者timestamp
字段对重索引的任务进行划分,成为规模较小的任务:GET /old_index/_search?search_type=scan&scroll=1m { "query": { "range": { "date": { "gte": "2014-01-01", "lt": "2014-02-01" } } }, "size": 1000 }如果你正在持续地更改旧索引中的数据,想必你也希望这些更改也会被反映到新索引中。这可以通过再次运行重索引任务来完成,但是还是可以通过对日期字段进行过滤来得到在上次重索引开始后才被添加的文档。
索引别名和零停机时间(Index Alias and Zero Downtime)
重索引的问题在于你需要更新你的应用让它使用新的索引名。而索引别名可以解决这个问题。
一个索引别名就好比一个快捷方式(Shortcut)或一个符号链接(Symbolic Link),索引别名可以指向一个或者多个索引,可以在任何需要索引名的API中使用。使用别名可以给我们非常多的灵活性。它能够让我们:
- 在一个运行的集群中透明地从一个索引切换到另一个索引
- 让多个索引形成一个组,比如
last_three_months
- 为一个索引中的一部分文档创建一个视图(View)
我们会在本书的后面讨论更多关于别名的其它用途。现在我们要解释的是如何在零停机时间的前提下,使用别名来完成从旧索引切换到新索引。
有两个用来管理别名的端点(Endpoint):_alias
用来完成单一操作,_aliases
用来原子地完成多个操作。
在这个场景中,我们假设你的应用正在使用一个名为my_index
的索引。实际上,my_index
是一个别名,它指向了当前正在使用的真实索引。我们会在真实索引的名字中包含一个版本号码:my_index_v1
,my_index_v2
等。
首先,创建索引my_index_v1
,然后让别名my_index
指向它:
PUT /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" }}
]
}
现在你的应用就在零停机时间的前提下,实现了旧索引到新索引的透明切换。
TIP
即使你认为当前的索引设计是完美的,将来你也会发现有一些部分需要被改变,而那个时候你的索引已经在生产环境中被使用了。
在应用中使用索引别名而不是索引的真实名称,这样你就能够在任何需要的时候执行重索引操作。应该充分地使用别名。
原文来自:
http://blog.csdn.net/dm_vincent/article/details/41440539
http://blog.csdn.net/dm_vincent/article/details/41478321
http://blog.csdn.net/dm_vincent/article/details/41511821
http://blog.csdn.net/dm_vincent/article/details/41593987
http://blog.csdn.net/dm_vincent/article/details/41643793
原文翻译自:
http://blog.csdn.net/dm_vincent/article/details/41643793