Lucene4.2源码解析之fdt和fdx文件的读写——fdx文件存储一个个的Block,每个Block管理着一批Chunk,通过docID读取到document需要完成Segment、Block、Chunk、document四级查询,引入了LZ4算法对fdt的chunk docs进行了实时压缩/解压

前言

通常在搜索打分完毕后,IndexSearcher会返回一个docID序列,但是仅仅有docID我们是无法看到存储在索引中的document,这时候就需要通过docID来得到完整Document信息,这个过程就需要对fdx/fdt文件进行读操作。为了更清楚地了解fdx/fdt文件的作用,本文把fdx/fdt文件的读和写整合到了一起,尽管这在Lucene中是两个分开的过程。

1. 索引生成阶段

索引生成阶段包含着一个复杂的过程,所以了解本文前最好对Lucene的索引架构有一定的了解,可以参考博客:Lucene的索引链结构_IndexChain

由于在数据处理的过程中大量用到Packed,所以对数据的压缩最好也要有一点的了解,可以参考博客:Lucene源代码学习之PackedInts;由于在存储的过程也用到了LZ4算法,关于LZ4算法的原理,可以参考博客:lucene源代码学习之LZ4压缩算法在lucene中应用

1.1      fdx/fdt文件的创建。

fdx/fdt文件的创建完整线条如下:

在dwpt完成一个document的分析时,如果CompressionStoreFieldsWriter没有实例化,则创建:

1.2     fdx/fdt文件的格式。

具体参考Lucene41StoredFieldsFormat.html (见Lucene4.2.0的docs)

fdt文件结构:

上图理解起来也不难,<Header>和PackedIntsVersion略过,我们重点关注<Chunk>,Chunk的中文意思是”大块”,我们可以理解为数据的存储区域。在内存中表现为缓存。一个Chunk由5个部分组成:DocBase表示当前的Chunk块的起始DocId;ChunkDocs表示当前Chunk中的doc个数;DocFieldCounts是一个数组,表示每个doc中Field的个数;DocLengths也是一个数组,表示每个doc占用byte的个数,即doc的长度;<CompressedDocs>即doc的内容,用LZ4算法压缩存储。FieldNumAndType是把FieldNumber和FieldType合并到一个VLong字段里面,整个<CompressedDocs>就是FieldNumAndType和Value的交替序列。

fdx文件结构:

fdx文件重点关注的是<Block>,一个Block由三个部分组成:BlockChunks表示当前Block中Chunk的个数;<DocBases>表示当前Block中每个Chunk的doc个数,可以看作一个数组;<StartPointers>表示当前Block中每个Chunk在fdt文件中的起始位置,其结构与<DocBases>相同。

尽管fdx/fdt文件只是Lucene的正向文件,并不是Lucene的核心。但是还是有干货的。在Lucene4中引入了LZ4算法对fdt的doc进行了实时压缩/解压。而且用SPI(Service Provider Interface)技术对架构进行了重构。

1.3    fdx/fdt文件的写入。

fdx/fdt文件的写入操作非常清晰。逻辑上都在CompressingStoredFieldsWriter类中完成,而CompressingStoredFieldsIndexWriter则作为其成员变量。其写入的顺序与上面的格式一致,只是有些名字不一样。在写入docs的过程中,用GrowableByteArrayDataOutput作为缓存,直到缓存满了,才flush到硬盘上去。用LZ4算法压缩就是在flush时处理的。(关于LZ4算法会在另外的博文中描述)

fdt文件的写入:

fdt文件的基本单位是Chunk,这一点需要牢记。一个Chunk写入到文件中的代码如下:<对照着前面的图看代码>

那么什么时候会调用上面的flush函数呢?

情况一:索引提交.

情况二:doc的大小或者doc的数量超过设定阈值.一般是1<<14=16384 (参见函数triggerFlush)

通过观察flush函数,我们会发现fdt文件的写入非常简单,就两句代码:

前面一句代码记录整个chunk中的docBase(最小docID),numBufferedDocs(doc数量),numStoredFields(每个doc的Field个数),lengths(每个doc的长度),一共四种信息.在记录numStoredFields和lengths时,用PackedInts及其它的方式对内容进行了压缩。后面一句代码记录整个chunk中的doc的完整内容(用LZ4算法进行压缩).

关于writeHeader(docBase,numBufferedDocs, numStoredFields, lengths); 这一句代码,存储numBufferedDocs和存储numStoredFields方式是一样的,存储方式如下:

(上图截自于Lucene41StoredFieldsFormat.html)

解释一下上图:在存储DocFieldCounts,即numBufferedDocs时,如果ChunkDocs=1(即当前Chunk只有一个doc),那么一个VInt存储就足够了;否则首先存储一个VInt的标志位,暂时称为bitsRequired。如果bitsRequired = 0 ,代表当前Chunk中所有doc中FieldCount相同;否则用Packed Array来存储DocFieldCounts,Packed Array中每个值占用的bit数即bitsRequired。

DocLengths的存储方式与DocFieldCounts相同,实现的代码如下:

fdx文件的写入

fdx是fdt文件的辅助文件.如果说fdt是一本书的正文,那么fdx就是目录.fdx的基本单位是Block,一个Block中包含多个Chunk。

一个Block写入到fdx文件中代码参看CompressingStoredFieldsIndexWriter.wirteBlock方法。由于代码太长,这里就不贴出来了。

一个Block包含三方面的内容:

1 ChunkCount;

2 <DocBases>;

3 <StartPointers> ;

如果细细读这两段代码,会发现两段代码逻辑相似性达90%。确实,这两段代码的内容的处理方式上是一样的。在LUCENE-4512这个Improvement里面,有这样一段文字能帮助我们理解上面代码:

存的是文件位置startpointer!

这段文字讲了一个技巧:“存储真实值和平均值的差值来代替存储真实值”;比如有下面几个数据需要存储到文件中:[10000,9888,10002,99997,10003];各个数与平均值之间的差值如下:[0,-2,2,-3,3] ,用差值存储就可以节约很多bits了。但是这样做又带来一个新的问题:负数的符号位都在最高位,而且PackedInts无法存储负数。因此需要对数据进行转码,转码方式就是zigzag转码。Zigzag编码的方法非常简单:

Int32: (n << 1) ^ (n >> 31)

Int64: (n << 1) ^ (n >> 63)

Zigzag编码主要在于对负数的压缩,比如-1(1111 1111 1111 1111 1111 1111 1111 1111),经过转码后,变成了1(0000 0000 0000 0000 0000 0000 0000 0001),节约了很多符号位。

经过Zigzag编码的数怎么还原呢?

(n>>> 1) ^ -(n & 1)

了解了原理,我们再来分析<DocBases>内容的写入过程:

第一步:计算平均值(avgChunkDocs)

一般最后一个chunk都没有存满,所以docNum会低于其它的Chunk,所以在计算平均值的时候不用它。

第二步:存储docBase和平均值(avgChunkDocs)

第三步:计算最大的差值(delta),这个delta是用来计算bitsRequired

第四步:用PackedInts来压缩并存储docBaseDeltas。

存储<startPointerDeltas>的逻辑与<DocBases>类似,就不再赘述了。

本文出自 “每天进步一点点” 博客,请务必保留此出处http://sbp810050504.blog.51cto.com/2799422/1533162

时间: 2024-12-20 01:04:52

Lucene4.2源码解析之fdt和fdx文件的读写——fdx文件存储一个个的Block,每个Block管理着一批Chunk,通过docID读取到document需要完成Segment、Block、Chunk、document四级查询,引入了LZ4算法对fdt的chunk docs进行了实时压缩/解压的相关文章

Block源码解析和深入理解

Block源码解析和深入理解 Block的本质 Block是"带有自动变量值的匿名函数". 我们通过Clang(LLVM编译器)来将OC的代码转换成C++源码的形式,通过如下命令: clang -rewrite-objc 源代码文件名 下面,我们要转换的Block语法 1 2 3 4 5 6 7 int main(int argc, const char * argv[]) { void (^blk)(void) = ^{ printf("Block\n"); };

IdentityServer4源码解析_5_查询用户信息接口

目录 IdentityServer4源码解析_1_项目结构 IdentityServer4源码解析_2_元数据接口 IdentityServer4源码解析_3_认证接口 IdentityServer4源码解析_4_令牌发放接口 IdentityServer4源码解析_5_查询用户信息接口 [IdentityServer4源码解析_6_结束会话接口] [IdentityServer4源码解析_7_查询令牌信息接口] [IdentityServer4源码解析_8_撤销令牌接口] 协议简析 UserI

Python2 基本数据结构源码解析

Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. PyStringObject 0x05. PyListObject 0x06. PyDictObject 0x07. PyLongObject 0x00. Preface 一切皆对象,这是Python很重要的一个思想之一,虽然在语法解析上有些细节还是不够完全对象化,但在底层源码里,这个思想还是贯穿

jQuery源码解析(架构与依赖模块)第一章 理解架构

1-1 jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+),jQuery2.0及后续版本将不再支持IE6/7/8浏览器.jQuery使用户能更方便地处理HTML(标准通用标记语言下的一个应用).events.实现动画效果,并且方便地为网站提供AJAX交互.jQuery还有一个比较大的优势是,它

Spring 源码解析之HandlerAdapter源码解析(二)

Spring 源码解析之HandlerAdapter源码解析(二) 前言 看这篇之前需要有Spring 源码解析之HandlerMapping源码解析(一)这篇的基础,这篇主要是把请求流程中的调用controller流程单独拿出来了 解决上篇文章遗留的问题 getHandler(processedRequest) 这个方法是如何查找到对应处理的HandlerExecutionChain和HandlerMapping的,比如说静态资源的处理和请求的处理肯定是不同的HandlerMapping ge

(三)Mina源码解析之IoFilter

本篇文章主要剖析Mina中的过滤器是如何实现的 首先还是引入一个简单的完整的server端的例子,代码如下 public class Server { public static void main(String[] args) { IoAcceptor acceptor = new NioSocketAcceptor(); acceptor.getSessionConfig().setReadBufferSize(2048); acceptor.getSessionConfig().setId

YTKNetwork源码解析

对于iOS开发者来说,就算是没有用过YTKNetwork框架,应该也见过,听过了.它是猿题库技术团队开源的一个网络请求框架,内部封装了AFNetworking.它把每个请求实例化,管理它的生命周期,也可以管理多个请求. 在正式讲解源码之前,我会先讲一下该框架所用的架构和设计模式.我总觉得对架构和设计有一定的了解的话,会有助于对源码的理解. 1. 架构 先上图: YTKRequest架构图 在这里简单说明一下: YTKNetwork框架将每一个请求实例化,YTKBaseRequest是所有请求类的

[Spark內核] 第42课:Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践

本课主题 Broadcast 运行原理图 Broadcast 源码解析 Broadcast 运行原理图 Broadcast 就是将数据从一个节点发送到其他的节点上; 例如 Driver 上有一张表,而 Executor 中的每个并行执行的Task (100万个Task) 都要查询这张表的话,那我们通过 Broadcast 的方式就只需要往每个Executor 把这张表发送一次就行了,Executor 中的每个运行的 Task 查询这张唯一的表,而不是每次执行的时候都从 Driver 中获得这张表

Retrofit源码解析

square公司开源了一系列的优秀库,比如Retrofit,OkHttp,Picasso等, 前面简单分析了Picasso的源码,这里来分析下Retrofit的使用: 一.gradle添加依赖 compile 'com.squareup.okhttp:okhttp:2.4.0' compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0' compile 'com.squareup.okio:okio:1.5.0' compile 'com.g