咱們生活中的數據整體分爲兩種:結構化數據 和 非結構化數據。node
結構化數據:也稱做行數據,是由二維表結構來邏輯表達和實現的數據,嚴格地遵循數據格式與長度規範,主要經過關係型數據庫進行存儲和管理。指具備固定格式或有限長度的數據,如數據庫,元數據等。mysql
非結構化數據:又可稱爲全文數據,不定長或無固定格式,不適於由數據庫二維表來表現,包括全部格式的辦公文檔、XML、HTML、word文檔,郵件,各種報表、圖片和咅頻、視頻信息等。sql
說明:若是要更細緻的區分的話,XML、HTML可劃分爲 半結構化數據。由於它們也具備本身特定的標籤格式,因此既能夠根據須要按結構化數據來處理,也可抽取出純文本按非結構化數據來處理。數據庫
根據兩種數據分類,搜索也相應的分爲兩種:結構化數據搜索和非結構化數據搜索。apache
對於結構化數據,由於它們具備特定的結構,因此咱們通常都是能夠經過關係型數據庫(mysql,oracle等)的 二維表(table)的方式存儲和搜索,也能夠創建索引。瀏覽器
對於非結構化數據,也即對全文數據的搜索主要有兩種方法:順序掃描法,全文檢索。緩存
順序掃描:經過文字名稱也可瞭解到它的大概搜索方式,即按照順序掃描的方式查詢特定的關鍵字。例如給你一張報紙,讓你找到該報紙中「平安」的文字在哪些地方出現過。你確定須要從頭至尾把報紙閱讀掃描一遍而後標記出關鍵字在哪些版塊出現過以及它的出現位置。性能優化
這種方式無疑是最耗時的最低效的,若是報紙排版字體小,並且版塊較多甚至有多份報紙,等你掃描完你的眼睛也差很少了。服務器
全文搜索:對非結構化數據順序掃描很慢,咱們是否能夠進行優化?把咱們的非結構化數據想辦法弄得有必定結構不就好了嗎?將非結構化數據中的一部分信息提取出來,從新組織,使其變得有必定結構,而後對此有必定結構的數據進行搜索,從而達到搜索相對較快的目的。網絡
這種方式就構成了全文檢索的基本思路。這部分從非結構化數據中提取出的而後從新組織的信息,咱們稱之索引。這種方式的主要工做量在前期索引的建立,可是對於後期搜索倒是快速高效的。
經過對生活中數據的類型做了一個簡短了解以後,咱們知道關係型數據庫的SQL檢索是處理不了這種非結構化數據的。這種非結構化數據的處理須要依賴全文搜索,而目前市場上開放源代碼的最好全文檢索引擎工具包就屬於 apache 的 Lucene了。
可是 Lucene 只是一個工具包,它不是一個完整的全文檢索引擎。Lucene的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此爲基礎創建起完整的全文檢索引擎。
目前以 Lucene 爲基礎創建的開源可用全文搜索引擎主要是 Solr 和 Elasticsearch。
Solr 和 Elasticsearch 都是比較成熟的全文搜索引擎,能完成的功能和性能也基本同樣。可是 ES 自己就具備分佈式的特性和易安裝使用的特色,而Solr的分佈式須要藉助第三方來實現,例如經過使用ZooKeeper來達到分佈式協調管理。
無論是 Solr 仍是 Elasticsearch 底層都是依賴於 Lucene,而 Lucene 能實現全文搜索主要是由於它實現了倒排索引的查詢結構。
如何理解倒排索引呢?假如現有三份數據文檔,文檔的內容以下分別是:
Java is the best programming language.
PHP is the best programming language.
Javascript is the best programming language.
爲了建立倒排索引,咱們經過分詞器將每一個文檔的內容域拆分紅單獨的詞(咱們稱它爲詞條或 Term),建立一個包含全部不重複詞條的排序列表,而後列出每一個詞條出如今哪一個文檔。結果以下所示:
Term Doc_1 Doc_2 Doc_3
-------------------------------------
Java | X | |
is | X | X | X
the | X | X | X
best | X | X | X
programming | x | X | X
language | X | X | X
PHP | | X |
Javascript | | | X
-------------------------------------
這種結構由文檔中全部不重複詞的列表構成,對於其中每一個詞都有一個文檔列表與之關聯。這種由屬性值來肯定記錄的位置的結構就是倒排索引。帶有倒排索引的文件咱們稱爲倒排文件。
咱們將上面的內容轉換爲圖的形式來講明倒排索引的結構信息,以下圖所示,
其中主要有以下幾個核心術語須要理解:
詞條(Term):索引裏面最小的存儲和查詢單元,對於英文來講是一個單詞,對於中文來講通常指分詞後的一個詞。
詞典(Term Dictionary):或字典,是詞條Term的集合。搜索引擎的一般索引單位是單詞,單詞詞典是由文檔集合中出現過的全部單詞構成的字符串集合,單詞詞典內每條索引項記載單詞自己的一些信息以及指向「倒排列表」的指針。
倒排表(Post list):一個文檔一般由多個詞組成,倒排表記錄的是某個詞在哪些文檔裏出現過以及出現的位置。每條記錄稱爲一個倒排項(Posting)。倒排表記錄的不單是文檔編號,還存儲了詞頻等信息。
倒排文件(Inverted File):全部單詞的倒排列表每每順序地存儲在磁盤的某個文件裏,這個文件被稱之爲倒排文件,倒排文件是存儲倒排索引的物理文件。
從上圖咱們能夠了解到倒排索引主要由兩個部分組成:詞典和倒排文件。詞典和倒排表是Lucene中很重要的兩種數據結構,是實現快速檢索的重要基石。詞典和倒排文件是分兩部分存儲的,詞典在內存中而倒排文件存儲在磁盤上。
一些基礎知識的鋪墊以後咱們正式進入今天的主角Elasticsearch的介紹, ES是使用Java編寫的一種開源搜索引擎,它在內部使用Lucene作索引與搜索,經過對Lucene的封裝,隱藏了Lucene的複雜性,取而代之的提供一套簡單一致的 RESTful API。
然而,Elasticsearch 不只僅是 Lucene,而且也不只僅只是一個全文搜索引擎。它能夠被下面這樣準確的形容:
一個分佈式的實時文檔存儲,每一個字段能夠被索引與搜索。
一個分佈式實時分析搜索引擎。
能勝任上百個服務節點的擴展,並支持 PB 級別的結構化或者非結構化數據。
官網對Elasticsearch的介紹是Elasticsearch 是一個分佈式、可擴展、近實時的搜索與數據分析引擎。咱們經過一些核心概念來看下Elasticsearch 是如何作到分佈式,可擴展和近實時搜索的。
ES的集羣搭建很簡單,不須要依賴第三方協調管理組件,自身內部就實現了集羣的管理功能。ES集羣由一個或多個Elasticsearch節點組成,每一個節點配置相同的 cluster.name
便可加入集羣,默認值爲 「elasticsearch」。確保不一樣的環境中使用不一樣的集羣名稱,不然最終會致使節點加入錯誤的集羣。
一個Elasticsearch服務啓動實例就是一個節點(Node)。節點經過 node.name
來設置節點名稱,若是不設置則在啓動時給節點分配一個隨機通用惟一標識符做爲名稱。
那麼有一個問題,ES內部是如何經過一個相同的設置 cluster.name
就能將不一樣的節點鏈接到同一個集羣的?答案是 ZenDiscovery
。
Zen Discovery是Elasticsearch的內置默認發現模塊(發現模塊的職責是發現集羣中的節點以及選舉master節點)。它提供單播和基於文件的發現,而且能夠擴展爲經過插件支持雲環境和其餘形式的發現。Zen Discovery 與其餘模塊集成,例如,節點之間的全部通訊都使用Transport模塊完成。節點使用發現機制經過Ping的方式查找其餘節點。
Elasticsearch 默認被配置爲使用單播發現,以防止節點無心中加入集羣。只有在同一臺機器上運行的節點纔會自動組成集羣。
若是集羣的節點運行在不一樣的機器上,使用單播,你能夠爲 Elasticsearch 提供一些它應該去嘗試鏈接的節點列表。當一個節點聯繫到單播列表中的成員時,它就會獲得整個集羣全部節點的狀態,而後它會聯繫 master 節點,並加入集羣。
這意味着單播列表不須要包含集羣中的全部節點, 它只是須要足夠的節點,當一個新節點聯繫上其中一個而且說上話就能夠了。若是你使用 master 候選節點做爲單播列表,你只要列出三個就能夠了。這個配置在 elasticsearch.yml 文件中:
discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]
節點啓動後先 ping ,若是 discovery.zen.ping.unicast.hosts
有設置,則 ping 設置中的 host ,不然嘗試 ping localhost 的幾個端口, Elasticsearch 支持同一個主機啓動多個節點, Ping 的 response 會包含該節點的基本信息以及該節點認爲的 master 節點。選舉開始,先從各節點認爲的 master 中選,規則很簡單,按照 id 的字典序排序,取第一個。若是各節點都沒有認爲的 master ,則從全部節點中選擇,規則同上。
這裏有個限制條件就是 discovery.zen.minimum_master_nodes
,若是節點數達不到最小值的限制,則循環上述過程,直到節點數足夠能夠開始選舉。最後選舉結果是確定能選舉出一個 master ,若是隻有一個 local 節點那就選出的是本身。若是當前節點是 master ,則開始等待節點數達到 discovery.zen.minimummasternodes,而後提供服務。若是當前節點不是 master ,則嘗試加入 master 。Elasticsearch 將以上服務發現以及選主的流程叫作 ZenDiscovery 。
因爲它支持任意數目的集羣( 1- N ),因此不能像 Zookeeper 那樣限制節點必須是奇數,也就沒法用投票的機制來選主,而是經過一個規則,只要全部的節點都遵循一樣的規則,獲得的信息都是對等的,選出來的主節點確定是一致的。但分佈式系統的問題就出在信息不對等的狀況,這時候很容易出現腦裂( Split-Brain )的問題,大多數解決方案就是設置一個 quorum 值,要求可用節點必須大於 quorum (通常是超過半數節點),才能對外提供服務。而 Elasticsearch 中,這個 quorum 的配置就是 discovery.zen.minimum_master_nodes
。
每一個節點既能夠是候選主節點也能夠是數據節點,經過在配置文件 ../config/elasticsearch.yml
中設置便可,默認都爲 true
。
node.master: true //是否候選主節點
node.data: true //是否數據節點
數據節點負責數據的存儲和相關的操做,例如對數據進行增、刪、改、查和聚合等操做,因此數據節點(data節點)對機器配置要求比較高,對CPU、內存和I/O的消耗很大。一般隨着集羣的擴大,須要增長更多的數據節點來提升性能和可用性。
候選主節點能夠被選舉爲主節點(master節點),集羣中只有候選主節點纔有選舉權和被選舉權,其餘節點不參與選舉的工做。主節點負責建立索引、刪除索引、跟蹤哪些節點是羣集的一部分,並決定哪些分片分配給相關的節點、追蹤集羣中節點的狀態等,穩定的主節點對集羣的健康是很是重要的。
一個節點既能夠是候選主節點也能夠是數據節點,可是因爲數據節點對CPU、內存核I/0消耗都很大,因此若是某個節點既是數據節點又是主節點,那麼可能會對主節點產生影響從而對整個集羣的狀態產生影響。
所以爲了提升集羣的健康性,咱們應該對Elasticsearch集羣中的節點作好角色上的劃分和隔離。可使用幾個配置較低的機器羣做爲候選主節點羣。
主節點和其餘節點之間經過Ping的方式互檢查,主節點負責Ping全部其餘節點,判斷是否有節點已經掛掉。其餘節點也經過Ping的方式判斷主節點是否處於可用狀態。
雖然對節點作了角色區分,可是用戶的請求能夠發往任何一個節點,並由該節點負責分發請求、收集結果等操做,而不須要主節點轉發,這種節點可稱之爲協調節點,協調節點是不須要指定和配置的,集羣中的任何節點均可以充當協調節點的角色。
同時若是因爲網絡或其餘緣由致使集羣中選舉出多個Master節點,使得數據更新時出現不一致,這種現象稱之爲腦裂,即集羣中不一樣的節點對於master的選擇出現了分歧,出現了多個master競爭。
「腦裂」問題可能有如下幾個緣由形成:
網絡問題:集羣間的網絡延遲致使一些節點訪問不到master,認爲master掛掉了從而選舉出新的master,並對master上的分片和副本標紅,分配新的主分片
節點負載:主節點的角色既爲master又爲data,訪問量較大時可能會致使ES中止響應(假死狀態)形成大面積延遲,此時其餘節點得不到主節點的響應認爲主節點掛掉了,會從新選取主節點。
內存回收:主節點的角色既爲master又爲data,當data節點上的ES進程佔用的內存較大,引起JVM的大規模內存回收,形成ES進程失去響應。
爲了不腦裂現象的發生,咱們能夠從緣由着手經過如下幾個方面來作出優化措施:
適當調大響應時間,減小誤判經過參數 discovery.zen.ping_timeout
設置節點狀態的響應時間,默認爲3s,能夠適當調大,若是master在該響應時間的範圍內沒有作出響應應答,判斷該節點已經掛掉了。調大參數(如6s,discovery.zen.ping_timeout:6),可適當減小誤判。
選舉觸發咱們須要在候選集羣中的節點的配置文件中設置參數 discovery.zen.munimum_master_nodes
的值,這個參數表示在選舉主節點時須要參與選舉的候選主節點的節點數,默認值是1,官方建議取值 (master_eligibel_nodes/2)+1
,其中 master_eligibel_nodes
爲候選主節點的個數。這樣作既能防止腦裂現象的發生,也能最大限度地提高集羣的高可用性,由於只要很多於discovery.zen.munimum_master_nodes個候選節點存活,選舉工做就能正常進行。當小於這個值的時候,沒法觸發選舉行爲,集羣沒法使用,不會形成分片混亂的狀況。
角色分離便是上面咱們提到的候選主節點和數據節點進行角色分離,這樣能夠減輕主節點的負擔,防止主節點的假死狀態發生,減小對主節點「已死」的誤判。
ES支持PB級全文搜索,當索引上的數據量太大的時候,ES經過水平拆分的方式將一個索引上的數據拆分出來分配到不一樣的數據塊上,拆分出來的數據庫塊稱之爲一個分片。
這相似於MySql的分庫分表,只不過Mysql分庫分表須要藉助第三方組件而ES內部自身實現了此功能。
在一個多分片的索引中寫入數據時,經過路由來肯定具體寫入哪個分片中,因此在建立索引的時候須要指定分片的數量,而且分片的數量一旦肯定就不能修改。
分片的數量和下面介紹的副本數量都是能夠經過建立索引時的 settings
來配置,ES默認爲一個索引建立5個主分片, 並分別爲每一個分片建立一個副本。
PUT /myIndex
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
}
ES經過分片的功能使得索引在規模上和性能上都獲得提高,每一個分片都是Lucene中的一個索引文件,每一個分片必須有一個主分片和零到多個副本。
副本就是對分片的Copy,每一個主分片都有一個或多個副本分片,當主分片異常時,副本能夠提供數據的查詢等操做。主分片和對應的副本分片是不會在同一個節點上的,因此副本分片數的最大值是 n -1(其中n爲節點數)。
對文檔的新建、索引和刪除請求都是寫操做,必須在主分片上面完成以後才能被複制到相關的副本分片,ES爲了提升寫入的能力這個過程是併發寫的,同時爲了解決併發寫的過程當中數據衝突的問題,ES經過樂觀鎖的方式控制,每一個文檔都有一個 _version (版本)號,當文檔被修改時版本號遞增。一旦全部的副本分片都報告寫成功纔會向協調節點報告成功,協調節點向客戶端報告成功。
從上圖能夠看出爲了達到高可用,Master節點會避免將主分片和副本分片放在同一個節點上。
假設這時節點Node1服務宕機了或者網絡不可用了,那麼主節點上主分片S0也就不可用了。幸運的是還存在另外兩個節點能正常工做,這時ES會從新選舉新的主節點,並且這兩個節點上存在咱們的所須要的S0的全部數據,咱們會將S0的副本分片提高爲主分片,這個提高主分片的過程是瞬間發生的。此時集羣的狀態將會爲 yellow。
爲何咱們集羣狀態是 yellow 而不是 green 呢?雖然咱們擁有全部的2個主分片,可是同時設置了每一個主分片須要對應兩份副本分片,而此時只存在一份副本分片。因此集羣不能爲 green 的狀態。若是咱們一樣關閉了 Node2 ,咱們的程序依然能夠保持在不丟任何數據的狀況下運行,由於Node3 爲每個分片都保留着一份副本。
若是咱們從新啓動Node1 ,集羣能夠將缺失的副本分片再次進行分配,那麼集羣的狀態又將恢復到原來的正常狀態。若是Node1依然擁有着以前的分片,它將嘗試去重用它們,只不過這時Node1節點上的分片再也不是主分片而是副本分片了,若是期間有更改的數據只須要從主分片上覆制修改的數據文件便可。
小結:
一、將數據分片是爲了提升可處理數據的容量和易於進行水平擴展,爲分片作副本是爲了提升集羣的穩定性和提升併發量。二、副本是乘法,越多消耗越大,但也越保險。分片是除法,分片越多,單分片數據就越少也越分散。三、副本越多,集羣的可用性就越高,可是因爲每一個分片都至關於一個Lucene的索引文件,會佔用必定的文件句柄、內存及CPU,而且分片間的數據同步也會佔用必定的網絡帶寬,因此索引的分片數和副本數也不是越多越好。
映射是用於定義ES對索引中字段的存儲類型、分詞方式和是否存儲等信息,就像數據庫中的 schema ,描述了文檔可能具備的字段或屬性、每一個字段的數據類型。只不過關係型數據庫建表時必須指定字段類型,而ES對於字段類型能夠不指定而後動態對字段類型猜想,也能夠在建立索引時具體指定字段的類型。
對字段類型根據數據格式自動識別的映射稱之爲動態映射(Dynamic mapping),咱們建立索引時具體定義字段類型的映射稱之爲靜態映射或顯示映射(Explicit mapping)。
在講解動態映射和靜態映射的使用前,咱們先來了解下ES中的數據有哪些字段類型?以後咱們再講解爲何咱們建立索引時須要創建靜態映射而不使用動態映射。
ES(v6.8)中字段數據類型主要有如下幾類:
類別 | 數據類型 |
---|---|
核心類型 | text, keywords, long, integer, short, double, data, boolean等等 |
複雜類型 | Object, Nested |
地理類型 | geopoint, geoshape |
特殊類型 | ip, completion, token_count, join等等 |
....... | ... |
text 用於索引全文值的字段,例如電子郵件正文或產品說明。這些字段是被分詞的,它們經過分詞器傳遞 ,以在被索引以前將字符串轉換爲單個術語的列表。分析過程容許Elasticsearch搜索單個單詞中每一個完整的文本字段。文本字段不用於排序,不多用於聚合。
keyword 用於索引結構化內容的字段,例如電子郵件地址,主機名,狀態代碼,郵政編碼或標籤。它們一般用於過濾,排序,和聚合。keyword字段只能按其確切值進行搜索。
經過對字段類型的瞭解咱們知道有些字段須要明肯定義的,例如某個字段是text類型仍是keword類型差異是很大的,時間字段也許咱們須要指定它的時間格式,還有一些字段咱們須要指定特定的分詞器等等。若是採用動態映射是不能精確作到這些的,自動識別經常會與咱們指望的有些差別。
因此建立索引給的時候一個完整的格式應該是指定分片和副本數以及Mapping的定義,以下:
在決定使用 Elasticsearch 的時候首先要考慮的是版本問題,Elasticsearch (排除 0.x 和 1.x)目前有以下經常使用的穩定的主版本:2.x,5.x,6.x,7.x(current)。你可能會發現沒有 3.x 和 4.x,ES 從 2.4.6 直接跳到了 5.0.0。
實際上是爲了ELK(ElasticSearch, logstash, kibana)技術棧的版本統一,免的給用戶帶來混亂。在 Elasticsearch 是 2.x (2.x 的最後一版 2.4.6 的發佈時間是 July 25, 2017) 的狀況下,kibana 已是 4.x(Kibana 4.6.5 的發佈時間是 July 25, 2017),那麼在 kibana 的下一主版本確定是 5.x 了,因此 Elasticsearch 直接將本身的主版本發佈爲 5.0.0 了。統一以後,咱們選版本就不會猶豫困惑了,咱們選定 elasticsearch 的版本後再選擇相同版本的 kibana 就好了,不用擔心版本不兼容的問題。
Elasticsearch是使用Java構建,因此除了注意 ELK 技術的版本統一,咱們在選擇 Elasticsearch 的版本的時候還須要注意 JDK的版本。由於每一個大版本所依賴的 JDK版本也不一樣,目前7.2版本已經能夠支持 jdk11。
bin:二進制系統指令目錄,包含啓動命令和安裝插件命令等。
config:配置文件目錄。
data:數據存儲目錄。
lib:依賴包目錄。
logs:日誌文件目錄。
modules:模塊庫,例如x-pack的模塊。
plugins:插件目錄
二、安裝目錄下運行 bin/elasticsearch
來啓動 ES。三、默認在9200端口運行,請求 curl http://localhost:9200/
或者瀏覽器輸入 http://localhost:9200
,獲得一個 JSON 對象,其中包含當前節點、集羣、版本等信息。
{
}
集羣狀態經過 綠,黃,紅 來標識
綠色:集羣健康無缺,一切功能齊全正常,全部分片和副本均可以正常工做。
黃色:預警狀態,全部主分片功能正常,但至少有一個副本是不能正常工做的。此時集羣是能夠正常工做的,可是高可用性在某種程度上會受影響。
紅色:集羣不可正常使用。某個或某些分片及其副本異常不可用,這時集羣的查詢操做還能執行,可是返回的結果會不許確。對於分配到這個分片的寫入請求將會報錯,最終會致使數據的丟失。
當集羣狀態爲紅色時,它將會繼續從可用的分片提供搜索請求服務,可是你須要儘快修復那些未分配的分片。
ES的基本概念和基本操做介紹完了以後咱們可能還有不少疑惑,它們內部是如何運行的?主分片和副本分片是如何同步的?建立索引的流程是什麼樣的?ES如何將索引數據分配到不一樣的分片上的?以及這些索引數據是如何存儲的?爲何說ES是近實時搜索引擎而文檔的 CRUD (建立-讀取-更新-刪除) 操做是實時的?以及Elasticsearch 是怎樣保證更新被持久化在斷電時也不丟失數據?還有爲何刪除文檔不會馬上釋放空間?帶着這些疑問咱們進入接下來的內容。
下圖描述了3個節點的集羣,共擁有12個分片,其中有4個主分片(S0、S一、S二、S3)和8個副本分片(R0、R一、R二、R3),每一個主分片對應兩個副本分片,節點1是主節點(Master節點)負責整個集羣的狀態。
寫索引是隻能寫在主分片上,而後同步到副本分片。這裏有四個主分片,一條數據ES是根據什麼規則寫到特定分片上的呢?這條索引數據爲何被寫到S0上而不寫到S1或S2上?那條數據爲何又被寫到S3上而不寫到S0上了?
首先這確定不會是隨機的,不然未來要獲取文檔的時候咱們就不知道從何處尋找了。實際上,這個過程是根據下面這個公式決定的:
shard = hash(routing) % number_of_primary_shards
routing 是一個可變值,默認是文檔的 _id
,也能夠設置成一個自定義的值。routing 經過 hash 函數生成一個數字,而後這個數字再除以 number_of_primary_shards
(主分片的數量)後獲得餘數 。這個在 0 到 numberofprimary_shards-1 之間的餘數,就是咱們所尋求的文檔所在分片的位置。
這就解釋了爲何咱們要在建立索引的時候就肯定好主分片的數量而且永遠不會改變這個數量:由於若是數量變化了,那麼全部以前路由的值都會無效,文檔也再也找不到了。
因爲在ES集羣中每一個節點經過上面的計算公式都知道集羣中的文檔的存放位置,因此每一個節點都有處理讀寫請求的能力。在一個寫請求被髮送到某個節點後,該節點即爲前面說過的協調節點,協調節點會根據路由公式計算出須要寫到哪一個分片上,再將請求轉發到該分片的主分片節點上。
假如此時數據經過路由計算公式取餘後獲得的值是 shard = hash(routing) % 4 = 0,則具體流程以下:
客戶端向ES1節點(協調節點)發送寫請求,經過路由計算公式獲得值爲0,則當前數據應被寫到主分片S0上。
ES1節點將請求轉發到S0主分片所在的節點ES3,ES3接受請求並寫入到磁盤。
併發將數據複製到兩個副本分片R0上,其中經過樂觀併發控制數據的衝突。一旦全部的副本分片都報告成功,則節點ES3將向協調節點報告成功,協調節點向客戶端報告成功。
上面介紹了在ES內部索引的寫處理流程,這個流程是在ES的內存中執行的,數據被分配到特定的分片和副本上以後,最終是存儲到磁盤上的,這樣在斷電的時候就不會丟失數據。具體的存儲路徑可在配置文件 ../config/elasticsearch.yml
中進行設置,默認存儲在安裝目錄的data文件夾下。建議不要使用默認值,由於若ES進行了升級,則有可能致使數據所有丟失。
path.data: /path/to/data //索引數據
path.logs: /path/to/logs //日誌記錄
索引文檔以段的形式存儲在磁盤上,何爲段?索引文件被拆分爲多個子文件,則每一個子文件叫做段, 每個段自己都是一個倒排索引,而且段具備不變性,一旦索引的數據被寫入硬盤,就不可再修改。在底層採用了分段的存儲模式,使它在讀寫時幾乎徹底避免了鎖的出現,大大提高了讀寫性能。
段被寫入到磁盤後會生成一個提交點,提交點是一個用來記錄全部提交後段信息的文件。一個段一旦擁有了提交點,就說明這個段只有讀的權限,失去了寫的權限。相反,當段在內存中時,就只有寫的權限,而不具有讀數據的權限,意味着不能被檢索。
段的概念提出主要是由於:在早期全文檢索中爲整個文檔集合創建了一個很大的倒排索引,並將其寫入磁盤中。若是索引有更新,就須要從新全量建立一個索引來替換原來的索引。這種方式在數據量很大時效率很低,而且因爲建立一次索引的成本很高,因此對數據的更新不能過於頻繁,也就不能保證時效性。
索引文件分段存儲而且不可修改,那麼新增、更新和刪除如何處理呢?
新增,新增很好處理,因爲數據是新的,因此只須要對當前文檔新增一個段就能夠了。
刪除,因爲不可修改,因此對於刪除操做,不會把文檔從舊的段中移除而是經過新增一個 .del
文件,文件中會列出這些被刪除文檔的段信息。這個被標記刪除的文檔仍然能夠被查詢匹配到, 但它會在最終結果被返回前從結果集中移除。
更新,不能修改舊的段來進行反映文檔的更新,其實更新至關因而刪除和新增這兩個動做組成。會將舊的文檔在 .del
文件中標記刪除,而後文檔的新版本被索引到一個新的段中。可能兩個版本的文檔都會被一個查詢匹配到,但被刪除的那個舊版本文檔在結果集返回前就會被移除。
段被設定爲不可修改具備必定的優點也有必定的缺點,優點主要表如今:
不須要鎖。若是你歷來不更新索引,你就不須要擔憂多進程同時修改數據的問題。
一旦索引被讀入內核的文件系統緩存,便會留在哪裏,因爲其不變性。只要文件系統緩存中還有足夠的空間,那麼大部分讀請求會直接請求內存,而不會命中磁盤。這提供了很大的性能提高。
其它緩存(像filter緩存),在索引的生命週期內始終有效。它們不須要在每次數據改變時被重建,由於數據不會變化。
寫入單個大的倒排索引容許數據被壓縮,減小磁盤 I/O 和 須要被緩存到內存的索引的使用量。
段的不變性的缺點以下:
當對舊數據進行刪除時,舊數據不會立刻被刪除,而是在 .del
文件中被標記爲刪除。而舊數據只能等到段更新時才能被移除,這樣會形成大量的空間浪費。
如有一條數據頻繁的更新,每次更新都是新增新的標記舊的,則會有大量的空間浪費。
每次新增數據時都須要新增一個段來存儲數據。當段的數量太多時,對服務器的資源例如文件句柄的消耗會很是大。
在查詢的結果中包含全部的結果集,須要排除被標記刪除的舊數據,這增長了查詢的負擔。
介紹完了存儲的形式,那麼索引是寫入到磁盤的過程是這怎樣的?是不是直接調 fsync 物理性地寫入磁盤?
答案是顯而易見的,若是是直接寫入到磁盤上,磁盤的I/O消耗上會嚴重影響性能,那麼當寫數據量大的時候會形成ES停頓卡死,查詢也沒法作到快速響應。若是真是這樣ES也就不會稱之爲近實時全文搜索引擎了。
爲了提高寫的性能,ES並無每新增一條數據就增長一個段到磁盤上,而是採用延遲寫的策略。
每當有新增的數據時,就將其先寫入到內存中,在內存和磁盤之間是文件系統緩存,當達到默認的時間(1秒鐘)或者內存的數據達到必定量時,會觸發一次刷新(Refresh),將內存中的數據生成到一個新的段上並緩存到文件緩存系統 上,稍後再被刷新到磁盤中並生成提交點。
這裏的內存使用的是ES的JVM內存,而文件緩存系統使用的是操做系統的內存。新的數據會繼續的被寫入內存,但內存中的數據並非以段的形式存儲的,所以不能提供檢索功能。由內存刷新到文件緩存系統的時候會生成了新的段,並將段打開以供搜索使用,而不須要等到被刷新到磁盤。
在 Elasticsearch 中,寫入和打開一個新段的輕量的過程叫作 refresh (即內存刷新到文件緩存系統)。默認狀況下每一個分片會每秒自動刷新一次。這就是爲何咱們說 Elasticsearch 是近實時搜索,由於文檔的變化並非當即對搜索可見,但會在一秒以內變爲可見。咱們也能夠手動觸發 refresh, POST/_refresh
刷新全部索引, POST/nba/_refresh
刷新指定的索引。
Tips:儘管刷新是比提交輕量不少的操做,它仍是會有性能開銷。當寫測試的時候, 手動刷新頗有用,可是不要在生產> 環境下每次索引一個文檔都去手動刷新。並且並非全部的狀況都須要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日誌文件, 你可能想優化索引速度而不是> 近實時搜索, 這時能夠在建立索引時在
settings
中經過調大refresh_interval="30s"
的值 , 下降每一個索引的刷新頻率,設值時須要注意後面帶上時間單位,不然默認是毫秒。當refresh_interval=-1
時表示關閉索引的自動刷新。
雖然經過延時寫的策略能夠減小數據往磁盤上寫的次數提高了總體的寫入能力,可是咱們知道文件緩存系統也是內存空間,屬於操做系統的內存,只要是內存都存在斷電或異常狀況下丟失數據的危險。
爲了不丟失數據,Elasticsearch添加了事務日誌(Translog),事務日誌記錄了全部尚未持久化到磁盤的數據。添加了事務日誌後整個寫索引的流程以下圖所示。
一個新文檔被索引以後,先被寫入到內存中,可是爲了防止數據的丟失,會追加一份數據到事務日誌中。不斷有新的文檔被寫入到內存,同時也都會記錄到事務日誌中。這時新數據還不能被檢索和查詢。
當達到默認的刷新時間或內存中的數據達到必定量後,會觸發一次 refresh,將內存中的數據以一個新段形式刷新到文件緩存系統中並清空內存。這時雖然新段未被提交到磁盤,可是能夠提供文檔的檢索功能且不能被修改。
隨着新文檔索引不斷被寫入,當日志數據大小超過512M或者時間超過30分鐘時,會觸發一次 flush。內存中的數據被寫入到一個新段同時被寫入到文件緩存系統,文件系統緩存中數據經過 fsync 刷新到磁盤中,生成提交點,日誌文件被刪除,建立一個空的新日誌。
經過這種方式當斷電或須要重啓時,ES不只要根據提交點去加載已經持久化過的段,還須要工具Translog裏的記錄,把未持久化的數據從新持久化到磁盤上,避免了數據丟失的可能。
因爲自動刷新流程每秒會建立一個新的段 ,這樣會致使短期內的段數量暴增。而段數目太多會帶來較大的麻煩。每個段都會消耗文件句柄、內存和cpu運行週期。更重要的是,每一個搜索請求都必須輪流檢查每一個段而後合併查詢結果,因此段越多,搜索也就越慢。
Elasticsearch經過在後臺按期進行段合併來解決這個問題。小的段被合併到大的段,而後這些大的段再被合併到更大的段。段合併的時候會將那些舊的已刪除文檔從文件系統中清除。被刪除的文檔不會被拷貝到新的大段中。合併的過程當中不會中斷索引和搜索。
段合併在進行索引和搜索時會自動進行,合併進程選擇一小部分大小類似的段,而且在後臺將它們合併到更大的段中,這些段既能夠是未提交的也能夠是已提交的。合併結束後老的段會被刪除,新的段被 flush 到磁盤,同時寫入一個包含新段且排除舊的和較小的段的新提交點,新的段被打開能夠用來搜索。
段合併的計算量龐大, 並且還要吃掉大量磁盤 I/O,段合併會拖累寫入速率,若是任其發展會影響搜索性能。Elasticsearch在默認狀況下會對合並流程進行資源限制,因此搜索仍然有足夠的資源很好地執行。
磁盤在現代服務器上一般都是瓶頸。Elasticsearch 重度使用磁盤,你的磁盤能處理的吞吐量越大,你的節點就越穩定。這裏有一些優化磁盤 I/O 的技巧:
使用 SSD。就像其餘地方提過的, 他們比機械磁盤優秀多了。
使用 RAID 0。條帶化 RAID 會提升磁盤 I/O,代價顯然就是當一塊硬盤故障時整個就故障了。不要使用鏡像或者奇偶校驗 RAID 由於副本已經提供了這個功能。
另外,使用多塊硬盤,並容許 Elasticsearch 經過多個 path.data 目錄配置把數據條帶化分配到它們上面。
不要使用遠程掛載的存儲,好比 NFS 或者 SMB/CIFS。這個引入的延遲對性能來講徹底是背道而馳的。
若是你用的是 EC2,小心 EBS。即使是基於 SSD 的 EBS,一般也比本地實例的存儲要慢。
Elasticsearch爲了能快速找到某個term,先將全部的term排個序,而後根據二分法查找term,時間複雜度爲logN,就像經過字典查找同樣,這就是Term Dictionary。如今再看起來,彷佛和傳統數據庫經過B-Tree的方式相似。
可是若是term太多,term dictionary也會很大,放內存不現實,因而有了Term Index,就像字典裏的索引頁同樣,A開頭的有哪些term,分別在哪頁,能夠理解term index是一顆樹。這棵樹不會包含全部的term,它包含的是term的一些前綴。經過term index能夠快速地定位到term dictionary的某個offset,而後從這個位置再日後順序查找。
在內存中用FST方式壓縮term index,FST以字節的方式存儲全部的term,這種壓縮方式能夠有效的縮減存儲空間,使得term index足以放進內存,但這種方式也會致使查找時須要更多的CPU資源。演示地址:Build your own FST
對於存儲在磁盤上的倒排表一樣也採用了壓縮技術減小存儲所佔用的空間,更多能夠閱讀 Frame of Reference and Roaring Bitmaps。
給每一個文檔指定有序的具備壓縮良好的序列模式ID,避免隨機的UUID-4 這樣的 ID,這樣的ID壓縮比很低,會明顯拖慢 Lucene。
對於那些不須要聚合和排序的索引字段禁用Doc values。Doc Values是有序的基於document => field value的映射列表;
不須要作模糊檢索的字段使用 keyword類型代替 text 類型,這樣能夠避免在創建索引前對這些文本進行分詞。
若是你的搜索結果不須要近實時的準確度,考慮把每一個索引的 index.refreshinterval 改到 30s 。若是你是在作大批量導入,導入期間你能夠經過設置這個值爲 -1 關掉刷新,還能夠經過設置 index.numberof_replicas: 0關閉副本。別忘記在完工的時候從新開啓它。
避免深度分頁查詢建議使用Scroll進行分頁查詢。普通分頁查詢時,會建立一個from + size的空優先隊列,每一個分片會返回from + size 條數據,默認只包含文檔id和得分score給協調節點,若是有n個分片,則協調節點再對(from + size)× n 條數據進行二次排序,而後選擇須要被取回的文檔。當from很大時,排序過程會變得很沉重佔用CPU資源嚴重。
減小映射字段,只提供須要檢索,聚合或排序的字段。其餘字段可存在其餘存儲設備上,例如Hbase,在ES中獲得結果後再去Hbase查詢這些字段。
建立索引和查詢時指定路由routing值,這樣能夠精確到具體的分片查詢,提高查詢效率。路由的選擇須要注意數據的分佈均衡。
確保堆內存最小值( Xms )與最大值( Xmx )的大小是相同的,防止程序在運行時改變堆內存大小。Elasticsearch 默認安裝後設置的堆內存是 1 GB。可經過 ../config/jvm.option
文件進行配置,可是最好不要超過物理內存的50%和超過32GB。
GC 默認採用CMS的方式,併發可是有STW的問題,能夠考慮使用G1收集器。
ES很是依賴文件系統緩存(Filesystem Cache),快速搜索。通常來講,應該至少確保物理上有一半的可用內存分配到文件系統緩存。