ES中的索引是一系列文档的集合,文档以JSON的数据格式存储在索引中,我们都知道,JSON数据格式是有一系列K-V键值对组成的,在K-V键值对中,每个value都是有数据类型的,如:字符串,整数,日期等类型。我们在创建一个索引时候,会定义这个索引中有哪些字段,以及这些字段的数据类型,这个类似于关系型数据库的schema。
在ES中我们可以使用如下脚本来定义一个索引:
PUT /my_index1
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"seq": {
"type": "long"
},
"other": {
"type": "text"
}
}
}
}
上述脚本,我们创建了一个索引:my_index1,在该索引中添加了一个默认映射类型:_doc,然后给这个映射类型添加了一些字段,如:name,seq和other,并给每个字段指定了字段类型。
当指定了字段的类型后,再向索引中索引文档时,要保证被索引文档中各个字段的类型和定义的映射类型中的字段类型匹配,否则会报错。
正确索引一个文档:
POST /my_index1/_doc/1
{
"name":"Smith",
"seq":"25",
"other":"sports"
}
错误索引一个文档
POST /my_index1/_doc/2
{
"name":"Smith",
"seq":"25d",
"other":"sports"
}
报错如下:
{
"error" : {
"root_cause" : [
{
"type" : "mapper_parsing_exception",
"reason" : "failed to parse field [seq] of type [long] in document with id '1'. Preview of field's value: '25d'"
}
],
"type" : "mapper_parsing_exception",
"reason" : "failed to parse field [seq] of type [long] in document with id '1'. Preview of field's value: '25d'",
"caused_by" : {
"type" : "illegal_argument_exception",
"reason" : "For input string: \"25d\""
}
},
"status" : 400
}
这个是符合预期的,因为seq是long类型的,但是在错误场景下,被索引文档的seq的类型不是long类型。但是这里也暴露出来一个问题,如果我们需要修改一个索引中某个字段类型的话,该如何操作呢?虽然ES直接对字段类型就行修改,但是这样做的话,那么索引中现有的存量数据就会有问题,会影响对存量数据的检索。
对于上述问题,在ES中常用的做法是“重建索引”,所谓重建索引就是:重新创建一个新的索引,在新的索引中设置符合预期的字段类型,然后将旧索引中的全部数据索引到新索引中,最后在业务层面,查询切换到新索引上,把旧索引删除。
为了更好的实现“重建索引”,ES提供了重建索引的API:reIndex。而且在索引文档时,ES会存储文档的全部数据到字段_source中,这也为重建索引提供了极大的方便,否则我们可能需要从其他数据源获取存量的历史数据。
在进行大量文档重建索引时,需要有一下注意点:
同时并行运行多个重建索引任务的 时候,正确的做法是按日期或者时间 这样的字段作为过滤条件把大的重建索引分成小的任务:
GET /old_index/_search?scroll=1m
{
"query": {
"range": {
"date": {
"gte": "2014-01-01",
"lt": "2014-02-01"
}
}
},
"sort": ["_doc"],
"size": 1000
}
分成多批来进行数据查询,然后在索引到新的索引中。
reindex的基本使用方式如下:
POST _reindex
{
"source": {
"index": "index1",
"type": "type1",
"query": {
"term": {
"field": "value"
}
}
},
"dest": {
"index": "index2"
}
}
将索引index1中类型为type1,且满足指定条件的文档复制一个新的索引index2中。Reindex的具体使用方式可以参考reindex。
上面我们说了使用重建索引可以更新索引中的字段类型,但是这里有一个问题:在索引重建后,需要在业务层面做一定的调整,将检索对象设置为新的索引,这个调整会影响业务的可用性,也就是业务层面要感知这个调整。
而在ES中提供了“别名”的功能,可以实现在业务无感知的前提下,完成新旧索引的切换。
索引别名就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何一个需要索引名的API来使用。别名带给我们极大的灵活性,允许我们做下面这些:
1.在运行的集群中可以无缝的从一个索引切换到另一个索引。
2.给多个索引分组。
3.给索引的一个子集创建视图。
有两种方式管理别名: _alias 用于单个操作, _aliases 用于执行多个原子级操作。下面我么介绍一些对索引别名的一些基本操作
1.绑定别名
创建索引 my_index_v1 ,然后将别名 my_index 指向它:
PUT /my_index_v1
PUT /my_index_v1/_alias/my_index
2.查看索引绑定的别名
GET /my_index_v1/_alias/*
3.查看别并被绑定的索引
GET /_cat/indices/my_index
4.原子切换新旧索引
如果我们想修改my_index1中的索引类型,按照前文所述,此时我们需要重新创建一个新的索引 my_index2,然后将my_index1中的文档迁移到my_index2中,然后使用别名的方式完成两者的切换。
POST /_aliases
{
"actions": [
{ "remove": { "index": "my_index_v1", "alias": "my_index" }},
{ "add": { "index": "my_index_v2", "alias": "my_index" }}
]
}
即使我们觉得现有的索引设计已经很完美了,然而在生产环境中,还是有可能需要做一些修改的,为了方便索引切换,在做业务开发的时候,我们要尽量面向别名检索,而非具体的索引,就像在面向对象编程范式中,尽量面向抽象而非面向具体进行编程一样。