【移動(dòng)應(yīng)用開發(fā)技術(shù)】Sphinx源碼分析-Indexer_第1頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Sphinx源碼分析-Indexer_第2頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Sphinx源碼分析-Indexer_第3頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Sphinx源碼分析-Indexer_第4頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】Sphinx源碼分析-Indexer_第5頁
已閱讀5頁,還剩21頁未讀, 繼續(xù)免費(fèi)閱讀

付費(fèi)下載

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

【移動(dòng)應(yīng)用開發(fā)技術(shù)】Sphinx源碼分析——Indexer

Sphinx作為一款優(yōu)秀的全文檢索開源軟件確實(shí)是很不錯(cuò),最近工作需要,要求在其上進(jìn)行二次開發(fā),第一次接觸這樣一款開源軟件,興奮和緊張心情難免,作為一個(gè)剛畢業(yè)的應(yīng)屆生,看了一周的源代碼,現(xiàn)在奉上一篇博文來對其Indexer部分代碼進(jìn)行分析,共各位及自己做一個(gè)參考,其中只代表個(gè)人的一些粗淺看法,如果不對,請各位大神一定要指正,這樣才能提高,謝謝!

Indexer作為Sphinx的重要組成部分之一,其主要作用是將數(shù)據(jù)源進(jìn)行索引化操作,其中涉及到了索引結(jié)構(gòu)的問題,我會(huì)單獨(dú)開一篇博文去詳細(xì)講解這個(gè)問題,下面我們開始進(jìn)行代碼分析。//Indexer.cpp——intmain(intargc,char**argv)這一部分主要是命令行下參數(shù)的讀入,我們這里采用的是Indexer.exe-cd:/csft_mysql.conf-All命令,這里,argc=4,即有3個(gè)參數(shù),argv[1]:-c就是讀取指定config文檔,argv[2]:d:/csft_mysql.conf就配置文件config的位置,argv[3]就代表對config文件中指定的所有數(shù)據(jù)源進(jìn)行索引構(gòu)建//Doargv[i]解析命令行

constchar*sOptConfig=NULL;

boolbMerge=false;

CSphVector<CSphFilterSettings>dMergeDstFilters;

CSphVector<constchar*>dIndexes;

boolbIndexAll=false;

boolbMergeKillLists=false;

inti;

for(i=1;i<argc;i++)//依次處理命令行中的每一個(gè)部分

{

if((!strcmp(argv[i],"--config")||!strcmp(argv[i],"-c"))&&(i+1)<argc)

{

sOptConfig=argv[++i];

if(!sphIsReadable(sOptConfig))

sphDie("configfile'%s'doesnotexistorisnotreadable",sOptConfig);

}

elseif(strcasecmp(argv[i],"--merge")==0&&(i+2)<argc)

{

bMerge=true;

dIndexes.Add(argv[i+1]);

dIndexes.Add(argv[i+2]);

i+=2;

}

elseif(bMerge&&strcasecmp(argv[i],"--merge-dst-range")==0&&(i+3)<argc)

{

dMergeDstFilters.Resize(dMergeDstFilters.GetLength()+1);

dMergeDstFilters.Last().m_eType=SPH_FILTER_RANGE;

dMergeDstFilters.Last().m_sAttrName=argv[i+1];

dMergeDstFilters.Last().m_uMinValue=(SphAttr_t)strtoull(argv[i+2],NULL,10);

dMergeDstFilters.Last().m_uMaxValue=(SphAttr_t)strtoull(argv[i+3],NULL,10);

i+=3;

}

elseif(strcasecmp(argv[i],"--buildstops")==0&&(i+2)<argc)

{

g_sBuildStops=argv[i+1];

g_iTopStops=atoi(argv[i+2]);

if(g_iTopStops<=0)

break;

i+=2;

}elseif(strcasecmp(argv[i],"--rotate")==0)

{

g_bRotate=true;

}elseif(strcasecmp(argv[i],"--buildfreqs")==0)

{

g_bBuildFreqs=true;

}elseif(strcasecmp(argv[i],"--quiet")==0)

{

g_bQuiet=true;//這里quiet就是只輸出error信息其余信息都不輸出

sphSetQuiet(true);

}elseif(strcasecmp(argv[i],"--noprogress")==0)

{

g_bProgress=false;

}elseif(strcasecmp(argv[i],"--all")==0)

{

bIndexAll=true;//表明所有數(shù)據(jù)源都要進(jìn)行索引構(gòu)建

}elseif(strcasecmp(argv[i],"--merge-killlists")==0)

{

bMergeKillLists=true;

}elseif(sphIsAlpha(argv[i][0]))

{

dIndexes.Add(argv[i]);

}else

{

break;

}

}

if(!g_bQuiet)

fprintf(stdout,SPHINX_BANNER);//如果不是quiet模式則輸出歡迎信息

if(!isatty(fileno(stdout)))

g_bProgress=false;

if(i!=argc||argc<2)

{

if(argc>1)

{

fprintf(stdout,"ERROR:malformedorunknownoptionnear'%s'.\n",argv[i]);

}else

{

fprintf(stdout,

"Usage:indexer[OPTIONS][indexname1[indexname2[...]]]\n"

"\n"

"Optionsare:\n"

"--config<file>\t\treadconfigurationfromspecifiedfile\n"

"\t\t\t(defaultiscsft.conf)\n"

"--all\t\t\treindexallconfiguredindexes\n"

"--quiet\t\t\tbequiet,onlyprinterrors\n"

"--noprogress\t\tdonotdisplayprogress\n"

"\t\t\t(automaticallyonifoutputisnottoatty)\n"

#if!USE_WINDOWS

"--rotate\t\tsendSIGHUPtosearchdwhenindexingisover\n"

"\t\t\ttorotateupdatedindexesautomatically\n"

#endif

"--buildstops<output.txt><N>\n"

"\t\t\tbuildtopNstopwordsandwritethemtogivenfile\n"

"--buildfreqs\t\tstorewordsfrequenciestooutput.txt\n"

"\t\t\t(usedwith--buildstopsonly)\n"

"--merge<dst-index><src-index>\n"

"\t\t\tmerge'src-index'into'dst-index'\n"

"\t\t\t'dst-index'willreceivemergeresult\n"

"\t\t\t'src-index'willnotbemodified\n"

"--merge-dst-range<attr><min><max>\n"

"\t\t\tfilter'dst-index'onmerge,keeponlythosedocuments\n"

"\t\t\twhere'attr'isbetween'min'and'max'(inclusive)\n"

"--merge-killlists"

"\t\t\tmergesrcanddstkilllistsinsteadofapplyingsrckilllisttodst"

"\n"

"Examples:\n"

"indexer--quietmyidx1\treindex'myidx1'definedin'csft.conf'\n"

"indexer--all\t\treindexallindexesdefinedin'csft.conf'\n");

}

return1;

}

下一步則是進(jìn)行Config文件的讀取工作,這涉及到兩個(gè)重要的類,在后面的索引及查詢過程中會(huì)多次使用它們,一個(gè)是CSphConfigParser,一個(gè)是CSphConfig。首先看下CSphConfigParser的結(jié)構(gòu)://Sphinxutils.hclassCSphConfigParser

{

public:

CSphConfigm_tConf;

public:

CSphConfigParser();

boolParse(constchar*sFileName,constchar*pBuffer=NULL);

protected:

CSphStringm_sFileName;

intm_iLine;

CSphStringm_sSectionType;

CSphStringm_sSectionName;

charm_sError[1024];

intm_iWarnings;

staticconstintWARNS_THRESH=5;

protected:

boolIsPlainSection(constchar*sKey);

boolIsNamedSection(constchar*sKey);

boolAddSection(constchar*sType,constchar*sSection);

voidAddKey(constchar*sKey,char*sValue);

boolValidateKey(constchar*sKey);

#if!USE_WINDOWS

boolTryToExec(char*pBuffer,char*pEnd,constchar*szFilename,CSphVector<char>&dResult);

#endif

char*GetBufferString(char*szDest,intiMax,constchar*&szSource);

};它是解析Config文檔的主要的數(shù)據(jù)結(jié)構(gòu),其中存儲Config文檔信息的就是我們這里提到的第二個(gè)數(shù)據(jù)結(jié)構(gòu),它同樣也是CSphConfigParser中的一個(gè)成員變量,即CSphConfig,我們可以看到CSphConfig實(shí)際上是一個(gè)哈希表,通過代碼發(fā)現(xiàn)它是一個(gè)擁有256個(gè)鍵值對的哈希表,后面我們會(huì)講到,通過CSphConfigParser類函數(shù),將Config文件解析,讀取到某一Config名字插入CsphConfig哈希表中的Key值,讀取到該Config對應(yīng)的值插入到Value中,方便后面構(gòu)建索引時(shí)使用。//Sphinxutils.h///config(hashofsectiontypes)

typedefSmallStringHash_T<CSphConfigType>CSphConfig;

說完了這兩個(gè)數(shù)據(jù)結(jié)構(gòu)我們來看下Indexer是如何讀取Config信息的,其中主要是通過一個(gè)sphLoadConfig函數(shù)完成讀取操作,將相關(guān)Config信息以鍵值對的形式存入cp.m_tConf中,然后檢查重要的參數(shù)是否讀入且存在,例如Source相關(guān)信息,數(shù)據(jù)源是否被讀入,Sphinx中Mysql默認(rèn)Source對應(yīng)值為mysql,Indexer,即全局Index定義中是否定義了mem_limit的值,即索引過程中最大緩存限制,等等。//Indexer.cpp——intmain(intargc,char**argv)///////////////

//loadconfig

///////////////

CSphConfigParsercp;

CSphConfig&hConf=cp.m_tConf;

sOptConfig=sphLoadConfig(sOptConfig,g_bQuiet,cp);

if(!hConf("source"))

sphDie("noindexesfoundinconfigfile'%s'",sOptConfig);

g_iMemLimit=0;

if(hConf("indexer")&&hConf["indexer"]("indexer"))

{

CSphConfigSection&hIndexer=hConf["indexer"]["indexer"];

g_iMemLimit=hIndexer.GetSize("mem_limit",0);

g_iMaxXmlpipe2Field=hIndexer.GetSize("max_xmlpipe2_field",2*1024*1024);

g_iWriteBuffer=hIndexer.GetSize("write_buffer",1024*1024);

sphSetThrottling(hIndexer.GetInt("max_iops",0),hIndexer.GetSize("max_iosize",0));

}這其中,主要解析函數(shù)為CSphConfig中的Parser函數(shù),其里面比較復(fù)雜,大意就是按照字符流讀取Config文檔,遇到配置信息及其值就存儲到CSphconfig這個(gè)哈希表中//Sphinxutils.hconstchar*sphLoadConfig(constchar*sOptConfig,boolbQuiet,CSphConfigParser&cp)

{

//fallbacktodefaultsiftherewasnoexplicitconfigspecified

while(!sOptConfig)

{

#ifdefSYSCONFDIR

sOptConfig=SYSCONFDIR"/csft.conf";

if(sphIsReadable(sOptConfig))

break;

#endif

sOptConfig="./csft.conf";

if(sphIsReadable(sOptConfig))

break;

sOptConfig=NULL;

break;

}

if(!sOptConfig)

sphDie("noreadableconfigfile(lookedin"

#ifdefSYSCONFDIR

SYSCONFDIR"/csft.conf,"

#endif

"./csft.conf)");

if(!bQuiet)

fprintf(stdout,"usingconfigfile'%s'...\n",sOptConfig);

//loadconfig

if(!cp.Parse(sOptConfig))//Parser為實(shí)際解析函數(shù)

sphDie("failedtoparseconfigfile'%s'",sOptConfig);

CSphConfig&hConf=cp.m_tConf;

if(!hConf("index"))

sphDie("noindexesfoundinconfigfile'%s'",sOptConfig);

returnsOptConfig;

}當(dāng)我們順利的讀取完Config信息后,我們進(jìn)入構(gòu)建索引階段,前面我們提到了第三個(gè)參數(shù),我們選用的是ALL即為所有的數(shù)據(jù)源構(gòu)建索引,故bMerge(合并索引)為false,bIndexALL為true,我們開始為每一數(shù)據(jù)源構(gòu)建索引,程序會(huì)開始在類型為CSphConfig的hConf哈希表中搜索Key為index的值,即需要構(gòu)建的索引,然后取出該索引的名稱,結(jié)合數(shù)據(jù)源Source信息構(gòu)建索引,執(zhí)行DoIndex函數(shù)。//Indexer.cpp——intmain(intargc,char**argv)/////////////////////

//indexeachindex

////////////////////

sphStartIOStats();

boolbIndexedOk=false;//ifanyoftheindexesareok

if(bMerge)

{

if(dIndexes.GetLength()!=2)

sphDie("theremustbe2indexestomergespecified");

if(!hConf["index"](dIndexes[0]))

sphDie("nomergedestinationindex'%s'",dIndexes[0]);

if(!hConf["index"](dIndexes[1]))

sphDie("nomergesourceindex'%s'",dIndexes[1]);

bIndexedOk=DoMerge(

hConf["index"][dIndexes[0]],dIndexes[0],

hConf["index"][dIndexes[1]],dIndexes[1],dMergeDstFilters,g_bRotate,bMergeKillLists);

}elseif(bIndexAll)

{

hConf["index"].IterateStart();

while(hConf["index"].IterateNext())

bIndexedOk|=DoIndex(hConf["index"].IterateGet(),hConf["index"].IterateGetKey().cstr(),hConf["source"]);//在這里構(gòu)建索引,核心函數(shù)為DoIndex

}else

{

ARRAY_FOREACH(i,dIndexes)

{

if(!hConf["index"](dIndexes[i]))

fprintf(stdout,"WARNING:nosuchindex'%s',skipping.\n",dIndexes[i]);

else

bIndexedOk|=DoIndex(hConf["index"][dIndexes[i]],dIndexes[i],hConf["source"]);

}

}

sphShutdownWordforms();

constCSphIOStats&tStats=sphStopIOStats();

if(!g_bQuiet)

{

ReportIOStats("reads",tStats.m_iReadOps,tStats.m_iReadTime,tStats.m_iReadBytes);

ReportIOStats("writes",tStats.m_iWriteOps,tStats.m_iWriteTime,tStats.m_iWriteBytes);

}DoIndex為整個(gè)Indexer中最核心的函數(shù),下面我們來詳細(xì)分析下.//Indexer.cpp——DoIndex(constCSphConfigSection&hIndex,constchar*sIndexName,constCSphConfigType&hSource)首先判斷數(shù)據(jù)源類型是否為分布式,是否采用quiet模式只輸出error信息if(hIndex("type")&&hIndex["type"]=="distributed")

{

if(!g_bQuiet)

{

fprintf(stdout,"distributedindex'%s'cannotbedirectlyindexed;skipping.\n",sIndexName);

fflush(stdout);

}

returnfalse;

}

if(!g_bQuiet)

{

fprintf(stdout,"indexingindex'%s'...\n",sIndexName);

fflush(stdout);

}然后檢查hIndex信息中的配置信息是否齊全正確//checkconfig

if(!hIndex("path"))

{

fprintf(stdout,"ERROR:index'%s':key'path'notfound.\n",sIndexName);

returnfalse;

}

if((hIndex.GetInt("min_prefix_len",0)>0||hIndex.GetInt("min_infix_len",0)>0)

&&hIndex.GetInt("enable_star")==0)

{

constchar*szMorph=hIndex.GetStr("morphology","");

if(szMorph&&*szMorph&&strcmp(szMorph,"none"))

{

fprintf(stdout,"ERROR:index'%s':infixesandmorphologyareenabled,enable_star=0\n",sIndexName);

returnfalse;

}

}接著開始準(zhǔn)備分詞器,其中主要就是初始化一些參數(shù),例如在sphConfTokenizer中會(huì)根據(jù)Config配置文件中設(shè)置的charsetType類型選擇合適的編碼解析字符,以及采用何種中文分詞器來對中文進(jìn)行分詞操作。然后就是初始化參數(shù)后創(chuàng)建分析器實(shí)例指定分詞所用的詞典的地址位置等///////////////////

//spawntokenizer

///////////////////

CSphStringsError;

CSphTokenizerSettingstTokSettings;

if(!sphConfTokenizer(hIndex,tTokSettings,sError))

sphDie("index'%s':%s",sIndexName,sError.cstr());

ISphTokenizer*pTokenizer=ISphTokenizer::Create(tTokSettings,sError);

if(!pTokenizer)

sphDie("index'%s':%s",sIndexName,sError.cstr());

CSphDict*pDict=NULL;

CSphDictSettingstDictSettings;

if(!g_sBuildStops)

{

ISphTokenizer*pTokenFilter=NULL;

sphConfDictionary(hIndex,tDictSettings);

pDict=sphCreateDictionaryCRC(tDictSettings,pTokenizer,sError);

if(!pDict)

sphDie("index'%s':%s",sIndexName,sError.cstr());

if(!sError.IsEmpty())

fprintf(stdout,"WARNING:index'%s':%s\n",sIndexName,sError.cstr());

pTokenFilter=ISphTokenizer::CreateTokenFilter(pTokenizer,pDict->GetMultiWordforms());

pTokenizer=pTokenFilter?pTokenFilter:pTokenizer;

}然后是前綴后綴索引設(shè)置(這個(gè)地方研究的不細(xì)致,先把代碼貼出來,待補(bǔ)充)//prefix/infixindexing

intiPrefix=hIndex("min_prefix_len")?hIndex["min_prefix_len"].intval():0;

intiInfix=hIndex("min_infix_len")?hIndex["min_infix_len"].intval():0;

iPrefix=Max(iPrefix,0);

iInfix=Max(iInfix,0);

CSphStringsPrefixFields,sInfixFields;

if(hIndex.Exists("prefix_fields"))

sPrefixFields=hIndex["prefix_fields"].cstr();

if(hIndex.Exists("infix_fields"))

sInfixFields=hIndex["infix_fields"].cstr();

if(iPrefix==0&&!sPrefixFields.IsEmpty())

fprintf(stdout,"WARNING:min_prefix_len=0.prefix_fieldsareignored\n");

if(iInfix==0&&!sInfixFields.IsEmpty())

fprintf(stdout,"WARNING:min_infix_len=0.infix_fieldsareignored\n");然后設(shè)置boundary信息(詞組邊界符列表,此列表控制哪些字符被視作分隔不同詞組的邊界,每到一個(gè)這樣的邊界,其后面的詞的“位置”值都會(huì)被加入一個(gè)額外的增量,可以借此用近似搜索符來模擬詞組搜索。)//boundary

boolbInplaceEnable=hIndex.GetInt("inplace_enable",0)!=0;

intiHitGap=hIndex.GetSize("inplace_hit_gap",0);

intiDocinfoGap=hIndex.GetSize("inplace_docinfo_gap",0);

floatfRelocFactor=hIndex.GetFloat("inplace_reloc_factor",0.1f);

floatfWriteFactor=hIndex.GetFloat("inplace_write_factor",0.1f);

if(bInplaceEnable)

{

if(fRelocFactor<0.01f||fRelocFactor>0.9f)

{

fprintf(stdout,"WARNING:inplace_reloc_factormustbe0.01to0.9,clamped\n");

fRelocFactor=Min(Max(fRelocFactor,0.01f),0.9f);

}

if(fWriteFactor<0.01f||fWriteFactor>0.9f)

{

fprintf(stdout,"WARNING:inplace_write_factormustbe0.01to0.9,clamped\n");

fWriteFactor=Min(Max(fWriteFactor,0.01f),0.9f);

}

if(fWriteFactor+fRelocFactor>1.0f)

{

fprintf(stdout,"WARNING:inplace_write_factor+inplace_reloc_factormustbelessthan0.9,scaled\n");

floatfScale=0.9f/(fWriteFactor+fRelocFactor);

fRelocFactor*=fScale;

fWriteFactor*=fScale;

}

}接下來準(zhǔn)備數(shù)據(jù)源,其實(shí)發(fā)現(xiàn)Indexer在準(zhǔn)備這些工作時(shí)很繁瑣,一遍又一遍的檢查相關(guān)配置信息是否完全,前面檢查了后面還查,可能是出于嚴(yán)謹(jǐn)?shù)目紤]吧,這里提一下dSource是一個(gè)CSphSource的數(shù)組,每一個(gè)CSphSource類型的pSource對應(yīng)一個(gè)數(shù)據(jù)源,因?yàn)榕渲眯畔⒅锌赡軙?huì)存在多個(gè)數(shù)據(jù)源,所以會(huì)有多個(gè)pSource。程序會(huì)在hIndex中搜索Key值為Source的鍵值對,提取出對應(yīng)的值作為pSourceName,在本例中,我們只有配置文件中的一個(gè)Source即mysql。我們看一下CSphSource類型結(jié)構(gòu)。其中包含有三個(gè)大部分,第一大部分存儲文本分詞后的word信息,每一個(gè)word(也許是字也許是詞)對應(yīng)一個(gè)WordHit,這個(gè)WordHit描述該word的相關(guān)信息,唯一標(biāo)示該word。其中WordHit中又包含三部分,分別為word的文檔ID,表示該word屬于哪一篇文檔;word的ID,表示該word在字典中的對應(yīng)ID;Word的位置,表示該word在文檔中的偏移量。第二大部分存儲Source中文檔的相關(guān)信息,其中亦包含了三部分,分別問文檔ID;文檔中列的數(shù)目,以及列對應(yīng)的指針。第三大部分存儲的就是doc中的屬性字段信息。///genericdatasource

classCSphSource:publicCSphSourceSettings

{

public:

CSphVector<CSphWordHit>m_dHits;///<currentdocumentsplitintowords

CSphDocInfom_tDocInfo;///<currentdocumentinfo

CSphVector<CSphString>m_dStrAttrs;///<currentdocumentstringattrs123//parseallsources

CSphVector<CSphSource*>dSources;

boolbGotAttrs=false;

boolbSpawnFailed=false;

for(CSphVariant*pSourceName=hIndex("source");pSourceName;pSourceName=pSourceName->m_pNext)

{

if(!hSources(pSourceName->cstr()))

{

fprintf(stdout,"ERROR:index'%s':source'%s'notfound.\n",sIndexName,pSourceName->cstr());

continue;

}

constCSphConfigSection&hSource=hSources[pSourceName->cstr()];

CSphSource*pSource=SpawnSource(hSource,pSourceName->cstr(),pTokenizer->IsUtf8());//通過SpawnSource完成對于數(shù)據(jù)源的解析,其中包括了屬性列,需要構(gòu)建索引列等相關(guān)信息

if(!pSource)

{

bSpawnFailed=true;

continue;

}

if(pSource->HasAttrsConfigured())

bGotAttrs=true;//判斷數(shù)據(jù)源中是否有指定屬性項(xiàng)

pSource->SetupFieldMatch(sPrefixFields.cstr(),sInfixFields.cstr());

pSource->SetTokenizer(pTokenizer);//為每一個(gè)Source準(zhǔn)備一個(gè)分詞器

dSources.Add(pSource);//將解析好的某個(gè)Source加入Source數(shù)組中去,因?yàn)榭赡艽嬖诙鄠€(gè)Source

}Source信息準(zhǔn)備好后,開始準(zhǔn)備Index的構(gòu)建工作,首先檢測該Index是否被使用,即是否被上鎖,其次通過CSphIndexSettings類型的tSettings對創(chuàng)建好的pIndex進(jìn)行初始化,主要是一些索引構(gòu)建的信息,例如緩存大小,Boudary大小,停用詞初始化,分詞器初始化等等。準(zhǔn)備完相關(guān)信息后,重要的就是Build函數(shù),這是索引構(gòu)建的核心函數(shù),我們來仔細(xì)分析//doindex

CSphIndex*pIndex=sphCreateIndexPhrase(sIndexPath.cstr());

assert(pIndex);

//checklockfile

if(!pIndex->Lock())

{

fprintf(stdout,"FATAL:%s,willnotindex.Try--rotateoption.\n",pIndex->GetLastError().cstr());

exit(1);

}

CSphIndexSettingstSettings;

sphConfIndex(hIndex,tSettings);

if(tSettings.m_bIndexExactWords&&!tDictSettings.HasMorphology())

{

tSettings.m_bIndexExactWords=false;

fprintf(stdout,"WARNING:index'%s':nomorphology,index_exact_words=1hasnoeffect,ignoring\n",sIndexName);

}

if(bGotAttrs&&tSettings.m_eDocinfo==SPH_DOCINFO_NONE)

{

fprintf(stdout,"FATAL:index'%s':gotattributes,butdocinfois'none'(fixyourconfigfile).\n",sIndexName);

exit(1);

}

pIndex->SetProgressCallback(ShowProgress);

if(bInplaceEnable)

pIndex->SetInplaceSettings(iHitGap,iDocinfoGap,fRelocFactor,fWriteFactor);

pIndex->SetTokenizer(pTokenizer);

pIndex->SetDictionary(pDict);

pIndex->Setup(tSettings);

bOK=pIndex->Build(dSources,g_iMemLimit,g_iWriteBuffer)!=0;//Build函數(shù)是索引構(gòu)建的重點(diǎn),所有的核心操作都在其中

if(bOK&&g_bRotate)

{

sIndexPath.SetSprintf("%s.new",hIndex["path"].cstr());

bOK=pIndex->Rename(sIndexPath.cstr());

}

if(!bOK)

fprintf(stdout,"ERROR:index'%s':%s.\n",sIndexName,pIndex->GetLastError().cstr());

pIndex->Unlock();對于Build函數(shù)而言,它是單次處理一個(gè)數(shù)據(jù)源并為此構(gòu)建索引信息,//sphinx.cppBuild(constCSphVector<CSphSource*>&dSources,intiMemoryLimit,intiWriteBuffer)首先是準(zhǔn)備Source,還是把dSource中的每一個(gè)pSource檢查下是否都存在,詞典是否都準(zhǔn)備好,各種初始化是否都齊備//setupsources

ARRAY_FOREACH(iSource,dSources)

{

CSphSource*pSource=dSources[iSource];

assert(pSource);

pSource->SetDict(m_pDict);

pSource->Setup(m_tSettings);

}鏈接第一個(gè)數(shù)據(jù)源,獲取數(shù)據(jù)源的Schema信息,就是數(shù)據(jù)源的Doc中哪些是屬性,哪些列是要構(gòu)建索引的信息//connect1stsourceandfetchitsschema

if(!dSources[0]->Connect(m_sLastError)

||!dSources[0]->IterateHitsStart(m_sLastError)

||!dSources[0]->UpdateSchema(&m_tSchema,m_sLastError))

{

return0;

}后面就是初始化一些存儲結(jié)構(gòu),其中重點(diǎn)說下緩存出來的幾個(gè)臨時(shí)文件分別的作用。結(jié)尾時(shí)tmp0的存儲的是被上鎖的Index,有些Index正在被查詢使用故上鎖。tmp1,即對應(yīng)將來生成的spp文件,存儲詞匯的位置信息,包含該詞所在的文檔ID,該詞所在詞典對應(yīng)的ID,以及該詞在本文檔中的位置信息。tmp2,即對應(yīng)將來生成的spa文件存儲的是文檔信息,包含了DocID以及DocInfo信息。tmp7對應(yīng)的是多值查詢,感興趣的可以度娘,這是一種查詢方式,這里不做過多解釋//createtempfiles

CSphAutofilefdLock(GetIndexFileName("tmp0"),SPH_O_NEW,m_sLastError,true);

CSphAutofilefdHits(GetIndexFileName(m_bInplaceSettings?"spp":"tmp1"),SPH_O_NEW,m_sLastError,!m_bInplaceSettings);

CSphAutofilefdDocinfos(GetIndexFileName(m_bInplaceSettings?"spa":"tmp2"),SPH_O_NEW,m_sLastError,!m_bInplaceSettings);

CSphAutofilefdTmpFieldMVAs(GetIndexFileName("tmp7"),SPH_O_NEW,m_sLastError,true);

CSphWritertOrdWriter;

CSphStringsRawOrdinalsFile=GetIndexFileName("tmp4");下面具體處理每一個(gè)Source取出的每一個(gè)文檔,主要是通過這個(gè)IterateHitsNext實(shí)現(xiàn)的//fetchdocuments

for(;;)

{

//getnextdoc,andhandleerrors

if(!pSource->IterateHitsNext(m_sLastError))

return0;具體到該函數(shù)可以看到,該函數(shù)主要是有兩部分組成,即提取索引列(NextDocument),針對該索引列構(gòu)建索引(BuildHits)boolCSphSource_Document::IterateHitsNext(CSphString&sError)

{

assert(m_pTokenizer);

PROFILE(src_document);

BYTE**dFields=NextDocument(sError);//從數(shù)據(jù)源中提取需要構(gòu)建索引的列

if(m_tDocInfo.m_iDocID==0)

returntrue;

if(!dFields)

returnfalse;

m_tStats.m_iTotalDocuments++;

m_dHits.Reserve(1024);

m_dHits.Resize(0);

BuildHits(dFields,-1,0);//針對提取出的需要索引的列構(gòu)建索引

returntrue;

}具體看一下NexDocument的操作,通過Sql.h中的API——sqlFetchRow,取出一條記錄,驗(yàn)證該記錄是否合法//getnextnon-zero-idrow

do

{

//trytogetnextrow

boolbGotRow=SqlFetchRow();//首先嘗試能否正常取出一條記錄

//whentheparty'sover...

while(!bGotRow)//如果取不出來這條記錄,再繼續(xù)思考原因

{

//isthatanerror?

if(SqlIsError())

{

sError.SetSprintf("sql_fetch_row:%s",SqlError());

m_tDocInfo.m_iDocID=1;//0meanslegaleof

returnNULL;

}

//maybewecandonextstepyet?

if(!RunQueryStep(m_tParams.m_sQuery.cstr(),sError))

{

//ifthere'samessage,there'sanerror

//otherwise,we'rejustover

if(!sError.IsEmpty())

{

m_tDocInfo.m_iDocID=1;//0meanslegaleof

returnNULL;

}

}else

{

//stepwentfine;trytofetch

bGotRow=SqlFetchRow();

continue;

}

SqlDismi***esult();

//ok,we'reover

ARRAY_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;

}

SqlDismi***esult();

}

m_tDocInfo.m_iDocID=0;//0meanslegaleof

returnNULL;

}

//gethim!//成功取得后

m_tDocInfo.m_iDocID=VerifyID(sphToDocid(SqlColumn(0)));//判斷ID是否為0,是否越界

m_uMaxFetchedID=Max(m_uMaxFetchedID,m_tDocInfo.m_iDocID);

}while(!m_tDocInfo.m_iDocID);將條記錄按照Schema分成Feild部分,即需要構(gòu)建索引的部分,以及Attribute部分,即排序需要用到的屬性部分ARRAY_FOREACH(i,m_tSchema.m_dFields)

{

#ifUSE_ZLIB

if(m_dUnpack[i]!=SPH_UNPACK_NONE)

{

m_dFields[i]=(BYTE*)SqlUnpackColumn(i,m_dUnpack[i]);

continue;

}

#endif

m_dFields[i]=(BYTE*)SqlColumn(m_tSchema.m_dFields[i].m_iIndex);

}

intiFieldMVA=0;

for(inti=0;i<m_tSchema.GetAttrsCount();i++)

{

constCSphColumnInfo&tAttr=m_tSchema.GetAttr(i);//shortcut

if(tAttr.m_eAttrType&SPH_ATTR_MULTI)

{

m_tDocInfo.SetAttr(tAttr.m_tLocator,0);

if(tAttr.m_eSrc==SPH_ATTRSRC_FIELD)

ParseFieldMVA(m_dFieldMVAs,iFieldMVA++,SqlColumn(tAttr.m_iIndex));

continue;

}

switch(tAttr.m_eAttrType)

{

caseSPH_ATTR_ORDINAL:

//memorizestring,fixupNULLs

m_dStrAttrs[i]=SqlColumn(tAttr.m_iIndex);

if(!m_dStrAttrs[i].cstr())

m_dStrAttrs[i]="";

m_tDocInfo.SetAttr(tAttr.m_tLocator,0);

break;

caseSPH_ATTR_FLOAT:

m_tDocInfo.SetAttrFloat(tAttr.m_tLocator,sphToFloat(SqlColumn(tAttr.m_iIndex)));//FIXME?reportconversionerrorsmaybe?

break;

caseSPH_ATTR_BIGINT:

m_tDocInfo.SetAttr(tAttr.m_tLocator,sphToInt64(SqlColumn(tAttr.m_iIndex)));//FIXME?reportconversionerrorsmaybe?

break;

default:

//juststoreasuintbydefault

m_tDocInfo.SetAttr(tAttr.m_tLocator,sphToDword(SqlColumn(tAttr.m_iIndex)));//FIXME?reportconversionerrorsmaybe?

break;

}

}

returnm_dFields;提取出相關(guān)數(shù)據(jù)后,針對每一條需要索引的item開始構(gòu)建索引,進(jìn)入BuildHit函數(shù),首先先初始化相關(guān)參數(shù),準(zhǔn)備分詞器緩存ARRAY_FOREACH(iField,m_tSchema.m_dFields)

{

//BYTE*sField=dFields[iField];

BYTE*sField=GetField(dFields,iField);//取出索引字段

if(!sField)

continue;

if(m_bStripHTML)

m_pStripper->Strip(sField);

intiFieldBytes=(int)strlen((char*)sField);

m_tStats.m_iTotalBytes+=iFieldBytes;

m_pTokenizer->SetBuffer(sField,iFieldBytes);//設(shè)置分詞器緩存,實(shí)際上就是索引字段大小,準(zhǔn)備針對索引字段進(jìn)行分詞

BYTE*sWord;

intiPos=HIT_PACK(iField,0);

intiLastStep=1;

boolbPrefixField=m_tSchema.m_dFields[iField].m_eWordpart==SPH_WORDPART_PREFIX;

boolbInfixMode=m_iMinInfixLen>0;

BYTEsBuf[16+3*SPH_MAX_WORD_LEN];然后開始分詞,分詞的過程在這里不具體講了,這不屬于Sphinx的主要涉足領(lǐng)域,當(dāng)我們把iField即要索引的字段放入分詞器中依次解析,然后將分出的詞賦值給sWord,將sWord的位置計(jì)算后賦值給ipos//indexwordsonly

while((sWord=m_pTokenizer->GetToken())!=NULL)

{

iPos+=iLastStep+m_pTokenizer->GetOvershortCount()*m_iOvershortStep;

if(m_pTokenizer->GetBoundary())

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論