共计 3368 个字符,预计需要花费 9 分钟才能阅读完成。
前言
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。