Solr4.8.0源码分析(12)之Lucene的索引文件(5)

Solr4.8.0源码分析(12)之Lucene的索引文件(5)

1. 存储域数据文件(.fdt和.fdx)

Solr4.8.0里面使用的fdt和fdx的格式是lucene4.1的。为了提升压缩比,StoredFieldsFormat以16KB为单位对文档进行压缩,使用的压缩算法是LZ4,由于它更着眼于速度而不是压缩比,所以它能快速压缩以及解压。

1.1 存储域数据文件(.fdt)

  • 真正保存存储域(stored field)信息的是fdt文件,该文件存放了压缩后的文档,按16kb或者更大的模块大小为单位进行压缩。当要写入segment时候,文档会先被存储在内存的buffer里面,当buffer大小大于16kb或者更大时候,这些文档就会被刷入磁盘以LZ4格式压缩存放。
  • fdt文件主要由三部分组成,Header信息,PacjedIntsVersion信息,以及多个块chunk。
  • fdt是以chunk为单位进行压缩以及解压缩的,一个chunk块内含有一个或者多个document
  • chunk内含有第一个document的编号即DocBase,块内document的个数即ChunkDocs,每一个Document的存储的Field的个数即DocFieldCounts,所有在块内的document的长度即DocLengths,以及多个压缩的document。
  • CompressedDoc由FieldNumAndType和Value组成。FieldNumAndType是一个Vlong型,它的最低三位表示Type,其他位数表示FieldNum即域号。
  • Value对应Type,
    • 0: Value is String
    • 1: Value is BinaryValue
    • 2: Value is Int
    • 3: Value is Float
    • 4: Value is Long
    • 5: Value is Double
    • 6, 7: unused
  • 如果文档大于16KB,那么chunk只会存在一个文档。因为一个文档的所有域必须全部在同一chunk种
  • 如果在chunk块中多个文档较大且使得chunk大于32kb时,那么chunk会被压缩成多个16KB大小的LZ4块。
  • 该结构不支持大于(231 - 214) bytes的单个文档

StoredFieldsFormat继承了CompressingStoredFieldsFormat,所以先通过学习CompressingStoredFieldsReader来Solr是怎么解析.fdx和.fdt的

 1 public CompressingStoredFieldsReader(Directory d, SegmentInfo si, String segmentSuffix, FieldInfos fn,
 2       IOContext context, String formatName, CompressionMode compressionMode) throws IOException {
 3     this.compressionMode = compressionMode;
 4     final String segment = si.name;
 5     boolean success = false;
 6     fieldInfos = fn;
 7     numDocs = si.getDocCount();
 8     ChecksumIndexInput indexStream = null;
 9     try {
10       //打开.fdx名字
11       final String indexStreamFN = IndexFileNames.segmentFileName(segment, segmentSuffix, FIELDS_INDEX_EXTENSION);
12       //打开.fdt名字
13       final String fieldsStreamFN = IndexFileNames.segmentFileName(segment, segmentSuffix, FIELDS_EXTENSION);
14       // Load the index into memory
15       //解析.fdx文件
16       indexStream = d.openChecksumInput(indexStreamFN, context);
17       //获取header
18       final String codecNameIdx = formatName + CODEC_SFX_IDX;
19       version = CodecUtil.checkHeader(indexStream, codecNameIdx, VERSION_START, VERSION_CURRENT);
20       assert CodecUtil.headerLength(codecNameIdx) == indexStream.getFilePointer();
21       //开始解析blocks
22       indexReader = new CompressingStoredFieldsIndexReader(indexStream, si);
23
24       long maxPointer = -1;
25
26       if (version >= VERSION_CHECKSUM) {
27         maxPointer = indexStream.readVLong();
28         CodecUtil.checkFooter(indexStream);
29       } else {
30         CodecUtil.checkEOF(indexStream);
31       }
32       indexStream.close();
33       indexStream = null;
34
35       // Open the data file and read metadata
36       //解析.fdt文件
37       fieldsStream = d.openInput(fieldsStreamFN, context);
38       if (version >= VERSION_CHECKSUM) {
39         if (maxPointer + CodecUtil.footerLength() != fieldsStream.length()) {
40           throw new CorruptIndexException("Invalid fieldsStream maxPointer (file truncated?): maxPointer=" + maxPointer + ", length=" + fieldsStream.length());
41         }
42       } else {
43         maxPointer = fieldsStream.length();
44       }
45       this.maxPointer = maxPointer;
46       final String codecNameDat = formatName + CODEC_SFX_DAT;
47       final int fieldsVersion = CodecUtil.checkHeader(fieldsStream, codecNameDat, VERSION_START, VERSION_CURRENT);
48       if (version != fieldsVersion) {
49         throw new CorruptIndexException("Version mismatch between stored fields index and data: " + version + " != " + fieldsVersion);
50       }
51       assert CodecUtil.headerLength(codecNameDat) == fieldsStream.getFilePointer();
52
53       if (version >= VERSION_BIG_CHUNKS) {
54         chunkSize = fieldsStream.readVInt();
55       } else {
56         chunkSize = -1;
57       }
58       packedIntsVersion = fieldsStream.readVInt();
59       //开始解析chunks
60       decompressor = compressionMode.newDecompressor();
61       this.bytes = new BytesRef();
62
63       success = true;
64     } finally {
65       if (!success) {
66         IOUtils.closeWhileHandlingException(this, indexStream);
67       }
68     }
69   }

1.2 存储域索引文件(.fdx)

  • BlockEndMarker:该值为0,表示后面没有接着Block。因为Block不是以0开始的
  • 这里的一个Block包含了多个chunk,chunk对应了.fdt的chunk。所以可以通过.fdx快速的定位到.fdt的chunk。
  • Block有三部分组成,BlockChunks表示该block内含有的chunk的数量,DocBases表示了该block的第一个document的ID并可以通过它获取任意一个该block内的chunk的docbase,同理StartPointer表示了该block内所有的chunk在.fdt文件里的位置信息。
  • DocBases由DocBase, AvgChunkDocs, BitsPerDocBaseDelta, DocBaseDeltas组成。DocBase是Block内的第一个document ID,AvgChunkDocs是Chunk内document平均个数,BitsPerDocBaseDelta是与AvgChunkDocs的差值,DocBaseDeltas是BlockChunks大小的数组,表示平均的doc base的差值。
  • StartPointers由StartPointerBase(block的第一个指针,它对应DocBase),AvgChunkSize(chunk的平均大小,对应AvgChunkDocs), BitPerStartPointerDelta以及StartPointerDeltas组成
  • 第N个chunk的起始docbase可以用如下公式计算:DocBase + AvgChunkDocs * n + DocBaseDeltas[n]
  • 第N个chunk的起始point可以用如下公式计算:StartPointerBase + AvgChunkSize * n + StartPointerDeltas[n]
  • .fdx文件的解析主要用到了 CompressingStoredFieldsFormat,其中以CompressingStoredFieldsIndexReader为例,查看如何读取.fdx文件:
 1 // It is the responsibility of the caller to close fieldsIndexIn after this constructor
 2   // has been called
 3   CompressingStoredFieldsIndexReader(IndexInput fieldsIndexIn, SegmentInfo si) throws IOException {
 4     maxDoc = si.getDocCount();
 5     int[] docBases = new int[16];
 6     long[] startPointers = new long[16];
 7     int[] avgChunkDocs = new int[16];
 8     long[] avgChunkSizes = new long[16];
 9     PackedInts.Reader[] docBasesDeltas = new PackedInts.Reader[16];
10     PackedInts.Reader[] startPointersDeltas = new PackedInts.Reader[16];
11     //读取packedIntsVersion
12     final int packedIntsVersion = fieldsIndexIn.readVInt();
13
14     int blockCount = 0;
15     //开始遍历并读取所有block
16     for (;;) {
17       //numChunks即当做BlockChunks,表示一个Block内Chunks的个数;当Block读取完时候会读取一个为0的值即为BlocksEndMarker,
18       //表示已读取完所有 block。
19       final int numChunks = fieldsIndexIn.readVInt();
20       if (numChunks == 0) {
21         break;
22       }
23       //初始化时候,定义大小为16的数组docBases,startPointers,avgChunkDocs,avgChunkSizes表示16个模块。
24       //当Block大于16时候,会生成新的大小的数组,并将原数据复制过去。
25       if (blockCount == docBases.length) {
26         final int newSize = ArrayUtil.oversize(blockCount + 1, 8);
27         docBases = Arrays.copyOf(docBases, newSize);
28         startPointers = Arrays.copyOf(startPointers, newSize);
29         avgChunkDocs = Arrays.copyOf(avgChunkDocs, newSize);
30         avgChunkSizes = Arrays.copyOf(avgChunkSizes, newSize);
31         docBasesDeltas = Arrays.copyOf(docBasesDeltas, newSize);
32         startPointersDeltas = Arrays.copyOf(startPointersDeltas, newSize);
33       }
34
35       // doc bases
36       //读取block的docBase
37       docBases[blockCount] = fieldsIndexIn.readVInt();
38       //读取avgChunkDocs,block中chunk内含有平均的document个数
39       avgChunkDocs[blockCount] = fieldsIndexIn.readVInt();
40       //读取bitsPerDocBase,block中与avgChunkDocs的delta的位数,根据这个位数获取docBasesDeltas数组内具体delta
41       final int bitsPerDocBase = fieldsIndexIn.readVInt();
42       if (bitsPerDocBase > 32) {
43         throw new CorruptIndexException("Corrupted bitsPerDocBase (resource=" + fieldsIndexIn + ")");
44       }
45       //获取docBasesDeltas值,docBasesDeltas是一个numChunks大小的数组,存放每一个chunk起始的docbase与avgChunkDocs的差值
46       docBasesDeltas[blockCount] = PackedInts.getReaderNoHeader(fieldsIndexIn, PackedInts.Format.PACKED, packedIntsVersion, numChunks, bitsPerDocBase);
47
48       // start pointers
49       //读取block的startPointers
50       startPointers[blockCount] = fieldsIndexIn.readVLong();
51       //读取startPointers,chunk的平均大小
52       avgChunkSizes[blockCount] = fieldsIndexIn.readVLong();
53       //读取bitsPerStartPointer,block中与avgChunkSizes的delta的位数,根据这个位数获取startPointersDeltas数组内具体delta
54       final int bitsPerStartPointer = fieldsIndexIn.readVInt();
55       if (bitsPerStartPointer > 64) {
56         throw new CorruptIndexException("Corrupted bitsPerStartPointer (resource=" + fieldsIndexIn + ")");
57       }
58       //获取startPointersDeltas值,startPointersDeltas是一个numChunks大小的数组,
59       //存放每一个chunk起始的startPointer与avgChunkSizes的差值。
60       startPointersDeltas[blockCount] = PackedInts.getReaderNoHeader(fieldsIndexIn, PackedInts.Format.PACKED, packedIntsVersion, numChunks, bitsPerStartPointer);
61
62       //下一个block
63       ++blockCount;
64     }
65     //将遍历完的数据放入全局变量中
66     this.docBases = Arrays.copyOf(docBases, blockCount);
67     this.startPointers = Arrays.copyOf(startPointers, blockCount);
68     this.avgChunkDocs = Arrays.copyOf(avgChunkDocs, blockCount);
69     this.avgChunkSizes = Arrays.copyOf(avgChunkSizes, blockCount);
70     this.docBasesDeltas = Arrays.copyOf(docBasesDeltas, blockCount);
71     this.startPointersDeltas = Arrays.copyOf(startPointersDeltas, blockCount);
72   }
时间: 2024-12-28 01:01:36

Solr4.8.0源码分析(12)之Lucene的索引文件(5)的相关文章

Solr4.8.0源码分析(10)之Lucene的索引文件(3)

Solr4.8.0源码分析(10)之Lucene的索引文件(3) 1. .si文件 .si文件存储了段的元数据,主要涉及SegmentInfoFormat.java和Segmentinfo.java这两个文件.由于本文介绍的Solr4.8.0,所以对应的是SegmentInfoFormat的子类Lucene46SegmentInfoFormat. 首先来看下.si文件的格式 头部(header) 版本(SegVersion) doc个数(SegSize) 是否符合文档格式(IsCompoundF

Solr4.8.0源码分析(11)之Lucene的索引文件(4)

Solr4.8.0源码分析(11)之Lucene的索引文件(4) 1. .dvd和.dvm文件 .dvm是存放了DocValue域的元数据,比如DocValue偏移量. .dvd则存放了DocValue的数据. 在Solr4.8.0中,dvd以及dvm用到的Lucene编码格式是Lucene45DocValuesFormat.跟之前的文件格式类似,它分别包含Lucene45DocValuesProducer 和Lucene45DocValuesConsumer来实现该文件的读和写. 1 @Ove

Solr4.8.0源码分析(8)之Lucene的索引文件(1)

Solr4.8.0源码分析(8)之Lucene的索引文件(1) 题记:最近有幸看到觉先大神的Lucene的博客,感觉自己之前学习的以及工作的太为肤浅,所以决定先跟随觉先大神的博客学习下Lucene的原理.由于觉先大神主要介绍的是Lucene3.X系的,那我就根据源码以及结合觉先大神的来学习下4.X系的.内容可能会有些变化,且加入下我个人的理解. http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623597.html 一. 基本类型

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

Solr4.8.0源码分析(13)之LuceneCore的索引修复

Solr4.8.0源码分析(13)之LuceneCore的索引修复 题记:今天在公司研究elasticsearch,突然看到一篇博客说elasticsearch具有索引修复功能,顿感好奇,于是点进去看了下,发现原来是Lucene Core自带的功能,于是就回家先学习下,正好也跟之前看的索引文件的格式相应.有空也研究下Lucene的一些小工具. 索引的修复主要是用到CheckIndex.java这个类,可以直接查看类的Main函数来了解下. 1. CheckIndex的使用 首先使用以下命令来查看

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三)

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及PeerSync策略.本文以及后续的文章将重点介绍Replication策略.Replication策略不但可以在SolrCloud中起到leader到replica的数据同步,也可以在用多个单独的Solr来实现主从同步.本文先介绍在SolrCloud的leader到replica的数据同步,下一篇

Solr4.8.0源码分析(4)之Eclipse Solr调试环境搭建

Solr4.8.0源码分析(4)之Eclipse Solr调试环境搭建 由于公司里的Solr调试都是用远程jpda进行的,但是家里只有一台电脑所以不能jpda进行调试,这是因为jpda的端口冲突.所以只能在Eclipse 搭建Solr的环境,折腾了一小时终于完成了. 1. JDPA远程调试 搭建换完成Solr环境后,对${TOMCAT_HOME}/bin/startup.sh 最后一行进行修改,如下所示: 1 set JPDA_ADDRESS=7070 2 exec "$PRGDIR"

Solr4.8.0源码分析(25)之SolrCloud的Split流程

Solr4.8.0源码分析(25)之SolrCloud的Split流程(一) 题记:昨天有位网友问我SolrCloud的split的机制是如何的,这个还真不知道,所以今天抽空去看了Split的原理,大致也了解split的原理了,所以也就有了这篇文章.本系列有两篇文章,第一篇为core split,第二篇为collection split. 1. 简介 这里首先需要介绍一个比较容易混淆的概念,其实Solr的HTTP API 和 SolrCloud的HTTP API是不一样,如果接受到的是Solr的

Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五)

Solr4.8.0源码分析(24)之SolrCloud的Recovery策略(五) 题记:关于SolrCloud的Recovery策略已经写了四篇了,这篇应该是系统介绍Recovery策略的最后一篇了.本文主要介绍Solr的主从同步复制.它与前文<Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三)>略有不同,前文讲到的是SolrCloud的leader与replica之间的同步,不需要通过配置solrconfig.xml来实现.而本文主要介绍单机模式下,利用so