前言

Elasticsearch 是 Java 语言开发,底层的存储引擎是基于Lucene实现,Lucene的倒排索引(Inverted Index)是先在内存里生成,而后定期以段文件(segment file)的模式刷到磁盘的。

每个段文件理论就是一个残缺的倒排索引,并且一旦写到磁盘上就不会做批改。API层面的文档更新和删除实际上是增量写入的一种非凡文档,会保留在新的段里。不变的段文件易于被操作系统cache,热数据简直等效于内存拜访。

内存构造

ElasticSearch的内存从大的构造能够分堆内存(On Heap)和堆外内存(Off Heap)。Off Heap局部由Lucene进行治理。On Heap局部存在可GC局部和不可GC局部,可GC局部通过GC回收垃圾对象,从而开释内存。不可GC局部不能通过GC回收垃圾对象,这部分会通过LRU算法进行对象革除并开释内存。更加具体的内存占用与调配如下图:

On Heap内存占用

这部分内存占用的模块包含:Indexing Buffer、Node Query Cache、Shard Request Cache、Field Data Cache以及Segments Cache。

Indexing Buffer

默认调配的内存大小是10% heap size,当缓存满了或者refresh/flush interval到了,就会以segment file的模式写入到磁盘。

Indexing Buffer的存在能够进步文档写入申请的响应速度,取得更高的吞吐量,缩小磁盘IO的拜访频率,节俭了CUP资源。这部分空间是能够通过GC开释被重复利用的。

缓存机会:新文档数据写入的时候
生效或者回收:当空间满了的时候会触发GC分明缓存对象,开释空间

Node Query Cache (Filter Cache)

节点级别的缓存,节点上的所有分片共享此缓存,是Lucene层面的实现。缓存的是某个filter子查问语句在一个segment上的查问后果。如果一个segment缓存了某个filter子查问的后果,下次能够间接从缓存获取后果,无需再在segment内进行过滤查问。

每个segment有本人的缓存,缓存的key为filter子查问(query clause ),缓存内容为查问后果,这些查问后果是匹配到的document numbers,保留在位图FixedBitSet中。

缓存的构建过程是:对segment执行filter子查问,先获取查问后果中最大的 document number: maxDoc(document number是lucene为每个doc调配的数值编号,fetch的时候也是依据这个编号获取文档内容)。而后创立一个大小为 maxDoc的位图:FixedBitSet,遍历查问命中的doc,将FixedBitSet中对应的bit设置为1。

例如:查问后果的maxDoc是8,那么创立出的FixedBitSet就是:[0,0,0,0,0,0,0,0],能够了解为是一个长度为8的二值数组,初始值都是0,假如filter查问后果的doc列表是:[1,4,8],那么FixedBigSet就设置为:
[1,0,0,1,0,0,0,1],当查问有多个filter子查问时,对位图做交并集位运算即可。

用一个例子来阐明Node Query Cache构造。如下图查问语句蕴含两个子查问,别离是对date和age字段的range查问,Lucene在查问过程中遍历每个 segment,查看其各自的LRUQueryCache是否命中filter子查问,segment 8命中了对age和date两个字段的缓存,将会间接返回后果。segment 2只命中了对age字段的缓存,没有命中date字段缓存,将继续执行查问过程。

缓存机会:
1.拜访频率大于等于特定阈值之后,query后果才会被缓存
2.segment的 doc 数量须要大于10000,并且占整个分片的3%以上

生效或回收:segment合并会导致缓存生效。内存的治理应用LRU算法。

Shard Request Cache

Shard Request Cache简称Request Cache,他是分片级别的查问缓存,每个分片有本人的缓存,属于ES层面的实现。ES默认状况下最多应用堆内存的1%用作 Request Cache,这是一个节点级别的配置。内存的治理应用LRU算法。

缓存的实现在IndicesRequestCache类中,缓存的key是一个复合构造,次要包含shard,indexreader,以及客户端申请。缓存的value是将查问后果序列化之后的二进制数据。

final Key key =  new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey);

cacheEntity:次要是shard信息,代表该缓存是哪个shard上的查问后果。
readerCacheKey:次要用于辨别不同的IndexReader。
cacheKey:次要是整个客户端申请的申请体(source)和申请参数(preference、indexRoutings、requestCache等)。

Request Cache的次要作用是对聚合的缓存,聚合过程是实时计算,通常会耗费很多资源,缓存对聚合来说意义重大。

因为客户端申请信息间接序列化为二进制作为缓存key的一部分,所以客户端申请的json程序,聚合名称等变动都会导致cache无奈命中。

缓存机会:简略的能够了解成只有客户端查问申请中size=0的状况下才会被缓存

生效或回收:
1.新的segment写入到分片后,缓存会生效,因为之前的缓存后果曾经无奈代表整个分片的查问后果。
2.分片refresh的时候,缓存生效

Field Data Cache

在有大量排序、数据聚合的利用场景,须要将倒排索引里的数据进行解析,按列结构成 docid->value 的模式才可能做后续疾速计算。对于数据量很大的索引,这个结构过程会十分消耗工夫,因而ES 2.0以前的版本会将结构好的数据缓存起来,晋升性能。因为heap空间无限,当遇到用户对海量数据做计算的时候,就很容易导致heap吃紧,集群频繁GC,根本无法实现计算过程。内存的治理应用LRU算法。

Segment Cache(Segment FST Cache)

一个segment是一个齐备的lucene倒排索引,倒排索引是通过词典 (Term Dictionary)到文档列表(Postings List)的映射关系实现疾速查问的。因为词典和文档的数据量比拟大,全副装载到heap里不事实,所以存储在硬盘上的。

为了疾速定位一个词语在词典中的地位。Lucene为词典(Term Dictionary)做了一层词典索引(Term Index)。这个词典索引采纳的数据结构是FST (Finite State Transducer)。Lucene在关上索引的时候将词典索引(Term Index)全量装载到内存中,即:Segment FST Cache,这部分数据永驻堆内内存,且无奈设置大小,长期占用50% ~ 70%的堆内存。内存治理应用LRU算法。

FST(具体参考这里) 。能够参考TRIE树进行了解。FST在工夫复杂度和空间复杂度上都做了最大水平的优化,使得Lucene可能将Term Dictionary(词典)齐全加载到内存,疾速的定位Term找到响应的output(posting倒排列表)。

内存回收与开释:
1.删除不必的索引
2.敞开索引(文件依然存在于磁盘,只是开释掉内存),须要的时候可从新关上。
3.定期对不再更新的索引做force merge。本质是对segment file强制做合并,segment数量的缩小能够节俭大量的Segment Cache的内存占用。

Off Heap内存占用

Lucene中的倒排索引以段文件(segment file)的模式存储在磁盘上,为了进步倒排索引的加载与检索速度,防止磁盘IO拜访导致的性能损耗,Lucene会把倒排索引数据加载到磁盘缓存(操作系统个别会用零碎内存来实现磁盘缓存),所以在进行内存调配的时候,须要思考到这部分内存,个别倡议是把50%的内存给Elasticsearch,剩下的50%留给Lucene。