Solr4.8.0源码分析(9)之Lucene的索引文件(2)
Solr4.8.0源码分析(9)之Lucene的索引文件(2)
一. Segments_N文件
一个索引对应一个目录,索引文件都存放在目录里面。Solr的索引文件存放在Solr/Home下的core/data/index目录中,一个core对应一个索引。
Segments_N例举了索引所有有效的segments信息以及删除的具体信息,一个索引可以有多个Segments_N,但是有效的往往总是N最大的那个,为什么会出现多个segments_N,主要是由于暂时无法删除它们或者有indexwriter在进行commit操作,又或者IndexDeletionPolicy
在进行。Segments_N的代码主要在Segmentsinfos.java里面。
1.1 Segments_N的选取
如何选择Segments_N文件进行读取:
- 遍历索引目录内以Segments开头但是不是Segments_gen的文件,取出最大的N作为genA。
String[] files = null;
long genA = -1;
files = directory.listAll();
if (files != null) {
genA = getLastCommitGeneration(files);
} ...
public static long getLastCommitGeneration(String[] files) {
if (files == null) {
return -1;
}
long max = -1;
for (String file : files) {
if (file.startsWith(IndexFileNames.SEGMENTS) && !file.equals(IndexFileNames.SEGMENTS_GEN)) {
long gen = generationFromSegmentsFileName(file);
if (gen > max) {
max = gen;
}
}
}
return max;
}
- 打开segments.gen,其中保存了当前的N值。其格式如下,读出版本号(Version),然后再读出两个N,如果两者相等,则作为genB。
long genB = -1;
ChecksumIndexInput genInput = null;
try {
genInput = directory.openChecksumInput(IndexFileNames.SEGMENTS_GEN, IOContext.READONCE);
} catch (IOException e) {
...
int version = genInput.readInt();
long gen0 = genInput.readLong();
long gen1 = genInput.readLong();
10if (gen0 == gen1) {
genB = gen0;
}
在上述得到的genA和genB中选择最大的那个作为当前的N,方才打开segments_N文件
gen = Math.max(genA, genB);
1.2 Segments_N的结构
Segment的结构:
Header, Version, NameCounter, SegCount, <SegName, SegCodec, DelGen, DeletionCount, FieldInfosGen, UpdatesFiles>SegCount, CommitUserData, Footer
其中<SegName, SegCodec, DelGen, DeletionCount, FieldInfosGen, UpdatesFiles>表示一个段的信息,SegCount表示段的数量,所以
<SegName, SegCodec, DelGen, DeletionCount, FieldInfosGen, UpdatesFiles>SegCount 表示这样的SegCount个段连在一起。
- head:head是一个
CodecHeader,包含了Magic,CodecName,Version三部分。
Magic是一个开始表示符,通常情况下为1071082519.
CodecName是文件的标识符
Version索引文件版本信息,当用某个版本号的IndexReader读取另一个版本号生成的索引的时候,会因为此值不同而报错。
public static int checkHeader(DataInput in, String codec, int minVersion, int maxVersion)
throws IOException { // Safety to guard against reading a bogus string:
final int actualHeader = in.readInt(); //读取Magic
if (actualHeader != CODEC_MAGIC) {
throw new CorruptIndexException("codec header mismatch: actual header=" + actualHeader + " vs expected header=" + CODEC_MAGIC + " (resource: " + in + ")");
}
return checkHeaderNoMagic(in, codec, minVersion, maxVersion); //读取CodecName和Version,并判断
}
- Version:
- 索引的版本号,记录了IndexWriter将修改提交到索引文件中的次数
- 其初始值大多数情况下从索引文件里面读出。
- 我们并不关心IndexWriter将修改提交到索引的具体次数,而更关心到底哪个是最新的。IndexReader中常比较自己的version和索引文件中的version是否相同来判断此IndexReader被打开后,还有没有被IndexWriter更新。
public boolean isCurrent() throws IOException {
ensureOpen();
if (writer == null || writer.isClosed()) {
// Fully read the segments file: this ensures that it's
// completely written so that if
// IndexWriter.prepareCommit has been called (but not
// yet commit), then the reader will still see itself as
// current:
SegmentInfos sis = new SegmentInfos();
sis.read(directory); // we loaded SegmentInfos from the directory
return sis.getVersion() == segmentInfos.getVersion();
} else {
return writer.nrtIsCurrent(segmentInfos);
}
- NameCount
- 是下一个新段(Segment)的段名。
- 所有属于同一个段的索引文件都以段名作为文件名,一般为_0.xxx, _0.yyy, _1.xxx, _1.yyy ……
- 新生成的段的段名一般为原有最大段名加一。
- SegCount
- 段(Segment)的个数。
- SegCount个段的元数据信息:
- SegName:段名,所有属于同一个段的文件都有以段名作为文件名。
- SegCodec:编码segment的codec名字
- del文件的版本号
- Lucene中,在optimize之前,删除的文档是保存在.del文件中的。
- DelGen是每当IndexWriter向索引文件中提交删除操作的时候,加1,并生成新的.del文件
- 如果该值设为-1表示没有删除的document
- DeletionCount:本segment删除的documents个数
- FieldInfosGen:segment中域文件的版本信息,如果该值为-1表示对域文件未有更新操作,如果大于0表示有更新操作
- UpdatesFiles:存储本segment更新的文件列表
- CommitUserData:
- Footer:codec编码的结尾,包含了检验和以及检验算法ID
1.3 read()
可以通过查看read()函数来对照Segment_N的格式
public final void read(Directory directory, String segmentFileName) throws IOException {
boolean success = false; // Clear any previous segments:
this.clear();
//获取现在的segment代号,即Segment_N的N值
generation = generationFromSegmentsFileName(segmentFileName); lastGeneration = generation;
//获取检验和
ChecksumIndexInput input = directory.openChecksumInput(segmentFileName, IOContext.READ);
try {
//获取Header的Magic一般情况下为1071082519 ,Header由Magic,Codecname以及Version组成
final int format = input.readInt();
final int actualFormat;
if (format == CodecUtil.CODEC_MAGIC) {
// 4.0+ 获取Header的Codecname信息
actualFormat = CodecUtil.checkHeaderNoMagic(input, "segments", VERSION_40, VERSION_48);
version = input.readLong(); //获取Header的version信息
counter = input.readInt(); //获取NameCount,即下一段新的段名
int numSegments = input.readInt(); //获取segment个数
if (numSegments < 0) {
throw new CorruptIndexException("invalid segment count: " + numSegments + " (resource: " + input + ")");
}
//遍历SegCount个的段数据
for(int seg=0;seg<numSegments;seg++) {
String segName = input.readString(); //SegName
Codec codec = Codec.forName(input.readString()); //SegCodec
//System.out.println("SIS.read seg=" + seg + " codec=" + codec);
SegmentInfo info = codec.segmentInfoFormat().getSegmentInfoReader().read(directory, segName, IOContext.READ);
info.setCodec(codec);
long delGen = input.readLong(); //DelGen
int delCount = input.readInt(); //DeletionCount
if (delCount < 0 || delCount > info.getDocCount()) {
throw new CorruptIndexException("invalid deletion count: " + delCount + " vs docCount=" + info.getDocCount() + " (resource: " + input + ")");
}
long fieldInfosGen = -1;
if (actualFormat >= VERSION_46) {
fieldInfosGen = input.readLong(); //FieldInfosGen
}
SegmentCommitInfo siPerCommit = new SegmentCommitInfo(info, delCount, delGen, fieldInfosGen);
if (actualFormat >= VERSION_46) {
//UpdatesFiles 首先读取UpdatesFiles的个数,如果等于0则后续接着没有更新的文件,
//否则就获取所有numGensUpdatesFiles个文件并写入SegmentCommitInfo中。
int numGensUpdatesFiles = input.readInt();
final Map<Long,Set<String>> genUpdatesFiles;
if (numGensUpdatesFiles == 0) {
genUpdatesFiles = Collections.emptyMap();
} else {
genUpdatesFiles = new HashMap<>(numGensUpdatesFiles);
for (int i = 0; i < numGensUpdatesFiles; i++) {
genUpdatesFiles.put(input.readLong(), input.readStringSet());
}
}
siPerCommit.setGenUpdatesFiles(genUpdatesFiles);
}
add(siPerCommit);
}
userData = input.readStringStringMap(); //CommitUserData
} else {
actualFormat = -1;
Lucene3xSegmentInfoReader.readLegacyInfos(this, directory, input, format);
Codec codec = Codec.forName("Lucene3x");
for (SegmentCommitInfo info : this) {
info.info.setCodec(codec);
}
}
//Footer
if (actualFormat >= VERSION_48) {
CodecUtil.checkFooter(input);
} else {
final long checksumNow = input.getChecksum();
final long checksumThen = input.readLong();
if (checksumNow != checksumThen) {
throw new CorruptIndexException("checksum mismatch in segments file (resource: " + input + ")");
}
CodecUtil.checkEOF(input);
} success = true;
} finally {
if (!success) {
// Clear any segment infos we had loaded so we
// have a clean slate on retry:
this.clear();
IOUtils.closeWhileHandlingException(input);
} else {
input.close();
}
}
}
SegmentInfo info = codec.segmentInfoFormat().getSegmentInfoReader().read(directory, segName, IOContext.READ);
前文《Solr4.8.0源码分析(7)之Solr SPI》中讲到,Solr会从看resources/META-INF/services/org.apache.lucene.codecs.Codec中读取相应版本的Codec,在solrconfg.xml中配置<luceneMatchVersion>4.8</luceneMatchVersion>,那么solr会去获取org.apache.lucene.codecs.lucene46.Lucene46Codec。所以上面代码的codec其实是Lucene46Codec,segmentInfoFormat返回的是Lucene46SegmentInfoFormat。由此可见在读取segment_N时候,Solr也会取读取每一个Segment的信息(.si).
private final StoredFieldsFormat fieldsFormat = new Lucene41StoredFieldsFormat();
private final TermVectorsFormat vectorsFormat = new Lucene42TermVectorsFormat();
private final FieldInfosFormat fieldInfosFormat = new Lucene46FieldInfosFormat();
private final SegmentInfoFormat segmentInfosFormat = new Lucene46SegmentInfoFormat();
private final LiveDocsFormat liveDocsFormat = new Lucene40LiveDocsFormat();
对照read()和write(),基本上可以看出write是read是逆过程。
read的过程首要保证的是我们读到的segment是最新的。read()是个不停循环尝试读取最新segmentinfo的过程,如果发生IOException则说明此时正在进行commit操作,那么这个时候获取的segment信息就不是最新的。Lucene提供三种方法来尝试获取最新的segment信息:
1.首先就是前文提到的获取最大的gen(generation),当尝试两次之后,如果最大的gen大于lastgen说明segment信息已经更新,否则说明没有更新或者该方法不适用所以转入第二这种方法。
2. 如果第一种方法失败,则直接gen++,即直接去解析下一个gen的segment_N文件。
3. 如果解析失败,则进行gen的回退,gen--,尝试解析该gen的segment_N文件,即segment信息并未更新
1.4 write()
write的过程跟read()是相反,这里主要想了解下SegmentCommitInfo与genUpdatesFiles。
首先看下write的调用关系:commit操作分为两部分prepareCommit和finishcommit。prepareCommit调用write将新的Segment_N,之后在finishcommit中进行真正的commit操作,如果操作失败就进行回归。commit成功后再把gen信息写入segment.gen.
final void prepareCommit(Directory dir) throws IOException {
if (pendingSegnOutput != null) {
throw new IllegalStateException("prepareCommit was already called");
}
write(dir);
}
for (SegmentCommitInfo siPerCommit : this) {
SegmentInfo si = siPerCommit.info;
segnOutput.writeString(si.name);
segnOutput.writeString(si.getCodec().getName());
segnOutput.writeLong(siPerCommit.getDelGen());
int delCount = siPerCommit.getDelCount();
if (delCount < 0 || delCount > si.getDocCount()) {
throw new IllegalStateException("cannot write segment: invalid docCount segment=" + si.name + " docCount=" + si.getDocCount() + " delCount=" + delCount);
}
segnOutput.writeInt(delCount);
segnOutput.writeLong(siPerCommit.getFieldInfosGen());
final Map<Long,Set<String>> genUpdatesFiles = siPerCommit.getUpdatesFiles();
segnOutput.writeInt(genUpdatesFiles.size());
for (Entry<Long,Set<String>> e : genUpdatesFiles.entrySet()) {
segnOutput.writeLong(e.getKey());
segnOutput.writeStringSet(e.getValue());
}
...
}
在finishcommit中主要完成三个操作,
1.在Segment_N中加入footer,如果加入失败则进行回滚。
2.调用Directory.syc()将所有的writer写入磁盘,如果写入失败则进行回滚
3.生成segment_gen。
在prepareCommit和finishcommit过程中间会出现一个IndexOutput流 pendingSegnOutput 负责将当前commit时的Sgementinfos的状态写入到Segment_N中,它只存在prepareCommit和finishcommit之间,在其他时候为null。
至此,基本上对Segmentinfos.java这个类有了初步的了解,以及对Segment_N以及Segment_gen的结构理解。但是到这里我还是有点疑问:Segment_N结构每一个segment段信息中有个updatefiles,它到底有啥用处,留着以后再回过头来学习。
Solr4.8.0源码分析(9)之Lucene的索引文件(2)的更多相关文章
- Solr4.8.0源码分析(12)之Lucene的索引文件(5)
Solr4.8.0源码分析(12)之Lucene的索引文件(5) 1. 存储域数据文件(.fdt和.fdx) Solr4.8.0里面使用的fdt和fdx的格式是lucene4.1的.为了提升压缩比,S ...
- Solr4.8.0源码分析(11)之Lucene的索引文件(4)
Solr4.8.0源码分析(11)之Lucene的索引文件(4) 1. .dvd和.dvm文件 .dvm是存放了DocValue域的元数据,比如DocValue偏移量. .dvd则存放了DocValu ...
- Solr4.8.0源码分析(10)之Lucene的索引文件(3)
Solr4.8.0源码分析(10)之Lucene的索引文件(3) 1. .si文件 .si文件存储了段的元数据,主要涉及SegmentInfoFormat.java和Segmentinfo.java这 ...
- Solr4.8.0源码分析(8)之Lucene的索引文件(1)
Solr4.8.0源码分析(8)之Lucene的索引文件(1) 题记:最近有幸看到觉先大神的Lucene的博客,感觉自己之前学习的以及工作的太为肤浅,所以决定先跟随觉先大神的博客学习下Lucene的原 ...
- Solr4.8.0源码分析(13)之LuceneCore的索引修复
Solr4.8.0源码分析(13)之LuceneCore的索引修复 题记:今天在公司研究elasticsearch,突然看到一篇博客说elasticsearch具有索引修复功能,顿感好奇,于是点进去看 ...
- Solr4.8.0源码分析(25)之SolrCloud的Split流程
Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大 ...
- Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五)
Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五) 题记:关于SolrCloud的Recovery策略已经写了四篇了,这篇应该是系统介绍Recovery策略的最后一篇了 ...
- Solr4.8.0源码分析(23)之SolrCloud的Recovery策略(四)
Solr4.8.0源码分析(23)之SolrCloud的Recovery策略(四) 题记:本来计划的SolrCloud的Recovery策略的文章是3篇的,但是没想到Recovery的内容蛮多的,前面 ...
- Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三)
Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及P ...
随机推荐
- python list排序的两种方法及实例讲解
对List进行排序,Python提供了两个方法方法1 用List的内建函数list sort进行排序list sort(func=None, key=None, reverse=False)Pytho ...
- 怎样在loop中处理异常
怎样在loop中处理异常,而不跳出 出现符号“exception”在需要下下列之一时的解决办法; 如果sql中发生异常,我们可以用 exception when others then d ...
- 实战:sqlserver 数据实时同步到mysql
1.安装安装mysqlconnector 2.配置mysqlconnector ODBC数据管理器->系统DSN->加入->mysql ODBC 5.3 ANSI driver-&g ...
- setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key delete.的问题
今天弄ios的sqlite数据库,程序写完后编译发现一个奇怪的问题,错误信息也不提示行号,只有如下信息: 一遍遍的查找代码也没有发现啥问题,后来在storyboard中找到了该错误的原因 原来是一个按 ...
- 解决zabbix图中出现中文乱码问题
我这周部署了zabbix监控服务器,但是配置过程中发现当有中文时,图中的中文会变成方块 如下图所示: 这个问题是由于zabbix的web端没有中文字库,我们最需要把中文字库加上即可 解决办法如下 1. ...
- [转载]使用兼容ie6 ie7 ie8 FF的text-overflow:ellips
使用兼容ie6 ie7 ie8 FF的text-overflow:ellipsis超出文本显示省略号来代替截取函数更有利于seo,如果使用截取函数,源代码中的标题是显示不完整的,即便是在title属性 ...
- CSS3 @font-face使用实例
Windows10操作系统使用实例: 1.准备好对应格式的字体库 2.方正字体使用实例 <!DOCTYPE html> <html> <head> <meta ...
- (转)PHP函数spl_autoload_register()用法和__autoload()介绍
转--http://www.jb51.net/article/29624.htm 又是框架冲突导致__autoload()失效,用spl_autoload_register()重构一下,问题解决 ...
- (转)织梦dedecms后台发布文章提示“标题不能为空”
问题症状:V5.7登录后台后,发布英文标题没问题,发布中文会提示“标题不能为空”. 问题根源:htmlspecialchars在php5.4默认为utf8编码,gbk编码字符串经 htmlspecia ...
- ORACLE 解析xml字符串-转载的
--------------------------方法一------------------------------------- 1.xml字符串 /* <orderlist> ...