当前位置: 首页 > 编程日记 > 正文

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

在本节中我会介绍Sphinx在构建索引之前做的一些事情,主要是从mysql拉取数据保存,然后分词排序保存到内存等等一系列的操作。下面是几个相关指令

    sql_query = \SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, \title, content \FROM documentssql_query_range = SELECT MIN(id),MAX(id) FROM documentssql_range_step = 1000

其中sql_query是sphinx每次从mysql拉取数据的sql,而sql_query_range则是取得需要从mysql拉取的数据条目,而sql_rang_step则是表示每次从mysql拉取多少数据。sql_rang_range执行分两种情况,第一种是第一次拉取数据的时候,第二种是当当前的range数据读取完毕之后。

首先来看CSphSource_SQL::NextDocument函数,这个函数的主要作用是从mysql读取数据然后切分保存,首先我们来看读取数据这一部分,这里步骤很简单,就是执行对应的sql,然后判断当前range的数据是否读取完毕,如果读取完毕则继续执行sql_query_rang(RunQueryStep)。这里要注意的是,sphinx读取数据是一条一条的读取然后执行的.

	do{// try to get next rowbool bGotRow = SqlFetchRow ();// when the party's over...while ( !bGotRow ){// is that an error?if ( SqlIsError() ){sError.SetSprintf ( "sql_fetch_row: %s", SqlError() );m_tDocInfo.m_uDocID = 1; // 0 means legal eofreturn NULL;}// maybe we can do next step yet?if ( !RunQueryStep ( m_tParams.m_sQuery.cstr(), sError ) ){// if there's a message, there's an error// otherwise, we're just overif ( !sError.IsEmpty() ){m_tDocInfo.m_uDocID = 1; // 0 means legal eofreturn NULL;}} else{// step went fine; try to fetchbGotRow = SqlFetchRow ();continue;}SqlDismissResult ();// ok, we're overARRAY_FOREACH ( i, m_tParams.m_dQueryPost ){if ( !SqlQuery ( m_tParams.m_dQueryPost[i].cstr() ) ){sphWarn ( "sql_query_post[%d]: error=%s, query=%s",i, SqlError(), m_tParams.m_dQueryPost[i].cstr() );break;}SqlDismissResult ();}m_tDocInfo.m_uDocID = 0; // 0 means legal eofreturn NULL;}// get him!m_tDocInfo.m_uDocID = VerifyID ( sphToDocid ( SqlColumn(0) ) );m_uMaxFetchedID = Max ( m_uMaxFetchedID, m_tDocInfo.m_uDocID );} while ( !m_tDocInfo.m_uDocID );

上面的代码我们可以看到一个很关键的字段m_uDocID,这个字段表示当前doc的id(因此数据库的表设计必须有这个id字段).

读取完毕数据之后,开始处理读取的数据,这里会按照字段来切分,主要是将对应的数据库字段保存到索引fielld

	// split columns into fields and attrsfor ( int i=0; i<m_iPlainFieldsLength; i++ ){// get that field#if USE_ZLIBif ( m_dUnpack[i]!=SPH_UNPACK_NONE ){DWORD uUnpackedLen = 0;m_dFields[i] = (BYTE*) SqlUnpackColumn ( i, uUnpackedLen, m_dUnpack[i] );m_dFieldLengths[i] = (int)uUnpackedLen;continue;}#endifm_dFields[i] = (BYTE*) SqlColumn ( m_tSchema.m_dFields[i].m_iIndex );m_dFieldLengths[i] = SqlColumnLength ( m_tSchema.m_dFields[i].m_iIndex );}

紧接着就是处理attribute,后续我们会详细介绍attribute,现在我们只需要知道它是一个类似二级索引的东西(不进入全文索引).

		switch ( tAttr.m_eAttrType ){case SPH_ATTR_STRING:case SPH_ATTR_JSON:// memorize string, fixup NULLsm_dStrAttrs[i] = SqlColumn ( tAttr.m_iIndex );if ( !m_dStrAttrs[i].cstr() )m_dStrAttrs[i] = "";m_tDocInfo.SetAttr ( tAttr.m_tLocator, 0 );break;
..................................default:// just store as uint by defaultm_tDocInfo.SetAttr ( tAttr.m_tLocator, sphToDword ( SqlColumn ( tAttr.m_iIndex ) ) ); // FIXME? report conversion errors maybe?break;}

然后我们来看Sphinx如何处理得到的数据,核心代码在 RtIndex_t::AddDocument中,这个函数主要是用来分词(IterateHits中)然后保存数据到对应的数据结构,而核心的数据结构是RtAccum_t,也就是最终sphinx在写索引到文件之前,会将数据保存到这个数据结构,这里要注意一般来说sphinx会保存很多数据,然后最后一次性提交给索引引擎来处理.而索引引擎中处理的就是这个数据结构.因此最终会调用RtAccum_t::AddDocument.

这里需要注意两个地方,第一个是m_dAccum这个域,这个域是一个vector,而这个vector里面保存了CSphWordHit这个结构,我们来看这个结构的定义

    struct CSphWordHit{SphDocID_t		m_uDocID;		///< document IDSphWordID_t		m_uWordID;		///< word ID in current dictionaryHitpos_t		m_uWordPos;		///< word position in current document};

可以看到其实这个结构也就是保存了对应分词的信息.

然后我们来看核心代码,这里主要是便利刚才从mysql得到的数据,去重然后保存数据.

	int iHits = 0;if ( pHits && pHits->Length() ){CSphWordHit tLastHit;tLastHit.m_uDocID = 0;tLastHit.m_uWordID = 0;tLastHit.m_uWordPos = 0;iHits = pHits->Length();m_dAccum.Reserve ( m_dAccum.GetLength()+iHits );for ( const CSphWordHit * pHit = pHits->First(); pHit<=pHits->Last(); pHit++ ){// ignore duplicate hitsif ( pHit->m_uDocID==tLastHit.m_uDocID && pHit->m_uWordID==tLastHit.m_uWordID && pHit->m_uWordPos==tLastHit.m_uWordPos )continue;// update field lengthsif ( pFieldLens && HITMAN::GetField ( pHit->m_uWordPos )!=HITMAN::GetField ( tLastHit.m_uWordPos ) )pFieldLens [ HITMAN::GetField ( tLastHit.m_uWordPos ) ] = HITMAN::GetPos ( tLastHit.m_uWordPos );// accumulatem_dAccum.Add ( *pHit );tLastHit = *pHit;}if ( pFieldLens )pFieldLens [ HITMAN::GetField ( tLastHit.m_uWordPos ) ] = HITMAN::GetPos ( tLastHit.m_uWordPos );}

做完上面这些事情之后,就需要提交数据给索引处理引擎了,这里核心的代码都是在RtIndex_t::Commit中.

这个函数主要做两个事情,第一个提取出前面我们构造好的RtAccum_t,然后对于所有的doc进行排序,创建segment,也就是对应的索引块(ram chunk),最后调用CommitReplayable来提交ram chunk到磁盘.

其实可以这么理解,保存在内存中的索引也就是segment,然后当内存的大小到达限制后就会刷新内存中的索引到磁盘.

    void RtIndex_t::Commit ( int * pDeleted, ISphRtAccum * pAccExt ){assert ( g_bRTChangesAllowed );MEMORY ( MEM_INDEX_RT );RtAccum_t * pAcc = AcquireAccum ( NULL, pAccExt, true );if ( !pAcc )return;...................................pAcc->Sort();RtSegment_t * pNewSeg = pAcc->CreateSegment ( m_tSchema.GetRowSize(), m_iWordsCheckpoint );.............................................// now on to the stuff that needs locking and recoveryCommitReplayable ( pNewSeg, pAcc->m_dAccumKlist, pDeleted );......................................}

然后我们来看RtAccum_t::CreateSegment函数,这个函数用来将分词好的数据保存到ram chunk,这里需要注意两个数据结构分别是RtDoc_t和RtWord_t,这两个数据结构分别表示doc信息和分词信息.

结构很简单,后面的注释都很详细

    template < typename DOCID = SphDocID_t >struct RtDoc_T{DOCID						m_uDocID;	///< my document idDWORD						m_uDocFields;	///< fields maskDWORD						m_uHits;	///< hit countDWORD						m_uHit;		///< either index into segment hits, or the only hit itself (if hit count is 1)};template < typename WORDID=SphWordID_t >struct RtWord_T{union{WORDID					m_uWordID;	///< my keyword idconst BYTE *			m_sWord;};DWORD						m_uDocs;	///< document count (for stats and/or BM25)DWORD						m_uHits;	///< hit count (for stats and/or BM25)DWORD						m_uDoc;		///< index into segment docs};

然后来看代码,首先是初始化对应的写结构,可以看到都是会写到我们创建好的segment中.

	RtDocWriter_t tOutDoc ( pSeg );RtWordWriter_t tOutWord ( pSeg, m_bKeywordDict, iWordsCheckpoint );RtHitWriter_t tOutHit ( pSeg );

然后就是写数据了,这里主要是做一个聚合,也就是将相同的keyword对应的属性聚合起来.

	ARRAY_FOREACH ( i, m_dAccum ){.......................................// new keyword; flush current keywordif ( tHit.m_uWordID!=tWord.m_uWordID ){tOutDoc.ZipRestart ();if ( tWord.m_uWordID ){if ( m_bKeywordDict ){const BYTE * pPackedWord = pPacketBase + tWord.m_uWordID;assert ( pPackedWord[0] && pPackedWord[0]+1<m_pDictRt->GetPackedLen() );tWord.m_sWord = pPackedWord;}tOutWord.ZipWord ( tWord );}tWord.m_uWordID = tHit.m_uWordID;tWord.m_uDocs = 0;tWord.m_uHits = 0;tWord.m_uDoc = tOutDoc.ZipDocPtr();uPrevHit = EMPTY_HIT;}..................}

这次就分析到这里,下次我们将会分析最核心的部分就是Sphinx如何刷新数据到磁盘.

相关文章:

C#给图片添加版权信息

现在越来越多的网站都喜欢将用户上传的图片加上网站的版权信息&#xff0c;不要以为那是用photoshop之类的图片处理软件加上去的&#xff0c;其实我们只要写一小段代码&#xff0c;就可以实现这个功能。 添加版权信息的原理其实挺简单&#xff1a;通过图片获取Graphics类的对象…

Python + 爬虫:可视化大屏帮你选粽子

来源 | 数据分析与统计学之美头图 | 下载于ICphoto端午节快要到了&#xff0c;旅游&#xff1f;回家&#xff1f;拜访亲友&#xff1f;少不了要带上粽子。那么&#xff0c;选择什么牌子的粽子呢&#xff1f;选择什么口味的粽子呢&#xff1f;选择什么价格区间呢&#xff1f;今年…

adviser vs mentor

研究生或博士生提到自己导师的时候是说adviser呢&#xff1f;还是mentor呢&#xff1f; 至少我认识一个Berkeley的博士是说adviser的。 另外&#xff0c;我的导师也是说adviser。 那还是说adviser吧……

T extends Serializable这是什么意思呢?看明白这个,你的问题就自然而然的明白了!...

1.转自&#xff1a;https://blog.csdn.net/liwenqiang758/article/details/8131185 自己动手丰衣足食!!! 泛型是Java SE 1.5的新特性&#xff0c;泛型的本质是参数化类型&#xff0c;也就是说所操作的数据类型被指定为一个参数。 这种参数类型可以用在类、接口和方法的创建中&a…

C#中switch语句注意

大家肯定对switch语句并不陌生&#xff0c;它能够让程序根据控制表达式的值&#xff0c;从多个动作中作出选择(从逻辑过程看&#xff0c;和多分支语句if&#xff0d;else有些相似)。在C和java中也有这一语句&#xff0c;不过在C#中&#xff0c;这一语句则有了些变化。先看下边一…

对Cost (%CPU) 粗略的理解

今天研究执行计划&#xff0c;看到执行计划里面有Cost (%CPU)&#xff0c;我这边研究了一把&#xff0c;不知道对与否&#xff0c;拿出来晒晒在Oracle 10g中&#xff0c;Oracle 把CPU的cost也统计在执行计划中去了&#xff0c; 这和以前的8i,9i(9i其实已经开始了)有很大的不同。…

后疫情时代,RTC期待新的场景大爆发

整理 | 寇雪芹头图 | 下载于ICphoto出品 | AI 科技大本营&#xff08;ID:rgznai100&#xff09; 过去的一年中&#xff0c;新冠疫情悄然改变了社会发展和人们的生活&#xff0c;也助推了实时音视频&#xff08;Real-Time Communication&#xff09;技术的落地应用&#xff0c;数…

JS-DOM-元素节点

查看元素节点: 1、getElementById():通过 id 取到唯一节点;如果 id 重名,只能取到第一个 getElementByName(): 通过name属性 getElementByTagName(): 通过标签名 getElementByClassName(): 通过class名 获取元素节点时,一定要注意:获取节点的语句,必须在 DOM 渲染完成之后执行。…

现代软件工程 第十章 【典型用户和场景】 练习与讨论

1. 讨论&#xff1a;下面的老板犯了什么错误? 只看用户的表面语言或行动还是不够的。我们还要找到用户语言行动背后的动机! (图像来源: http://www.weibo.com/funnyshoelace) 2. 是否要文档 有人说&#xff0c;我们敏捷的团队&#xff0c;就喜欢直接的面对面的交流&#xff0…

赠书 | 读懂生成对抗神经网络 GAN,看这文就够了

生成对抗神经网络&#xff08;Generative Adversarial Nets&#xff0c;GAN&#xff09;是一种深度学习的框架&#xff0c;它是通过一个相互对抗的过程来完成模型训练的。典型的GAN包含两个部分&#xff0c;一个是生成模型&#xff08;Generative Model&#xff0c;简称G&#…

把Doc文档转换成rtf格式

先在项目引用里添加上对Microsoft Word 9.0 object library的引用。 using System; namespace DocConvert { class DoctoRtf { static void Main() { //创建一个word的实例 Word.Application newApp new Word.Application(); // 指定源文件和目标文件 object Source&quo…

中国书法的造型元素与原理 刘彦湖

为什么80%的码农都做不了架构师&#xff1f;>>> --------------------------------------------------------------------------------中国书法的造型元素与原理 刘彦湖 中国书法是用最基本的元素遵从中国人特有的形式原则建构起来的大厦。 对于元素及其品质的认…

融云任杰:强互动,RTC 下一个“爆点”场景 | 拟合

从无序中寻找踪迹&#xff0c;从眼前事探索未来。2021 年正值黄金十年新开端&#xff0c;CSDN 以中立技术社区专业、客观的角度&#xff0c;深度探讨中国前沿 IT 技术演进&#xff0c;推出年度重磅企划栏目——「拟合」&#xff0c;通过对话企业技术高管大咖&#xff0c;跟踪报…

推荐一个个人感觉比较有吸引力的网站

2019独角兽企业重金招聘Python工程师标准>>> 廖雪峰的官方网站 转载于:https://my.oschina.net/AaronCN/blog/294293

C# 2进制、8进制、10进制、16进制...各种进制间的轻松转换

在.NET Framework中&#xff0c;System.Convert类中提供了较为全面的各种类型、数值之间的转换功能。其中的两个方法可以轻松的实现各种进制的数值间的转换&#xff1a; Convert.ToInt32(string value, int fromBase)&#xff1a; 可以把不同进制数值的字符串转换为数字&#x…

solrj操作单机solr

2019独角兽企业重金招聘Python工程师标准>>> 在目前的生产过程中全文检索技术应用越来越广&#xff0c;其中涌现了一批非常好得开源搜索引擎框架&#xff0c;如solr,elasticsearch等等。其中我工作使用最多的是solr&#xff0c;并在此对之前工作做一个总结。 solr的…

基于微软开源深度学习算法,用 Python 实现图像和视频修复

‍‍作者 | 李秋键编辑 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;图像修复是计算机视觉领域的一个重要任务&#xff0c;在数字艺术品修复、公安刑侦面部修复等种种实际场景中被广泛应用。图像修复的核心挑战在于为缺失区域合成视觉逼真和语义合理的像素&…

C#实现光盘做启动盘

一 &#xff1a;编程思想 1、创建启动盘 插入要创建的启动盘&#xff0c;程序自动检测光驱中光盘&#xff0c;利用WMI&#xff08;Windows管理架构&#xff1a;Windows Management Instrumentation&#xff09;读取该光盘的序列号&#xff08;具有唯一性&#xff09;&#xf…

为云服务立规矩——首批可信云服务认证名单公布

俗话说&#xff0c;没有规矩不成方圆。在云服务蓬勃发展的今天&#xff0c;无论从规范行业发展&#xff0c;还是为用户提供保障&#xff0c;推动政府云服务采购的角度来说&#xff0c;云服务市场都需要立规矩。7月15日至16日&#xff0c;以“可信中国云&#xff0c;未来新生态”…

Java:多个文档合并输出到一个文档

多个文档合并输出到一个文档 方法&#xff1a;Java NIO package First;import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel;public class Test {pu…

线性表之顺序表(C语言实现)

线性表是从数据元素的逻辑结构上定义的. 这种数据元素的逻辑结构的特征如下: 1.除开第一个和最后一个元素之外.所有元素都有一个前驱元素和后继元素. 2.第一个元素无前驱元素,但有后继元素. 3.最后一个元素有前驱元素,单无后继元素. 可以抽象为如下表述: 元素1元素2元素3元素4元…

C# POP3编程

POP3的使用很简单,所以.net没有向SMTP那样给出相应的类来控制. 废话少说,程序员最需要的使代码,:)! 1.打开VS.NET 2003. 2.新建一个WinForm Application. 3.添加命名空间 using System; using System.Drawing; using System.Collections; using System.ComponentModel; usi…

终于有人把 Python 讲清楚了!

Python因为其优越的特性广泛应用于数据分析、人工智能、Web开发、后端开发、自动化测试/运维、爬虫等领域&#xff0c;也得到了很多企业的青睐。甚至连BATZJ的技术大牛&#xff0c;都无可否认Python现在对于一个程序员发展的重要性&#xff01;最近一两年&#xff0c;我身边也有…

ASP.NET Core的配置(5):配置的同步[设计篇]

本节所谓的“配置同步”主要体现在两个方面&#xff1a;其一&#xff0c;如何监控配置源并在其变化的时候自动加载其数据&#xff0c;其目的是让应用中通过Configuration对象承载的配置与配置源的数据同步&#xff1b;其二、当Configuration对象承载的配置放生变换的时候如何向…

C#分析数据库结构,使用XSL模板自动生成代码

<html> <head> <TITLE>分析数据库结构,自动生成代码</TITLE> <meta http-equiv"Content-Type" content"text/html; charsetgb2312"> </head> <frameset cols"237,767" rows"*"> <…

超棒整理 | Python 关键字知识点大放送

作者 | 黄伟呢来源 | 数据分析与统计学之美其实前面我已经为大家总结了《Python系统关键字 “超全总结” 及其 “含义”》。今天呢&#xff0c;我将对每一个关键字列出一个例子&#xff0c;供大家参考学习和记忆。1、and、or、notand、or、not关键字都是逻辑运算符&#xff0c;…

linux下java进程占用高问题分析过程

2019独角兽企业重金招聘Python工程师标准>>> 1.用top命令找出占用cpu高的进程&#xff0c;记录下pid 2.用top -H -p pid(上面的pid)查看该进和下各线程占用cpu的情况&#xff0c;找出占用cpu高的线程pid 3.printf "%x\n" pid(上面线程的pid)打印出对应的十…

AWS - Couldformation 初探

AWS里面&#xff0c;所有的管理界面的功能都可以通过API或者JSON脚本实现&#xff0c;这样的好处是很容易的就和cloudwatch一起实现各种HA和autoscaling的应用。豆子初次使用cloudformation&#xff0c;写了一个简单JSON来创建一个EBS的Volume。我的模板定义的很简单&#xff0…

清华团队让 AI 写诗“更上一层楼”,诗歌图灵测试迷惑近半数玩家

作者 | 黄珊来源 | 数据实战派比特币外挖无穷洞&#xff0c;机神犹未休。卡中窥币影&#xff0c;池里验沙流。屡载吸金主&#xff0c;孤深渍盗求。方知区块链&#xff0c;本是古来游。这首诗歌来自一支清华团队开发的古诗 AI。它的创作才华可不仅限于此。再看下面这首诗&#x…

js中Dom元素及获取方法

DOM基础对象documentdocument.documentElement html部分document.head document.titledocument.body body部分获取元素对象方法document.getElementById(); 通过id找到对象document.getElementsByTagName(); 通过标签名找到对象并放到数组集合中document.getElementsByNam…