1.集群
一個運行的Elasticsearch實例被稱為一個節點,而集群是有一個或多個擁有相同claster.name配置的節點組成,他們共同承擔數據和負載壓力,當有節點加入或從集群中移除的時候,
集群或自動平局分布所有數據。
當一個節點被選舉成為主節點時,他哈不負責額管理集群范圍內的所有變更,例如增加、刪除索引,或者增加、刪除節點等。而主節點不涉及文檔級別的變更和搜索操作,所以集群只有一個主節點,即使流量增加,他也不會成為瓶頸。任何節點都可以成為主節點。
3.添加索引
PUT /blogs
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1
}
}
設置三個主分片,1個復制分片。
4.水平擴展
PUT /blogs/_settings
{
"number_of_replicas":2
}
復制分片從一個增加到兩個。不能修改主分片個數。
5.路由一個文檔到分片
當索引一個文檔時,文檔會被存儲到主分片中。存到哪個主分片由下面這個公式決定:
shard = hash(routing) % number_of_primary_shards.
routing是一個可變值,默認是文檔_id,也可以設置一個自定義的值。
number_of_primary_shards主分片數。
這里解釋了為什么不能改變主分片數量:因為
如果數量變化了,那么所有的之前路由的值都會無效,文檔也找不到了。
6.新建、索引和刪除單個文檔
1).客戶端向集群中的一個節點(協調節點)發送請求。
2).節點使用文檔的_id確定文檔所屬的分片,轉發請求到分片所屬的節點(master).
3).主分片執行請求。若執行成功,將請求並行轉發到復制節點。一旦所有復制節點報告執行成功。master節點向協調節點報告成功,協調節點向客戶端報告成功。
7.檢索單個文檔
1).客戶端向集群中的一個節點(node1)發出請求。
2).
node1節點使用_id確定文檔所屬分片,將請求轉發大其中個一個分片所屬節點(node2)。
3).node2將文檔返回給node1,然后將文檔返回給客戶端。
協調節點每次請求的時候選擇不同的副本分片達到負載均衡。
8.局部更新
1).客戶端向node1發送更新請求。
2).轉發請求到主分片所在的node2。
3).node2從主分片檢索文檔,修改_source字段中的JSON,並嘗試重新索引主分片文檔。如果文檔已經被另一個進程修改了,他將重試步驟3,超過retry_on_conflict次后放棄。
4).如果node2成功更新索引,它將新版本的文檔轉發到所有副本分片所在的節點,重新建立索引。一旦所有副本分片都成功返回了,node2向協調節點返回成功,協調節點向客戶端返回成功。
9.倒排索引
Elasticsearch使用一種稱為倒排索引的結構,它適用於快速的全文搜索。一個倒排索引有文檔中所有不重復的詞的列表構成,對於其中每個詞,有一個包含他的文檔列表。類似下面:(被索引的字段都有自己的倒排索引)
Term | Doc 1 | Doc 2 | Doc 3 | ...
------------------------------------
brown | X | | X | ...
fox | X | X | X | ...
quick | X | X | | ...
the | X | | X | ...
它會保存每一個詞項出現過的文檔總數, 在對應的文檔中一個具體詞項出現的總次數,詞項在文檔中的順序,每個文檔的長度,所有文檔的平均長度,等等。
不變性:
倒排索引被芯茹磁盤后是不可改變的:它永遠不會被修改。不可修改的意義:
1).不需要鎖。
2).寫入單個大的倒排索引允許被壓縮。
3).一旦索引被讀入內核的文件系統緩存,便會留在哪里,由於其不變性。只要文件系統緩存中還有足夠的空間,那么大部分讀請求會直接請求內存,而不會命中磁盤。這提供了很大的性能提升。
4).其它緩存(像filter緩存),在索引的生命周期內始終有效。它們不需要在每次數據改變時被重建,因為數據不會變化。
當然,一個不變的索引也有不好的地方。主要事實是它是不可變的! 你不能修改它。如果你需要讓一個新的文檔 可被搜索,你需要重建整個索引。這要么對一個索引所能包含的數據量造成了很大的限制,要么對索引可被更新的頻率造成了很大的限制。
10.動態更新索引
Elasticsearch 基於 Lucene, 這個 java 庫引入了 按段搜索(per-segment search) 的概念。 每一 segment 本身都是一個倒排索引, 但 索引 在 Lucene 中除表示所有 segment 的集合外, 還增加了 提交點 的概念 — 一個列出了所有已知段的文件。
索引與分片的比較:
被混淆的概念是,一個 Lucene 索引 我們在 Elasticsearch 稱作 分片 。 一個 Elasticsearch 索引 是分片的集合。 當 Elasticsearch 在索引中搜索的時候, 他發送查詢到每一個屬於索引的分片(Lucene 索引),然后像 執行分布式檢索 提到的那樣,合並每個分片的結果到一個全局的結果集。
一個 Lucene 索引包含一個提交點和三個段:

一個在內存緩存中包含新文檔的 Lucene 索引:

在一次提交后,一個新的segment被添加到提交點而且緩存被清空:

1).新文檔被收集到內存索引緩存, “一個在內存緩存中包含新文檔的 Lucene 索引” 。
2).不時地, 緩存被 提交 :
一個新的segment --一個追加的倒排索引--被寫入磁盤。
一個新的包含新segment 名字的 提交點 被寫入磁盤。
磁盤進行 同步 — 所有在文件系統緩存中等待的寫入都刷新到磁盤,以確保它們被寫入物理文件。
3).新的segment 被開啟,讓它包含的文檔可見以被搜索。
4).內存緩存被清空,等待接收新的文檔。
刪除和更新:
段是不可改變的,所以既不能從把文檔從舊的段中移除,也不能修改舊的段來進行反映文檔的更新。 取而代之的是,每個提交點會包含一個 .del 文件,文件中會列出這些被刪除文檔的段信息。
當一個文檔被 “刪除” 時,它實際上只是在 .del 文件中被 標記 刪除。一個被標記刪除的文檔仍然可以被查詢匹配到, 但它會在最終結果被返回前從結果集中移除。
文檔更新也是類似的操作方式:當一個文檔被更新時,舊版本文檔被標記刪除,文檔的新版本被索引到一個新的段中。 可能兩個版本的文檔都會被一個查詢匹配到,但被刪除的那個舊版本文檔在結果集返回前就已經被移除。
11.近實時搜索
隨着按段(per-segment)搜索的發展, 一個新的文檔從索引到可被搜索的延遲顯著降低了。新文檔在幾分鍾之內即可被檢索,但這樣還是不夠快。
磁盤在這里成為了瓶頸。提交(Commiting)一個新的段到磁盤需要一個 fsync 來確保段被物理性地寫入磁盤,這樣在斷電的時候就不會丟失數據。 但是 fsync 操作代價很大; 如果每次索引一個文檔都去執行一次的話會造成很大的性能問題。
我們需要的是一個更輕量的方式來使一個文檔可被搜索,這意味着 fsync 要從整個過程中被移除。
在Elasticsearch和磁盤之間是文件系統緩存,在內存索引緩沖區中的文檔會被寫入到一個新的segment中。 但是這里
新段會被先寫入到文件系統緩存--這一步代價會比較低,稍后再被刷新到磁盤--這一步代價比較高。不過只要文件已經在緩存中, 就可以像其它文件一樣被打開和讀取了。
在內存緩沖區中包含了新文檔的 Lucene 索引:
緩沖區的內容已經被寫入一個可被搜索的段中,但還沒有進行提交:

Refresh API
在 Elasticsearch 中,寫入和打開一個新段的輕量的過程叫做 refresh 。 默認情況下每個分片會每秒自動刷新一次。這就是為什么我們說 Elasticsearch 是 近 實時搜索: 文檔的變化並不是立即對搜索可見,但會在一秒之內變為可見。
POST /_refresh :刷新(Refresh)所有的索引。
POST /blogs/_refresh :只刷新(Refresh) blogs 索引。
並不是所有的情況都需要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日志文件, 你可能想優化索引速度而不是近實時搜索, 可以通過設置 refresh_interval , 降低每個索引的刷新頻率:
PUT /my_logs
{
"settings": {
"refresh_interval": "30s"
}
}
在生產環境中,當你正在建立一個大的新索引時,可以先關閉自動刷新,待開始使用該索引時,再把它們調回來:
PUT /my_logs/_settings
{ "refresh_interval": -1 }
PUT /my_logs/_settings
{ "refresh_interval": "1s" }
12.持久化變更
如果沒有用 fsync 把數據從文件系統緩存刷(flush)到硬盤,我們不能保證數據在斷電甚至是程序正常退出之后依然存在。為了保證 Elasticsearch 的可靠性,需要確保數據變化被持久化到磁盤。
Elasticsearch 增加了一個 translog ,或者叫事務日志,在每一次對 Elasticsearch 進行操作時均進行了日志記錄。通過 translog ,整個流程看起來是下面這樣:
1).一個文檔被索引之后,就會被添加到內存緩沖區,並且 追加到了 translog.
2).刷新(refresh)使分片,分片每秒被刷新(refresh)一次:
a.這些在內存緩沖區的文檔被寫入到一個新的段中,且沒有進行 fsync 操作。 ** 這個段被打開,使其可被搜索。
b.內存緩沖區被清空。
3).這個進程繼續工作,更多的文檔被添加到內存緩沖區和追加到事務日志.
4).每隔一段時間--例如 translog 變得越來越大--索引被刷新(flush);一個新的 translog 被創建,並且一個全量提交被執行:
a.所有在內存緩沖區的文檔都被寫入一個新的段。
b.緩沖區被清空。
c.一個提交點被寫入硬盤。
d.文件系統緩存通過 fsync 被刷新(flush)。
e.老的 translog 被刪除。
translog 提供所有還沒有被刷到磁盤的操作的一個持久化紀錄。當 Elasticsearch 啟動的時候, 它會從磁盤中使用最后一個提交點去恢復已知的段,並且會重放 translog 中所有在最后一次提交后發生的變更操作。
Flush API
這個執行一個提交並且截斷 translog 的行為在 Elasticsearch 被稱作一次 flush 。 分片每30分鍾被自動刷新(flush),或者在 translog 太大的時候也會刷新。可以手動刷新:
POST /blogs/_flush
POST /_flush?wait_for_ongoing
Translog 的安全問題:默認,在每次請求后都會通過fsync把請求寫到日志文件。你也可以使用異步的 fsync ,寫入的數據被緩存到內存中,每5秒執行一次 fsync :
PUT /my_index/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "5s"
}
13.segment合並
由於自動刷新流程每秒會創建一個新的segment ,這樣會導致短時間內的segment 數量暴增。而segment 數目太多會帶來較大的麻煩。 每一個segment 都會消耗文件句柄、內存和cpu運行周期。更重要的是,每個搜索請求都必須輪流檢查每個segment ;所以segment 越多,搜索也就越慢。
Elasticsearch通過在后台進行segment 合並來解決這個問題。小的segment 被合並到大的segment ,然后這些大的segment 再被合並到更大的segment 。
segment 合並的時候會將那些舊的已刪除文檔 從文件系統中清除。 被刪除的文檔(或被更新文檔的舊版本)不會被拷貝到新的大segment 中。
啟動segment 合並不需要你做任何事。進行索引和搜索時會自動進行。
1).當索引的時候,刷新(refresh)操作會創建新的segment 並將segment 打開以供搜索使用。
2).合並進程選擇一小部分大小相似的segment ,並且在后台將它們合並到更大的segment 中。這並不會中斷索引和搜索。
3).一旦合並結束,老的段被刪除, 說明合並是完成的活動:
a.新的段被刷新(flush)到了磁盤。 ** 寫入一個包含新段且排除舊的和較小的段的新提交點。
b.新的段被打開用來搜索。
c.老的段被刪除。
合並大的segment需要消耗大量的I/O和CPU資源,如果任其發展會影響搜索性能。Elasticsearch在默認情況下會對合並流程進行資源限制,所以搜索仍然 有足夠的資源很好地執行。
Optimize API
optimize API大可看做是 強制合並 API 。它會將一個分片強制合並到 max_num_segments 參數指定大小的段數目。 這樣做的意圖是減少段的數量(通常減少到一個),來提升搜索性能。
optimize API 不應該 被用在一個動態索引————一個正在被活躍更新的索引。后台合並流程已經可以很好地完成工作。 optimizing 會阻礙這個進程。不要干擾它!
在特定情況下,使用 optimize API 頗有益處。例如在日志這種用例下,每天、每周、每月的日志被存儲在一個索引中。 老的索引實質上是只讀的;它們也並不太可能會發生變化。在這種情況下,使用optimize優化老的索引,將每一個分片合並為一個單獨的段就很有用了;這樣既可以節省資源,也可以使搜索更加快速:
POST /logstash-2014-10/_optimize?max_num_segments=1
請注意,使用 optimize API 觸發段合並的操作一點也不會受到任何資源上的限制。這可能會消耗掉你節點上全部的I/O資源, 使其沒有余裕來處理搜索請求,從而有可能使集群失去響應。 如果你想要對索引執行 `optimize`,你需要先使用分片分配(查看 遷移舊索引)把索引移到一個安全的節點,再執行。