內(nèi)存管理與數(shù)據(jù)存儲
???????? 索引(index):Lucene的索引由許多個文件組成赶熟,這些文件放在同一個目錄下
???????? 段(segment):一個Lucene的索引由多個段組成逗噩,段與段之間是獨立的浸间。添加新的文檔時可以生成新的段玩徊,達到閾值(段的個數(shù)僧叉,段中包含的文件數(shù)等)時拌滋,不同的段可以合并赞辩。在文件夾下雌芽,具有相同前綴的文件屬于同一個段segments.gen 和 segments_N(N表示一個具體數(shù)字,eg:segments_5)是段的元數(shù)據(jù)文件辨嗽,他們保存了段的屬性信息世落。
??????? 文檔(document):文檔時建索引的基本單位,一個段中可以包含多篇文檔糟需。新添加的文檔時單獨保存在一個新生成的段中屉佳,隨著段的合并谷朝,不同的文檔會合并到至相同的段中。
???????? 域(Field):一個文檔有可由多個域(Field)組成武花,比如一篇新聞圆凰,有 標題,作者体箕,正文等多個屬性专钉,這些屬性可以看作是文檔的域。不同的域可以指定不同的索引方式累铅,比如指定不同的分詞方式跃须,是否構(gòu)建索引,是否存儲等
???????? 詞(Term):詞 是索引的最小單位争群,是經(jīng)過詞法分詞和語言處理后的字符串回怜。
Lucene的索引結(jié)構(gòu)中,即保存了正向信息换薄,也保存了反向信息玉雾。
正向信息:
按層次保存了從索引,一直到詞的包含關(guān)系:索引(Index) –> 段(segment) –> 文檔(Document) –> 域(Field) –> 詞(Term)
也即此索引包含了那些段轻要,每個段包含了那些文檔复旬,每個文檔包含了那些域,每個域包含了那些詞冲泥。
既然是層次結(jié)構(gòu)驹碍,則每個層次都保存了本層次的信息以及下一層次的元信息,也即屬性信息凡恍,比如一本介紹中國地理的書志秃,應該首先介紹中國地理的概況,以及中國包含多少個省嚼酝,每個省介紹本省的基本概況及包含多少個市浮还,每個市介紹本市的基本概況及包含多少個縣,每個縣具體介紹每個縣的具體情況闽巩。
如上圖钧舌,包含正向信息的文件有:
????????? segments_N保存了此索引包含多少個段,每個段包含多少篇文檔涎跨。
????????? XXX.fnm保存了此段包含了多少個域洼冻,每個域的名稱及索引方式。
????????? XXX.fdx隅很,XXX.fdt保存了此段包含的所有文檔撞牢,每篇文檔包含了多少域,每個域保存了那些信息。
????????? XXX.tvx普泡,XXX.tvd播掷,XXX.tvf保存了此段包含多少文檔审编,每篇文檔包含了多少域撼班,每個域包含了多少詞,每個詞的字符串垒酬,位置等信息砰嘁。
反向信息:
保存了詞典到倒排表的映射:詞(Term) –> 文檔(Document)
如上圖,包含反向信息的文件有:
????????? XXX.tis勘究,XXX.tii保存了詞典(Term Dictionary)矮湘,也即此段包含的所有的詞按字典順序的排序。
???????? XXX.frq保存了倒排表口糕,也即包含每個詞的文檔ID列表缅阳。
???????? XXX.prx保存了倒排表中每個詞在包含此詞的文檔中的位置。
倒排
倒排存儲示例
文章1的所有關(guān)鍵詞為:[tom] [live] [guangzhou] [i] [live] [guangzhou] 文章2的所有關(guān)鍵詞為:[he] [live] [shanghai]
通常有兩種位置:
a.字符位置景描,即記錄該詞是文章中第幾個字符(優(yōu)點是關(guān)鍵詞亮顯時定位快)十办;
b.關(guān)鍵詞位置,即記錄該詞是文章中第幾個關(guān)鍵詞(優(yōu)點是節(jié)約索引空間超棺、詞組(phase)查詢快)向族,lucene中記錄的就是這種位置。
lucene將上面三列分別作為詞典文件(Term Dictionary)棠绘、頻率文件(frequencies)件相、位置文件 (positions)保存。其中詞典文件不僅保存有每個關(guān)鍵詞氧苍,還保留了指向頻率文件和位置文件的指針夜矗,通過指針可以找到該關(guān)鍵字的頻率信息和位置信息。
倒排信息
參考鏈接:Lucene索引過程中的內(nèi)存管理與數(shù)據(jù)存儲
?在Lucene的設計里让虐,IntBlockPool和ByteBlockPool的作用域是IndexChain紊撕,即每個IndexChain都會生成獨立的ByteBlockPool和IntBlockPool ,這樣就不會出現(xiàn)多線程間可變數(shù)據(jù)共享的問題澄干,這種做法實際上是一種約定方式的線程封閉逛揩,即ByteBlockPool本身并不是線程安全的,不像ThreadLocal或者棧封閉麸俘。由于每個IndexChain都需要處理多個Field辩稽,所以IntBlockPool和ByteBlockPool是Field所共享的。需要注意的是ParallelPostingsArray的作用域是Field从媚,即每個Field都有一個postingsArray逞泄。
? ParallelPostingsArray的三個成員變量:
?? textStarts存儲的是每一個term在ByteBlockPool里面的起始位置,通過textStarts[termID]可以快速找到termID對應的term 。
?? byteStarts存儲的是term在ByteBlockPool的結(jié)束位置的下一個位置喷众。
?? IntStarts存儲的是term在IntBlockPool的地址信息各谚,而IntBlockPool則存儲著term在ByteBlockPool中的Slice位置信息。
DOCID;Freq;Positions這三種信息都是隨著Term存儲在ByteBlockPool中到千,其存儲過程如下:
???? 第一步:把term.length存儲到ByteBlockPool.buffer中昌渤。這會占用1或者2個byte,由term的大小決定憔四。由于term的最大長度為32766膀息,所以term.length最多會占用兩個byte。
??? 第二步:把term的byte數(shù)組形式存儲到ByteBlockPool.buffer中了赵。
??? 第三步:緊接著term開辟5個byte大小的slice,用來存儲term在每個doc中的freq信息潜支。
??? 第四步:再開辟一塊Slice用來存儲positions信息。
Lucene存儲在索引中的并非真正的docId,而是docDelta,即兩個docId的差值.這樣存儲能夠起到節(jié)約空間的作用柿汛。
索引實現(xiàn)
參考鏈接:Lucene底層實現(xiàn)原理冗酿,它的索引結(jié)構(gòu)
Lucene3.0之前使用的也是跳躍表結(jié)構(gòu),后換成了FST络断,但跳躍表在Lucene其他地方還有應用如倒排表合并和文檔號索引裁替。
FST
理論基礎:《Direct constructionofminimal acyclic subsequential transducers》,通過輸入有序字符串構(gòu)建最小有向無環(huán)圖妓羊。
優(yōu)點:內(nèi)存占用率低胯究,壓縮率一般在3倍~20倍之間、模糊查詢支持好躁绸、查詢快
缺點:結(jié)構(gòu)復雜裕循、輸入要求有序、更新不易
Lucene里有個FST的實現(xiàn)净刮,從對外接口上看剥哑,它跟Map結(jié)構(gòu)很相似,有查找淹父,有迭代株婴。
倒排索引
往索引庫里插入四個單詞abd、abe暑认、acf困介、acg,看看它的索引文件內(nèi)容如下:
構(gòu)建過程如下:
Lucene的FST實現(xiàn)的主要優(yōu)化策略有:
1. 最小后綴數(shù)。Lucene對寫入tip的前綴有個最小后綴數(shù)要求蘸际,默認25座哩,這時為了進一步減少內(nèi)存使用。如果按照25的后綴數(shù)粮彤,那么就不存在ab根穷、ac前綴姜骡,將只有一個跟節(jié)點,abd屿良、abe圈澈、acf、acg將都作為后綴存在tim文件中尘惧。我們的10g的一個索引庫康栈,索引內(nèi)存消耗只占20M左右。
2.前綴計算基于byte褥伴,而不是char谅将,這樣可以減少后綴數(shù)漾狼,防止后綴數(shù)太多重慢,影響性能。如對宇(e9 b8 a2)逊躁、守(e9 b8 a3)似踱、安(e9 b8 a4)這三個漢字,F(xiàn)ST構(gòu)建出來稽煤,不是只有根節(jié)點核芽,三個漢字為后綴,而是從unicode碼出發(fā)酵熙,以e9轧简、b8為前綴,a2匾二、a3哮独、a4為后綴。
倒排表的docId壓縮
跳躍表加速合并察藐,因為布爾查詢時皮璧,and 和or 操作都需要合并倒排表,這時就需要快速定位相同文檔號分飞,所以利用跳躍表來進行相同文檔號查找悴务。
正排:
正向信息在Lucene中只有docId-document的映射,由CompressingStoredFieldsWriter類來完成譬猫。
Lucene的正向信息存儲比較簡單讯檐,按Field依次把內(nèi)容寫入到bufferedDocs中,然后把偏移量寫入到endOffsets中就OK了染服。
當滿足flush條件或者執(zhí)行了IndexWriter.commit()方法别洪,則會進行一次flush操作,把內(nèi)存中緩存的document及倒排信息flush到硬盤中肌索。
正向索引
fnm中為元信息存放了各列類型蕉拢、列名特碳、存儲方式等信息。
fdt為文檔值晕换,里面一個chunk就是一個塊午乓,Lucene索引文檔時,先緩存文檔闸准,緩存大于16KB時益愈,就會把文檔壓縮存儲。一個chunk包含了該chunk起始文檔夷家、多少個文檔蒸其、壓縮后的文檔內(nèi)容。
fdx為文檔號索引库快,倒排表存放的時文檔號摸袁,通過fdx才能快速定位到文檔位置即chunk位置,它的索引結(jié)構(gòu)比較簡單义屏,就是跳躍表結(jié)構(gòu)靠汁,首先它會把1024個chunk歸為一個block,每個block記載了起始文檔值,block就相當于一級跳表闽铐。
所以查找文檔蝶怔,就分為三步:
??? 第一步二分查找block,定位屬于哪個block兄墅。
??? 第二步就是根據(jù)從block里根據(jù)每個chunk的起始文檔號踢星,找到屬于哪個chunk和chunk位置。
?? 第三步就是去加載fdt的chunk隙咸,找到文檔沐悦。這里還有一個細節(jié)就是存放chunk起始文檔值和chunk位置不是簡單的數(shù)組,而是采用了平均值壓縮法扎瓶。所以第N個chunk的起始文檔值由DocBase + AvgChunkDocs * n + DocBaseDeltas[n]恢復而來所踊,而第N個chunk再fdt中的位置由StartPointerBase + AvgChunkSize * n + StartPointerDeltas[n]恢復而來。
???? 從上面分析可以看出概荷,lucene對原始文件的存放是行式存儲秕岛,并且為了提高空間利用率,是多文檔一起壓縮误证,因此取文檔時需要讀入和解壓額外文檔继薛,因此取文檔過程非常依賴隨機IO,以及l(fā)ucene雖然提供了取特定列愈捅,但從存儲結(jié)構(gòu)可以看出遏考,并不會減少取文檔時間。
列式存儲
詞向量 信息是從索引(index)到文檔(document)到域(field)到詞(term)的正向信息蓝谨,有了詞向量信息灌具,就可以得到一篇文檔包含哪些詞的信息青团。
Lucene目前有五種類型的DocValues:NUMERIC、BINARY咖楣、SORTED督笆、SORTED_SET、SORTED_NUMERIC诱贿,針對每種類型Lucene都有特定的壓縮方法娃肿。
如對NUMERIC類型即數(shù)字類型,數(shù)字類型壓縮方法很多珠十,如:增量料扰、表壓縮、最大公約數(shù)焙蹭,根據(jù)數(shù)據(jù)特征選取不同壓縮方法晒杈。
SORTED類型即字符串類型,壓縮方法就是表壓縮:預先對字符串字典排序分配數(shù)字ID壳嚎,存儲時只需存儲字符串映射表桐智,和數(shù)字數(shù)組即可,而這數(shù)字數(shù)組又可以采用NUMERIC壓縮方法再壓縮烟馅。
對DocValues的應用,ElasticSearch功能實現(xiàn)地更系統(tǒng)然磷、更完整郑趁,即ElasticSearch的Aggregations——聚合功能,它的聚合功能分為三類: Metric -> 統(tǒng)計 姿搜、 Bucket ->分組 寡润、 Pipline -> 基于聚合再聚合 。