狼廠專案實踐:通用檢索框架準實時流的設計與實現
背景
檢索對實時性的要求很高,不僅是對索引建立、結果召回、策略干擾等核心部分,也包括資料錄入的部分。檢索的資料流主要包括全量資料與增量資料,其中全量資料是在執行前就已經生成好的,在檢索程序執行開始時就直接解析載入了,後面不會再產生,所以不會對錄入有高實時性的需求;而增量資料理論上在整個檢索程序執行過程中隨時都可能新增,新增了就需要錄入。所以,提高增量資料錄入的實時性,對提升整個檢索的效能有重要作用。
設計思路與折衷
目前搜尋引擎實現增量更新的方案主要有這幾種:(1)提供寫介面,(2)使用檔案,(3)使用訊息佇列。
方案1即檢索框架本身提供寫資料的介面,資料釋出方直接呼叫,寫入資料。這種方式應該是單次寫入速度最快的,但會使兩方程式碼過於耦合,不易擴充套件和維護。且檢索框架需要提供資料接收讀取的完整機制,也要消耗很多資源。對於釋出方來說,釋出操作也必須依賴接收方的進度。如果資料更新頻率很低,資料量很小,可以考慮使用這種方式。

方案2主要是通過檔案形式,即每次有新資料到來時,都先寫入一個檔案中,然後定期將檔案配送到檢索程序本地。檢索服務執行時,會監控本地資料夾的inode變化,一旦有檔案產生或修改,就執行回撥函式,讀取該檔案,進行增量載入。這種方式一定程度上實現了資料釋出方和接收方的解耦,但代價是要增加對檔案的操作,並且實際應用中需要依賴資料配送,這是個很耗時的過程。檔案本身的產生,拷貝,讀寫,效率也不算高,尤其在檔案內容堆積得很大的時候。所以這種方案,實時性很難保證。

方案3是通過訊息佇列。資料釋出方將資料推到訊息佇列中,接收方自己讀取。這種方式很好地實現了雙方業務的解耦,且無需維護對檔案的一系列操作,也無需資料配送,實時性大大提升。對於釋出方來說,不用再記錄資料到底要發給哪幾個檢索例項,只需釋出一份資料到訊息佇列中即可,檢索例項的增加、減少也都不需要在釋出方進行修改,更加靈活。對於接收方,不用再定期處理堆積得很多的資料,資源使用也更平衡。同時也可以方便地實現流量控制等。不過穩定性需要依賴於訊息佇列本身。

目前糯米的檢索使用的是方案2,框架成熟,執行穩定,容錯容災也都很完整。但針對糯米本身的業務特點,仍有可以改進的空間。糯米主要是提供生活服務類的檢索,特點就是資料更新頻繁,資料量大。而檔案形式的更新,第一是實時性較差,第二是靈活性不高。而這些都可以通過訊息佇列的特點進行優化。
訊息佇列大都分為佇列模式和訂閱模式,根據業務需求,多個檢索例項都需要相同的一份資料,所以選擇訂閱模式。
針對業務特點,最終選擇方案3,使用訊息佇列的訂閱模式,來實現資料的實時載入。
具體實現
糯米現有檢索框架一般都是在一個單獨的執行緒中監控檔案變化,通過回撥實現增量資料載入。主執行緒只需在其時傳入需要的配置以及對資料進行處理的回撥函式即可,耦合度很低。所以訊息佇列的新增理論上只需對這個執行緒所做的工作進行相應修改即可。
糯米現有檢索框架中,增量資料載入的工作流程大致如下:

首先讀取配置檔案資訊,包括增量檔案的命名規則以及讀到的行數等,這是為了後面開啟檔案及移動讀指標做準備。這些配置放在本地一個單獨的檔案中。之後註冊監控的回撥函式,在資料夾inode發生變化時,會觸發raise喚醒wait中的處理執行緒,從指定行開始逐個位元組讀檔案,每讀完一條資料就進行一次處理,讀完整個檔案後,就wait直到下個檔案產生。
可見,整個流程都是圍繞著檔案展開的。改為使用訊息佇列讀取資料後,這些和檔案相關的操作就都不需要了,改為接入相應訊息佇列的訂閱相關介面。下面描述一種使用訊息佇列(Kafka)的訂閱模式進行資料載入的大致流程:

首先新增一個訊息佇列的訂閱類,定義實現非同步訂閱的基本方法。
init中,主要實現對各個引數的配置,以及訂閱起始點的讀取。這個起始點是由資料釋出方給出的,所以要在發起訂閱前就設定好。目前的做法和上面一樣,單獨在本地建一個配置檔案,裡面存放了起始點的相關資料,init中直接讀取即可。
StartSubscribe主要包含兩個方法:獲取要訂閱的訊息佇列的子通道的數量,然後對每個子通道發起訂閱請求。
之後進入SubscribeMainloop,在while迴圈中接收、處理資料。接收事件通過epoll_wait,只要有可讀或報錯事件觸發,epoll_wait就會返回,否則會阻塞直到超時或有新事件到來。
epoll_wait返回後,如果包含可讀事件,就呼叫回撥函式進行處理即可。如果包含報錯事件,會根據報錯嘗試重新發起訂閱請求。
這樣一條增量資料的載入就完成了,while迴圈會一直重複這個流程,直到載入完訊息佇列裡最新的一條資料。之後就會阻塞在epoll_wait上,直到有新的資料釋出進來。
總結
本文簡單介紹了一種使用訊息佇列的訂閱模式實現通用檢索框架增量資料載入的新方案,及其設計與實現。糯米現有檢索框架檔案形式的更新,由於資料配送系統本身的複雜性,需要至少半小時才能更新一次資料。而使用訊息佇列更新一條資料的用時在0.5秒以內,更新1000條資料也可在2秒以內完成,實現了準實時流,值得全面推廣在檢索框架的增量資料錄入部分使用。
為什麼某些人會一直比你優秀,是因為他本身就很優秀還一直在持續努力變得更優秀,而你是不是還在滿足於現狀內心在竊喜,
歡迎工作一到五年的Java工程師朋友們加入Java架構開發:744677563
群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!