容量控制
_split接口
_split接口可以在新索引中将每个主分片分裂为两个或更多分片,所以使用split扩容时分片总量都是成倍增加而不能逐个增加。使用split接口分裂分片虽然会创建新的索引,但新索引中的数据只是通过文件系统硬连接到新索引中,所以并不存在数据复制过程。而扩容的分片又是在本地分裂,所以不存在不同节点间网络传输数据的开销,所以split扩容效率相对其他方案来说还是比较高的。
_split接口做动态扩容需要预先设置索引的number_of_routing_shards参数,Elasticsearch向分片散列文档采用一致性哈希算法,这个参数实际上设置了索引分片散列空间。所以分裂后分片数量必须是number_of_routing_shards的因数,同时是 number_of_shards的倍数。
例如,设置number_of_routing_shards为12,number_of_shards为2,则分片再分裂存在2->4->12、2->6->12和2->12三种可能的扩容路径。分裂后分片数量可通过_split接口的index.number_of_shards参数设置,数量必须满足前述整数倍的要求。上面讲解的这些规则比较抽象,下面通过创建一个具体示例来看一下如何通过split接口扩容索引。
首先创建一个索引test_split,将它的主分片数量number_of_shards设置为2,散列空间number_of_routing_shards设置为12。然后通过将索引的blocks.write参数设置为true,将索引设置为只读,这是因为使用_split接口要求索引必须为只读。最后调用_spit接口将test_split索引的分片分裂到新索引splited_test_split中,index.number_of_shards参数设置为4,即分裂为4个分片。如示例所示:
put test_split
{
"settings": {
"number_of_shards": 2,
"number_of_routing_shards": 12
}
}
put test_split/_settings
{
"blocks.write": true
}
put test_split/_split/splited_test_split
{
"settings": {
"index.number_of_shards": 4,
"index.number_of_replicas": 1,
"index.blocks.write": false
},
"aliases": {
"sts": {}
}
}
在执行成功后,可调用GET _cat/shards
查看分片。在返回结果中可以看到test_split索引共有4个分片,即2个主分片和2个副本分片;新索引splited_test_split会有8个分片,即4个主分片和4个副本分片。
_split接口在创建新索引的同时,会将原索引的配置也一同设置到新索引中。所以index.blocks.write参数也会一同被复制过来,但这可能并不是我们想要的。所以在分裂分片的同时支持通过aliases和settings设置新索引的别名和配置,所以可以在分裂分片的同时将index.blocks.write参数覆盖。在示例中就将这个参数覆盖了,同时还添加了新的别名sts。另外,还可以在地址中添加copy_settings=false参数禁止从源索引中复制配置,但这个参数在版本8中有可能被废止,所以添加这个参数会收到警告。
使用_split接口成功分裂分片后,原索引并不会被自动删除。通过原索引和新索引都可以查看到相同的文档数据,原索引是否删除应根据业务需要具体判断。
_shrink接口
与_split接口相反,_shrink接口用于缩减索引分片。尽管它们在逻辑上正好相反,但它们在应用时的规则基本上是一致的。比如,_shrink接口在缩减索引分片数量时也要求原始分片数量必须是缩减后分片数量的整数倍。例如原始分片数
量为12,则可以按12->6->3->1的路径缩减,也可以按12->4->2->1的路径缩减。
在调用_shrink接口前要满足两个条件,第一个条件与_split接口类似,就是要求索引在缩容期间必须只读;第二个条件有些特殊,就是要求索引所有分片(包括副本分片)都要复制一份存储在同一节点,并且要求健康状态为green,这可以通过routing.allocation.require._name指定节点名称实现。如果想要查看节点名称,可调用GET _nodes
接口相看所有集群节点。
与_split接口类似,索引在缩减后的具体分片数量可通过_shrink接口的index.number_of_shards参数设置。但它的值必须与原始分片数量保持整数比例关系,如果不设置该参数将直接缩减为1个分片。如示例所示,缩减后索引分片数量为2,同时还清除了两项配置:
PUT splited_test_split/_settings
{
"settings": {
"blocks.write": true,
"routing.allocation.require._name": "3f068cc83647"
}
}
PUT splited_test_split/_shrink/shrinked_test_split?copy_settings=true
{
"settings": {
"index.routing.allocation.require.name": null,
"index.blocks.write": null,
"index.number_of_shards": 2
}
}
同样地,使用_shrink接口缩容后会创建新索引shrinked_test_split,原索引和新索引都可以查询到相同的文档数据。
_reindex接口
尽管_split接口和_shrink接口可以对索引分片数量做扩容和缩容,但在分片数量上有倍数要求,并且分片总量受散列空间(即number_of_routing_shards参数)的限制。如果索引容量超出了散列空间或者有其他特殊要求,则可以按新需求创
建新的索引。Elasticsearch提供的_reindex接口支持将文档从一个索引重新索引到到另一个索引中。但显然重新索引在性能上的开销要比_split和_shrink大,所以尽量不要使用这种办法。
_reindex接口需要两个参数source和dest,前者指明文档来源索引,而后者则指明了文档添加的新索引。例如在示例中是将users索引中的文档添加到users_copy索引中:
POST _reindex
{
"source": {
"index": "users"
},
"dest": {
"index": "users_copy"
}
}
需要注意的是,在重新索引时,不会将原索引的配置信息复制到新索引中。如果事先没有指定索引配置,重新索引时将根据默认配置创建索引及映射。另外,使用_reindex接口必须将索引的_source字段开启。
缓存相关
为了提升数据检索时的性能,Elatisearch为索引提供了三种缓存:
第一种缓存是节点查询缓存(Node Query Cache),负责存储节点查询结果。节点查询缓存是节点级别的,一个节点只有一个缓存,同一节点上的分片共享同一缓存。 在默认情况下,节点查询缓存是开启的,可通过索引index.queries.cache.enabled参数关闭。节点查询缓存默认使用节点内存的10%作为缓存容量上限,可通过indices.queries.cache_size更改,这个参数是节点的配置而非索引配置。
第二种缓存是分片请求缓存(Shard Request Cache),负责存储分片接收到的查询结果。分片请求缓存不会缓存查询结果的hits字段,也就是具体的文档内容,它一般只缓存聚集查询的相关结果。在默认情况下,分片请求缓存也是开启的,通过索引index.requests.cache.enable参数关闭。另一种关闭该缓存的办法,是在调用search接口时添加request._cache = false参数。
分片请求缓存使用的键是作为查询条件JSON字符串,所以如果查询条件JSON串完全相同,文档的查询几乎可以达到实时。但由于JSON属性之间并没有次序要求,这意味着即使JSON描述的是同一个对象,只要它们属性的次序不同就不能在缓存中命中数据。这点在使用时需要格外注意。
最后一种缓存就是text类型字段在开启fielddata机制后使用的缓存,它会将text类型字段提取的所有词项全部加载到内存中,以提高使用该字段做排序和聚集运算的效率。由于fielddata是text类型对文档值机制的代替,所以这种缓存机制天然就是开启的且不能关闭。但可通过indices.fielddata.cache.size设置这个缓存的容量,默认情况下该缓存没有容量上限。
缓存的引入使得文档检索性能得到了提升,但缓存一般会带来两个主要问题:一是如何保证缓存数据与实际数据的一致; 另一个问题是当缓存容量超出时如何清理缓存。
数据一致性问题:Elasticsearch是通过让缓存与索引刷新频率保持一致实现的。还记得索引是准实时的吗?索引默认情况下会以每秒1次的频率将文档编入索引,Elasticsearch会在索引更新的同时让缓存也失效,这就保证了索引数据与缓存数据的一致性。
缓存数据容量问题:通过LRU的方式,将最近最少使用的缓存条目清除。同时,Elasticsearch还提供了一个cache接口用于主动清理缓存。
_refresh接口
_refresh接口用于主动刷新一个或多个索引,将已经添加的文档编入索引以使它们在检索时可见。在调用该接口时,可以直接调用或与一个或多个索引起使用, 还可以使用_all刷新所有索引。
GET 索引1/_refresh
POST _refresh
GET _all/_refresh
POST 索引1,索引2/_refresh
事实上,除了使用_refresh接口主动刷新索引外,也可以在操作文档时通过refresh参数刷新索引。
_cache 接口
_cache接口用于主动清理缓存,在调用该接口时需要在_cache后附加关键字clear。
_cache接口可以清理所有缓存,也可以清理某一索引甚至某一字段的缓存,还可以只清理某种类型的缓存。例如:
POST 索引1/_cache/clear?query=true
POST 索引1,索引2/_cache/clear?request=true
POST 索引1/_cache/clear?fielddata=true&fields=notes
在示例中,query、request、fielddata参数分别对应于不同的缓存类型,而fields参数则用于定义清理哪一个字段的缓存。
查看运行状态
除此上述接口以外,Elasticsearch还提供了一组用于查看索引及分片运行情况的接口,包括_stat、_shard_stores和_segments等。由于它们往往在性能分析时使用。
_stats接口
_stats接口用于查看索引上不同操作的统计数据,可以直接请求也可以与索引名称一起使用。_stats接口返回的统计数据非常多,如果只对其中某一组统计数据感兴趣,可以在_stats接口后附加统计名称。 例如以下对_stats接口的调用都是正确的:
GET _stats
GET _stats/store
GET kibana_sample_data_flights/_stats
GET kibana_sample_data_flights/_stats/fielddata
_segments和_shard_stores接口
_shard_stores接口用于查询索引分片存储情况,而_segments接口则用于查看底层Lucene的分段情况。这两个接口都只能通过GET方法请求,同时都可以针对一个或多个索引,例如:
GET _shard_stores
GET /kibana_sample_data_flights/_shard_stores
GET _segments
GET /kibana_sample_data_flights/_segments/
其他检索接口
我们前面的检索实际上都是围绕着_search接口,但实际上Elasticsearch还提供了许多与文档检索有关的接口。比如,如果想要查看索引中满足条件的文档数量可以使用_count接口,如果想要执行一组检索可以使用_msearch接口。
_count接口
Elasticsearch提供了查看文档总数的_count接口,可通过GET或POST方法请求该接口。在请求该接口的路径上,可以添加索引、映射类型,以限定统计文档数量的范围。所以在示例中对_count接口的请求都是正确的:
GET _count
POST kibana_sample_data_logs/_count
{
"query": {
"match": {
"message": "chrome"
}
}
}
_msearch接口
_msearch接口,可以在一次接口调用中执行多次查询,可以使用GET或POST方法请求。请求体每两行为一组视为一个查询,第一行为查询头包含index、search_type、preference和routing等基本信息,第二行为查询体包含具体要检索的内容如query、aggregation等。例如:
POST kibana_sample_data_flights/_msearch
{}
{"query":{"match_all":{}},"from" : 0,"size" : 10 }
{}
{"query" : {"match_all" : {}}}
{"index" : "kibana_sample_data_logs" }
{"query" : {"match_all": {}}}
在示例中包含了3个查询,前两个查询的查询头都是空的,所以默认在请求路径中指定的kibana_sample_data_flights索引中查询;最后一个则在查询头中指定了索引为kibana_sample_data_logs,所以会在kibana_sample_data_logs索引中查询。
_validate接口
_validate接口用于在不执行查询的情况下,评估一个查询是否合法可执行,这通常用于验证执行开销比较高的查询。_validate接口可通过GET或POST方法请求,请求路径中必须要包含_validate/query,也可以在路径中添加索引名称以限
定查询执行的范围。类似_search接口。示例:
POST _validate/query
{
"query": {
"range": {
"AvgTicketPrice": {
"gte": 1000,
"lte": 1500
}
}
}
}
_field_caps接口
_field_caps接口用于查看某一字段支持的功能,主要包括字段是否可检索以及是否可聚集等。需要查看的字段可以通过 URI参数fields设置,可以使用GET或POST方法请求。在请求地址中,还可以添加索引名称以限定查询范围。
例如:
GET _field_caps?fields=AvgTicketPrice
POST kibana_sample_data_logs/_field_caps?fields=message,agent
批量接口
批量操作除了_msearch接口接口以外,还有_bulk接口和_mget接口。
_bulk
如果需要批量地对Elasticsearch中的文档进行操作,可以使用_bulk接口执行以提升效率和性能。 _bulk接口一组请求体,请求体一般每两个一组,对应种对文档的操作;第一个请求体代表操作文档的类型,而第二个请求体则代表操作文档所需要的参数。但对于某些不需要参数的文档操作来说,则可能只有一个请求体。
操作类型包括index、create、delete、update等,其中index和create都代表创建文档,区别在于当要创建的文档存在时,create会失败而index则可以变为更新;delete和update则分别代表删除和更新文档。例如:
POST _bulk
{ "index" : {"_index": "students","_id":"10"}}
{ "name" : "smith" }
{ "delete" : {"_index": "test","_id":"5"}}
{ "create": {"_index": "test","_id":"11"}}
{ "age" : 30,"name":"Candy" }
{ "update" : {"_id" :"1","_index" : "students"} }
{ "doc": {"age" : "20"}}
_mget
一条一条的查询,比如说要查询100条数据,那么就要发送100次网络请求,这个开销还是很大的,如果批量查询的话,查询100条数据,就只要发送1次网络请求,网络请求的性能开销缩减100倍。
批量查询的使用:
GET /_mget
{
"docs": [
{
"_index": "kibana_sample_data_flights",
"_id": "NMw4Dn0B5AahuA_56Op_"
},
{
"_index": "kibana_sample_data_flights",
"_id": "a804Dn0B5AahuA_5-A98"
}
]
}