一、Solr索引

我们知道Solr的索引(准确说是Lucene索引,Solr是基于Lucene开发,后面我提到的索引文件都是Lucene的,后面就不再解释了)称为倒排索引,我们经常说到的倒排索引到底是什么?如何让Solr查询更快,索引写吞吐量更大。

1.1 Solr索引文件

在了解索引文件之前,我们可以先了解几个概念:

  1. 索引(Index)

可以提供完整查询的索引目录就称为Index,Solr中一个core的索引数据文件目录就是一个Index

  1. 段(Segment)

一个索引由多个段组成, 多个段可以合并, 以减少读取内容时候的磁盘IO。Solr中的数据写入会先写内存的一个Buffer,当Buffer内数据到一定量后会被flush成一个 Segment,每个Segment有自己独立的索引,可独立被查询,Segment中写入的文档不可被修改。

  1. 文档(Document)

文档是我们建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档。

  1. 域(Field)

一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,描述等,都可以保存在不同的域里。

这是我电脑上博客网站的一个Index,这个索引下有一个索引段,_8为前缀的文件则是其段文件

文件结构如下: 我们可以看到索引文件有不同的后缀,常见的文件解释可以参考文件对照表:

文件后缀 文件类型 描述
segments_N Segments File 保存一个提交的段文件信息
write.lock Lock File 进程级文件锁,防止多个IndexWriter同时写到一份索引文件中
.si Segment Info 保存了索引段的元数据信息
.cfs,.cfe Compound File 复合索引文件(一般不推荐使用),所有索引信息都存储到复合索引文件中
.fnm Fields 保存fields的相关信息
.fdx Field Index 保存指向field data的指针
.fdt Field Data 文档存储的字段的值
.tim Term Dictionary term词典,存储term信息
.tip Term Index 到词典的索引
.doc Frequencies 主要存储词频
.pos Positions 存储出现在索引中的term的位置信息
.nvd,.nvm Norms .nvm文件保存索引字段加权因子的元数据,.nvd文件保 存索引字段加权数据

1.2 Solr索引文件的生成和Merge过程

对于索引文件,我们首先应该明确几个点:

  • 索引文件写入磁盘后,不可更改
  • Solr采用的是标记删除,会有专门的.del文件标记删除
  • 索引更新也是采用标记删除完成的
  • 写入磁盘的索引段,必须提交后才会更新到segments_N和.si中
  • 索引依赖于Index文件重新打开Search,才能可见(即可搜索)

下面大家请看索引段和Merge的过程:

二、索引段优化

2.1 为什么要进行段Merge

  1. 如果我们不进行段Merge,那么Search就会越来越慢,Solr中默认是采用串行遍历所有的段文件最后合并结果,因此段越多,Search就会越慢
  2. 如果不Merge段,标记删除的文档,将会越来越多,进行合并处理标记删除耗时将会非常多
  3. 由于段越来越多,将会产生很多文件句柄,会影响系统稳定性

因此对于Solr 集群来说进行Merge调优,对集群IO、响应性能、写索引吞吐量和操作系统稳定性都是必要的

2.2 段Merge策略

2.2.1 TieredMergePolicy

Solr默认采用的就是同层合并策略,同层合并大致流程为,我们会定义一个形似大小的索引为同一层的索引,当同一层段数量达到设置的阈值后,将会触发Merge,合并至上一层,如果上一层的段数量正好达到了阈值,将会触发联动Merge

2.2.2 其他Merge策略

Solr还提供了其他的Merge策略,LogByteSizeMergePolicy 、LogDocMergePolicy ,从名称可以知道,这两种Merge策略是依赖trascation log进行Merge的配置项仅有mergeFactor,对于Log的merge策略我们不推荐使用,因为我们不能通过日志准确知道段段数量。 当然用户还可以自定义Merge策略。可以配置SortingMergePolicy,通过内置一个TieredMergePolicy来实现。

2.3 TieredMergePolicy优化

我们所提到的Merge主要就是针对TieredMergePolicy进行优化。首先我们看看TieredMergePolicy的可控制参数。在Solr文档中也对参数进行了解释,我们可以在Solr源码中找到更加详细的解释

org.apache.lucene.index.TieredMergePolicy

见参数列表:

参数名 参数解释 默认值
maxMergeAtOnce 在Normal Merge的情况下,一次Merge的最大段数量 10
maxMergedSegmentMB 在Normal Merge情况下,允许生成的最大合并段的大小 5GB
maxMergeAtOnceExplicit 在Force Merge情况下,一次Merge的最大段数量 30
floorSegmentMB 同层段定义的阈值,段大小差值小于此阈值将被认定是同层段 2MB
segmentsPerTier 同层允许的最大段数量,达到此阈值将会触发Merge 10
forceMergeDeletesPctAllowed 主动调用forceMergeDeletes接口,删除文档的百分超过此值将会触发Merge并清除删除文档 10%
deletesPctAllowed 索引段允许的最大删除文档比例 33%

对于Merge策略的调优,我们主要看集群的几个重要因素:

  • 索引总量:文档数量、索引总大小
  • 单个文档的平均大小
  • 文档的更新速率
  • 机器的IO和CPU能力

三、示例

3.1 实战优化

我们来看一个调优实战:

  • 索引总量5千万文档数量,索引文件大小总量,150GB
  • 索引平均文档大小 3.2kb
  • 文档更新速率 140 doc/sec

假设我们部署的Solr Cloud集群的是30个分片的,因此可以得出我们每个core的索引在5GB左右,为了保持Solr段在20个以下(这个值是经过多次测试,保持Search性能的一个阈值),同时我们需要兼顾磁盘IO和CPU的负荷,我们进行了如下设置:


<!-- 假设我们采用2个线程(core级别)写索引,那么每分钟将会生成2个大小约为1MB的文件,5分钟将会产生一次Merge,且由于总大小为5GB,我们设置最大段为1GB,将会产生5个大段,顶级大段不再参与Merge -->
<!-- buffer最大内存大小,线程共享,每个线程生成一个段 -->
<ramBufferSizeMB>100</ramBufferSizeMB>
<!-- buffer的最大文档数量 -->
<maxBufferedDocs>25000</maxBufferedDocs>

<mergePolicyFactory class="org.apache.solr.index.TieredMergePolicyFactory">
  <int name="maxMergeAtOnce">10</int>
  <int name="segmentsPerTier">10</int>
	<double name="maxMergedSegmentMB">1024.0</double>
	<double name="floorSegmentMB">2.0</double>
  <double name="forceMergeDeletesPctAllowed">10.0</double>
  <double name="deletesPctAllowed">33.0</double>
</mergePolicyFactory>