一、Solr索引
我们知道Solr的索引(准确说是Lucene索引,Solr是基于Lucene开发,后面我提到的索引文件都是Lucene的,后面就不再解释了)称为倒排索引,我们经常说到的倒排索引到底是什么?如何让Solr查询更快,索引写吞吐量更大。
1.1 Solr索引文件
在了解索引文件之前,我们可以先了解几个概念:
- 索引(Index)
可以提供完整查询的索引目录就称为Index,Solr中一个core的索引数据文件目录就是一个Index
- 段(Segment)
一个索引由多个段组成, 多个段可以合并, 以减少读取内容时候的磁盘IO。Solr中的数据写入会先写内存的一个Buffer,当Buffer内数据到一定量后会被flush成一个 Segment,每个Segment有自己独立的索引,可独立被查询,Segment中写入的文档不可被修改。
- 文档(Document)
文档是我们建索引的基本单位,不同的文档是保存在不同的段中的,一个段可以包含多篇文档。
- 域(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
- 如果我们不进行段Merge,那么Search就会越来越慢,Solr中默认是采用串行遍历所有的段文件最后合并结果,因此段越多,Search就会越慢
- 如果不Merge段,标记删除的文档,将会越来越多,进行合并处理标记删除耗时将会非常多
- 由于段越来越多,将会产生很多文件句柄,会影响系统稳定性
因此对于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>