• 堆内内存
    • 索引缓冲index buffer
    • 节点查询缓存Node Query Cache
    • 分片请求缓存Shard Request Cache
    • 字段缓存Fielddata Cache
    • FST缓存 Segments Cache
  • 堆外内存 Segments Memory
  • 断路器和驱逐线

Elasticsearch内存分为on heap以及off heap两部分.Elasticsearch能控制的是On Heap内存部分,这部分由JVM管理;Off Heap由Lucene管理,负责缓存倒排索引数据空间(Segment Memory).

es内存模型

堆内内存On Heap

堆内主要由写入缓冲,节点查询缓存,分片查询缓存,字段缓存和FST缓存组成.其中只有写入缓冲能被GC,其它缓存都是常驻内存的,满了之后通过LRU来驱逐.

堆内存最大不能超过32GB。因为在Java中,所有对象都分配在堆上并由指针引用。32位的系统,堆内存大小最大为 4 GB。对于64位系统,可通过内存指针压缩(compressed oops)技术,依旧可以使用32位的指针来指向堆对象,这样可以大大节省CPU 内存带宽,提高操作效率。但当内存大小超过32G时候,对象指针就需要变大,操作效率就大大降低。

写入缓冲index buffer

新数据写入时不直接写入磁盘,而是先写入index buffer,等缓冲区填满或是经过一定时间再提交为段.

缓冲区默认大小为堆的10%

indices.memory.index_buffer_size: 10%

节点查询缓存Node Query Cache

Node Query Cache属于node-level级别的缓存,能被当前节点所有分片共享,用于缓存filter的查询结果,即文档在该filter下是true还是false.

几个参数:

# 默认大小
indices.queries.cache.size: 10%
# 用来控制索引是否启用缓存,默认是开启的,在每个索引的setting里面配置
index.queries.cache.enabled: true
# 是否在所有Segment上启用缓存,默认false
indices.queries.cache.all_segments: false

分片请求缓存Shard Request Cache

Shard Request Cache属于shard-level级别的缓存,默认开启.在索引搜索的query阶段,每个分片独自在本地搜索相关文档,并把结果缓存在这里.

PUT /my-index-000001
{
  "settings": {
    "index.requests.cache.enable": true
  }
}

默认情况下,分片请求缓存只缓存size=0的搜索请求,它不保存hits数组,只保存hits.total,aggregations和suggestions,这可能是为了控制缓存的大小.

即使在索引setting上设置开启了缓存,如果size不为0的话缓存也不会生效.如果要强制为这些请求打开缓存,可以在请求时带上查询参数

GET /my-index-000001/_search?request_cache=true
{
  "size": 10,
  "aggs": {
    "popular_colors": {
      "terms": {
        "field": "colors"
      }
    }
  }
}

对于还在频繁更新的索引最好不要启用该缓存.

字段缓存Fielddata Cache

在对Text类型字段排序或聚合时,由于倒排索引保存在磁盘,把相关字段加载到Fielddata Cache可以加速查询.

# 大小默认无限
indices.fielddata.cache.size: unbounded

Fielddata Cache大小默认无限是个很容易踩到的坑,如果任由它无限膨胀,会把内存吃满,持续触发断路器,因此建议一定要给设置它.

断路器默认是95%触发

indices.breaker.total.limit: 95% 

因此这里建议设置60%~80%.

FST缓存 Segments Cache

Segments Cache是segments FST(Finite State Transducer)数据的缓存,为倒排索引的二级索引.

FST 永驻堆内内存,无法被 GC 回收。该部分内存无法设置大小,长期占用 50% ~ 70% 的堆内存,只能通过delete index,close index以及force-merge index释放内存。从7.3版本开始,FST由堆外内存来存储。

ES 底层存储采用 Lucene,写入时会根据原始数据的内容,分词,然后生成倒排索引。查询时,先通过 查询倒排索引找到数据地址(DocID),再读取原始数据(行数据、列数据)。但由于 Lucene 会为原始数据中的每个词都生成倒排索引,数据量较大。所以倒排索引对应的倒排表被存放在磁盘上。这样如果每次查询都直接读取磁盘上的倒排表,再查询目标关键词,会有很多次磁盘 IO,严重影响查询性能。为了解磁盘 IO 问题,Lucene 引入排索引的二级索引 FST。实现原理上可以理解为前缀树,加速查询。

堆外内存Off Heap

在设置ES堆内存时候至少要预留50%物理内存,因为这部分内存主要用做ES堆外内存,堆外内存主要用来来存储Lucene的segment.

index buffer写满了之后就会提交为segment,再通过fsync写回硬盘.

最上面的图里有Segment Cache,Segment Memory以及Segment,容易混淆,这里对比解释下

位置 释义
Segment Cache On Heap Segment的FST字典树,并非Segment本身,用于加速对Segment的查询
Segment Memory Off Heap Segment的缓存,因为Segment很大,不可能全部放在内存里,因此不常用的Segment会被淘汰,这里只保存常用的和新提交的
Segment Disik 原始的Lucene数据,包括倒排索引,docValue等

断路器Circuit Breaker

断路器和驱逐线

Elasticsearch 在 total,fielddata,request 三个层面上都设计有 circuit breaker 以保护进程不至于发生 OOM 事件。

# 针对整个堆内存
indices.breaker.total.limit: 95%
# 针对单次查询需要新增的fielddata,假如fielddata上限是60%,假设当前fielddata占用是0,当前查询需要新增50%,虽然总大小没超过上限,但是单次新增超了,查询会被拒绝
indices.breaker.fielddata.limit: 40%
# 针对单次查询需要使用的总cache,包括fielddata,filter cache等,需要大于上面
indices.breaker.request.limit: 60%

上图中的断路器对应indices.breaker.total.limit,驱逐线对应indices.fielddata.cache.size.

二者间的空间为可容纳的查询大小,如果需要进行的查询需使用较大内存,就要把这个空间调大