基於Lucene的搜索引擎核心技術實踐

搜索服務,已經成爲了互聯網最經常使用的基本服務: 從谷歌、百度搜索關鍵字,到電商平臺搜索商品,再到微信查看附近的人。咱們幾乎每時每刻都在用到它。因此,搜索引擎技術一直爲你們關注。做者本人曾負責一些大型的分佈式搜索系統,本文從我的項目出發,講講基於 Lucene 的核心搜索引擎技術實踐。但願讓你們對搜索系統有進一步瞭解和啓發。java

以前,我曾分享過 Qunar 的機票搜索系統,一種基於航運業務的垂直搜索應用。今天聊到的 Lucene,是一種最經常使用的文本搜索引擎技術。node

Lucene 介紹正則表達式

Lucene是一個高性能、可伸縮的文本搜索引擎庫,誕生於 2000 年。它能夠爲應用程序添加索引和搜索能力,是一個 Java 語言編寫的開源項目,也是著名的 Apache Jakarta 你們庭的一員。目前國內的阿里、美團,國外的 Netflix、MySpace、LinkedIn、Twitter、IBM 都有基於Lucene 的搜索服務。Lucene 是很是經典的搜索引擎,基於 Lucene 上誕生了很多企業搜索平臺,好比 Elastic Search、Solr、Index Tank。算法

Lucene 的特色:數據庫

Lucene能夠支持多種數據來源創建索引庫:支持 PDF、Word、txt 等經常使用文檔,也能夠支持數據庫,搜索索引的大體流程如圖:apache

wKiom1kzvZPizRGCAAD5j8LPtaw274.png

Lucene做爲搜索引擎,具有如下優點。緩存

1)高性能服務器

·        一小時能夠索引 150GB 的數據微信

·        千萬級增量索引能達到毫秒級數據結構

2)搜索高擴展性

·        可定製的排序模型

·        支持多種查詢類型

·        經過特定的字段搜索、排序

·        經過特定的字段排序

·        近實時的索引和搜索

·        Faceting,Grouping,Highlighting,Suggestions 等

3)對 LBS 服務更友好的支持

目前Lucene 最新版本已經到 6.X,它最重要的變化引用了一種新的重要數據結構,這種數據採用 K-D trees 存儲方式,叫作 block K-D trees , 其針對於數值型和地理位置的新的數據結構。Lucene 低版本對 LBS、多維數值查詢性能並非很好。 6.X 在一些官方測評查詢性能上升最少 30%,磁盤空間縮小 50%

KD-Tree本質上是一種二叉樹,該算法將散佈在空間中的點經過超平面切分在不一樣的空間中,在搜索的過程當中,若是某個空間中最近的點離目標點距離超過目標距離的話,整個空間將會被拋棄。對於全部點與目標點的距離都小於目標距離的空間,算法將進行一次子空間遍歷。

Lucene 的存儲結構

wKioL1kzvZugAxRsAAAIJgngWbQ419.png

如上圖,Lucene 基本存儲單元從上往下,分別有:

1. Index(索引):通常對應文件目錄,包含了多個 Segment。能夠理解爲數據庫中表。

2. Document(文檔):文檔是咱們建索引的基本單位,能夠理解爲數據庫表一條行數據。

3. Segment(段):不一樣的文檔是保存在不一樣的段中的,一個段能夠包含多篇文檔。新添加的文檔是單獨保存在一個新生成的段中,隨着段的合併,不一樣的文檔合併到同一個段中。

從存儲結構上看,在使用 Lucene 提供搜索服務時,業務場景須要考慮一些性能因素:

1. Lucene 有讀寫鎖,能支持到相似 DB 的行鎖粒度。

2. Lucene 的數據更新會寫入索引文件,這會涉及磁盤的讀寫 IO。不過,Lucene 採用異步更新機制,同時優化了併發讀寫的問題。後面文中會提到。

3. 索引更新:索引有全量更新、增量更新兩種,增量更新就是局部更新,若是數據量在百萬量級以上,數據變化很少的場景下,儘可能用增量更新。另外,索引的 Update 實際是先 Delete 指定記錄,而後再把指定記錄對應的新值 Add 到索引。

基於 Lucene 實現的企業搜索平臺

鑑於Lucene 強大的特性和穩定性,有不少種基於 Lucene 封裝的企業級搜索平臺。其中最流行有兩個:Apache Solr 和 Elastic search。

·        Apache Solr:它自己是 Apache Lucene 項目下的開源企業搜索平臺,算是 Lucene 的直系。美團、阿里搜索服務是基於 Solr 來搭建的。

·        Elastic Search:簡稱 ES,由 Elastic 公司開發。Elastic成立於 2012 年,總部在阿姆斯特丹,不久前 Google 宣佈與 Elastic 達成戰略合做協議,爲谷歌雲提供新的搜索以及相關分析服務。 最近幾年,ES變得愈來愈普及,StackOverflow、Github、百度等都在使用。

企業搜索都有些什麼不一樣,解決了什麼需求呢?綜合 Solr 和 ES,我以爲主要有兩點:

1. 高可用的分佈式集羣管理

Solr有 SolrCloud 來管理集羣,它是基於ZooKeeper 來控制節點的負載均衡:

wKioL1kzvaXAIuiGAABqUBeE9gs385.jpg

Solr控制節點的管理後臺:

wKiom1kzvbDzGWbLAAAvaJYc6p4158.jpg


ES 集羣管理是透明化,它基於 Cluster+Node+Shards(分片實現主從複製) 機制,本身實現節點管理。它的主從配置 Demo:

Master的配置 (elasticsearch.yml):

cluster.name: esappnode.name: esnode0node.master:truenode.data: truenetwork.host: 0.0.0.0

Slave的配置:

cluster.name: esappnode.name: esnode2node.master:falsenode.data: truenetwork.host: 0.0.0.0discovery.zen.ping.unicast.hosts:["esnode0"]

其中,network.host:0.0.0.0 表明了沒有綁定具體的 ip,這樣其餘機器能夠經過 9200 這個默認端口經過 http 方式訪問查看服務。而 slave 中的 discovery.zen.ping.unicast.hosts 指定了 master 的地址。

2. 健全的管理平臺和搜索 API

Solr、ES 都提供了基於 HTTP 的搜索管理平臺,Solr 自帶管理後臺, ES 有獨立數據視圖產品,以下圖:

wKiom1kzvbrS9zAXAAB1QqtfNjg905.jpg

此外,Solr和 ES 都提供方便的 REST API,以供各類客戶端調用搜索服務,好比 Solr API:

wKioL1kzvcTj3Mw4AABuLYcUWp8269.jpg

搜索服務的高併發實例

由於ES 在 12 年後纔出現,早年 Solr 在企業級搜索市場算是一枝獨秀。我在阿里的時候,早期 Taobao SKU 搜索服務仍是基於 Solr 實現,那時的 Solr 對百萬量級 SKU 作全量更新就已是毫秒級別。

在美團,我也採用 Solr 集羣搭建團購 SKU 搜索系統。大致的架構實現:

wKioL1kzvc3jJlv8AABNwJ8Ey4M240.jpg

美團團購搜索主要有:商品列表按價格、購買量、人氣等各類排序;移動端有大量 LBS 服務,它比較耗性能;一些熱詞的關鍵字搜索。最先,美團採用 MongoDB 提供的搜索,當時考慮:

·        MongoDB 存儲是 JSON 數據,查詢也方便。

·        它是基於平衡二叉樹的內存索引,查詢比較快,當時一臺實例能撐到3000 的 QPS(裏面有大概 30% 是 LBS 查詢)。不過 MongoDB 如今已經採用了新的搜索引擎叫 WiredTiger,一種文檔級鎖的存儲引擎取代過去內存存儲引擎 MMAP。

·        友好支持基於 GEO Hash 算法的 LBS 搜索。這點知足咱們的移動端的 LBS 服務,它還能夠一個 SKU 有多個座標(分店)的查詢。而 Solr 當時只能支持一個 SKU 一個座標關聯,遇到多個分店要拆解成多個 Docs 記錄放在索引庫中。

後來咱們發現 MongoDB 就愈來愈不適合咱們的業務場景,也踩過來不少坑。

當時咱們 SKU 有幾十萬量級,每月 30% 增量。由於銷量、價格是時常變更的。當時策略隔五分鐘來一次全量更新,中間增量更新是實時的。

雖然商戶數量基數不大,對於 MongoDB 這樣的 NoSQL 來講數據量並不高。可是短短几個月,APP 用戶量卻從 0 開始陡增到千萬量級。天天數百萬的日活下,可想而知,高併發下的讀寫壓力就上來了。以前說過 MongoDB 基於內存引擎,它的存儲結構最大的問題是它的鎖是庫鎖級別!對,是庫鎖。能夠想象咱們深刻了解之後心裏的尷尬。

由於鎖的巨大瓶頸,MongoDB 竭盡全力想解決鎖粒度的問題。後來幾個大的版本迭代很快,但針對這個問題,也只優化到表鎖粒度。對同一個表併發讀寫仍是很容易被鎖住。搜索服務沒秒承載上千的 QPS 查詢時,咱們全量索引一來,MongoDB 的服務就幾乎變得不可用。

因而咱們嘗試作基於 MongoDB 的讀寫分離,結果發現它在作分佈式集羣時,寫庫同步數據到讀庫的時候,讀庫的請求也在隊列堵塞!從 MongoDB 的控制檯 Mongostat 你會看到一個實時的統計:qr|qw。qr 表示當前排在隊列中的讀請求數,qw 表示寫。當寫請求來時,qr 就會持續飆高,直到 MongoDB 服務掛掉。當全量索引一來,哪怕沒有其餘寫,qw 爲 1,讀隊列也是堵塞的。

考慮鎖瓶頸的問題,MongoDB 嘗試過優化版,關注早期 2.X~3.0 都在致力解決這塊問題。可能後來基於內存這種方式很難根本解決鎖問題,也很差作分佈式方案,最後纔有後來用 WiredTiger 的文件引擎做爲缺省引擎,算是完全放棄了內存引擎。

基於週期成本過高,我決定採用 Solr 取代了 Mongo,經過SolrCloud 技術,搭建了 Solr 分佈式搜索集羣。

SolrCloud大體原理:基於 ZooKeeper 管理節點、索引分片、節點作主從。

wKiom1kzvfqTt2-EAABE7h8IEgo829.jpg

Solr單臺實例只讀的 QPS 不如 MongoDB,大概在 1500QPS。在 Solr4 版本LBS 搜索在 700~800 QPS。不過關鍵是,在併發讀寫時候,Solr 不存在併發讀寫鎖的問題。不會出現卡頓。並且它的主從同步是毫秒級別。這些優勢是基於它的 NRT(NearRealTime) 技術來實現的:

wKioL1kzvgPAYzwTAABFfrRkkUE246.jpg

NRT:Near Real Time , Lucene 爲了支持實時搜索,在 2.9 版本就已經設計出來。想更多瞭解能夠看看 http://wiki.apache.org/lucene-java/NearRealtimeSearch它的原理記錄在 LUCENE-1313 和 LUCENE-1516。介紹下代碼實現的過程:

·        在 Index Writer 內部維護了一個 Ram Directory,在內存夠用前,flush 和 merge 操做只是把數據更新到 Ram Directory,這個時候讀寫最新的索引都在內存中。只有 Index Writer 在 optimize 和 commit 操做會把 Ram Directory 上的數據徹底同步到文件

·        當內存索引達到一個閥值時,程序主動執行 commit 操做時,內存索引中的數據異步寫入硬盤。當數據已經所有寫入硬盤以後,程序會對硬盤索引重讀,造成新的 IndexReader,在新的硬盤 IndexReader 替換舊的硬盤 IndexReader 時,造成新的 IndexReader。後面再來的讀請求交給新的 IndexReader 處理。

·        補充一下,在 1 過程當中,變更的數據不是簡單更新到 Old IndexReader 裏面,它是暫存到一個新的 Reader.clone,在新的 IndexReader 生成前,讀請求獲得數據是 OldIndexReader+Reader.clone 它們 merge 的結果。

Lucene的 index 組織方式爲一個 index 目錄下的多個 segment。新的 doc 會加入新的 segment 裏,這些新的小 segment 每隔一段時間就合併起來。由於合併,總的 segment 數量保持的較小,整體 search 速度仍然很快。爲了防止讀寫衝突,lucene 只建立新的 segment,並在任何 active 的 reader 不在使用後刪除掉老的 segment。

另外,解釋下上面的幾個專業詞語。

·        flush:把數據寫入到操做系統的緩衝區,只要緩衝區不滿,就不會有硬盤操做。

·        commit:把全部內存緩衝區的數據寫入到硬盤,是徹底的硬盤操做。

·        optimize:是對多個 segment 進行合併,這個過程涉及到老 segment 的從新讀入和新 segment 的合併,這個過程是不按期。

同理Elastic Search 也支持 NRT,實例也作到了讀寫分離。

從MongoDB 遷移到 Solr 實踐過程,在架構方面給我深入的啓發:

1. 架構的設計和選型花時間調研是必要的,不要太盲目的應用新技術,尤爲是一些方案不完備的開源框架。看似跑個 Demo 很好,實際的坑還得填。

2. 新機會要掌握核心原理,掌握它合理的應用場景,MongoDB 也許只適合併發只讀的搜索服務,好比不少公司用來搜索日誌。

企業搜索高可用的優化

緩存調優

爲了提升查詢速度,Solr 和 ES 支持使用 Cache,仍是以 Solr 爲例:

Solr支持 queryResultCache,documentCache,filtercache 主要緩存結果集。其中 filtercache、queryResultCache 運用得好對性能會有明顯提高。

filtercache:它存儲了 filter queries(「fq」參數) 獲得的 document id 集合結果,你能夠理解查詢語句中的過濾條件。好比:下面業務場景一組搜索條件:

q=status:0 AND biz_type:1 AND class_id:1 ANDgroup_id:3q=status:0 AND biz_type:1 AND class_id:1 AND group_id:4q=status:0 ANDbiz_type:1 AND class_id:1 AND group_id:5

能夠看到 status、biz_type、class_id是固定查詢條件,惟一動態變化的是 group_id。

所以,咱們把整個查詢條件可分紅兩部分:一部分是以 status,biz_type,class_id 這幾個條件組成的子查詢條件,另一部分是除它們外的子查詢。在進程查詢的時候,先將 status,biz_type,class_id 條件組成的條件做爲 key,對應的結果做爲 value 進行緩存,而後和另一部分查詢的結果進行求交運算。

這樣,減小了查詢過程的 IO 操做。

queryResultCache:比較好理解,就是整個查詢結果緩存。這個在一些業務場景:好比排行榜、美團 APP 缺省列表首頁,推薦列表頁,這些高頻固定查詢,能夠直接有queryResultCache 返回結果。

這樣,減小了查詢次數和提升了響應時間。

通常搜索的 Cache 常基於 LRU 算法來調度。

分片 (Shard)

分片(Shard) 能夠減低大數據量的索引庫操做粒度,和數據庫分庫分表思想一致。

Solr的 DataBase 叫作 Core,ES 叫作 index,它們和Shard 是一對多的關係。根據數據量和訪問 QPS,合理設置分片數量,以指望到達搜索節點最大併發數。

Elastic Search VS Solr 對比

數據源

Solr支持添加多種格式的索引,好比:HTML、PDF、微軟 Office 系列軟件格式以及 JSON、XML、CSV 等文件格式,還支持 DB數據源。而 Elastic Search 僅支持 JSON 數據源。

高併發的實時搜索

基於Solr 和 ES 都有成熟高可用架構設計。高併發的實時搜索二者都沒有太大問題。可是 Elastic Search 讀寫併發性能更優於 Solr。

須要注意的是,搜索引擎不推薦像 DB 同樣作相似 like 的通配符查詢,這樣會致使性大大下降。以前線上有一個 ES 搜索集羣,一段時間 8 核CPU 的 load 飈到了 10 以上,後來排查,原來是用到了 wildcard query(通配符查詢),出現了大量的慢查詢,致使服務變得不可用。下面我具體介紹下。

當時的查詢條件:

}},{"range":{"saleTime":{"from":"20170514000000000","to":"20170515000000000","include_lower":true,"include_upper":false}}},{"match":{"terminalNumber":{"query":"99996DEE5CB2","type":"boolean"}}}]}}}

監控天天 1min load、5 min load、15min load 統計狀況:

wKiom1kzvg2yXG7oAABGmQE1Qio555.jpg

很是明顯看出來,當咱們去掉通配符(改用普通全匹配查詢)後,load 立馬降下來。可見通配符查詢都 CPU 性能影響很大。並且,若是首尾通配符中間輸入的字符串越長。對應的 wildcard Query 執行更慢。性能越差。

這是什麼緣由呢?

在Lucene 4.0 開始,爲了加速通配符和正則表達式的匹配速度,將輸入的字符串模式構建成一個 DFA(Deterministic Finite Automaton),帶有通配符的 pattern 構造出來的 DFA 可能會很複雜,開銷很大。具體原理能夠了解下 DFA。

wildcardquery 應杜絕使用通配符打頭,改變實現方式:使用更廉價的 term query 來實現同等的模糊搜索功能。或者獲取一個大的結果集,在內存裏面匹配。

易用性:

Solr分佈式基於 ZooKeeper,而 ES 自帶分佈式管理。二者在分佈式管理和部署都比較成熟。

擴展性

Elastic公司除了開發 ES 之外, 還基於此,開發了 Kinbana(針對Elasticsearch 的開源分析及可視化平臺,用來搜索、查看Elasticsearch 索引中的數據)、Logstash(開源的具備實時輸入數據能力的數據收集引擎, 主要方便分佈式系統收集彙總日誌) 等一整套服務產品。

目前,Kinbana、Logstash 在不少公司被使用。基於 Elastic + LogStash +Kinbana 的 ELK 框架成爲了一種流行的分佈式日誌收集監控技術方案。

wKiom1kzvhfD0z3-AABGmQE1Qio768.jpg

Solr自帶了管理索引的 Web 控制檯,只專一在企業級搜索引擎。

搜索引擎拓展應用

推薦系統使用搜索

推薦系統每每利用搜索進行復雜的離線查詢和數據過濾。早期,美團團購 App 作了一個每日推薦功能,主要基於用戶購買記錄,個性化天天推送相關團購。當時這樣作的:

首先,數據組在天天的前一天算好用戶推薦規則,固定早上一段時間,批量執行推薦規則和用戶匹配操做,大致過程:

wKioL1kzviGxAfjdAAB-f-9BxTw752.jpg

整個操做上午串行開始推送。咱們是併發請求多臺搜索服務器,獲得推薦數據,並行開始多個用戶的消息推送。大概在 9:00~12:00 APP 用戶會收到一條團購推送(如上面截圖)。當時,推薦功能經過搜索進行個性化推薦,由於匹配的好,下單重複轉化率是不錯的。

數據分析、BI 調用搜索服務

咱們提到數據分析、BI,老是聯想到大數據,但並非每家公司的數據都有海量規模。

實際狀況,每每必定數據規模下,爲了更低、更高效知足數據分析業務場景,每每用搜索系統承擔一部分數據集合存儲、處理的功能(這樣的比例不低)。這樣的好處是:

1. 搜索系統查詢太方便,對一些實時性,數據關聯不大業務徹底適用 。

2. 搜索系統也是一種穩定的數據源,它的數據持久化也是很穩定的。

好比以前咱們數據部門就大量使用 ES 作一些負責的查詢,幫忙他們作數據分析。

思考總結

搜索服務應用的領域太普遍,隨着人工智能技術發展,個性化搜索服務愈來愈人性化。從近幾年火熱的內容、短視頻個性推送,語音搜索。搜索技術還會有一個新的革新。

做者介紹

蔣志偉,前美團、Qunar 架構師,前後就任於阿里、Qunar、美團,精通在線旅遊、O2O 等業務,擅長大型用戶的 SOA 架構設計,在垂直搜索系統領域有豐富的經驗,尤爲在高併發線上系統方面有深刻的理論和實踐,曾在 pmcaff 擔任 CTO。

相關文章
相關標籤/搜索