elasticsearnch索引管理
索引管理
一. 创建索引
创建时做到
- 确保索引被创建在适当数量的分片上
- 在索引数据之前设置好分析器和类型映射
在请求加入所有设置和类型映射
PUT /my_index
{
    "settings": { ... any settings ... },
    "mappings": {
        "type_one": { ... any mappings ... },
        "type_two": { ... any mappings ... },
        ...
}
可以通过在 config/elasticsearch.yml 中添加下面的配置来防止自动创建索引
action.auto_create_index: false
索引模板自动预先配置索引,可以将日志数据索引在一个以日期结尾的索引上;
这样第二天会创建一个新的索引;
删除索引
删除索引:
DELETE /my_index
删除多个索引
DELETE /index_one,index_two
DELETE /index_*
删除所有索引
DELETE /_all
二. 索引设置
默认配置做好了优化,非常了解才会改
两个重要配置:
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
}
三. 配置分析器
standard分析器
- standard 分词器,在词层级上分割输入的文本。
- standard 标记过滤器,被设计用来整理分词器触发的所有标记(但是目前什么都没做)。
- lowercase 标记过滤器,将所有标记转换为小写。
- stop 标记过滤器,删除所有可能会造成搜索歧义的停用词,如 a,the,and,is
默认情况停用词过滤器是被禁用的,如果开启;
可以基于 standard 分析器的自定义分析器,并且设置 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
下面简化的结果中显示停用词 El 被正确的删除了
{
  "tokens" : [
    { "token" :    "veloz",   "position" : 2 },
    { "token" :    "zorro",   "position" : 3 },
    { "token" :    "marrón",  "position" : 4 }
  ]
}
四. 自定义分析器
分析器:字符过滤器,分词器,标记过滤器的组合
设置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 标记过滤器将词转为小写
用 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" ]
    }
}
用下面的方式可以将以上请求合并成一条
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"
        }
    }
}
五. 映射
类型表示一组相似的文档,名称+映射组成;
描述了文档中可能包含的每个字段的属性,数据类型,以及是否这些字段需要被lucene索引;
类型类似关系型数据库中的表格
Lucene 如何处理文档
Lucene中一个文档由一组简单的键值对组成,一个字段至少需要有一个值或多个值;
Lucene 不关心这些值是字符串,数字或日期,所有的值都被当成 不透明字节
类型是怎么实现的
一个索引可能包含多个类型
每个类型有各自的映射和文档,保存在同一个索引中
每个文档的类型名被储存在一个叫 _type 的元数据字段上
当我们搜索一种特殊类型的文档时,Elasticsearch 简单的通过 _type 字段来过滤出这些文档
Lucene 没有映射的概念,映射是 Elasticsearch 将复杂 JSON 文档映射成 Lucene 需要的扁平化数据的方式。
user 类型中 name 字段的映射声明这个字段是一个 string 类型,在被加入倒排索引之前,它的数据需要通过 whitespace 分析器来分析.
"name": {
    "type":     "string",
    "analyzer": "whitespace"
}
预防类型陷阱
如果在一个索引中有不同的类型,字段相同;
比如:blog_en表示英文博客,blog_es表示西班牙博客;
两种类型有相同的title字段,一个使用english分析器,一个使用spanish 分析器
下面的查询会出现问题:
GET /_search
{
    "query": {
        "match": {
            "title": "The quick brown fox"
        }
    }
}
Elasticsearch 会采用第一个被找到的 title 字段使用的分析器
通常通过取别名避免这种错误,比如,用 title_en 和 title_es,或者在查询中明确包含各自的类型名
GET /_search
{
    "query": {
        "multi_match": { <1>
            "query":    "The quick brown fox",
            "fields": [ "blog_en.title", "blog_es.title" ]
        }
    }
}
english分析器用于 blog_en.title 字段,spanish 分析器用于 blog_es.title 字段;
这种方法解决相同字段,不同分词器的问题;
将下面两个文档加入同一个索引
类型: user
{ "login": "john_smith" }
类型: event
{ "login": "2014-06-01" }
Lucene不在乎一个字段是字符串而另一个字段是日期,它会一视同仁的索引这两个字段;
排序 event.login 字段,Elasticsearch 需要将 login 字段的值加载到内存中,不会在意类型;
按照第一次加载的login类型加载,所以后面可能会遇到错误后发生其他问题;
六. 根对象
根对象
映射的最高一层被称为根对象
- 一个 properties 节点,列出了文档中可能包含的每个字段的映射
- 多个元数据字段,每一个都以下划线开头,例如 _type, _id 和 _source
- 设置项,控制如何动态处理新的字段,例如 analyzer, dynamic_date_formats 和 dynamic_templates。
- 其他设置,可以同时应用在根对象和其他 object 类型的字段上,例如 enabled, dynamic 和 include_in_all
属性
文档字段和属性的三个最重要的设置
- type: 字段的数据类型,例如 string 和 date
- index: 字段是否应当被当成全文来搜索(analyzed),或被当成一个准确的值(not_analyzed),还是完全不可被搜索(no)
- analyzer: 确定在索引和或搜索时全文字段使用的 分析器。
七. 元数据:_source 字段
- json主体保存在_source字段中,写入硬盘前压缩;
- 搜索结果中能得到完整的文档 —— 不需要额外去别的数据源中查询文档
- 如果缺少 _source 字段,部分 更新 请求不会起作用
- 当你的映射有变化,而且你需要重新索引数据时,你可以直接在 Elasticsearch 中操作而不需要重新从别的数据源中取回数据。
- 你可以从 _source 中通过 get 或 search 请求取回部分字段,而不是整个文档。
- 这样更容易排查错误,因为你可以准确的看到每个文档中包含的内容,而不是只能从一堆 ID 中猜测他们的内容。
禁用 _source 字段
PUT /my_index
{
    "mappings": {
        "my_type": {
            "_source": {
                "enabled":  false
            }
        }
    }
}
在搜索请求中你可以通过限定 _source 字段来请求指定字段
GET /_search
{
    "query":   { "match_all": {}},
    "_source": [ "title", "created" ]
}
这些字段会从_source中提出来;
八. 元数据:_all 字段
默认使用_all字段,作为一种探索
GET /_search
{
    "match": {
        "_all": "john smith marketing"
    }
}
字段长短会影响相关性,但是在_all中不会有影响,可以通过下面的映射禁用它
PUT /my_index/_mapping/my_type
{
    "my_type": {
        "_all": { "enabled": false }
    }
}
通过include_in_all字段,可以控制字段是否要被包含在_all 字段中;
不同的查询通过include_in_all控制是很灵活的;
你可能想要保留 _all 字段来查询所有特定的全文字段,例如 title, overview, summary 和 tags。
相对于完全禁用 _all 字段,你可以先默认禁用 include_in_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字段
文档唯一标识由四个元数据字段组成
- _id:文档的字符串 ID
- _type:文档的类型名 (被索引但是没有保存)
- _index:文档所在的索引
- _uid:_type 和 _id 连接成的 type#id (被保存(可取回)和索引(可搜索))
_id 和 _index 字段则既没有索引也没有储存,它们并不是真实存在的,但是仍然可以像真实字段一样查询 _id 字段。
Elasticsearch 使用 _uid 字段来追溯 _id;
_id 字段有一个你可能用得到的设置:path 设置告诉 Elasticsearch 它需要从文档本身的哪个字段中生成 _id
PUT /my_index
{
    "mappings": {
        "my_type": {
            "_id": {
                "path": "doc_id" <1>
            },
            "properties": {
                "doc_id": {
                    "type":   "string",
                    "index":  "not_analyzed"
                }
            }
        }
    }
}
从 doc_id 字段生成 _id
然后,当你索引一个文档时
POST /my_index/my_type
{
    "doc_id": "123"
}
_id 值由文档主体的 doc_id 字段生成
{
    "_index":   "my_index",
    "_type":    "my_type",
    "_id":      "123", <1>
    "_version": 1,
    "created":  true
}
这样对bulk请求,有个轻微的性能影响,处理请求的节点将不能仅靠解析元数据行来决定将请求分配给哪一个分片,而需要解析整个文档主体;
十. 动态映射
遭遇一个位置的字段时,它通过【动态映射】来确定字段的数据类型且自动将该字段加到类型映射中。
dynamic 设置
- true:自动添加字段(默认)
- false:忽略字段
- strict:当遇到未知字段时抛出异常
dynamic 设置可以用在根对象或任何 object 对象上。你可以将 dynamic 默认设置为 strict,而在特定内部对象上启用它;
PUT /my_index
{
    "mappings": {
        "my_type": {
            "dynamic":      "strict", <1>
            "properties": {
                "title":  { "type": "string"},
                "stash":  {
                    "type":     "object",
                    "dynamic":  true <2>
                }
            }
        }
    }
}
当遇到未知字段时,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!"
}
将 dynamic 设置成 false 完全不会修改 _source 字段的内容。_source 将仍旧保持你索引时的完整 JSON 文档。然而,没有被添加到映射的未知字段将不可被搜索。
十一. 自定义动态索引
动态索引不那么智能,有时需要自己定制
日期检测
当 Elasticsearch 遇到一个新的字符串字段时,它会检测这个字段是否包含一个可识别的日期,比如 2014-01-01。如果它看起来像一个日期,这个字段会被作为 date 类型添加,否则,它会被作为 string 类型添加。
第一次遇到日期,这个字段以后都要检查是否为日期,如果不合法会抛出异常;
日期检测可以通过在根对象上设置 date_detection 为 false 来关闭:
PUT /my_index
{
    "mappings": {
        "my_type": {
            "date_detection": false
        }
    }
}
使用这个映射,字符串将始终是 string 类型
假如你需要一个 date 字段,你得手动添加它
动态模板
使用 dynamic_templates,你可以完全控制新字段的映射
你设置可以通过字段名或数据类型应用一个完全不同的映射
每个模板都有一个名字用于描述这个模板的用途,一个 mapping 字段用于指明这个映射怎么使用,和至少一个参数(例如 match)来定义这个模板适用于哪个字段
模板按照顺序来检测,第一个匹配的模板会被启用,定义两个模板
- es: 字段名以 _es 结尾需要使用 spanish 分析器
- en: 所有其他字段使用 english 分析器
我们将 es 模板放在第一位,因为它比匹配所有字符串的 en 模板更特殊一点
PUT /my_index
{
    "mappings": {
        "my_type": {
            "dynamic_templates": [
                { "es": {
                      "match":              "*_es", <1>
                      "match_mapping_type": "string",
                      "mapping": {
                          "type":           "string",
                          "analyzer":       "spanish"
                      }
                }},
                { "en": {
                      "match":              "*", <2>
                      "match_mapping_type": "string",
                      "mapping": {
                          "type":           "string",
                          "analyzer":       "english"
                      }
                }}
            ]
}}}
<1> 匹配字段名以 _es 结尾的字段
<2> 匹配所有字符串类型字段
match_mapping_type 允许你限制模板只能使用在特定的类型上
match 参数只匹配字段名,path_match 参数则匹配字段在一个对象中的完整路径,所以 address.*.name 规则将匹配一个这样的字段
{
    "address": {
        "city": {
            "name": "New York"
        }
    }
}
unmatch 和 path_unmatch 规则将用于排除未被匹配的字段
十二. 默认映射
通常,一个索引中的所有类型具有共享的字段和设置;
用 default 映射来指定公用设置会更加方便,而不是每次创建新的类型时重复操作。
所有在 default 映射 之后 的类型将包含所有的默认设置,除非在自己的类型映射中明确覆盖这些配置.
例如,我们可以使用 default 映射对所有类型禁用 _all 字段,而只在 blog 字段上开启它
PUT /my_index
{
    "mappings": {
        "_default_": {
            "_all": { "enabled":  false }
        },
        "blog": {
            "_all": { "enabled":  true  }
        }
    }
}
default 映射也是定义索引级别的动态模板的好地方
十三. 重新索引数据
虽然你可以给索引添加新的类型,或给类型添加新的字段,但是你不能添加新的分析器或修改已有字段。假如你这样做,已被索引的数据会变得不正确而你的搜索也不会正常工作.
修改在已存在的数据最简单的方法是重新索引:创建一个新配置好的索引,然后将所有的文档从旧的索引复制到新的上.
_source 字段的一个最大的好处是你已经在 Elasticsearch 中有了完整的文档,你不再需要从数据库中重建你的索引,这样通常会比较慢.
了更高效的索引旧索引中的文档,使用【scan-scoll】来批量读取旧索引的文档,然后将通过【bulk API】来将它们推送给新的索引.
批量重新索引:
你可以在同一时间执行多个重新索引的任务,但是你显然不愿意它们的结果有重叠。所以,可以将重建大索引的任务通过日期或时间戳字段拆分成较小的任务:
GET /old_index/_search?search_type=scan&scroll=1m
{
    "query": {
        "range": {
            "date": {
                "gte":  "2014-01-01",
                "lt":   "2014-02-01"
            }
        }
    },
    "size":  1000
}
假如你继续在旧索引上做修改,你可能想确保新增的文档被加到了新的索引中。这可以通过重新运行重建索引程序来完成,但是记得只要过滤出上次执行后新增的文档就行了。
十四. 索引别名
别名的作用
- 在一个运行的集群上无缝的从一个索引切换到另一个
- 给多个索引分类(例如,last_three_months)
- 给索引的一个子集创建 视图
现在我们将介绍用它们怎么在零停机时间内从旧的索引切换到新的索引
这里有两种管理别名的途径:_alias 用于单个操作,_aliases 用于原子化多个操作。
我们创建一个索引 my_index_v1,然后将别名 my_index 指向它
PUT /my_index_v1 <1>
PUT /my_index_v1/_alias/my_index <2>
<1> 创建索引 my_index_v1
<2> 将别名 my_index 指向 my_index_v1
你可以检测这个别名指向哪个索引
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" }}
    ]
}
这样,你的应用就从旧索引迁移到了新的,而没有停机时间。
在应用中使用别名而不是索引。然后你就可以在任何时候重建索引。别名的开销很小,应当广泛使用.
 
                    
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号