别名,有点类似数据库的视图,别名一般都会和一些过滤条件相结合,可以做到即使是同一个索引上,让不同人看到不同的数据。别名的访问接口是_alias。
PUT /index_alias
{
"aliases": {
"index1": {
"filter": {
"term": {
"name": "Apache"
}
}
}
}
}
PUT /open-soft/_alias/open-soft-alias
{
"filter": {
"match": {
"lang": "Java8"
}
}
}
查询指定索引的别名:
GET /open-soft/_alias
删除别名:
DELETE /open-soft/_alias/open-soft-alias
GET _cat/aliases
get索引名/_alias,看索引所有的别名,如:GET /open-soft/_alias
get别名,看别名关联索引的详情,如:GET open-soft-alias
通过别名查询:
别名的使用和索引差不多,比如:
GET open-soft-alias/_search
同时_aliases接口可以做批量操作,比如通过_aliases接口将一个别名关联多个索引:
POST /_aliases
{
"actions": [
{
"add": {
"index": "my_index",
"alias": "my_index_alias"
}
},
{
"add": {
"index": "my_index2",
"alias": "my_index_alias"
}
}
]
}
或者对于同一个index,我们给不同人看到不同的数据,如my_index有个字段是team,team字段记录了该数据是那个team的。team之间的数据是不可见的。
POST /_aliases
{
"actions": [
{
"add": {
"index": "my_index",
"alias": "my_index_teamA_alias",
"filter": {
"term": {
"team": "teamA"
}
}
}
},
{
"add": {
"index": "my_index",
"alias": "my_index_teamB_alias",
"filter": {
"term": {
"team": "teamB"
}
}
}
},
{
"add": {
"index": "my_index",
"alias": "my_index_team_alias"
}
}
]
}
只要有可能,尽量使用别名,推荐为Elasticsearch的每个索引都使用别名,因为在未来重建索引的时候,别名会赋予你更多的灵活性。假设一开始创建的索引只有一个主分片,之后你又决定为索引扩容。如果为原有的索引使用的是别名,现在你可以修改别名让其指向额外创建的新索引,而无须修改被搜索的索引之名 称(假设一开始你就为搜索使用了别名)。
另一个有用的特性是,在不同的索引中创建窗口。比如,如果为数据创建了每日索引,你可能期望一个滑动窗口涵盖过去一周的数据,别名就称为last-7-days。然后,每天创建新的每日索引时,你可以将其加入别名,同时停用或者删除第8天前的旧索引。
别名还能提供另一个特性,那就是路由。不过,在谈论使用别名进行路由之前,我们先来讲述一下通常情况下路由是如何使用的。
在之前的章节中,我们已经了解到别名是索引之上的抽象,非常强大和灵活。假设别名指向一个单独的索引,那么它们也可以和路由一起使用,在查询或索引 的时候自动地使用路由值。如
POST /_aliases
{
"actions": [
{
"add": {
"index": "open-soft",
"alias": "my_index_alias",
"filter": {
"match": {
"lang": "Java"
}
},
"routing": "AKey"
}
},
{
"add": {
"index": "open-soft",
"alias": "my_index_alias2",
"filter": {
"match": {
"lang": "Java"
}
},
"routing": "BKey"
}
}
]
}
或者:
PUT /open-soft/_alias/open-soft-alias2
{
"filter": {
"match": {
"lang": "Java"
}
},
"routing": "CKey"
}
_rollover接口用于根据一系列条件将别名指向一个新的索引,这些条件包括存续时间、文档数量和存储容量等。这与日志文件使用的文件滚动类似,文件滚动是通过不断创建新文件并滚动旧文件来保证日志文件不会过于庞大,而_ rollover接口则是通过不断将别名指向新的索引以保证索引容量不会过大。但这种别名滚动并不会自动完成,需要主动调用_rollover接口。
别名滚动的条件可通过conditions参数设置,包括max_age、max_docs和max_size等三个子参数。例如,创建一个索引logs- 1并分配别名logs,然后调用logs别名的_rollover接口设置别名滚动条件,如:
PUT /logs-1
{
"aliases": {
"logs": {}
}
}
POST /logs/_rollover
{
"conditions": {
"max_age": "14d",
"max_docs": 10000,
"max_size": "4gb"
}
}
在示例中,logs别名指向logs-1索引,最大存活周期为14天,最大文档数量10000条,最大存储容量4GB。因为logs-1索引刚刚创建,存活时间、文档数量和存储容量都不满足条件,所以使用示例的请求不会对logs别名产生任何影响。这通过请求返回的结果也可以看到:
{
"acknowledged" : false,
"shards_acknowledged" : false,
"old_index" : "logs-1",
"new_index" : "logs-000002",
"rolled_over" : false,
"dry_run" : false,
"conditions" : {
"[max_size: 4gb]" : false,
"[max_docs: 10000]" : false,
"[max_age: 14d]" : false
}
}
从返回结果的conditions属性来看,三个条件匹配的结果都是false,所以不会触发索引滚动。如果想体验别名滚动的效果,可以将max_age设置为1s再调用上面的请求。之后通过“GET _cat/indices"接口就会发现有新的索引logs-000002产生,再分别查看这两个索引就会发现,logs-1的别名已经被清空,而 logs-000002 的别名中则已经添加了logs。新索引的命名规则在原索引名称数字的基础上加1,并且将数值长度补0凑足6位。
所以使用_rollover接口时,要求索引名称必须以数字结尾,数字与前缀之间使用连接线“-”连接。
由于_rollover接口在滚动新索引时,会将别名与原索引的关联取消,所以通过别名再想查找已经编入索引的文档就不可能了。为了保证原文档可检索,可以通过别名is_ write_index参数保留索引与别名的关系。当使用is_ write_index参数设置了哪一个索引为写索引时,_rollover接口滚动别名指向索引时将不会取消别名与原索引之间的关系。它会将原索引的is_ write_index参数设置为false,并将新索引的is_ write_index参数设置为true。
例如在创建 logs-4 时添加参数所示:
PUT /logs-4
{
"aliases": {
"logs4": {
"is_write_index": true
}
}
}
再执行示例中的请求时:
POST /logs4/_rollover
{
"conditions": {
"max_age": "1s",
"max_docs": 10000,
"max_size": "4gb"
}
}
查看别名:
GET _cat/aliases?v
会发现logs-4的is _write_ index参数被设置为false,而新生成索引logs-000005的is_ write_ index参数则为true。在两者的别名列表中都包含有logs,可以继续通过logs别名对原索引进行查询。
_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 接口扩容索引。
首先创建一个索引employee,将它的主分片数量number_ _of _shards设置为2,散列空间number. of. routing shards设置为12。然后,通过将索引的blocks. write参数设置为true,将索引设置为只读,这是因为使用split接口要求索引必须为只读。最后调用spit接口将employee索引的分片分裂到新索引 splited_employee中,index.number_of_shards参数设置为4,即分裂为4个分片。
如示例所示:
DELETE employee
PUT /employee
{
"settings": {
"number_of_shards": 2,
"number_of_routing_ shards": 12
}
}
PUT /employee/_settings
{
"blocks.write": true
}
PUT /employee/_split/splited_employee
{
"settings": {
"index.number_of_shards": 4,
"index.number_of_replicas": 1,
"index.blocks.write": false
},
"aliases": {
"stu": {}
}
}
在执行成功后,可调用“GET _cat/shards" 查看分片。在返回结果中可以看到employee索引共有4个分片,即2个主分片和2个副本分片;新索引splited_employee会有8个分片,即4个主分片和4个副本分片。split接口在创建新索引的同时,会将原索引的配置也一同设置到新索引中。所以index. blocks. write参数也会一同被复制过来,但这可能并不是我们想要的。
所以在分裂分片的同时支持通过aliases和settings设置新索引的别名和配置, 所以可以在分裂分片的同时将index. blocks. write参数覆盖。在示例中就将这个参数覆盖了,同时还添加了新的别名stu。另外,还可以在地址中添加copy_ settings = false参数禁止从源索引中复制配置,但这个参数在版本8中有可能被 废止,所以添加这个参数会收到警告。
使用split接口成功分裂分片后,原索引并不会被自动删除。通过原索引和新索引都可以查看到相同的文档数据,原索引是否删除应根据业务需要具体判断。
与_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_employee/_settings
{
"settings": {
"blocks.write": true,
"routing.allocation.require._name":"TIAN-PC"
}
}
PUT /splited employee/_shrink/shrinked_employee?copy_settings=true
{
"settings":{
"index.routing.allocation.require.name": null,
"index.blocks.write": null,
"index.number_of_shards" :2
}
}
同样地,使用shrink接口缩容后会创建新索引shrinked_employee,原索引和新索引都可以查询到相同的文档数据。
尽管_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字段开启。
缓存相关为了提升数据检索时的性能,Elasticsearch为索引提供了三种缓存。
第一种缓存称为节点查询缓存(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接口用于主动刷新一个或多个索引,将已经添加的文档编入索引以使它们在检索时可见。在调用该接口时,可以直接调用或与一个或多个索引起使用,还可以使用_all刷新所有索引。
GET索引1/_refresh
POST _refresh
GET all/_refresh
POST索引1,索引2/_ refresh
事实上,除了使用refresh接口主动刷新索引外,也可以在操作文档时通过refresh参数刷新索引。
_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接口的调用都是正确的:
GET _stats
GET _stats/store
GET kibana_sample_data_flights/_stats
GET kibana_sample_data_flights/_stats/fielddata
在stats接口中可以使用的统计名称及它们的含义见下面,它们在返回结果中的含义与此相同。
_shard_stores接口用于查询索引分片存储情况,而segments接口则用于查看底层Lucene的分段情况。这两个接口都只能通过GET方法请求,同时都可以针对一个或多个索引,例如:
GET _shard_storesGET /kibana_sample_data_flights/_shard_stores
GET _segments
GET /kibana_sample_data_flights/_segments/
我们前面的检索实际上都是围绕着search接口,但实际上Elasticsearch还提供了许多与文档检索有关的接口。比如,如果想要查看索引中满足条件的文档数量可以使用_count接口,如果想要执行一组检索可以使用msearch接口。
Elasticsearch提供了查看文档总数的_count接口,可通过GET或POST方法请求该接口。在请求该接口的路径上,可以添加索引、映射类型,以限定统计文档数量的范围。所以在示例中对count接口的请求都是正确的:
GET _count
POST /kibana_sample_data_logs/_count
{
"query": {
"match":{
"message": "chrome"
}
}
}
_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接口可通过GET或POST方法请求,请求路径中必须要包含_validate/ query,也可以在路径中添加索引名称以限定查询执行的范围。类似_search接口。示例:
POST _validate/query
{
"query": {
"range":{
"AvgTicketPrice": {
"gte": 1000,
"lte": 1500
}
}
}
}
_field_ caps接口用于查看某一字段支持的功能,主要包括字段是否可检索以及是否可聚集等。需要查看的字段可以通过URI参数fields设置,虽然可以使用GET或POST方法请求。在请求地址中,还可以添加索引名称以限定查询范围。例如:
GET _field_caps?fields=AvgTicketPrice
查询结果如下:
{
"indices" : [
"high_sdk_test",
"test",
"open-soft",
".apm-agent-configuration",
"pattern_custom",
"colleges",
"logs-1",
"kibana_sample_data_flights",
".kibana_1",
"logs-4",
"test2",
"test3",
".apm-custom-link",
".kibana_task_manager_1",
"my_index",
"pattern_test5",
"kibana_sample_data_logs",
"logs-000005",
"employees",
"index_alias",
"articles",
"pattern_test4"
],
"fields" : {
"AvgTicketPrice" : {
"float" : {
"type" : "float",
"searchable" : true,
"aggregatable" : true
}
}
}
}
示例2:
POST /kibana_sample_data_logs/_field_caps?fields=message,agent
{
"indices" : [
"kibana_sample_data_logs"
],
"fields" : {
"agent" : {
"text" : {
"type" : "text",
"searchable" : true,
"aggregatable" : false
}
},
"message" : {
"text" : {
"type" : "text",
"searchable" : true,
"aggregatable" : false
}
}
}
}
批量操作除了_msearch接口接口以外,还有_bulk接口和_mget接口。
如果需要批量地对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"}}
一条一条的查询,比如说要查询100条数据,那么就要发送100次网络请求,这个开销还是很大的,如果批量查询的话,查询100条数据,就只要发送1次网络请求,网络请求的性能开销缩减100倍。
比如一般的查询是:GET /open-soft/_doc/1
批量查询则是:
GET /_mget
{
"docs": [
{
"_index": " open-soft ",
"_id": 1
},
{
"_index": " open-soft-2",
"_id": 2
}
]
}
为了获得更快的索引速度,你能做的一项优化是通过批量API,一次发送多个命令进行操作;这个操作将节省网络来回的开销,并产生更大的索引吞吐量,一个单独的批量可以接受任何索引操作。
一旦Elasticsearch接收到了应用所发送的文档,它会将其索引到内存中称为分段(segments )的倒排索引。这些分段会不时地写入磁盘,这些分段是不能改变的,只能被删除,这是为了操作系统更好地缓存它们。另外,较大的分段会定期从较小的分段创建而来,用于优化倒排索引,使搜索更快。
有很多调节的方式来影响每一个环节中Elasticsearch对于这些分段的处理,根据你的使用场景来配置这些,常常会带来意义重大的性能提升。本节将讨论这些调优的方式,可以将它们分为以下3类。
·刷新(refresh )和冲刷( flush)的频率
刷新会让Elasticsearch重新打开索引,让新建的文档可用于搜索。冲刷是将索引的数据从内存写入磁盘。从性能的角度来看,刷新和冲刷操作都是非常消耗资源的,所以为你的应用正确地配置它们是十分重要的。
·合并的策略
Lucene ( Elasticsearch也是如此)将数据存储在不可变的一组文件中,也就是分段中。随着索引的数据越来越多,系统会创建更多的分段。由于在过多的分段中搜索是很慢的,因此在后台小分段会被合并为较大的分段,保持分段的数量可控。不过,合并也是十分消耗性能的,对于I/O子系统尤其如此。你可以调节合并的策略,来确定合并多久发生一次,而且分段应该合并到多大。
·存储和存储限流
Elasticsearch调节每秒写入的字节数,来限制合并对于I/O系统的影响。根据硬件和应用,你可以调整这个限制。还有一些其他的选项告诉Elasticsearch如何使用存储。例如,可以选择只在内存中存放索引。
Elasticsearch通常被称为近实时(或准实时)系统。这是因为搜索不是经常运行于最新的索引数据之上(这个数据是实时的),但是很接近了。打上近实时的标签是因为通常Elasticsearch保持了某个时间点索引打开的快照,所以多个搜索会命中同一个文件并重用相同的缓存。在这个时间段中,新建的文档对于那些搜索是不可见的,直到你再次刷新。
刷新,正如其名,是在某个时间点刷新索引的快照,这样你的搜索就可以命中新索引的数据。这是其优点。其不足是每次刷新都会影响性能:某些缓存将失效,拖慢搜索请求,而且重新打开索引的过程本身也需要一些处理能力,拖慢了索引的建立。
默认的行为是每秒自动地刷新每份索引。你可以修改其设置,改变每份索引的刷新间隔,这个是可以在运行时完成的。例如,下面的命令将自动刷新的间隔设置为了5秒。
PUT test1
{
"settings": {
"index. refresh_ interval ": "5s"
}
}
当增加refresh_ interval的值时,你将获得更大的索引吞吐量,因为花费在刷新上的系统资源更少了。
或者你也可以将refresh_ interval设置为-1,彻底关闭自动刷新并依赖手动刷新。这对于索引只是定期批量变化的应用非常有效,如产品和库存每晚更新的零售供应链。索引的吞吐量是非常重要的,因为你总想快速地进行更新,但是数据刷新不一定是最重要的,因为无论如何都不可能获得完全实时的更新。所以每晚你可以关闭自动刷新,进行批量的bulk索引和更新,完成后再进行手动刷新。
为了实现手动刷新,访问待刷新索引的refresh端点。
对于Elasticsearch而言,刷新的过程和内存分段写入磁盘的过程是相互独立的。实际上,数据首先索引到内存中,经过一次刷新后,Elasticsearch也会地搜索相应的内存分段。将内存中的分段提交到磁盘上的Lucene索引的过程,被称为冲刷( flush),无论分段是否能被搜到,冲刷都会发生。
为了确保某个节点宕机或分片移动位置的时候,内存数据不会丢失,Elasticsearch将使用事物日志来跟踪尚未冲刷的索引操作。除了将内存分段提交到磁盘,冲刷还会清理事物日志。
满足下列条件之一就会触发冲刷操作:
■内存缓冲区已满。
■自上次冲刷后超过了一定的时间。
■事物日志达到了一定的阈值。
为了控制冲刷发生的频率,你需要调整控制这3个条件的设置。
内存缓冲区的大小在elasticsearch.yml配置文件中定义,通过indices.memory . index_buffer_size来设置。这个设置控制了整个节点的缓冲区,其值可以是全部JVM堆内存的百分比,如10%,也可以是100 MB这样的固定值。事物日志的设置是具体到索引上的,而且同时控制了触动冲刷的规模(通过index. translog.flush_threshold_size )和多数索引设置一样,你可以在运行时修改它们。
PUT test1
{
"settings": {
"index.translog.flush_threshold_size": "500mb"
}
}
当冲刷发生的时候,它会在磁盘上创建一个或多个分段。执行一个查询的时候,Elasticsearch(通过Lucene )查看所有的分段,然后将结果合并到一个整体的分片中。
关于分段,这里需要记住的关键点是你需要搜索的分段越多,搜索的速度就越慢。为了防止分段的数量失去控制,Elasticsearch (也是通过Lucene )在后台将多组较小的分段合并为较大的分段。
如果Elasticsearch没有足够的堆来完成一个操作,它将抛出一个out-of-memory的异常,很快该节点就会宕机,并被移出集群。这会给其他的节点带来额外的负载,因为系统需要复制和重新分配分配,以恢复到初始配置所需的状态。由于节点通常是相同的配置,额外的负载很可能使得至少另一个节点耗尽内存,这种多米诺骨牌效应会拖垮整个集群。
当JVM堆的资源很紧张的时候,即使在日志中没有看见out- of-memory的异常,节点还是可能变得没有响应。这可能是因为,内存不够迫使垃圾回收器( GC)运行得更久或者更频繁来释放空闲的内存。由于GC消耗了更多的CPU资源,节点花费在服务请求甚至是应答主节点ping的计算能力就更少了,最后导致节点 被移出集群。
当垃圾回收消耗了过多的CPU时间,工程师总是试图发现些神奇的JVM设置来解决所有的麻烦。多数情况下,这不是解决问题的最佳途径,因为过度的垃圾回收只是Elasticsearch需要更多堆内存的征兆。
尽管增加堆的大小是很明显的解决方案,这并非总是可能的。增加数据节点同样如此。
然而,你可以参考些技巧来降低堆的使用。
■减小索引缓冲区的大小。
■减少查询缓存的大小。
■减少搜索和聚集请求中size参数的值(对于聚集,还需要考虑到shard size )。
■如果必须处理大规模的数据,你可以增加一些非数据节点和非主节点来扮演客户端的角色。它们将负责聚合每个分片的搜索结果,以及聚集操作。
最后,Elasticsearch使用另一种缓存类型来回避Java垃圾回收的方式。新的对象分配到名为新生代的空间。如果新对象存活得足够久,或者太多的新对象分配到新生代导致其被占满,这些新对象就会被“提升”到老年代。尤其是在聚集的时候,需要遍历大量的文档并创建很多可能被重用的对象,后一种填满新生代的情况就会出现。
通常,你希望将这些可能被聚集重用的对象提升到老年代,而不是新生代填满后恰巧放到老年代的一些随机的、临时的对象。为了实现这个目标, Elasticsearch实现了一个页面缓存回收器( PageCacheRecycler ),其中被聚集所使用的大数组被保留下来,不会被垃圾回收。默认的页面缓存是整个堆的10%,某些情况下这个值可能太大(例如,你有30 GB的堆内存,页面缓存就有3GB了)。
可以设置elasticsearch.yml中的cache.recycler.page.limit.heap来控该制缓存的大小。
不过,有的时候还是需要调优JVM的设置(尽管默认已经很好了),例如,你的内存基本够用,但是当一些罕见的长时间GC停顿产生的时候,集群还是会遇到麻烦。有一些设置让垃圾回收发生得更频繁,但是停顿时间更短,降低些整体的吞吐量来换取更少的延迟:
增加幸存者区(Survivor)的空间(降低-XX:SuvivorRatio )或者是整个新生代在堆中的占比(降低-XX: NewRatio )。你可以监控不同的年代来判断这些是否需要。提升到老年代需要花费更长的等待时间,而更富裕的空间使得新生代的垃圾回收有较多的时间来清理临时存在的对象,避免它们被提升到老年代。但是,将新生代设置得过大,将使得新生代的垃圾回收过于频繁而且变得效率低下,这是因为生存周期更长的对象需要在两个幸存者空间来回复制。
理想的堆大小:遵循“一半”原则:
在不知道堆的实际使用情况时,经验法则是将节点内存的一半分配给Elasticsearch,但是不要超过32GB。这个“一半”法则通常给出了堆大小和系统缓存之间良好的均衡点。
如果可以监控实际的堆使用情况,一个好的堆大小就是足够容纳常规的使用, 外加可预期的高峰冲击。内存使用的高峰可能会发生。例如,某些人决定在拥有许多唯一词条的分析字段上,对所有结果运行一次terms聚集。这强迫Elasticsearch将所有的词条加载到内存中用于统计。如果你不知道会有怎样的高峰冲击,经验法则同样是一半:将堆大小设置为比常规高出50%。