搜索之路:Elasticsearch的誕生
碼農翻身劉欣 合天智匯
經過三年的歷練,張大胖已經成為了一個利用Lucene這個著名的開源軟體做搜索的高手,各種細節知識和最佳實踐盡在掌握。
(張大胖的學搜索的歷程參見上一篇文章:《搜索之路》)
隨著互聯網應用的爆炸式增長,搜索變成了網站的一個常見需求,各個網站都想搜索產品,搜索帖子,搜索服務......張大胖的「業務」變得十分繁忙,經常在業餘時間給人做Lucene的諮詢,賺了不少外快。
但是張大胖也敏銳地覺察到了兩個問題:
1. Lucene做搜索很強大,但是API用起來太「低級」,很多人抱怨:我就想搜索一下我的產品描述,現在還得理解什麼「Directory」,"Analyzer","IndexWriter",實在是太複雜了!
2. 互聯網的數據是海量的,僅僅是單機存儲索引是遠遠不夠的。
俗話說:「軟體開發中遇到的所有問題,都可以通過增加一層抽象而得以解決」。 張大胖覺得,是時候對Lucene做一個抽象了。
Java API -> Web API
保險起見,張大胖拉了大神Bill來做顧問,幫自己設計。
這個新的抽象層應該對外提供一個什麼樣的API呢?
很多時候,Web開發面對的都是領域模型,比如User,Product, Blog,Account之類。用戶想做的無非就是搜索產品的描述, 搜索Blog的標題,內容等等。
張大胖說:「如果能圍繞著領域模型的概念進行搜索的各種操作就好了,這樣簡單而直接,如同CRUD。」
Bill 提示到:「Web時代了,程序員都喜歡採用RESTful的方式來描述一個Web資源, 對於搜索而言,完全可以借鑒一下嘛!」
張大胖眼前一亮: 「要不這樣?」
/coolspace/blog/1001 : 表示一個編號為1001的博客
/coolspace/user/u3876:表示一個ID為u3876的用戶
/coolspace表示一個「索引庫」
/blog ,/user 表示「索引的類型」(可以理解為編程中的領域模型)。 1001, u3876表示數據的ID
格式是/<index>/<type>/<id>
如果和關係資料庫做個類比的話:
索引庫<--->資料庫
索引的類型 <--->資料庫的表
Bill說:「這樣挺好的,用戶看到的就是領域模型, 當用戶想去操作時候,用HTTP的GET, PUT等操作就好了,交互的數據可以使用JSON這個簡單的格式。」
張大胖開始定義基本的操作。
(1) 把文檔加入索引庫
例如:把一個blog的「文檔」加入索引庫,這個文檔的數據是JSON格式,其中的每個欄位將來都可以被搜索:
PUT /coolspace/blog/1001
{
"title" : "xxxxxxx",
"content" : "xxxxxxxxxxxx",
"author" : "xxxxx",
"create_date": "xxxxxx"
...
}
(註:當然,在發起HTTP請求的時候,需要加上伺服器的地址和埠,下同。)
(2)把一個blog文檔刪除,從此就再也搜索不到了:
DELETE /coolspace/blog/1001
(3) 用戶搜索
用戶想搜索的時候也很簡單,發起一個這樣的請求就行:
GET /coolspace/blog/_search
但是如何表達查詢的具體需求呢,這時候必須得定義一個規範了,例如:想查詢一個內容欄位(content)包含「java"的 blog。
GET /coolspace/blog/_search
{
"query" : {
"match" : {
"content" : "java"
}
}
}
這個query下面可以增加更加複雜的條件,表示用戶的查詢需求,反正是JSON格式,可以非常靈活。
返回值也是JSON, 這裡就不再展示了。
這個抽象層是以HTTP+JSON來表示的, 和具體的編程語言無關,不管是Java, 還是Python,Ruby,只要能發起HTTP調用,就可以使用。
通過這樣一個抽象層, Lucene那些複雜的API全部被隱藏到了海平面以下。
對於程序員來說,使用HTTP+JSON是非常自然的事情,好用就是最大的生產力。
分散式
到目前為止,進展還算順利,接下來要考慮的就是如何存儲海量的索引數據。
張大胖說: 「這個簡單,如果索引太大,我們把它切割一下,分成一片一片的,存儲到各個機器上不就得了?」
Bill問道: 「想得美! 你分片以後,用戶去保存索引的時候,還有搜索索引數據的時候,到哪個機器上去取?」
張大胖說:「這個簡單,首先我們保存每個分片和機器之間的對應關係, 嗯,我覺得叫node顯得更專業。」
分片1 :node1
分片2 :node2
分片3 :node3
「分片在英文中叫做shard。 」 Bill 友情提示。
「好的, 然後可以用餘數演算法來確定一個『文檔』到底保存在哪個shard中。」 雖然張大胖覺得這個詞看起來不爽,還是開始使用了
shard 編號 = hash(文檔的ID) % shard 總數
「這樣對於任意一個文檔,對它的ID做hash計算,然後對總分片數量求余, 就可以得到shard的編號,然後就可以找到對應的機器了。 」 張大胖覺得自己的這個演算法又簡單,效率又高,洋洋得意。
Bill覺得這兩年張大胖進步不小,開始使用演算法來解決問題了, 他問道:「如果用戶想增加shard數該怎麼處理呢? 這個餘數演算法就會出問題啊 !」
比如原來shard 總數是3, hash值是100, shard編號 = 100 % 3 = 1
假設用戶又增加了兩台機器,shard總數變成了5, 此時 shard 編號 = 100 % 5 = 0 , 如果去0號機器上去找索引,肯定是找不到的。
張大胖撓撓頭:「要不採用分散式一致性演算法, 嗯,它會減少出錯的情況,還是無法避免,這該怎辦?」
Bill建議:「要不這樣,我們可以立下一個規矩: 用戶在創建索引庫的時候,必須要指定shard數量,並且一旦指定,就不能更改了!」
PUT /coolspace
{
"settings" : {
"number_of_shards" : 3
}
}
雖然對用戶來說有點不爽, 但餘數演算法的高效和簡單確實太吸引人了,張大胖表示同意。
「索引數據分布了,如果某個節點壞掉了,數據就會丟失,我們得做個備份才行。」 張大胖的思考很深入。
「對, 我們可以用新的node 來做replica,也可以為了節省空間, 復用現有的node來做replica。為了做區分,可以把之前的分片叫做主分片,primary shard。」 Bill英文就是好。
(此處的設置為:每個主分片有兩個副本)
PUT /coolspace/_settings
{
"number_of_replicas" : 2
}
雖然主分片的數目在創建「索引庫」的時候已經確定,但是副本的數目是可以任意增減的,這依賴於硬體的情況:性能和數量。
「現在每個主分片都有兩個副本, 如果某個節點掛了也不怕,比如節點1掛了,我們可以位於節點3上的副本0提升為 主分片0, 只不過每個主分片的副本數不夠兩個了。」 張大胖說道。
Bill 滿不在乎地說: 「沒事,等到節點1啟動後,還可以恢復副本。」
集群
Bill和張大胖立刻意識到,他們建立了一個集群, 這個集群中可以包含若干node , 有數據的備份,能實現高可用性。
但是另外一個問題馬上就出現了:對於客戶端來說,通過哪個node來讀寫『文檔』呢?
比如說用戶要把一個"文檔"加入索引庫: PUT /coolspace/blog/1001, 該如何處理?
Bill說:「這樣吧,我們可以讓請求發送到集群的任意一個節點,每個節點都具備處理任何請求的能力。」
張大胖說:「具體怎麼做呢? 」
Bill 寫下了處理過程:
(1)假設用戶把請求發給了節點1
(2)系統通過餘數演算法得知這個"文檔"應該屬於主分片2,於是請求被轉發到保存該主分片的節點3
(3)系統把文檔保存在節點3的主分片2中,然後將請求轉發至其他兩個保存副本的節點。副本保存成功以後,節點3會得到通知,然後通知節點1, 節點1再通知用戶。
「如果是做查詢呢? 比如說用戶查詢一個文檔: GET /coolspace/blog/1001, 該如何處理?」 張大胖問道。
「同樣,查詢的請求也可以分發到任意一個節點,然後該節點可以找到主分片或者任意一個副本,返回即可。 」
(1) 請求被發給了節點1
(2)節點1計算出該數據屬於主分片2,這時候,有三個選擇,分別是位於節點1的副本2, 節點2的副本2,節點3的主分片2, 假設節點1為了負載均衡,採用輪詢的方式,選中了節點2,把請求轉發。
(3) 節點2把數據返回給節點1, 節點1 最後返回給客戶端。
「這個方式比較靈活,但是要求各個節點之間得能互通有無才行!」 張大胖說道。
「不僅如此,對於一個集群來說,還得有一個主節點(master node),這個主節點在處理數據請求上和其他節點是平等的,但是它還有更重要的工作,需要維護整個集群的狀態,增加移除節點,創建/刪除索引庫,維護主分片和集群的關係等等。」
「那如果這個主節點掛了呢? 」 張大胖追問。
「那隻好從剩下的節點中重新選舉嘍!」
「哎呀,這就涉及到分散式系統的各種問題了,什麼一致性,腦裂,太難了!」 張大胖開始打退堂鼓。
「我們只是選取一個Master, 要簡單得多,你可以看看一個叫做Bully的演算法, 改進一下應該就可以用了。 」
開發分散式系統的難度要遠遠大於一個單機系統,半年以後,這個被Bill命名為Elasticsearch的系統才發布了第一個版本。
由於它屏蔽了很多Lucene的細節,又支持海量索引的存儲,很快就大受歡迎。
Elasticsearch 的真正傳奇
當然, Elasticsearch不是Bill和張大胖創造的,這裡才是其傳奇的歷史:(來源:《Elasticsearch權威指南》)
許多年前,一個剛結婚的名叫 Shay Banon 的失業開發者,跟著他的妻子去了倫敦,他的妻子在那裡學習廚師。 在尋找一個賺錢的工作的時候,為了給他的妻子做一個食譜搜索引擎,他開始使用 Lucene 的一個早期版本。
直接使用 Lucene 是很難的,因此 Shay 開始做一個抽象層,Java 開發者使用它可以很簡單的給他們的程序添加搜索功能。 他發布了他的第一個開源項目 Compass。
後來 Shay 獲得了一份工作,主要是高性能,分散式環境下的內存數據網格。這個對於高性能,實時,分散式搜索引擎的需求尤為突出, 他決定重寫 Compass,把它變為一個獨立的服務並取名 Elasticsearch。
第一個公開版本在2010年2月發布,從此以後,Elasticsearch 已經成為了 Github 上最活躍的項目之一,他擁有超過300名 contributors(目前736名 contributors )。 一家公司已經開始圍繞 Elasticsearch 提供商業服務,並開發新的特性,但是,Elasticsearch 將永遠開源並對所有人可用。
據說,Shay 的妻子還在等著她的食譜搜索引擎…
文章轉自公眾號:碼農翻身