Categories
程式開發

如何做到99%的搜索請求延遲低於150毫秒? LinkedIn全新消息搜索平台實踐


即時通訊的興起改變了我們的交流方式。與來回的電子郵件相比,我們發送和接收消息的數量和速度都要高得多。在進行即時對話時,我們也希望能夠輕鬆地搜索重要的短語、瞬間或有參考價值的東西。數據交換請求數量的快速增長為消息傳遞的可伸縮和快速發現帶來了許多新的工程挑戰。在這篇博文中,我們將討論如何改進消息搜索體驗,基本方法是從頭開始改進消息傳遞後端架構,並引入我們稱為InSearch的消息搜索後端。

本文最初發佈於LinkedIn工程博客,經原作者授權由InfoQ中文站翻譯並分享。

即時通訊的興起改變了我們的交流方式。與來回的電子郵件相比,我們發送和接收消息的數量和速度都要高得多。在進行即時對話時,我們也希望能夠輕鬆地搜索重要的短語、瞬間或有參考價值的東西。數據交換請求數量的快速增長為消息傳遞的可伸縮和快速發現帶來了許多新的工程挑戰。

在這篇博文中,我們將討論如何改進消息搜索體驗,方法是從頭開始改進消息傳遞後端架構,並引入我們稱為InSearch的消息搜索後端。

挑戰

如果我們使用LinkedIn的傳統搜索基礎設施來支持消息搜索,那麼構建並提供近線索引服務的成本將會高得令人望而卻步。這是因為:

  1. 與其他用例相比,要索引的消息數據總量非常大。
  2. 考慮到消息交換的增加(每秒數千次寫操作),對索引的更新速度要高得多。
  3. 這些數據需要進行靜態和傳輸加密,因為消息數據是高度機密的。

此外,我們注意到,搜索查詢與正在創建的消息的比率非常低。這使得降低搜索基礎設施的成本成為一個重要的問題。我們使用這個和其他對於數據和使用模式的觀察來設計了這樣一個系統,它滿足我們所有的需求,同時又具有成本效益。

整體架構

如何做到99%的搜索請求延遲低於150毫秒? LinkedIn全新消息搜索平台實踐 5

InSearch的高層架構

搜索器(Searcher)

消息搜索僅限於會員各自的收件箱。你只能在自己的收件箱中進行搜索,因此,只需要針對你的數據在內存中建立索引,從而快速地處理查詢。從使用情況來看,我們還知道,使用消息搜索的會員通常是高級用戶,這意味著他們經常依賴於我們的搜索功能。比較理想的情況是,按會員索引,並使索引可緩存,這使我們研究了僅在會員執行搜索時生成會員索引,然後緩存索引的想法。

我們的搜索服務內部使用Lucene作為搜索庫。處理高度機密數據(如消息)的一個關鍵要求是確保所有數據都在磁盤上加密。同時,我們還需要能夠支持高速率的更新。將索引存儲在磁盤上需要一個很長的過程(從磁盤讀取加密的索引、解密、更新索引、再次加密並將其持久化),這使得寫入操作非常低效——需要注意的是,這是一個寫密集型系統。

因此,我們犧牲了創建索引的速度來獲得更好的寫吞吐量。這是通過將每個原始文檔加密存儲在一個鍵-值存儲區來實現的,鍵是memberId和documentId的組合,而值是加密的文檔(即消息)。注意,這是一個簡化版本,在生產環境中,它更加複雜,因為我們有不同的文檔類型,而參與者不一定是會員。使用這種設計,新消息就是添加到鍵值存儲中的新行,這使得向系統寫入數據非常快。我們使用RocksDB作為我們的鍵值存儲,因為它在LinkedIn已經被驗證是可靠和有效的(參見FollowFeedSamza的例子)。它也不會傷害到我們,因為我們有大量的內部專家來支持它。

如何做到99%的搜索請求延遲低於150毫秒? LinkedIn全新消息搜索平台實踐 6

具有RocksDB鍵值結構的高級搜索器流

當一名會員執行他或她的第一個消息搜索時,我們從RocksDB運行一個鍵的前綴掃描(前綴是會員ID)。這將為我們提供該會員的所有文檔用於構造索引。在第一次搜索之後,索引被緩存在內存中。結果呢?我們觀察到的緩存命中率約為90%,而總第99百分位延遲約為150毫秒。

對於寫操作,我們將加密的數據插入到RocksDB中。如果索引被緩存,那麼緩存的索引也會通過再次從數據庫讀取更新後的文檔來更新。我們還持久化緩存的會員ID,以便在啟動時將它們的索引重新加載到緩存中。這使緩存即使在部署之後也能保持溫度。

分區、複製和備份

與大多數分佈式系統一樣,我們通過複製和分區來處理可伸縮性和可用性。數據按會員ID和文檔ID的組合進行分區。一名會員的數據可以分佈在多個分區上,這有助於我們針對具有大型收件箱的會員進行水平擴展,因為索引創建負載可以由多個分區分擔。

對於每個搜索器分區,我們有三個活動副本和一個備份副本。每個副本獨立地使用來自Kafka流的索引事件。我們在適當的地方進行了監控,以確保沒有一個副本比它的對等副本延遲高。備份副本定期將數據庫快照上傳到我們的內部HDFS集群。和備份副本一起,我們還會備份Kafka偏移量。這些偏移量用於確保在服務從備份數據集啟動之前,我們能夠完全捕獲Kafka丟失的數據。

攝入

消息數據的真實來源是Espresso表。我們使用Brooklin以流的方式將來自這些表的更新傳遞給一個Samza作業,然後將這些更改日誌轉換為搜索器索引所需要的格式。流處理作業將這個流與其他數據集連接,從而使用要使用的實際數據裝飾ID(例如,用姓名裝飾會員ID)。它還負責對搜索器所需的數據進行分區。現在,每個搜索器主機只需使用它所承載的特定Kafka分區的數據。

代理

代理服務是搜索查詢的入口點,它負責:

  • 查詢重寫:它根據查詢用例將原始查詢(例如:“apple banana”)重寫為InSearch格式(例如:TITLE:(apple AND banana) OR BODY:(apple AND banana))。不同的搜索查詢可能使用不同的評分參數對某些字段進行優先級排序。
  • 分發收集操作:它將請求分發給搜索器主機,整理從搜索器那裡返回的結果。
  • 重試:如果出現可重試的失敗,或者一個特定的搜索器耗時太長,那麼代理將在一個不同的搜索器副本上重試請求。
  • 重新排序:對所有搜索器主機的結果進行重新排序,得到最終的結果集,並根據分頁參數進行精簡。

代理使用我們的內部D2 zookeeper服務(它維護每個分區的搜索器主機列表)來發現每個分區的搜索器主機,以便選擇扇出主機。我們還確保在這些主機上進行嚴格的路由,這樣,給定會員的請求就會轉到相同的搜索器副本,進而就不會在多個副本上重建索引,並且提供了一致的搜索體驗。

小結

到目前為止,所有來自LinkedIn旗艦應用的消息搜索請求都由InSearch提供,我們能夠以低於150毫秒的延遲服務於99%的搜索請求。

目前,我們正在將幾個企業用例遷移到新系統中,並評估其他應用。此外,我們現在開始利用新的消息搜索系統來加速改進LinkedIn的消息傳遞體驗。

原文鏈接:

InSearch: LinkedIn’s new message search platform