搜索之路: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

格式是//

/

如果和關係數據庫做個類比的話:

索引庫數據庫

索引的類型 數據庫的表

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調用,就可以使用。

搜索之路:Elasticsearch的誕生

通過這樣一個抽象層, Lucene那些複雜的API全部被隱藏到了海平面以下。

對於程序員來說,使用HTTP+JSON是非常自然的事情,好用就是最大的生產力。

分佈式

到目前為止,進展還算順利,接下來要考慮的就是如何存儲海量的索引數據。

張大胖說: “這個簡單,如果索引太大,我們把它切割一下,分成一片一片的,存儲到各個機器上不就得了?”

搜索之路:Elasticsearch的誕生

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英文就是好。

搜索之路:Elasticsearch的誕生

此處的設置為:每個主分片有兩個副本

PUT /coolspace/_settings

{

"number_of_replicas" : 2

}

雖然主分片的數目在創建“索引庫”的時候已經確定,但是副本的數目是可以任意增減的,這依賴於硬件的情況:性能和數量。

“現在每個主分片都有兩個副本, 如果某個節點掛了也不怕,比如節點1掛了,我們可以位於節點3上的副本0提升為 主分片0, 只不過每個主分片的副本數不夠兩個了。” 張大胖說道。

搜索之路:Elasticsearch的誕生

Bill 滿不在乎地說: “沒事,等到節點1啟動後,還可以恢復副本。”

集群

Bill和張大胖立刻意識到,他們建立了一個集群, 這個集群中可以包含若干node , 有數據的備份,能實現高可用性。

但是另外一個問題馬上就出現了:對於客戶端來說,通過哪個node來讀寫‘文檔’呢?

比如說用戶要把一個'文檔'加入索引庫: PUT /coolspace/blog/1001, 該如何處理?

Bill說:“這樣吧,我們可以讓請求發送到集群的任意一個節點,每個節點都具備處理任何請求的能力。”

張大胖說:“具體怎麼做呢? ”

Bill 寫下了處理過程:

(1)假設用戶把請求發給了節點1

(2)系統通過餘數算法得知這個'文檔'應該屬於主分片2,於是請求被轉發到保存該主分片的節點3

(3)系統把文檔保存在節點3的主分片2中,然後將請求轉發至其他兩個保存副本的節點。副本保存成功以後,節點3會得到通知,然後通知節點1, 節點1再通知用戶。

搜索之路:Elasticsearch的誕生

“如果是做查詢呢? 比如說用戶查詢一個文檔: GET /coolspace/blog/1001, 該如何處理?” 張大胖問道。

“同樣,查詢的請求也可以分發到任意一個節點,然後該節點可以找到主分片或者任意一個副本,返回即可。 ”

(1) 請求被髮給了節點1

(2)節點1計算出該數據屬於主分片2,這時候,有三個選擇,分別是位於節點1的副本2, 節點2的副本2,節點3的主分片2, 假設節點1為了負載均衡,採用輪詢的方式,選中了節點2,把請求轉發。

(3) 節點2把數據返回給節點1, 節點1 最後返回給客戶端。

搜索之路:Elasticsearch的誕生

“這個方式比較靈活,但是要求各個節點之間得能互通有無才行!” 張大胖說道。

“不僅如此,對於一個集群來說,還得有一個主節點(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 的妻子還在等著她的食譜搜索引擎…


分享到:


相關文章: