sphinx 源码阅读之分词,压缩索引,倒排——单词对应的文档ID列表本质和lucene无异 也是外部排序再压缩 解压的时候须要所有扫描doc_ids列表偏移量相加得到最终的文档ID

转自:http://github.tiankonguse.com/blog/2014/12/03/sphinx-token-inverted-sort.html

如今咱们的背景是有16个已经排序的数据存在磁盘上。
因为数据量很大,咱们不能一次性所有读进来。html

咱们的目标是依次挑出最小的hit,而后交给索引引擎处理。git

sphinx 使用了 CSphHitQueue 这个数据结构。github

CSphHitQueue 你猜是什么? 队列? 恭喜你,猜错了。
CSphHitQueue 是一个最小堆。
且堆的最大个数是 iRawBlocks。数组

因为 iRawBlocks 个 hits 数组已经排序,因此咱们只须要获得 已排序的hits数组的第一个元素,就能够用堆获得最小的那个元素了。
而后咱们把最小的这个元素建索引压缩储存,删除最小元素,并取出最小元素所在 hits数组中下一个元素,扔到堆中。
这样就能够从小到大取出全部的元素,并逐个创建索引压缩储存了。数据结构

这段话看不懂的话,能够看下面的图。函数

2983121808

其中建立索引压缩储存主要依靠这个函数spa

  1. cidxHit ( tQueue.m_pData );

其中 tQueue.m_pData 的数据结构以下code

  1. /// fat hit, which is actually stored in VLN index
  2. struct CSphFatHit{
  3. DWORD m_iDocID; ///< document ID
  4. DWORD m_iGroupID; ///< documents group ID
  5. DWORD m_iTimestamp; ///< document timestamp
  6. DWORD m_iWordID; ///< word ID in current dictionary
  7. DWORD m_iWordPos; ///< word position in current document
  8. };

hit 是先按 m_iWordID 排序, 相等了再按 m_iDocID 排序, 最后才按 m_iWordPos 排序的。htm

如今咱们先不考虑上面的堆,咱们假设全部的 hit 已经在一个数组中了,且按上面的规则排序了。
如今咱们想作的是对这个 hit 数组建立索引,并压缩储存。blog

主要作了这个几件事。

第一,根据 m_iWordID 将分词分为 2014 块。
并使用 cidxPagesDir 记录块的偏移量(还记得索引文件第二个写入的数据吗)。

第二,对于每一块,咱们按分词分组,并在索引文件 spi 中储存每一个词组的信息。
具体储存的信息以下

  • 和上一个分词(wordID)的误差
  • 这个分词组在 spd 文件内的长度
  • 这个分词记录的变化次数
  • 这个分词的 hit 数量

第三,对于每一个hit,咱们存两部分信息。

  • 位置(pos)偏移量信息
  • 文档(docId)偏移量的信息

上面的三部分信息都储存后,咱们就能够快速的解析出来。

假设咱们又上面的压缩的信息了。
咱们要搜索一个词时,会如何工做呢?
假设咱们已经获得这个词的 wordId 了,只须要二分一下,就能够再 O(log(1024)) 的时间内获得 wordId 在那个块内。

找到一个块内,出现一个问题,咱们不能再次二分查找来找到对应的分词列表。 由于这个 index 储存的是和上一个分词的相对偏移量,那只好所有读入内存,扫描一遍对偏移量求和,而后才能找到对应的词。

这个过程当中咱们进行了两次 IO 操做。
第一次读取块列表信息 cidxPagesDir。
第二次读取选中的那一块的全部数据。

虽然储存偏移量节省了一些磁盘储存,可是倒是用扫描整块数据为代价的。咱们原本能够直接二分整块数据的。

无论怎样,咱们在索引中找到了须要查找的那个分词的位置。
而后咱们能够在数据文件内读取对应的信息,而后获得对应记录的id了。

固然,上面这个只是个人推理,下面咱们来看看 sphinx 是怎么搜索的吧。

看 sphinx 的搜索方法,只须要看 CSphIndex_VLN 的 QueryEx 函数便可。
首先对查询的语句进行分词,而后读取索引头 m_tHeader, 读取分块信息 cidxPagesDir。
而后就对分词进行搜索了。
为了防止相同的分词重复查找,这里采用二层循环,先来判断这个分词以前是否搜索过,搜索过就记下搜索过的那个词的位置。
没搜索过,就搜索。

xxx代码略!

 

看了这个代码,和我想的有点出入,可是整体思路仍是同样的。
它是把全部的 cidxPagesDir 全储存起来了,这样直接定位到指定的位置了。少了一个二分搜索。
定位到某个块以后, 果真采用暴力循环来一个一个的增长偏移,而后查找对应的分词。
找到了记录对应的位置的四大元信息。

再而后因为数据量已经很小了,就把匹配的数据取出来便可。固然,取数据的时候会进行布尔操做,并且会加上权值计算,这样就搜索知足条件的前若干条了。

相关文章
相关标签/搜索