在之前的章节介绍了集群规划及管理,本章讲从索引相关的方方面面开始介绍,它包索引管理、映射详解、数据类型、元数据、索引监控相关。
目录
一. index
1. 命名规则
_index | 命名必须小写,不能以下划线开头,不能包含逗号、 |
_type | 命名可以是大写或者小写,但是不能以下划线或者句号开头,不应该包含逗号, 并且长度限制为256个字符、 |
_id | 唯一确定的文档,要么提供自己的_id ,要么es会自动生成 |
2. 创建索引
如果你想禁止自动创建索引,在elasticsearch.yml配置action.auto_create_index: false,
number_of_shards | 分片数 |
number_of_replicas | 副本数 |
refresh_interval | 控制准实时的重要参数 |
index.write.wait_for_active_shards | 更改仅等待主分片通过索引设置 |
index.blocks.read_only | 设为true,则索引以及索引的元数据只可读 |
index.blocks.read_only_allow_delete | 设为true,只读时允许删除。 |
index.blocks.read | 设为true,则不可读。 |
index.blocks.write | 设为true,则不可写。 |
index.blocks.metadata | 设为true,则索引元数据不可读写。 |
创建index时设置
PUT /blogs
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}修改index配置
PUT /blogs/_settings
{
"index" : {
"number_of_replicas" : 1
}
}
3. 查看索引
GET /blogs/_settings
GET /twitter,kimchy/_settings
GET /_all/_settings
GET /log_2013_*/_settings
GET /log_2013_-*/_settings/index.number_*
在上一章集群篇讲过,也可以通过监控,http://localhost:9200/_cat/indices?v
4. Exists
HEAD /blogs
HTTP status code 表示结果 404 不存在 , 200 存在
5. 删除索引
DELETE /my_index //删除单个
DELETE /index_one,index_two //删除多个
DELETE /index_* //同上
DELETE /_all //删除全部
DELETE /* //同上
如果想禁用这个命令,在elasticsearch.yml配置action.destructive_requires_name: true
6. 索引别名
索引别名就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何一个需要索引名的API来使用。_alias用于单个操作,_aliases用于执行多个原子级操作,有了它你的应用已经在零停机的情况下从旧索引迁移到新索引了
- 在运行的集群中可以无缝的从一个索引切换到另一个索引
- 给多个索引分组
- 给索引的一个子集创建视图
PUT /my_index_v1 //创建索引my_index_v1
PUT /my_index_v1/_alias/my_index //设置别名my_index指向my_index_v1
GET /*/_alias/my_index //检测这个别名指向哪一个索引
GET /my_index_v1/_alias/ //哪些别名指向这个索引
POST /_aliases
{ "actions": [
{ "remove": { "index": "my_index_v1", "alias": "my_index" }},
{ "add": { "index": "my_index_v2", "alias": "my_index" }}
]
}
7. 数据迁移Reindex
尽管可以增加新的类型到索引中,或者增加新的字段到类型中,但是不能添加新的分析器或者对现有的字段做改动。如果你那么做的话,结果就是那些已经被索引的数据就不正确,搜索也不能正常工作。对现有数据的这类改变最简单的办法就是重新索引:用新的设置创建新的索引并把文档从旧的索引复制到新的索引。字段_source的一个优点是在Elasticsearch中已经有整个文档。你不必从源数据中重建索引,而且那样通常比较慢。为了有效的重新索引所有在旧的索引中的文档,用scroll从旧的索引检索批量文档,然后用bulk API把文档推送到新的索引中。从Elasticsearch v2.3.0开始, Reindex API被引入。它能够对文档重建索引而不需要任何插件或外部工具。
POST _reindex
{
"source" : {
"index" : "twitter"
},
"dest" : {
"index" : "new_twitter",
"version_type" : "external"
}
}
它有很多可以提高性能的参数和手段:batch_size、提高并发(slices大小=分片数)、减少复制带来重新索引的开销(副本数设置为0)、大量数据导入不要急于索引刷新(refresh_interval为-1来禁用刷新,完成后不要忘记重新启用它)
8. 打开关闭索引
开放和关闭索引API允许关闭索引,然后打开它。关闭索引几乎没有集群上的开销,并被阻止进行读/写操作。可以使用ignore_unavailable = true参数禁用此行为。
POST /my_index/_close
POST /my_index/_open
9. 收缩索引
收缩索引API允许您将现有索引缩小为具有较少主分片的新索引。目标索引中请求的主分片数必须是源索引中分片数的一个因子。例如,具有8个主分片的索引可以缩小为4个,2个或1个主分片,或者具有15个主分片的索引可以缩小为5个,3个或1个。
POST my_source_index/_shrink/my_target_index
{
"settings": {
"index.number_of_replicas": 1,
"index.number_of_shards": 1,
"index.codec": "best_compression"
},
"aliases": {
"my_search_indices": {}
}
}
10. 拆分索引
拆分索引API允许您将现有索引拆分为新索引,其中每个原始主分片在新索引中拆分为两个或更多主分片。_split API要求使用特定的number_of_routing_shards创建源索引,以便将来拆分。此要求已在Elasticsearch 7.0中删除。可以拆分索引的次数(以及每个原始分片可以拆分成的分片数)由index.number_of_routing_shards设置决定。路由分片的数量指定内部使用的散列空间,以便在具有一致散列的分片中分发文档。例如,将number_of_routing_shards设置为30(5 x 2 x 3)的5个分片索引可以按因子2或3分割。换句话说,它可以按如下方式拆分:
- 5 → 10 → 30 (split by 2, then by 3)
- 5 → 15 → 30 (split by 3, then by 2)
- 5 → 30 (split by 6)
POST my_source_index/_split/my_target_index
{
"settings": {
"index.number_of_shards": 2
}
}
11. Rollover Index
当现有索引被认为太大或太旧时,翻转索引API对于有时效性的索引数据,如日志,过一定时间后,老的索引数据就没有用了。我们可以像数据库中根据时间创建表来存放不同时段的数据一样,在ES中也可用建多个索引的方式来分开存放不同时段的数据。比数据库中更方便的是ES中可以通过别名滚动指向最新的索引的方式,让你通过别名来操作时总是操作的最新的索引。ES的rollover index API 让我们可以根据满足指定的条件(时间、文档数量、索引大小)创建新的索引,并把别名滚动指向新的索引。
12. Index templates
索引模板允许您定义在创建新索引时自动应用的模板。为每个索引写定义信息可能是一件繁琐的事情,ES提供了索引模板功能,让你可以定义一个索引模板,模板中定义好settings、mapping、以及一个模式定义来匹配创建的索引。
13. 索引状态管理
13.1 Clear Cache
Clear Cache,默认会清理所有缓存,可指定清理query, fielddata or request 缓存。
POST /_cache/clear
POST /kimchy,elasticsearch/_cache/clear
13.2 refresh
POST /kimchy,elasticsearch/_refresh
POST /_refresh
13.3 flush
Flush,将缓存在内存中的索引数据刷新到持久存储中。
问题:如果没有用fsync把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在?
每秒刷新(refresh)实现了近实时搜索,es还引入了事务日志(简称translog),来解决两次提交之间发生变化的文档的丢失。translog提供所有还没有被刷到磁盘的操作的一个持久化纪录。它的用途主要有两种:
- 当ES启动的时候它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放translog中所有在最后一次提交后发生的变更操作
- 也被用来提供实时CRUD 。当你试着通过ID查询、更新、删除一个文档,它会在尝试从相应的段中检索之前,首先检查translog任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本。
它的工作流程:
- 一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了translog
- 刷新(refresh)完成后, 缓存被清空但是事务日志不会,此时已经被写一个新的段中,但没有进行fsync操作
- 这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志
- 每隔一段时间(flush操作的频率是通过translog的大小控制的,当translog大小达到一定值的时候就执行一次flush,对应参数为index.translog.flush_threshold_size,默认值是512mb),例如translog 变得越来越大,索引被flush(文件系统缓存通过fsync被刷新),在刷新flush之后,段被全量提交,并且老的translog被删除
问题:Translog有多安全?
translog本身是文件,也需要存储到磁盘,它的存储方式通过index.translog.durability和index.translog.sync_interval(默认5秒)设定。
index.translog.durability
- request,意为每次请求都会把translog写到磁盘。这种设定可以降低数据丢失的风险,但是磁盘IO开销会较大,默认值。
- async,采用异步方式持久化translog,会采用index.translog.sync_interval间隔提交一次
默认模式每次写请求完成之后执行,是可以配置的。因此,重启节点或关闭索引之前执行flush有益于你的索引。
索引的 flush 过程通过将数据刷新到索引存储并清除内部trans log,基本上从索引中释放内存。默认情况下,ElasticSearch使用内存启发,以便根据需要自动触发刷新操作,以清除内存。
POST /blogs/_flush //手动刷新blogs索引
POST /_flush?wait_for_ongoing //手动刷新所有的索引并且并且等待所有刷新在返回前完成
13.4 Force Merge
Force Merge,强制段合并
问题:段是什么?
再回顾下,每一个段本身都是一个倒排索引。 一个Lucene索引包含一个提交点(一个列出了所有已知段的文件)和三个段(被混淆的概念是:一个Lucene索引我们在ES称作分片,一个ES索引是分片的集合)。
问题:为何要进行段合并呢?
在之前lucene的IndexWriter中讲过,由于自动刷新(refresh)流程每秒会创建一个新的段,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须逐段搜索,所以段越多,搜索也就越慢。
问题:逐段搜索流程又是什么?
当ES在索引中搜索的时候,它发送查询到每一个属于索引的分片(Lucene索引),然后所有已知的段按顺序被查询,词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。
- 新文档被收集到内存索引缓存(包含新文档的Lucene索引)
- 不时地,缓存被提交(可能是flush缓存操作)
- 新的段被开启,让它包含的文档可见以被搜索
- 内存缓存被清空,等待接收新的文档
问题: 段怎么更新或删除呢?
段是不可改变的。当一个文档被删除时,它实际上只是在.del文件中被标记删除,一个被标记删除的文档仍然可以被查询匹配到,但会在最终结果集被返回前从中移除。当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中,可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。
问题: 怎样合并?
ES通过在后台进行段合并(参阅Lucene的MergePolicy)来解决这个问题(小的段被合并到大的段,然后这些大的段再被合并到更大的段),它并不需要你做任何事,自动完成的。 段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
问题: 合并要注意什么?
合并大的段需要消耗大量的I/O和CPU资源,如果任其发展会影响搜索性能。ES在默认情况下会对合并流程进行资源限制,所以搜索仍然有足够的资源很好地执行。
二. type
类型由名称和映射(像数据库中的 schema,描述了文档可能具有的字段或属性、每个字段的数据类型)组成。
文档是怎样存取的?
在Lucene中,一个文档由一组简单的键值对组成。Lucene不关心这些值是字符串、数字或日期--所有的值都被当做不透明字节。当我们在Lucene中索引一个文档时,每个字段的值都被添加到相关字段的倒排索引中。你也可以将未处理的原始数据存储起来,以便这些原始数据在之后也可以被检索到。
类型如何被实现的?
ES类型是以 Lucene处理文档的这个方式为基础来实现的,但Lucene没有文档类型的概念,每个文档的类型名被存储在一个叫_type的元数据字段上。当我们要检索某个类型的文档时, ES通过在_type字段上使用过滤器限制只返回这个类型的文档。
映射如何被实现的?
Lucene也没有映射的概念。映射是ES将复杂JSON文档映射成Lucene需要的扁平化数据的方式。
二. mapping
在ES中创建一个mapping映射类似于在数据库中定义表结构,即表里面有哪些字段、字段是什么类型、字段的默认值等;
1. 根对象
映射的最高一层被称为根对象,包含:一个properties节点,各种元数据字段(它们都以一个下划线开头如:_type、_id和_source)
2. 动态映射
ES最重要的功能之一是它试图摆脱你的方式,让你尽快开始探索你的数据。要索引文档,您不必首先创建索引,定义映射类型和定义字段 - 您只需索引文档,索引,类型和字段将自动生命。自动检测和添加新字段称为动态映射。默认情况下,当在文档中找到以前未见过的字段时,ES会将新字段添加到类型映射中。通过将dynamic参数设置为false(忽略新字段)或strict(如果遇到未知字段则抛出异常),可以在文档和对象级别禁用此行为。
JSON datatype | Elasticsearch datatype |
| No field is added. |
|
|
floating point number |
|
integer |
|
object |
|
array | Depends on the first non- |
string | Either a |
日期检测
如果启用了date_detection(默认),则会检查新的字符串字段以查看其内容是否与dynamic_date_formats中指定的任何日期模式匹配。如果找到匹配项,则添加具有相应格式的新日期字段。
如果要禁用动态日期检测,可以通过date_detection设置为false
PUT my_index
{
"mappings": {
"_doc": {
"date_detection": false
}
}
}你也可以自定义规则,默认值为:["strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]
PUT my_index
{
"mappings": {
"_doc": {
"dynamic_date_formats": ["MM/dd/yyyy"]
}
}
}
数字检测
虽然JSON支持本机浮点和整数数据类型,但某些应用程序或语言有时可能将数字呈现为字符串。通常,正确的解决方案是显式映射这些字段,但可以启用数字检测(默认情况下禁用)以自动执行此操作:
PUT my_index
{
"mappings": {
"_doc": {
"numeric_detection": true
}
}
}
PUT my_index/_doc/1
{
"my_float": "1.0",
"my_integer": "1"
}
3. Types Exists
HEAD twitter/_mapping/tweet
用于检查index中是否存在type,404表示它不存在,200表示它存在。
4. Put Mapping
可以增加一个存在的映射,你不能修改存在的域映射(该域的数据可能已经被索引,你意图修改这个域的映射,索引的数据可能会出错,不能被正常的搜索)
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"name": {
"properties": {
"first": {
"type": "text"
}
}
},
"user_id": {
"type": "keyword"
}
}
}
}
}更新映射,更多
PUT my_index/_mapping/_doc
{
"properties": {
"name": {
"properties": {
"last": {
"type": "text"
}
}
},
"user_id": {
"type": "keyword",
"ignore_above": 100
}
}
}
5. Get Mapping
GET /twitter/_mapping/_doc
GET /_all/_mapping
GET /_mapping当然你也可以获取字段映射
GET publications/_mapping/_doc/field/title
GET /twitter,kimchy/_mapping/field/message
GET /_all/_mapping/_doc,tweet,book/field/message,user.id
GET /_all/_mapping/_do*/field/*.id
6. 删除映射类型
在ES 6.0.0或更高版本中创建的索引可能只包含单个映射类型。在具有多种映射类型的5.x中创建的索引将继续像以前一样在Elasticsearch 6.x中运行。映射类型将在ES 7.0.0中完全删除。具体移除映射类型的情况,详见
最初,我们谈到了一个“索引”类似于SQL数据库中的“数据库”,而“类型”等同于“表”。这是一个糟糕的比喻,导致错误的假设。在SQL数据库中,表彼此独立。一个表中的列与另一个表中具有相同名称的列无关。映射类型中的字段不是这种情况。在Elasticsearch索引中,不同映射类型中具有相同名称的字段在内部由相同的Lucene字段支持。换句话说,使用上面的示例,用户类型中的user_name字段存储在与tweet类型中的user_name字段完全相同的字段中,并且两个user_name字段在两种类型中必须具有相同的映射(定义)。最重要的是,在同一索引中存储具有很少或没有共同字段的不同实体会导致稀疏数据并干扰Lucene有效压缩文档的能力。出于这些原因,我们决定从ES中删除映射类型的概念。
7. 映射参数
以下映射参数对于某些或所有字段数据类型是通用的
analyzer
normalizer
boost
coerce
copy_to
doc_values
dynamic
enabled
fielddata
eager_global_ordinals
format
ignore_above
ignore_malformed
index_options
index
fields
norms
null_value
position_increment_gap
properties
search_analyzer
similarity
store
term_vector
8. 动态模板
dynamic_templates可以完全控制新检测生成字段的映射,可以通过字段名称或数据类型来应用不同的映射,了解更多。
PUT /my_index
{"mappings": {
"my_type": {
"dynamic_templates": [
{ "es": { //以_es结尾的字段名需要使用spanish分词器
"match": "*_es", //只匹配字段名称
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "spanish"
}
}},
{ "en": { //所有其他字段使用english分词器
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "english"
}
}}
]
}}}
9. 缺省映射
_default_映射更加方便地指定通用设置,而不是每次创建新类型时都要重复设置。_default_映射也是一个指定索引dynamic templates 的好方法。enabled:适用于只想存储字段而不对其进行索引,只能应用于映射类型(type根节点)和对象字段(type的内部对象)
{ "mappings": {
"_default_": { //所有的类型禁用_all字段
"_all": { "enabled": false }
},
"blog": { //只在blog类型启用
"_all": { "enabled": true }
}
}
}
四. 元数据
每个文档都有与之关联的元数据,例如_index, _type和_id元字段。创建映射类型时,可以自定义某些元字段的行为。
标示类元数据
文档所属的索引,_index作为虚拟字段公开 - 它不作为真实字段添加到Lucene索引中。 | |
由_type和_id组成的复合字段,被存储(可取回)和被索引(可搜索) | |
文档的映射类型,被索引但是没有存储 | |
文档的ID,被索引但是没有存储 |
文档存储源元数据
_source | 表示文档正文的原始JSON,该字段先会压缩然后写入磁盘,但是可以配置禁用。 |
_size | _source字段的大小(以字节为单位),由mapper-size plugin插件提供 |
索引元数据
_all | 一个catch-all字段,用于索引所有其他字段的值,默认情况下禁用。 当搜索需求不明确(无明确字段)时,可使用_all(应急之策),当索引一个文档的时候,同时把该文档的所有字段值拼接成一个字符串后,做分词,然后保存倒排索引,用于支持整个json的全文检索。 _all这种需求适用的场景较少,可以通过_mapping中enabled将all字段关闭,节约存储成本和cpu开销。_mapping中include_in_all也可以控制_all包含的每个字段,默认值是true(所以字段都包含)。 相关度算法中提到一个原则字段越短越重要,较短次出现在title字段比content字段更加重要,而字段长度的区别在_all中没有意义。 注:_all仅仅是一个经过分词的text字段。它使用默认分词器来分析它的值,不管这个值原本所在字段指定的分词器。
|
_field_names | 文档中包含非空值的所有字段 |
路由元数据
_routing | 自定义路由值,用于将文档路由到特定分片 |
其他元数据
_meta | 特定应用元数据 |
五. 数据类型
1.核心域类型
string |
text会分词,然后进行索引,支持模糊查询,适用于全文搜索(analyzed字符串) keyword不进行分词,直接索引,支持精确查询,适用于关键词搜索(not-analyzed字符串)
ES中的一个字段既可以是text,又可以是keyword类型吗?是没有的
|
long, integer, short, byte, double, float, half_float, scaled_float 其实一般可以视为not-analyzed,比keyword高效在于不做评分计算,此类型可视为已经忽略评分的语法。
| |
date | |
boolean | |
Binary datatype | binary |
Range datatypes | integer_range , float_range , long_range , double_range , date_range |
null | 下面被认为是空的,它们将不会被索引 "null_value": null, "empty_array": [], "array_with_null_value": [ null ] |
"user": {
"type": "text",
"fields": { //仅用于排序和聚合
"keyword": {
"type": "keyword",
"ignore_above": 256 //长度超过256的字符串(字符个数),analyzer不会进行处理,所以就不会索引起来,最终搜索引擎搜索不到了
}
}
}
2. 复杂域类型
数组支持不需要专用类型(理解为动态的字符串或object类型) | |
一般不需要显式地将字段类型设置为object,因为这是默认值。
| |
Nested datatype | 嵌套用于JSON对象数组,它允许对象数组彼此独立地被索引和查询。因为嵌套文档被索引为单独的文档,所以只能在nested查询、nested/reverse_nested聚合或嵌套内部命中的范围内访问它们。为了防止定义错误的映射,每个索引可以定义的nested类型的字段的数量被限制为50。
|
Geo-point datatype | 对于纬度/经度点 |
Geo-Shape datatype | 复杂的形状,如多边形 |
IP datatype | IPv4/IPv6 |
Completion datatype | 提供自动完成的建议 |
Token count datatype | 计算字符串中的标记数 |
join datatype | 为同一索引中的文档定义父/子关系 |
{ "tweet": "Elasticsearch is very flexible",
"user": {
"id": "@johnsmith",
"age": 26,
"name": {
"full": "John Smith"
}
}
}
上面对象的映射
{"gb": {
"tweet": { //根对象
"properties": {
"tweet": { "type": "string" },
"user": { //内部对象
"type": "object",
"properties": {
"id": { "type": "string" },
"age": { "type": "long" },
"name": { //内部对象
"type": "object",
"properties": {
"full": { "type": "string" }
}
}
}
}
}
}
}
}
思考这样一个问题,内部对象(user)及内部数组(followers)如何被索引?
Lucene不理解内部对象,Lucene文档是由一组键值对列表组成的。
为了能让ES有效地索引内部类,它把文档转化:"user.name.full": [john, smith]为内部对象数组时。也是同理(内部数组)扁平化处理,这样的话也有问题的({age: 35}和 {name:Mary White}之间的相关性已经丢失了)!
要想解决必须使用嵌套对象(nested对象)
{
"followers": [
{ "age": 35, "name": "Mary White"},
{ "age": 26, "name": "Alex Jones"},
{ "age": 19, "name": "Lisa Smith"}
]
}{
"followers.age": [19, 26, 35],
"followers.name": [alex, jones, lisa, smith, mary, white]
}
六. 索引监控
1. Indices Stats
Indices Stats,索引状态提供有关索引上发生的不同操作的统计信息。API提供有关索引级别范围的统计信息(尽管也可以使用节点级别范围检索大多数统计信息),通过GET /_stats
2. Indices Segments
Indices Segments,提供构建Lucene索引(分片级别)的低级别段信息。允许用于提供有关分片和索引状态的更多信息,可能是优化信息,删除时“wasted”的数据,等等,通过GET /test/_segments
3. Indices Recovery
Indices Recovery,索引恢复API提供对正在进行的索引碎片恢复的深入了解。可以针对特定索引或群集范围报告恢复状态。例如,以下命令将显示索引“index1”和“index2”的恢复信息:
GET index1,index2/_recovery?human
4. Indices Shard Stores
Indices Shard Stores,提供索引的分片副本的存储信息。存储有关哪些节点分片副本存在的信息报告,分片副本分配ID,每个分片副本的唯一标识符以及打开分片索引时遇到的任何异常或早期引擎故障。
通过GET /test/_shard_stores
总结,es的索引这章干货非常多,非常值得花些时间慢慢研究的。