58同城作為中國最大的分類信息網(wǎng)站芦疏,提供了房產(chǎn)、招聘微姊、黃頁和二手交易等多方面的生活服務(wù)信息酸茴,信息數(shù)據(jù)量和訪問量逐年增長,列表頁排序需求也時常變化兢交。在這樣的背景下薪捍,58搜索技術(shù)部使用C++語言自主研發(fā)了ESearch 搜索內(nèi)核,取代之前使用的 Solr,大幅提高了性能和可定制性酪穿。
經(jīng)過多年的實(shí)踐和優(yōu)化与倡,ESearch 已發(fā)展成為一個功能完善、性能優(yōu)異昆稿、運(yùn)行穩(wěn)定的搜索內(nèi)核,如今作為一個核心組件息拜,承擔(dān)了58集團(tuán)(包括58同城溉潭、趕集、安居客等)所有搜索業(yè)務(wù)少欺,承載每天幾十億的查詢流量喳瓣。ESearch的主要特性包括:
秒級實(shí)時索引:從文檔發(fā)布到能檢索出該文檔,可以做到平均一秒的時間延遲赞别。
大數(shù)據(jù)量畏陕、高并發(fā)、低延遲:基于本地緩存仿滔、自定義內(nèi)存池惠毁、自動Query改寫等多種性能優(yōu)化措施,可達(dá)到單機(jī)千萬級文檔崎页、8000 QPS鞠绰、毫秒級平均延遲。
功能豐富:支持復(fù)合條件查詢飒焦、空間查詢蜈膨、Facet查詢、分組查詢牺荠、結(jié)果去重等功能翁巍。
業(yè)務(wù)可定制:支持SKU查詢、一一抽取休雌、在線用戶實(shí)時過濾等個性化功能灶壶。
可擴(kuò)展、易復(fù)用的排序框架:支持簡單的線性加權(quán)杈曲、自定義的打分表達(dá)式例朱、基于機(jī)器學(xué)習(xí)的邏輯回歸、多決策樹等模型鱼蝉,以及多模型融合等多種排序方式洒嗤。
本文將介紹 ESearch 的整體架構(gòu),并重點(diǎn)說明其中實(shí)時索引的設(shè)計(jì)和實(shí)現(xiàn)細(xì)節(jié)魁亦。
一渔隶、整體架構(gòu)
上圖是 58 搜索系統(tǒng)的整體架構(gòu),分為應(yīng)用層和內(nèi)核層兩大部分,應(yīng)用層包括 Proxy 和 Builder 兩個模塊间唉,負(fù)責(zé)與業(yè)務(wù)相關(guān)的邏輯绞灼,內(nèi)核層就是本文重點(diǎn)介紹的 ESearch焊唬,包括 Merger 和 Searcher 兩個模塊祷肯,負(fù)責(zé)通用的檢索功能敏储。各個模塊的具體功能如下:
Proxy:查詢代理液荸,接收前端查詢請求老速,進(jìn)行意圖識別和查詢改寫等工作适室,然后發(fā)送請求到 Merger屁魏,獲取檢索結(jié)果怎茫。
Merger:基于一致性哈希算法將請求分發(fā)到多個 Searcher昨悼,然后合并結(jié)果蝗锥,并進(jìn)行一些排序調(diào)整。
Searcher:包含索引數(shù)據(jù)和排序模型率触,負(fù)責(zé)實(shí)時建索引并執(zhí)行召回终议、打分、排序等檢索流程葱蝗。
Builder:接收外部請求進(jìn)行進(jìn)行文檔構(gòu)建穴张,并將文檔發(fā)給 Searcher。
二两曼、實(shí)時索引
實(shí)時索引的設(shè)計(jì)主要需要考慮兩個需求:
文檔更新能在秒級時間內(nèi)生效:用戶信息發(fā)布或者修改更新之后陆馁,需要能盡快被檢索到。
文檔更新時不影響查詢效率:在大數(shù)據(jù)量大更新量的情況下合愈,不能影響查詢延時叮贩。
從查詢效率出發(fā),業(yè)界通常采用讀寫分離的設(shè)計(jì)佛析,即索引不提供更新功能益老,避免更新操作阻塞查詢線程。接收到新文檔之后寸莫,先與原索引數(shù)據(jù)合并為新索引捺萌,然后替換掉舊索引,從而使新文檔生效膘茎。但在文檔更新量比較大桃纯,并且索引量隨時間逐漸增大的情況下,合并時間會變得很長披坏,導(dǎo)致新文檔長時間無法生效态坦。此外還有一些搜索引擎采用了無鎖數(shù)據(jù)結(jié)構(gòu),支持索引的高并發(fā)讀寫棒拂,但實(shí)現(xiàn)相對復(fù)雜伞梯。
ESearch 也采用了讀寫分離設(shè)計(jì)玫氢,為了克服合并時間過長的問題,對合并機(jī)制做了改進(jìn)設(shè)計(jì):對每個檢索節(jié)點(diǎn)的索引數(shù)據(jù)按生命周期分為多段谜诫,每段是一份完整的小索引漾峡,包含獨(dú)立的倒排和正排索引等數(shù)據(jù)。新增的文檔數(shù)據(jù)只和最小的索引段合并喻旷,確保了索引的更新速度生逸。
在實(shí)際的應(yīng)用層面,根據(jù)自身數(shù)據(jù)量和業(yè)務(wù)特點(diǎn)且预,我們采取了以下方案:每個節(jié)點(diǎn)上索引按生命周期分為 3秒槽袄、15分鐘、6小時辣之、1天、1個月皱炉、1個月以前等6級怀估。
實(shí)時索引每3秒接收一批新文檔數(shù)據(jù),然后建成一個生命周期為3秒的小段合搅,此時這批文檔立即生效多搀,能被查詢線程召回。3秒后灾部,該段到達(dá)生命周期終點(diǎn)康铭,由一個專門負(fù)責(zé)索引段合并的線程將其與15分鐘生命周期的段合并。由于15分鐘索引段最多只包含了15分鐘內(nèi)的新增文檔赌髓,數(shù)據(jù)量較少从藤,所以合并速度非常快锁蠕,可以達(dá)到毫秒級夷野。同理,15分鐘荣倾、6小時等其他周期的段悯搔,到達(dá)各自的生命周期終點(diǎn)后,都會向上一級索引段合并舌仍。
生命周期在1天以上的段妒貌,數(shù)據(jù)量較多,合并時間較長铸豁,但其合并頻率較低灌曙,每天最多一次,可以指定其在每天凌晨進(jìn)行节芥,這是一天中訪問量和數(shù)據(jù)更新最少的時間段平匈,因此對系統(tǒng)性能影響不大。若1個月周期的數(shù)據(jù)量太大,可以在線下定期將其與1個月以前的索引合并增炭,合并好后推送到檢索節(jié)點(diǎn)忍燥,避免影響系統(tǒng)性能。
在這種分段設(shè)計(jì)下隙姿,查詢處理器可以為每個查詢分配多個線程梅垄,每個線程檢索一個段,最終將結(jié)果合并输玷。因此分段設(shè)計(jì)也便于利用并發(fā)能力降低查詢延時队丝。
如果文檔的更新和查詢的召回條件或排序均遵循時間序(例如日志索引),那么可以只檢索與查詢中的時間段相關(guān)的索引段欲鹏,提升檢索效率机久,因此這種設(shè)計(jì)很適合以時間序?yàn)橹饕判蛐枨蟮乃阉飨到y(tǒng)。
三赔嚎、索引結(jié)構(gòu)
上一節(jié)介紹了實(shí)時索引整體的設(shè)計(jì)膘盖,本節(jié)介紹索引段內(nèi)的具體結(jié)構(gòu)。
在 ESearch 中尤误,每個索引段包含四種索引結(jié)構(gòu):主鍵索引侠畔、刪除表、倒排索引和正排索引损晤。
3.1软棺、主鍵索引
為了便于高效計(jì)算,索引內(nèi)部為整個文檔集分配了從0開始遞增的doc id尤勋,倒排和正排索引均基于doc id來組織喘落,而不使用文檔的原始主鍵。為了能從文檔的原始主鍵查找到對應(yīng)的索引數(shù)據(jù)最冰,必須先將主鍵映射為doc id揖盘,主鍵索引就是為此而存在的,它本質(zhì)上是一個從主鍵映射到doc id的哈希表锌奴。
3.2兽狭、刪除表
倒排索引是為查詢優(yōu)化的,不利于原地更新和刪除鹿蜀,因此當(dāng)文檔被刪除時箕慧,我們需要一個刪除表來標(biāo)記文檔的刪除狀態(tài),它本質(zhì)上是一個bitmap茴恰。
3.3颠焦、倒排索引
倒排索引用于根據(jù)term快速查找包含該term的所有文檔。由于采用了讀寫分離設(shè)計(jì)往枣,ESearch的倒排索引數(shù)據(jù)結(jié)構(gòu)不需要支持更新伐庭,每個term對應(yīng)的doc id集合以有序數(shù)組的形式存儲即可粉渠。對于文本域,倒排數(shù)據(jù)還包含詞頻圾另、詞權(quán)霸株、位置等信息。
58的關(guān)鍵詞查詢通常需要在多個文本域中查詢集乔,比如“title:租房 OR content:租房”去件,需要召回title或者content這兩個域中包含租房的文檔。為了優(yōu)化這類常見查詢的性能扰路,同時也讓文本相關(guān)性計(jì)算更方便尤溜,ESearch支持倒排域打包功能(或稱聯(lián)合域),即自動把同一個term在多個文本域中的文檔集合并到同一個倒排鏈(Posting List)中汗唱,此功能只需在schema中稍作配置即可啟用宫莱。如此,相當(dāng)于在索引中對每個term預(yù)計(jì)算了多個域的并集哩罪。另外授霸,由于不同文本域的權(quán)重不同,進(jìn)行文本相關(guān)性打分時需要識別出一個term命中了哪些文本域识椰,因此聯(lián)合域倒排鏈中的每個doc id 還必須附帶一個微型 bitmap 來標(biāo)記其所命中的域绝葡。
下圖為把Title域和Content域合并為Fulltext域的例子:
3.4深碱、正排索引
正排索引是從doc id到文檔屬性值的映射腹鹉。在58的搜索系統(tǒng)中,正排索引除了存儲必要的文檔屬性以外敷硅,也存儲用于打分的文檔特征功咒。在檢索過程中,也有可能讀取某些正排域來進(jìn)行文檔過濾绞蹦。
ESearch 的正排索引采用列存儲設(shè)計(jì)力奋,即不同正排域的數(shù)據(jù)分開存儲。
針對58文檔的特性幽七,ESearch 提供了正排域的多種存儲形式景殷。58是一個分類信息網(wǎng)站,所有類目的信息都存在同一個數(shù)據(jù)庫中澡屡。類目不同猿挚,信息的字段差異也很大,譬如房產(chǎn)信息包含樓層驶鹉、戶型等字段绩蜻,二手交易信息包含品牌、型號等字段室埋。這些差異化的字段按統(tǒng)一格式合并存儲在同一個數(shù)據(jù)庫字段办绝,在建索引的時候伊约,仍會分別建到各自的倒排、正排域中孕蝉。但同時各類目信息也有不少通用字段屡律,例如標(biāo)題、城市ID昔驱、類目ID等疹尾。
這意味著有些域的值分布是稀疏的,例如在所有信息中骤肛,包含“手機(jī)品牌”這個字段的信息的占比可能遠(yuǎn)低于50%纳本,而有些域的值分布是緊密的,例如幾乎100%的信息都包含“類目ID”這個字段腋颠。
針對緊密型的正排域繁成,我們使用數(shù)組作為存儲形式,而針對稀疏型正排域淑玫,使用哈希表作為存儲形式巾腕,這樣能夠避免浪費(fèi)空間,也都保證了O(1)的平均查找時間復(fù)雜度絮蒿。
此外尊搬,對于布爾型的正排域,我們直接采用Bitmap 作為存儲形式土涝。
列存儲的一個缺陷是缺乏空間局部性佛寿,可能影響CPU緩存命中率。例如在給一個文檔打分時但壮,通常會從正排索引讀取多個特征值進(jìn)行計(jì)算冀泻,因此需要進(jìn)行多次查找和多處內(nèi)存訪問。針對此問題蜡饵,可以考慮使用聯(lián)合正排域弹渔,將經(jīng)常需要同時訪問的多個字段合并建到一個正排域中。
四溯祸、緩存與二次排序
為了降低檢索服務(wù)響應(yīng)時間肢专,對檢索結(jié)果進(jìn)行緩存是必需的,但緩存也會導(dǎo)致文檔字段和排序特征的更新無法立刻生效焦辅,影響實(shí)時性博杖。本節(jié)介紹 ESearch在使用了緩存的情況下如何保證召回結(jié)果和排序結(jié)果得到實(shí)時更新。
4.1氨鹏、召回結(jié)果
在實(shí)時索引中欧募,如前文所述,文檔更新時會被建入新的小索引段中仆抵。如果舊的索引段也包含此文檔跟继,那么此文檔在舊段中會被標(biāo)記刪除种冬。
ESearch 為每一個段分配了獨(dú)立的緩存,每個段各自將檢索結(jié)果從緩存中取出后舔糖,會用刪除表過濾掉已刪除的文檔娱两,從而確保只有更新后的文檔能被檢索出來。
4.2金吗、排序結(jié)果
為了便于打分排序十兢,我們會把一些文檔特征值存儲在索引中。這些特征值不會作為召回條件摇庙,因此只建在正排索引中旱物。對這類文檔屬性的更新可以不走前文所述的生成新段-段合并的更新流程,而是直接在正排索引中原地刷新卫袒,因此特征值刷新的性能遠(yuǎn)高于正常文檔更新宵呛,從而允許我們實(shí)時調(diào)整排序結(jié)果。
為了使特征值的改變能夠?qū)崟r影響排序結(jié)果夕凝,我們在從緩存中取出檢索結(jié)果后宝穗,對檢索結(jié)果會做第二次打分排序。由于緩存中的結(jié)果一般只包含Top幾百的文檔码秉,因此第二次排序耗時有限逮矛,不影響整體性能。
除了保證實(shí)時性以外转砖,這種兩階段排序也便于我們在58主站大規(guī)模應(yīng)用復(fù)雜的機(jī)器學(xué)習(xí)排序模型须鼎。
復(fù)雜的排序策略通常比較耗時,因此業(yè)界在處理海量數(shù)據(jù)的排序時堪藐,通常用“粗排-精排”兩階段排序的方式莉兰,在效果和性能之間做一個合理折中挑围,即先對海量數(shù)據(jù)進(jìn)行低代價的粗排礁竞,然后對粗排的TopN結(jié)果進(jìn)行代價較大的精排,最終整個處理過程耗時不會太大杉辙。
58分類信息的特點(diǎn)也很適合這種模式模捂,很多類目(例如租房)的信息注重時效性,時間序是主要排序因素蜘矢。在信息發(fā)布時間相近的前提下狂男,需要根據(jù)信息質(zhì)量、轉(zhuǎn)化率等其他因素進(jìn)行更精細(xì)的排序品腹。二次排序恰好可以用來實(shí)現(xiàn)這樣的流程:第一次排序?qū)θ课臋n按時間序求出 TopN 結(jié)果集保存在緩存中岖食,第二次排序使用復(fù)雜的機(jī)器學(xué)習(xí)模型來對 TopN 結(jié)果打分重排。
五舞吭、總結(jié)
本文介紹了在 58 搜索內(nèi)核 ESearch 中泡垃,如何根據(jù) 58 的數(shù)據(jù)和搜索特點(diǎn)來設(shè)計(jì)實(shí)時索引以及進(jìn)行索引存儲和查詢流程方面的優(yōu)化析珊。隨著業(yè)務(wù)的不斷發(fā)展,我們也將繼續(xù)在檢索性能蔑穴、資源利用率忠寻、排序等方面持續(xù)優(yōu)化,也歡迎感興趣的同學(xué)和我們一起交流存和。