数据库内核月报

数据库内核月报 - 2016 / 11

MySQL · 引擎介绍 · Sphinx源码剖析(一)

Author: 雕梁

介绍

Sphinx是一个全文索引引擎,他被设计为可以非常简单方便的与各种数据库(mysql,PG…)进行交互。它提供了两种读取接口,a) sphinx自己实现的mysql协议的接口, SphinxQL。b) 各种语言客户端的接口,也就是native搜索API. c) 也可以直接通过mysql server的一个存储引擎插件来访问, SphinxSE.

接下来我们会有一些列文章来分析Sphinx的设计以及源码实现。

本篇是第一篇,主要是简要的介绍Sphinx的源码结构,设计以及索引文件的构成。我们当前分析的代码版本是sphinx-2.3.2-beta.tar.gz.

源码结构

sphinx的源码结构比较简单,下面主要是一些比较重要的目录:

Sphinx最终会生成4个可执行文件,分别是:

我们先来看Sphinx源码的CmakeFiles(src/CMakeLists.txt) :

set (LIBSPHINX_SRCS sphinx.cpp sphinxexcerpt.cpp
		sphinxquery.cpp sphinxsoundex.cpp sphinxmetaphone.cpp
		sphinxstemen.cpp sphinxstemru.cpp sphinxstemcz.cpp
		sphinxstemar.cpp sphinxutils.cpp sphinxstd.cpp
		sphinxsort.cpp sphinxexpr.cpp sphinxfilter.cpp
		sphinxsearch.cpp sphinxrt.cpp sphinxjson.cpp
		sphinxaot.cpp sphinxplugin.cpp sphinxudf.c
		sphinxqcache.cpp sphinxrlp.cpp)
set (INDEXER_SRCS indexer.cpp)
set (INDEXTOOL_SRCS indextool.cpp)
set (SEARCHD_SRCS searchd.cpp searchdha.cpp http/http_parser.c searchdhttp.cpp)
set (SPELLDUMP_SRCS spelldump.cpp)
...
add_library (libsphinx STATIC ${LIBSPHINX_SRCS} ${HEADERS} ${GHEADERS})

通过上面的构建文件我们可以看到4个可执行文件对应4个源文件(除了搜索服务,searchdha.cpp是分布式搜索,searchdhttp.cpp是搜索服务的http接口实现).剩下的源代码都会被编译为一个libsphinx的库.

因此下面简单介绍下libsphinx的几个文件主要作用:

压缩格式

以RT索引为例,sphinx会有配置来决定内存中的索引大小(rt_mem_limit),超过这个大小后,sphinx将会把内存索引刷新到磁盘中。接下来我们就来看sphinx的索引的含义以及原始格式。

在Sphinx中所有的索引最终都是被压缩的,压缩算法比较简单,要么是delta encoding, 要么是VLB(variable length byte string):

	source-sequence = 3, 5, 7, 11, 13, 17, ...
	delta-encoded = 3, 2, 2, 4, 2, 4, ...
	source-value = 0x37
	encoded-value = 0x37

	source-value = 0x12345
	encoded-value = 0x84 0xC6 0x45
		// 0x84 == ( ( 0x12345>>14 ) & 0x7F ) | 0x80
		// 0xC6 == ( ( 0x12345>>7 ) & 0x7F ) | 0x80
		// 0x45 == ( ( 0x12345>>0 ) & 0x7F )
void CSphWriter::ZipInt ( DWORD uValue )
{
	int iBytes = 1;

	DWORD u = ( uValue>>7 );
	while ( u )
	{
		u >>= 7;
		iBytes++;
	}

	while ( iBytes-- )
		PutByte (
			( 0x7f & ( uValue >> (7*iBytes) ) )
			| ( iBytes ? 0x80 : 0 ) );
}

然后是解压缩,刚好和压缩相反,也就是通过最高位来判断是否结束解压缩,然后通过左移以及累加来不断地计算原有的值。

DWORD CSphReader::UnzipInt ()			{ SPH_VARINT_DECODE ( DWORD, GetByte() ); }

#define SPH_VARINT_DECODE(_type,_getexpr) \
	register DWORD b = _getexpr; \
	register _type res = 0; \
	while ( b & 0x80 ) \
	{ \
		res = ( res<<7 ) + ( b & 0x7f ); \
		b = _getexpr; \
	} \
	res = ( res<<7 ) + b; \
	return res;

索引文件介绍

在介绍索引文件之前,我们先介绍几个概念:

然后来看索引文件的简单介绍.

byte dummy = 0x01
keyword[] keyword_blocks
keyword is:
	byte keyword_editcode
	byte[] keyword_delta
	if keyword_editcode == 0:
		assert keyword_delta = { 0 }
		return block_end
	zint doclist_offset
	zint num_docs
	zint num_hits
	if num_docs >= DOCLIST_HINT_THRESH:
		byte doclist_sizehint
	if ver >= 31 and num_docs > SKIPLIST_BLOCK:
		zint skiplist_pos
		zint skiplist_len

if min_infix_len > 0:
	tag "infix-entries"
	infix_entry[] infix_hash_entries

checkpoint[] checkpoints
checkpoint is:
	dword keyword_len
	byte[] keyword [ keyword_len ]
	qword dict_offset

if min_infix_len > 0:
	tag "infix-blocks"
	infix_block[] infix_hash_blocks

tag "dict-header"
zint num_checkpoints
zint checkpoints_offset
zint infix_codepoint_bytes
zint infix_blocks_offset

其中spp/spi/spd/spa/spe文件的生成都在RtIndex_t::SaveDiskDataImpl中实现。

在下一篇文章,我们会详细的介绍每个索引文件的生成。

参考文档

http://sphinxsearch.com/docs/current.html