0
点赞
收藏
分享

微信扫一扫

Lucene

1 Lucene特性

一个Shard分片就是一个Lucene Index,每个Lucene里的Segment为Lucene存储的最小管理单元

1.1 Shard副本分片

可以设置分片的副本数量来提升高并发场景下的搜索速度,但是同时会降低索引的效率。

副本除了高可用,另外可以提高并发场景下吞吐量,加快检索,但同时会造成资源浪费

1.2 Segment不变性

在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升读写性能,不会出现阻塞。如果多进程同时修改数据,可以通过_version字段来保证并发情况下的正确性。

其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。

1.3 Segment压缩

写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量

1.4 Segment缓存

一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。

1.5 Segment高吞吐量

Segment在被refresh之前,数据保存在内存中,是不可被搜索的。但是如上这种机制避免了随机写,数据写入都是 Batch 和 Append,能达到很高的吞吐量。同时为了提高写入的效率,利用了文件缓存系统和内存来加速写入时的性能,并使用日志来防止数据的丢失。

2 Lucene底层文件

2.1 倒排索引  

     每个倒排索引由三个部分组成,Term Index,Term Dictionary以及Posting List,在Lucene中负责数据检索匹配

Term Index tip文件

整个 term dictionary 本身又太大了,无法完整地放到内存里。于是就有了 term index。term index 有点像一本字典的大的章节表。通过用二分查找的方式,比全遍历更快地找出目标的 term(term dictionary)。有了 term dictionary 之后,可以用 logN 次磁盘查找得到目标。

Lucene 内部的 Term Index 是FST数据结构,FST 既共享前缀也共享后缀,更加的节省空间。FST有更高的数据压缩率和查询效率。

Term Dictionary tim文件

在数据录入时,可将Term按照顺序排序,记录该Term出现的docid list。检索时通过Term Index找到对应Term Dictionary位置,可知道定位Term

大致位置,从而起到加快检索的作用。

Posting List(doc、pos文件)

每个field在建立一个倒排索引时,存储了所有符合某个term的文档id。Posting list就是一个int的数组。除此之外还包含:文档的数量、词条在每个文档中出现的次数、出现的位置、每个文档的长度、所有文档的平均长度等,在计算相关度时使用。

给定各种查询过滤条件得到posting list,多个过滤条件时,需要把多个posting list 做一个“与”的合并。

ES 支持两种的联合索引方式,如果查询的 filter 缓存到了内存中(以 bitset 的形式),那么合并就是两个 bitset 交集。Bitset通过分布密度不同,也会有大小不同程度的压缩。

如果查询的 filter 没有缓存,那么就用 skip list 的方式去遍历两个 on disk 的 posting list。

doc保存了每个term的doc id列表和term在doc中的词频

pos文件,保存了term在doc中的位置

2.2 正排索引

正排索引往往又叫docvalues,docvalues单独存储每个字段的值,采用键值对方式的列式存储,主要用于对字段进行排序、聚合等操作。

但因为它存储字段所有值,在索引文件中为较大的存在,检索后定位起来较为缓慢,故也做了dvm、dvd 的区分,dvm为dvd的索引文件。

除text,其他格式默认开启该功能(doc_values:true)。未开启该功能的字段无法进行排序和聚合。

2.3 原始文件

数据存储文件,存放所有字段的文件。fdx为索引文件,fdt为数据文件。文件主要储存于磁盘,如传统的数据库一样,为行式存储。在查询的fetch阶段,则是通过doc_id在该文件取出全部字段数据用于返还客户端。

2.4 复合文件

cfe为索引文,cfs 为数据文件,cfe文件保存Lucene各文件在.cfs文件的位置信息

cfs、cfe 在segment还很小的时候,将segment的所有文件都存在在cfs中,在cfs逐渐变大时,大小超过shard的10%,则会拆分为其他文件,如tim、dvd、fdt等文件。复合文件作用是用于控打开文件数量,在es频繁刷新的需求下(refresh为1s),大大减少了打开文件数。

2.5 其它文件

si为segment元信息文件,记录了segment的文档数量和对应的文件列表等信息

fnm为fields基本信息,包括field的数量,field的类型,以及IndexOpetions,包括是否存储、是否索引,是否分词,是否需要列存等等

3 基于Lucene底层优化方案设计

3.1 预加载

默认情况下与查询性能相关的模块文件tim、dvd、fdt均存在于磁盘上,当请求到shard时,需要一定的I/O将文件加载到内存中,从而完成检索、排序、取数等步骤。但这往往会消耗较久的时间,然而当数据文件在操作系统内存时,则往往会以更快的速度返还结果,达到毫秒级。

通过调研发现,大部分场景的查询时间主要消耗在检索和排序阶段,故通过预加载tim和dvd等文件到内存,加快整个查询请求的结果返还。

PUT /my-index-000001

{

  "settings": {

    "index.store.preload": ["dvd","tim","doc","dim","cfs"]

  }

}

    按照Lucene文件性质,只需要加载dvd、tim、doc、dim即可达到快速检索目的。但局点大多为周索引模式,当日数据进入ES中多为cfs文件(包含tim、dvd、fdt等数据文件),不同类型文件无法从中区分开,故只能将整个cfs加载到缓存中,以保证在清除系统缓存后cfs还能常驻的buffer中。

3.2 预热查询

预加载到内存中的文件,占用系统的buffer内存,处于不稳定状态。当其它进程需要大量使用buffer,直至占满,这时操作系统可能会将es已预加载的文件kiil掉,导致检索性能不稳定。

ES会有“越查越快”的现象,是因为在第一次查询时,会将查询命中分片的tim、dvd等文件从磁盘加载到内存中从而完成检索排序,但当第二次查询时,如命中相同的segment和相同的block,则往往不需要磁盘到内存这一步的耗费,直接在内存中完成检索排序,以更短的时间返还结果集。随着查询次数的增多,大比例的tim、dvd在内存buffer中,检索性能也慢慢趋于稳定,达到最优。

针对这一现象,在ES后台执行定时脚本,针对热数据索引常用字段进行查询排序,当预加载文件被操作系统kill后,也能及时重新加载。从而保证查询性能稳定。

3.2 内存占用评估

因使用Lucene文件预加载方式,所以占用内存资源较多,需对各类索引数据进行内存预估。按照底层文件格式,内存分为两类,一类为tim+dvd评估,此类评估方式适用于热数据结构化数据;二类为cfs评估,当天录入数据,因没有占据shard大小的十分之一,故还是复合文件。所以在实际应用中,内存评估应为29天(tim+dvd)文件大小和1天cfs文件大小。因为复合文件生成是随机的,故有小概率事件当周索引部分segment还是为cfs文件。

3.3 segments合并机制干预

根据Lucene机制,默认每1s进行一次refresh,形成一个segment file,这是由于后台始终有一个进程,负责数据的merge操作,以此减少文件句柄数。

索引和分片策略定为周索引,如果不加以调整,因文件大小不足整个shard的十分之一,则会出现当周的后几天,文件全部为cfs格式,大量的cfs文件堆积,导致内存占用更大(cfs模式是tim+dvd模式的2.5倍)。

调优策略在每天凌晨,通过forcemerge机制,将当前周索引的每个分片合并成固定个数的segment,以此将当天的数据进行合并,让数据文件变为tim+dvd的非复合模式,以此达到清除cfs文件的目的。

固定个数segment值评估,需要根据整个索引数据量,每个分片数据量。在制定分片策略时,根据2000万数据量进行划定,故每个分片上的数据大小不超过6G。利用forcemerge机制合并成1个segment,每次都会触发segment合并,即使该segment超过5G,也会强制merge,这会造成资源浪费。ES底层默认最大segment大小为4.9G,当segment为这个大小时,不再进行默认的merge合并操作。如果设置segment数量为3时,当检测到某个segment大小已为4.9G,则该segment不会触发forcemerge操作,会合并较小的segment,这极大节省开销和加快forcemerge进程。

举报

相关推荐

0 条评论