目錄
回憶時光
許多年前,一個剛結婚的名叫 Shay Banon 的失業開發者,跟着他的妻子去了倫敦,他的妻子在那裏學習廚師。 在尋找一個賺錢的工作的時候,爲了給他的妻子做一個食譜搜索引擎,他開始使用 Lucene 的一個早期版本。
直接使用 Lucene 是很難的,因此 Shay 開始做一個抽象層,Java 開發者使用它可以很簡單的給他們的程序添加搜索功能。 他發佈了他的第一個開源項目 Compass。
後來 Shay 獲得了一份工作,主要是高性能,分佈式環境下的內存數據網格。這個對於高性能,實時,分佈式搜索引擎的需求尤爲突出, 他決定重寫 Compass,把它變爲一個獨立的服務並取名 Elasticsearch。
第一個公開版本在2010年2月發佈,從此以後,Elasticsearch 已經成爲了 Github 上最活躍的項目之一,他擁有超過300名 contributors(目前736名 contributors )。 一家公司已經開始圍繞 Elasticsearch 提供商業服務,並開發新的特性,但是,Elasticsearch 將永遠開源並對所有人可用。
據說,Shay 的妻子還在等着她的食譜搜索引擎…
倒排索引原理:
將原始的文檔進行編號,創建文檔索引,形成文檔列表,對文檔進行分詞操作,以詞條爲key,文檔爲value,建立詞條到文檔的映射。
當我們搜索詞條時,可以得到對應的所有的文檔列表,這個就是倒排索引。
路由一個文檔到分片中
當索引一個文檔的時候,文檔會被存儲到一個主分片中。 Elasticsearch 如何知道一個文檔應該存放到哪個分片中呢?
首先這肯定不會是隨機的,否則將來要獲取文檔的時候我們就不知道從何處尋找了。實際上,這個過程是根據下面這個公式決定的:
shard = hash(routing) % number_of_primary_shards
routing
通過 hash 函數生成一個數字,然後這個數字再除以 number_of_primary_shards
(主分片的數量)後得到 餘數 。
這也是爲什麼主分片的數量確定之後,不允許被更改。
主分片和副本分片如何交互
爲了說明目的, 我們 假設有一個集羣由三個節點組成。 它包含一個叫 blogs
的索引,有兩個主分片,每個主分片有兩個副本分片。相同分片的副本不會放在同一節點,所以我們的集羣看起來像 下圖所示。
我們可以發送請求到集羣中的任一節點。 每個節點都有能力處理任意請求。 每個節點都知道集羣中任一文檔位置,所以可以直接將請求轉發到需要的節點上。 在下面的例子中,將所有的請求發送到 Node 1
,我們將其稱爲 協調節點(coordinating node) 。
取回一個文檔
可以從主分片或者從其它任意副本分片檢索文檔 ,如下圖所示 Figure 10, “取回單個文檔”.
Figure 10. 取回單個文檔
以下是從主分片或者副本分片檢索文檔的步驟順序:
1、客戶端向 Node 1
發送獲取請求。
2、節點使用文檔的 _id
來確定文檔屬於分片 0
。分片 0
的副本分片存在於所有的三個節點上。 在這種情況下,它將請求轉發到 Node 2
。
3、Node 2
將文檔返回給 Node 1
,然後將文檔返回給客戶端。
在處理讀取請求時,協調結點在每次請求的時候都會通過輪詢所有的副本分片來達到負載均衡。
在文檔被檢索時,已經被索引的文檔可能已經存在於主分片上但是還沒有複製到副本分片。 在這種情況下,副本分片可能會報告文檔不存在,但是主分片可能成功返回文檔。 一旦索引請求成功返回給用戶,文檔在主分片和副本分片都是可用的。
寫入一個文檔,以及實時搜索的原理
Segment(段):Lucene裏面的一個數據集概念
提交點文件:有一個列表存放着所有已知的所有段
ES底層是基於Lucene,最核心的概念就是Segment(段),每個段本身就是一個倒排索引。
ES中的Index由多個段的集合和commit point(提交點)文件組成。
提交點文件中有一個列表存放着所有已知的段,下面是一個帶有1個提交點和3個段的Index示意圖:
Doc新增提交主要過程如下:
一、寫入磁盤後可見:
1、Doc寫入Buffer
一個文檔被索引之後,就會被添加到內存緩衝區,並且 追加到了 translog ,此時文檔還不能被查詢。
2、Doc Commit
每隔一段時間,會將buffer提交,在flush磁盤後打開新段使得搜索可見,詳細過程如下:
- 這些在內存緩衝區的文檔被寫入到一個新的段中,且沒有進行
fsync
操作。 - 這個段被打開,使其可被搜索。
- 內存緩衝區被清空。
下面展示了這個過程完成後的段和提交點的狀態:
總結:
1.數據首先會被寫入到內存之中,同時寫入到translog文件。
2.每隔一秒執行refresh操作生成一個segment寫入到炒作的文件緩存中,同時將內存清空,translog文件保留,此時segment文件是可訪問狀態。這是個ES爲什麼快的原理。
3.每隔一段時間(30分鐘)或者translog文件太大時,會將segment文件和translog持久化,並生成一個新的translog。
局部更新文檔
協調節點轉發請求到主分片所在的節點上,主分片更新完成之後,轉發文本到副分片上。
以下是部分更新一個文檔的步驟:
- 客戶端向
Node 1
發送更新請求。 - 它將請求轉發到主分片所在的
Node 3
。 Node 3
從主分片檢索文檔,修改_source
字段中的 JSON ,並且嘗試重新索引主分片的文檔。 如果文檔已經被另一個進程修改,它會重試步驟 3 ,超過retry_on_conflict
次後放棄。- 如果
Node 3
成功地更新文檔,它將新版本的文檔並行轉發到Node 1
和Node 2
上的副本分片,重新建立索引。 一旦所有副本分片都返回成功,Node 3
向協調節點也返回成功,協調節點向客戶端返回成功。
update
API 還接受在 新建、索引和刪除文檔 章節中介紹的 routing
、 replication
、 consistency
和 timeout
參數。
基於文檔的複製
當主分片把更改轉發到副本分片時, 它不會轉發更新請求。 相反,它轉發完整文檔的新版本。請記住,這些更改將會異步轉發到副本分片,並且不能保證它們以發送它們相同的順序到達。 如果Elasticsearch僅轉發更改請求,則可能以錯誤的順序應用更改,導致得到損壞的文檔。
樂觀併發控制(使用的version版本號)
Elasticsearch 是分佈式的。當文檔創建、更新或刪除時, 新版本的文檔必須複製到集羣中的其他節點。Elasticsearch 也是異步和併發的,這意味着這些複製請求被並行發送,並且到達目的地時也許 順序是亂的 。 Elasticsearch 需要一種方法確保文檔的舊版本不會覆蓋新的版本。
當我們之前討論 index
, GET
和 delete
請求時,我們指出每個文檔都有一個 _version
(版本)號,當文檔被修改時版本號遞增。 Elasticsearch 使用這個 _version
號來確保變更以正確順序得到執行。如果舊版本的文檔在新版本之後到達,它可以被簡單的忽略。