作者:huateng | 来源:互联网 | 2023-10-12 17:10
ES基本原理
名词解释
In-memory buffer: ES内存缓冲区,新建的document写入的地方
document:索引和搜索的主要数据载体,对应写入到ES中的一个doc
Segment: Lucene里面的一个数据集概念,前边提到的document最终就是存放在这里边,一个Segment包含多个document
Refresh:将In-memory buffer中的数据生成一个segment并放入系统缓存区,同时清空In-memory buffer
Translog: 新建的document也会写入到这个里边,记录这些日志,并用于回放
Flush:会先执行一次refresh操作,之后将系统缓存区中的一个一个Segment拷贝到磁盘中,之后清空translog
Refresh过程
新建的document会先写进ES内存缓冲区中,但是这个区域是不可见的,不能被搜索到,所以Refresh的过程就是产生一个segment放入系统缓存区,这部分区域是可读的,可以被搜索到。在默认情况下ES每隔1秒钟,会自动Refresh,保证数据可见。
Refersh的执行条件:
- 手动触发,通过GET查询以及index、bulk写入时指定refresh
- 自动触发,配置refresh_interval
- In-memory buffer满了
可以更改这个配置
改为10秒
POST /index/_settings
{“refresh_interval”: “10s”}
改为不自动更新
POST /index/_settings
{“refresh_interval”: “-1″}
Segment合并
由于每一次refresh都会新建一个Segment,那么随着refresh次数越来越多,segment文件也会越来越多,而每一次查询最终都会检索segment文件,每一个segment都会占用操作系统的CPU、文件句柄和内存资源,而且,在查询的时候,需要在每个segment上都执行一次查询,这样是很消耗性能的。
为了解决这个问题,es会自动定期的将多个小segment合并为一个大的segment,这次合并是真正意义上的物理删除。
当新合并后的segment完全写入磁盘之后,es就会自动删除掉那些零碎的segment,之后的查询都在新合并的segment上执行。Segment的合并会消耗大量的IO和cpu资源,这会影响查询性能。
下面是几个常用配置项
- index.merge.policy.floor_segment:该属性用于阻止碎片段的频繁刷新。小于或者等于该设定值的段将考虑被合并。默认值为2M。
- index.merge.policy.max_merge_at_once:该属性指定了索引过程中同一时刻用于合并的段的最大数量,默认为10。如果将值设置得更大,一次合并操作将合并更多的段,同时合并过程也需要更多的I/O资源。
- index.merge.policy.max_merged_segment:默认值为5GB,该属性指定了索引过程中单个段的最大容量。这个值是一个近似值,因为合并操作中,段的大小等于待合并段的总大小减去各个段中删除文档的大小。
- index.merge.policy.segments_per_tier:该属性指定了每层段的数量。较小的值带来较少的段。这意味着更多的合并操作,和更低索引性能。默认值为10,其值应该不低于index.merge.policy.max_merge_at_once属性值,否则就会使合并次数过多,引起性能问题。
Translog与Flush
由于Refresh操作将数据放入到内存中,而内存中的不保险,所以就会把每一条document新建和更新记录得translog中,这样如果出现意外情况,可以从translog中找到这部分数据,并进行恢复。然后会通过Flush操作,将这部分数据内存中的数据,写入磁盘里,也为了避免translog太大,同时情空该部分数据。
Flush的具体流程:
- 将系统缓存区里的一个一个segment写入到磁盘中
- 执行一次refresh操作
- 清空translog日志
具体源码如下:
// Only flush if (1) Lucene has uncommitted docs, or (2) forced by caller, or (3) the
// newly created commit points to a different translog generation (can free translog)
if (indexWriter.hasUncommittedChanges() || force || shouldPeriodicallyFlush()) {
ensureCanFlush();
try {
translog.rollGeneration();
logger.trace("starting commit for flush; commitTranslog=true");
//将系统缓存区里的一个一个segment写入到磁盘中
commitIndexWriter(indexWriter, translog, null);
logger.trace("finished commit for flush");
//执行一次refresh操作
refresh("version_table_flush", SearcherScope.INTERNAL);
//清空translog日志
translog.trimUnreferencedReaders();
} catch (AlreadyClosedException e) {
throw e;
} catch (Exception e) {
throw new FlushFailedEngineException(shardId, e);
}
refreshLastCommittedSegmentInfos();
}
该段源码很好解释了执行flush的三个内部条件
- Lucene中有未提交的文档
- 强制调用
- 定期执行
ES默认配置中,translog的配置同flush进行绑定,每一次index、bulk这些写入更新操作,都会先flush然后返回200,但是这种配置会牺牲很大的性能,可以采用异步刷新的方式,并配置每隔几秒进行刷新,来提高写入性能。
注意:该操作要先关闭索引,才能执行
PUT /index/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "5s"
}
Flush执行条件:
- 默认配置下(index.translog.flush_threshold_period),每30分钟进行一次
- 当translog的大小超过设定值(index.translog.flush_threshold_size),默认是512MB
- 多少时间间隔内(index.translog.interval:多少时间间隔内会检查一次translog,来进行一次flush操作。es会随机的在这个值到这个值的2倍大小之间进行一次操作,默认是5s。)会检查一次translog,来进行一次flush操作。es会随机的在这个值到这个值的2倍大小之间进行一次操作,默认是5s。
近实时搜索与实时搜索
看过上边的Refresh操作,可以得知,在默认情况下,每隔一秒进行一次refresh操作,写入的docment才可以搜索出来,这是准实时的原因。但是ES也提供了实时搜索的功能。下面就是两种实时搜索的操作。
- GET查询
- 写入时同步执行refresh操作再返回200
这两种操作的原理都是手动执行refresh操作,保证数据从In-memory buffer写入到系统缓存区,但是这种操作,相比每秒或者定时刷新refresh,在性能上的开销是很大的,相当于每一次请求就会执行一次refresh操作,生成一个新的segment,完全没有用到In-memory buffer这个缓冲区。