[ES] ElasticSearch總結


一  ES簡介

1  ES簡介  

  Elasticsearch 是一個開源的搜索引擎,建立在全文搜索引擎庫 Apache Lucene 基礎之上用 Java 編寫的,它的內部使用 Lucene 做索引與搜索,但是它的目的是使全文檢索變得簡單, 通過隱藏 Lucene 的復雜性,取而代之的提供一套簡單一致的 RESTful API

  Elasticsearch 不僅僅只是一個全文搜索引擎。 它可以被下面這樣准確的形容:一個分布式的實時文檔存儲,每個字段可以被索引與搜索——作數據庫用或者一個分布式實時分析搜索引擎,能勝任上百個服務節點的擴展,並支持 PB 級別的結構化或者非結構化數據。

2  ES的特性

    (1)分布式:橫向擴展非常靈活

    (2)全文檢索:基於lucene的強大的全文檢索能力;

    (3)近實時搜索和分析:數據進入ES,可達到近實時搜索,還可進行聚合分析

    (4)高可用:容錯機制,自動發現新的或失敗的節點,重組和重新平衡數據

    (5)模式自由:ES的動態mapping機制可以自動檢測數據的結構和類型,創建索引並使數據可搜索。

    (6)RESTful API:JSON + HTTP

3  ES的架構

  

  (1)Gateway是ES用來存儲索引的文件系統,支持多種類型,包括Local FileSystem、Shared FileSystem等。

  (2)Gateway的上層是一個分布式的lucene框架,即Distributed Lucene Directory。

  (3)Lucene之上是ES的模塊,包括:索引模塊(Index Module)、搜索模塊(Search Module)、映射解析模塊(Mapping)等。  

  (4)ES模塊之上是 Discovery、Scripting和第三方插件。Discovery是ES的節點發現模塊,不同機器上的ES節點要組成集群需要進行消息通信,集群內部需要選舉master節點,這些工作都是由Discovery模塊完成。支持多種發現機制,如 Zen 、EC2、gce、Azure。

     Scripting用來支持在查詢語句中插入javascript、python等腳本語言,scripting模塊負責解析這些腳本,使用腳本語句性能稍低。ES也支持多種第三方插件。

  (5)再上層是ES的傳輸模塊和JMX.傳輸模塊支持多種傳輸協議,如 Thrift、memecached、http,默認使用http。JMX是java的管理框架,用來管理ES應用。

  (6)最上層是ES提供給用戶的接口,可以通過RESTful接口和ES集群進行交互。

4  ES的核心概念

  Near Realtime(NRT)近實時:數據提交索引后,立馬就可以搜索到。

  Cluster 集群:一個集群由一個唯一的名字標識,默認為“elasticsearch”。集群名稱非常重要,具有相同集群名的節點才會組成一個集群。集群名稱可以在配置文件中指定。

  Node 節點:存儲集群的數據,參與集群的索引和搜索功能。像集群有名字,節點也有自己的名稱,默認在啟動時會以一個隨機的UUID的前七個字符作為節點的名字,你可以為其指定任意的名字。通過集群名在網絡中發現同伴組成集群。一個節點也可是集群。

  Index 索引: 一個索引是一個文檔的集合(等同於solr中的集合)。每個索引有唯一的名字,通過這個名字來操作它。一個集群中可以有任意多個索引。

  Type 類型:指在一個索引中,可以索引不同類型的文檔,如用戶數據、博客數據。從6.0.0 版本起已廢棄,一個索引中只存放一類數據。

  Document 文檔:被索引的一條數據,索引的基本信息單元,以JSON格式來表示。

  Shard 分片:在創建一個索引時可以指定分成多少個分片來存儲。每個分片本身也是一個功能完善且獨立的“索引”,可以被放置在集群的任意節點上。分片的好處:

    允許我們水平切分/擴展容量

    可在多個分片上進行分布式的、並行的操作,提高系統的性能和吞吐量。

  注意:分片數創建索引時指定,創建后不可改了。備份數可以隨時改。

  Replication 備份: 一個分片可以有多個備份(副本)。備份的好處:

    高可用。一個主分片掛了,副本分片就頂上去

    擴展搜索的並發能力、吞吐量。搜索可以在所有的副本上並行運行。- 高並發下副本也可搜索。

  

  參考:https://www.cnblogs.com/leeSmall/p/9189078.html

二  索引數據結構

1  單詞 - 文檔矩陣

  單詞 - 文檔矩陣是表達兩者之間所具有的一種包含關系的概念模型,圖3-1展示了其含義。圖3-1的每列代表一個文檔,每行代表一個單詞,打對勾的位置代表包含關系。

  

  從縱向即文檔這個維度來看,每列代表文檔包含了哪些單詞,比如文檔1包含了詞匯1和詞匯4,而不包含其它單詞。

  從橫向即單詞這個維度來看,每行代表了哪些文檔包含了某個單詞。比如對於詞匯1來說,文檔1和文檔4中出現過單詞1,而其它文檔不包含詞匯1。矩陣中其它的行列也可作此種解讀。

  搜索引擎的索引其實就是實現“單詞-文檔矩陣”的具體數據結構。可以有不同的方式來實現上述概念模型,比如“倒排索引”、“簽名文件”、“后綴樹”等方式。但是各項實驗數據表明,“倒排索引”是實現單詞到文檔映射關系的最佳實現方式,所以本章主要介紹“倒排索引”的技術細節。

2  倒排索引基本概念

  1. 文檔(Document):一般搜索引擎的處理對象是互聯網網頁,而文檔這個概念要更寬泛些,代表以文本形式存在的存儲對象,相比網頁來說,涵蓋更多種形式,比如Word,PDF,html,XML等不同格式的文件都可以稱之為文檔。再比如一封郵件,一條短信,一條微博也可以稱之為文檔。

  2. 文檔集合(Document Collection):由若干文檔構成的集合稱之為文檔集合。比如海量的互聯網網頁或者說大量的電子郵件都是文檔集合的具體例子。

  3. 文檔編號(Document ID):在搜索引擎內部,會將文檔集合內每個文檔賦予一個唯一的內部編號,以此編號來作為這個文檔的唯一標識,這樣方便內部處理,每個文檔的內部編號即稱之為“文檔編號”,后文有時會用DocID來便捷地代表文檔編號。

  4. 單詞編號(Word ID):與文檔編號類似,搜索引擎內部以唯一的編號來表征某個單詞,單詞編號可以作為某個單詞的唯一表征。

  5. 倒排索引(Inverted Index):倒排索引是實現“單詞-文檔矩陣”的一種具體存儲形式,通過倒排索引,可以根據單詞快速獲取包含這個單詞的文檔列表。倒排索引主要由兩個部分組成:“單詞詞典”和“倒排文件

    (1)詞條(Term):索引里面最小的存儲和查詢單元,對於英文來說是一個單詞,對於中文來說一般指分詞后的一個詞。

    (2)單詞詞典(Lexicon):搜索引擎的通常索引單位是單詞,單詞詞典是由文檔集合中出現過的所有單詞構成的字符串集合,單詞詞典內每條索引項記載單詞本身的一些信息以及指向“倒排列表”的指針

    (3)倒排列表(PostingList):倒排列表記載了出現過某個單詞的所有文檔的文檔列表及單詞在該文檔中出現的位置信息,每條記錄稱為一個倒排項(Posting)。根據倒排列表,即可獲知哪些文檔包含某個單詞。

    (4)倒排文件(Inverted File):所有單詞的倒排列表往往順序地存儲在磁盤的某個文件里,這個文件即被稱之為倒排文件,倒排文件是存儲倒排索引的物理文件

  關於這些概念之間的關系,可以查看如下圖:

    

3  倒排索引簡單實例

  假設文檔集合包含五個文檔,每個文檔內容如圖所示,在圖中最左端一欄是每個文檔對應的文檔編號。我們的任務就是對這個文檔集合建立倒排索引。

   

  中文和英文等語言不同,單詞之間沒有明確分隔符號,所以首先要用分詞系統將文檔自動切分成單詞序列,這樣每個文檔就轉換為由單詞序列構成的數據流。

  為了系統后續處理方便,需要對每個不同的單詞賦予唯一的單詞編號,同時記錄下哪些文檔包含這個單詞,在如此處理結束后,我們可以得到最簡單的倒排索引(參考圖3-4)。

  在圖3-4中,“單詞ID”一欄記錄了每個單詞的單詞編號,第二欄是對應的單詞,第三欄即每個單詞對應的倒排列表。比如單詞“谷歌”,其單詞編號為1,倒排列表為{1,2,3,4,5},說明文檔集合中每個文檔都包含了這個單詞。
  

        圖3-4 簡單的倒排索引  

  之所以說上圖所示倒排索引是最簡單的,是因為這個索引系統只記載了哪些文檔包含某個單詞,而事實上,索引系統還可以記錄除此之外的更多信息。

  下圖是一個相對復雜些的倒排索引,與圖3-4的基本索引系統比,在單詞對應的倒排列表中不僅記錄了文檔編號,還記載了單詞頻率信息(TF),即這個單詞在某個文檔中的出現次數,之所以要記錄這個信息,是因為詞頻信息在搜索結果排序時,計算查詢和文檔相似度是很重要的一個計算因子,所以將其記錄在倒排列表中,以方便后續排序時進行分值計算。

  在圖3-5的例子里,單詞“創始人”的單詞編號為7,對應的倒排列表內容為:(3:1),其中的3代表文檔編號為3的文檔包含這個單詞,數字1代表詞頻信息,即這個單詞在3號文檔中只出現過1次,其它單詞對應的倒排列表所代表含義與此相同。

  

         圖3-5 帶有單詞頻率信息的倒排索引

  實用的倒排索引還可以記載更多的信息,圖3-6所示索引系統除了記錄文檔編號和單詞頻率信息外,額外記載了兩類信息,即每個單詞對應的“文檔頻率信息”(對應圖3-6的第三欄)以及在倒排列表中記錄單詞在某個文檔出現的位置信息。

   

  “文檔頻率信息”代表了在文檔集合中有多少個文檔包含某個單詞,之所以要記錄這個信息,其原因與單詞頻率信息一樣,這個信息在搜索結果排序計算中是非常重要的一個因子。而單詞在某個文檔中出現的位置信息並非索引系統一定要記錄的,在實際的索引系統里可以包含,也可以選擇不包含這個信息,之所以如此,因為這個信息對於搜索系統來說並非必需的,位置信息只有在支持“短語查詢”的時候才能夠派上用場。

     以單詞“拉斯”為例,其單詞編號為8,文檔頻率為2,代表整個文檔集合中有兩個文檔包含這個單詞,對應的倒排列表為:{(3;1;<4>),(5;1;<4>)},其含義為在文檔3和文檔5出現過這個單詞,單詞頻率都為1,單詞“拉斯”在兩個文檔中的出現位置都是4,即文檔中第四個單詞是“拉斯”。

     圖3-6所示倒排索引已經是一個非常完備的索引系統,實際搜索系統的倒排索引結構基本如此,區別無非是采取哪些具體的數據結構來實現上述邏輯結構。

     有了這個索引系統,搜索引擎可以很方便地響應用戶的查詢,比如用戶輸入查詢詞“Facebook”,搜索系統查找倒排索引,從中可以讀出包含這個單詞的文檔,這些文檔就是提供給用戶的搜索結果,而利用單詞頻率信息、文檔頻率信息即可以對這些候選搜索結果進行排序,計算文檔和查詢的相似性,按照相似性得分由高到低排序輸出,此即為搜索系統的部分內部流程。

4  單詞詞典

  單詞詞典是倒排索引中非常重要的組成部分,它用來維護文檔集合中出現過的所有單詞的相關信息,同時用來記載某個單詞對應的倒排列表在倒排文件中的位置信息。在支持搜索時,根據用戶的查詢詞,去單詞詞典里查詢,就能夠獲得相應的倒排列表,並以此作為后續排序的基礎。

  對於一個規模很大的文檔集合來說,可能包含幾十萬甚至上百萬的不同單詞,能否快速定位某個單詞,這直接影響搜索時的響應速度,所以需要高效的數據結構來對單詞詞典進行構建和查找,常用的數據結構包括哈希加鏈表結構和樹形詞典結構。

(1)哈希 + 鏈表

  如下圖所示,主體部分是哈希表,每個哈希表項保存一個指針,指針指向沖突鏈表,在沖突鏈表里,相同哈希值的單詞形成鏈表結構。之所以會有沖突鏈表,是因為兩個不同單詞獲得相同的哈希值,如果是這樣,在哈希方法里被稱做是一次沖突,可以將相同哈希值的單詞存儲在鏈表里,以供后續查找。

   

  在建立索引的過程中,詞典結構也會相應地被構建出來。比如在解析一個新文檔的時候,對於某個在文檔中出現的單詞T,首先利用哈希函數獲得其哈希值,之后根據哈希值對應的哈希表項讀取其中保存的指針,就找到了對應的沖突鏈表。如果沖突鏈表里已經存在這個單詞,說明單詞在之前解析的文檔里已經出現過。如果在沖突鏈表里沒有發現這個單詞,說明該單詞是首次碰到,則將其加入沖突鏈表里。通過這種方式,當文檔集合內所有文檔解析完畢時,相應的詞典結構也就建立起來了。

        在響應用戶查詢請求時,其過程與建立詞典類似,不同點在於即使詞典里沒出現過某個單詞,也不會添加到詞典內。以圖1-7為例,假設用戶輸入的查詢請求為單詞3,對這個單詞進行哈希,定位到哈希表內的2號槽,從其保留的指針可以獲得沖突鏈表,依次將單詞3和沖突鏈表內的單詞比較,發現單詞3在沖突鏈表內,於是找到這個單詞,之后可以讀出這個單詞對應的倒排列表來進行后續的工作,如果沒有找到這個單詞,說明文檔集合內沒有任何文檔包含單詞,則搜索結果為空。

(2)樹形結構

  B樹(或者B+樹)是另外一種高效查找結構,圖1-8是一個 B樹結構示意圖。B樹與哈希方式查找不同,需要字典項能夠按照大小排序(數字或者字符序),而哈希方式則無須數據滿足此項要求。

  B樹形成了層級查找結構,中間節點用於指出一定順序范圍的詞典項目存儲在哪個子樹中,起到根據詞典項比較大小進行導航的作用,最底層的葉子節點存儲單詞的地址信息,根據這個地址就可以提取出單詞字符串。
  

三  集群(Cluster)

  ES的集群搭建很簡單,不需要依賴第三方協調管理組件,自身內部就實現了集群的管理功能。ES集群由一個或多個Elasticsearch節點組成,每個節點配置相同的 cluster.name 即可加入集群,默認值為 “elasticsearch”。確保不同的環境中使用不同的集群名稱,否則最終會導致節點加入錯誤的集群。

  一個Elasticsearch服務啟動實例就是一個節點(Node)。節點通過 node.name 來設置節點名稱,如果不設置則在啟動時給節點分配一個隨機通用唯一標識符作為名稱。

  Solr 和 Elasticsearch 都是比較成熟的全文搜索引擎,能完成的功能和性能也基本一樣。但是 ES 本身就具有分布式的特性和易安裝使用的特點,而Solr的分布式需要借助第三方來實現,例如通過使用ZooKeeper來達到分布式協調管理。

1  發現機制

  ES內部是如何通過一個相同的設置 cluster.name 就能將不同的節點連接到同一個集群的?答案是 Zen Discovery

  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

2  節點角色

  每個節點既可以是 候選主節點 也可以是 數據節點,通過在配置文件 ../config/elasticsearch.yml中設置即可,默認都為 true

node.master:true//是否候選主節點
node.data: true//是否數據節點

  數據節點負責數據的存儲和相關的操作,例如對數據進行增、刪、改、查和聚合等操作,所以數據節點(data節點)對機器配置要求比較高,對CPU、內存和I/O的消耗很大。通常隨着集群的擴大,需要增加更多的數據節點來提高性能和可用性。

  候選主節點可以被選舉為主節點(master節點),集群中只有候選主節點才有選舉權和被選舉權,其他節點不參與選舉的工作。主節點負責創建索引、刪除索引、跟蹤哪些節點是群集的一部分,並決定哪些分片分配給相關的節點、追蹤集群中節點的狀態等,穩定的主節點對集群的健康是非常重要的。

  一個節點既可以是候選主節點也可以是數據節點,但是由於數據節點對CPU、內存核I/0消耗都很大,所以如果某個節點既是數據節點又是主節點,那么可能會對主節點產生影響從而對整個集群的狀態產生影響。

  因此為了提高集群的健康性,我們應該對Elasticsearch集群中的節點做好角色上的划分和隔離。可以使用幾個配置較低的機器群作為候選主節點群。

  主節點和其他節點之間通過Ping的方式互檢查,主節點負責Ping所有其他節點,判斷是否有節點已經掛掉。其他節點也通過Ping的方式判斷主節點是否處於可用狀態。

  雖然對節點做了角色區分,但是用戶的請求可以發往任何一個節點,並由該節點負責分發請求、收集結果等操作,而不需要主節點轉發,這種節點可稱之為協調節點,協調節點是不需要指定和配置的,集群中的任何節點都可以充當協調節點的角色。

3  腦裂現象

(1)可能導致腦裂的原因  

  同時如果由於網絡或其他原因導致集群中選舉出多個Master節點,使得數據更新時出現不一致,這種現象稱之為腦裂,即集群中不同的節點對於master的選擇出現了分歧,出現了多個master競爭。

  “腦裂”問題可能有以下幾個原因造成:

    1. 網絡問題:集群間的網絡延遲導致一些節點訪問不到master,認為master掛掉了從而選舉出新的master,並對master上的分片和副本標紅,分配新的主分片 

    2. 節點負載:主節點的角色既為master又為data,訪問量較大時可能會導致ES停止響應(假死狀態)造成大面積延遲,此時其他節點得不到主節點的響應認為主節點掛掉了,會重新選取主節點。

    3. 內存回收:主節點的角色既為master又為data,當data節點上的ES進程占用的內存較大,引發JVM的大規模內存回收,造成ES進程失去響應。

(2)如何優化防止腦裂  

  為了避免腦裂現象的發生,我們可以從原因着手通過以下幾個方面來做出優化措施:

    1. 適當調大響應時間:通過參數 discovery.zen.ping_timeout設置節點狀態的響應時間,默認為3s,可以適當調大,如果master在該響應時間的范圍內沒有做出響應應答,判斷該節點已經掛掉了。調大參數(如6s,discovery.zen.ping_timeout:6),可適當減少誤判。

    2. 選舉觸發條件:我們需要在候選集群中的節點的配置文件中設置參數 discovery.zen.munimum_master_nodes的值,這個參數表示在選舉主節點時需要參與選舉的候選主節點的節點數,默認值是1,官方建議取值 (master_eligibel_nodes/2)+1,其中 master_eligibel_nodes為候選主節點的個數。這樣做既能防止腦裂現象的發生,也能最大限度地提升集群的高可用性,因為只要不少於discovery.zen.munimum_master_nodes個候選節點存活,選舉工作就能正常進行。當小於這個值的時候,無法觸發選舉行為,集群無法使用,不會造成分片混亂的情況。

    3. 角色分離即是上面我們提到的候選主節點和數據節點進行角色分離,這樣可以減輕主節點的負擔,防止主節點的假死狀態發生,減少對主節點“已死”的誤判。

    候選主節點配置為:

      node.master: true

      node.data: false

    數據節點配置為:

      node.master: false

      node.data: true

(3)如何確定腦裂現象

  一個比較容易的方法是定時獲取每一個節點/_nodes響應,它返回了集群中所有節點的狀態報告,如果兩個節點返回的集群狀態不一樣,就是一個腦裂情況發生的警示信號。

  curl GET http://10.10.2.111:9200/_nodes

四  elasticsearch集群擴容和容災

1  集群健康

  Elasticsearch 的集群監控信息中包含了許多的統計數據,其中最為重要的一項就是集群健康,它在 status 字段中展示為 green 、 yellow 或者 red。

  在kibana中執行:GET /_cat/health?v

epoch      timestamp cluster   status node.total node.data shards  pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1571291441 13:50:41  jiesi-5.4 yellow          3         3   4635 2306    0    0        1             0                  -                100.0%

  其中我們可以看到當前我本地的集群健康狀態是yellow ,但這里問題來了,集群的健康狀況是如何進行判斷的呢?

green(很健康)
    所有的主分片和副本分片都正常運行。
yellow(亞健康)
    所有的主分片都正常運行,但不是所有的副本分片都正常運行。
red(不健康)
    有主分片沒能正常運行。

2 主分片和復制分片

  分片是Elasticsearch在集群中分發數據的關鍵。把分片想象成數據的容器。文檔存儲在分片中,然后分片分配到你集群中的節點上。當ES集群擴容或縮小,Elasticsearch將會自動在節點間遷移分片,以使集群保持平衡。 分片可以是主分片(primary shard)或者是復制分片(replica shard)。

  索引中的每個文檔都屬於一個單獨的主分片,所以主分片的數量決定了索引最多能存儲多少數據。 理論上主分片能存儲的數據大小是沒有限制的,限制取決於你實際的使用情況。分片的最大容量完全取決於你的使用狀況:硬件存儲的大小、文檔的大小和復雜度、如何索引 和查詢你的文檔,以及你期望的響應時間。

  復制分片只是主分片的一個副本,它可以防止硬件故障導致的數據丟失,同時可以提供讀請 求,比如搜索或者從別的shard取回文檔。 當索引創建完成的時候,主分片的數量就固定了,但是復制分片的數量可以隨時調整。 讓我們在集群中唯一一個空節點上創建一個叫做 blogs 的索引。默認情況下,一個索引被分配5個主分片,一個主分片默認只有一個復制分片。

重點:
shard分為兩種:
    1,primary shard --- 主分片
    2,replica shard --- 復制分片(或者稱為備份分片或者副本分片)

  需要注意的是,在業界有一個約定俗稱的東西,單說一個單詞shard一般指的是primary shard,而單說一個單詞replica就是指的replica shard。

  另外一個需要注意的是replica shard是相對於主分片而言的,如果說當前index有一個復制分片,那么相對於主分片來說就是每一個主分片都有一個復制分片,即如果有5個主分片就有5個復制分片,並且主分片和復制分片之間是一一對應的關系。

  很重要的一點:primary shard不能和自己的replica shard在同一個節點上。所以es最小的高可用配置為兩台服務器。 

3  擴容

  一般的擴容模式分為兩種,一種是水平擴容,一種是垂直擴容。

(1)垂直擴容:

  所謂的垂直擴容就是升級服務器,買性能更好的,更貴的然后替換原來的服務器,這種擴容方式不推薦使用。因為單台服務器的性能總是有瓶頸的。

(2)水平擴容:

  水平擴容也稱為橫向擴展,很簡單就是增加服務器的數量,這種擴容方式可持續性強,將眾多普通服務器組織到一起就能形成強大的計算能力。水平擴容 VS 垂直擴容用一句俗語來說再合適不過了:三個臭皮匠賽過諸葛亮。

(3)水平擴容的過程分析

  上面我們詳細介紹了分片,master和協調節點,接下來我們通過畫圖的方式一步步帶大家看看橫向擴容的過程。

首先呢需要鋪墊一點關於自定義索引shard數量的操作

PUT /student
{
    "settings" : {
       "number_of_shards" : 3,
       "number_of_replicas" : 1
    }
}

  以上代碼意味着我們創建的索引student將會分配三個primary shard和三個replica shard。

  1. 只有一台服務器 

   

  注:P代表primary shard, R代表replica shard。

  分析一下上面的過程,首先需要明確的兩點:

  1:primary shard和replica shard不能再同一台機器上,因為replica和shard在同一個節點上就起不到副本的作用了。

  2:當集群中只有一個節點的時候,node1節點將成為主節點。它將臨時管理集群級別的一些變更,例如新建或 刪除索引、增加或移除節點等。

  因為集群中只有一個節點,該節點將直接被選舉為master節點。其次我們為student索引分配了三個shard,由於只有一個節點,所以三個primary shard都被分配到該節點,replica shard將不會被分配。此時集群的健康狀況為yellow。

  2. 兩台服務器

  

  Rebalance(再平衡),當集群中節點數量發生變化時,將會觸發es集群的rebalance,即重新分配shard。Rebalance的原則就是盡量使shard在節點中分布均勻,達到負載均衡的目的。

  原先node1節點上有p0、p1、p2三個primary shard,另外三個replica shard還未分配,當集群新增節點node2,觸發集群的Rebalance,三個replica shard將被分配到node2上,即如上圖所示。

  此時集群中所有的primary shard和replica shard都是active(可用)狀態的所以此時集群的健康狀況為green。可見es集群的最小高可用配置就是兩台服務器。

   3. 三台服務器

  

  繼續新增服務器,集群將再次進行Rebalance,在primary shard和replica shard不能分配到一個節點上的原則,這次rebalance同樣本着使shard均勻分布的原則,將會從node1上將P1,P2兩個primary shard分配到node2,node3上面,然后將node2在primary shard和replica shard不能分配到一台機器上的原則上將另外兩個replica shard分配到node1和node3上面。

4  集群容災

  分布式的集群是一定要具備容災能力的,對於es集群同樣如此,那es集群是如何進行容災的呢?

  replica shard作為primary shard的副本當集群中的節點發生故障,replica shard將被提升為primary shard。具體的演示如下

   

  集群中有三台服務器,其中node1節點為master節點,primary shard 和 replica shard的分布如上圖所示。此時假設node1發生宕機,也就是master節點發生宕機。此時集群的健康狀態為red,因為不是所有的primary shard都是active的。

  具體的容災過程如下:

    1:重新選舉master節點,當es集群中的master節點發生故障,此時es集群將再次進行master的選舉,選舉出一個新的master節點。假設此時新的主節點為node2。

    2:node2被選舉為新的master節點,node2將作為master行使其分片分配的任務。

    3:replica shard升級,此時master節點會尋找node1節點上的P0分片的replica shard,發現其副本在node2節點上,然后將R0提升為primary shard。這個升級過程是瞬間完成的,就像按下一個開關一樣。因為每一個shard其實都是lucene的實例。此時集群如下所示,集群的健康狀態為yellow,因為不是每一個replica shard都是active的:

  

五  ES索引和分片

1  索引和分片的關系

  ES支持PB級全文搜索,當索引上的數據量太大的時候,ES通過水平拆分的方式將一個索引上的數據拆分出來分配到不同的數據塊上,拆分出來的數據庫塊稱之為一個分片。 

  1. 為了將數據添加到ES中,我們需要索引(index),索引是一個存儲關聯數據的地方。實際上,索引只是一個用來指定一個或多個分片的 "邏輯命名空間",實際數據都是存儲在索引對應的分片上。

  2. 一個分片(shard)是一個最小級別"工作單元",一個分片只是保存了索引中的所有數據的一部分,每個分片就是一個Lucene實例,並且它本身就是一個完整的搜索引擎。我們的文檔存儲在分片中,並且在分片中被索引,但是我們的應用程序不會直接與它們通信,取而代之的是,直接與索引通信

  3. 分片是ES在進群中分發數據的關鍵,可以把分片想象成數據的容器。文檔存儲在分片中,然后分片分配到集群中的節點上。當集群擴容或縮小,ES將會自動在節點間遷移分片,以使集群保持平衡。

  4. 分片可以是主分片或者是復制分片,索引中的每個文檔屬於一個單獨的主分片,所以主分片的數量決定了索引最多能存儲多少數據

  5. 理論上主分片能存儲的數據大小是沒有限制的,限制取決於你實際的使用情況:硬件存儲的大小,文檔的大小和復雜度、如何索引和查詢你的文檔,以及你期望的響應時間

  6. 復制分片只是主分片的一個副本,它可以防止硬件故障導致的數據丟失,同時可以提供請求,比如搜索或者從別的shard取回文檔。

  7. 在一個多分片的索引中寫入數據時,通過路由來確定具體寫入哪一個分片中,所以當索引創建完成的時候,主分片的數量就固定了,但是復制分片的數量可以隨時調整。

  分片的數量和下面介紹的副本數量都是可以通過創建索引時的 settings來配置,ES默認為一個索引創建5個主分片, 並分別為每個主分片創建一個副本。

PUT /myIndex
{
  "settings":{     "number_of_shards":5,     "number_of_replicas":1   } }

2  副本

  副本就是對分片的Copy,每個主分片都有一個或多個副本分片,當主分片異常時,副本可以提供數據的查詢等操作。主分片和對應的副本分片是不會在同一個節點上的,所以副本分片數的最大值是 n -1(其中n為節點數)

  對文檔的新建、索引和刪除請求都是寫操作,必須在主分片上面完成之后才能被復制到相關的副本分片,ES為了提高寫入的能力這個過程是並發寫的,同時為了解決並發寫的過程中數據沖突的問題,ES通過樂觀鎖的方式控制,每個文檔都有一個 _version (版本)號,當文檔被修改時版本號遞增。一旦所有的副本分片都報告寫成功才會向協調節點報告成功,協調節點向客戶端報告成功。

  從上圖可以看出為了達到高可用,Master節點會避免將主分片和副本分片放在同一個節點上。

  假設這時節點Node1服務宕機了或者網絡不可用了,那么主節點上主分片S0也就不可用了。幸運的是還存在另外兩個節點能正常工作,這時ES會重新選舉新的主節點,而且這兩個節點上存在我們的所需要的S0的所有數據,我們會將S0的副本分片提升為主分片,這個提升主分片的過程是瞬間發生的。此時集群的狀態將會為 yellow。

  為什么我們集群狀態是 yellow 而不是 green 呢?雖然我們擁有所有的2個主分片,但是同時設置了每個主分片需要對應兩份副本分片,而此時只存在一份副本分片。所以集群不能為 green 的狀態。如果我們同樣關閉了 Node2 ,我們的程序依然可以保持在不丟任何數據的情況下運行,因為Node3 為每一個分片都保留着一份副本。

  如果我們重新啟動Node1 ,集群可以將缺失的副本分片再次進行分配,那么集群的狀態又將恢復到原來的正常狀態。如果Node1依然擁有着之前的分片,它將嘗試去重用它們,只不過這時Node1節點上的分片不再是主分片而是副本分片了,如果期間有更改的數據只需要從主分片上復制修改的數據文件即可。

3  分片的詳細解說

  1. 我們能夠發送請求給集群中任意一個節點。每個節點都有能力處理任意請求。每個節點都知道任意文檔所在的節點。

  2. 新建索引和刪除請求都是寫操作,它們必須在主分片上成功完成才能復制到相關的復制分片上。

  3. 在主分片和復制分片上成功新建、索引或刪除一個文檔必要的順序步驟如下:

  

    (1) 客戶端給 Node1 發送新建、索引或刪除請求。

    (2) 節點使用文檔的 doc_id 確定文檔屬於主分片 P0 。轉發請求到Node3,主分片 P0 位於這個節點上。

    (3) Node3在主分片上執行請求,如果成功,它轉發請求到相應的位於Node1和Node2的復制分片 R0 上。當所有的復制分片報告成功,Node3報告成功到請求的節點,請求的節點再報告給客戶端。

    (4)客戶端接收到成功響應的時候,文檔的修改已經被用於主分片和所有的復制分片,修改生效了。

4  ES分片復制

  復制默認的值是sync。這將導致主分片得到復制分片的成功響應后才返回。

  如果你設置replication為async,請求在主分片上被執行后就會返回給客戶端。它依舊會轉發給復制節點,但你將不知道復制分片執行成功與否。

  上面的這個選項不建議使用。默認的sync復制允許ES強制反饋傳輸。async復制可能會因為在不等待其他分片就緒的情況下發送過多的請求而使ES過載。

六  ES索引過程

1  索引過程圖解

  寫索引是只能寫在主分片上,然后同步到副本分片。如果有四個主分片,一條數據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) % 3 = 2,則具體流程如下:

  (1)api向集群發送索引請求,集群會使用負載均衡節點來處理該請求,如果沒有單獨的負載均衡點,master節點會充當負載均衡點的角色。

  (2)負載均衡節點根據routing參數來計算要將該索引存儲到哪個primary shard上,然后將數據給到對應的primary shard。

  (3)對應的shard拿到數據后進行索引寫入,寫入成功后,將數據給到自己的replica shard。

  (4)當replica shard也將數據成功寫入后,返回成功的結果到負載均衡節點。

  (5)此時負載均衡節點才認為數據寫入成功,將成功索引的結果返回給請求的api

2  routing(路由)參數

1. routing參數的指定和計算原理

  每個document存放在哪個shard上是由routing參數決定的,那這個參數的值是什么,ElasticSearch又是怎么通過該參數來確定存放在哪個shard上呢?

  (1)routing參數的默認值為doc_id,也可以進行手動指定routing參數,可以是值,也可以是某個字段:

  PUT /index/type/id?routing=user_id 
  {
    "user_id":"M9472323048",
    "name":"zhangsan",
    "age":54
  }

  (2)ElasticSearch有個哈希算法,通過 Hash(routing) % number_of_shards算得存儲到哪個shard上面去,比如上面的語句,假設Hash("M9472323048") = 23,該index含有3個shard,則存儲到 23 % 3 = 2,即P2上面。shard編號取值為0 至 number_of_shards - 1。

2. 手動指定routing和自動routing的區別

  routing的值默認為_id字段,_id可以保證在集群中唯一,但是有時候需要手動指定routing來優化后續的查詢過程。因為routing確定,那就可以指定用哪個routing進行查詢,縮減了目標結果集,減少了ElasticSearch集群的壓力。

  • 使用自動routing:

    • 優點: 簡單,可以很均衡的分配每個shard中的文檔數量,做到負載均衡
    • 缺點: 當查詢一下復雜的數據時,需要到多個shard中查找,查詢偏慢
  • 使用手動routing:

    • 優點: 查詢時指定當初入庫的routing進行查詢,鎖定shard,直達目標,查詢速度快
    • 缺點: 麻煩,要保證存儲的均衡比較復雜

3  存儲過程

1  物理索引過程  

  在es中“索引”是分片(shard)的集合,在lucene中“索引”從宏觀上來說就是es中的一個分片,從微觀上來說就是segment的集合。

  

 

  文檔被索引的過程如上面所示,大致可以分為 內存緩沖區buffer、translog(事務日志)、filesystem cache(文件緩存系統)、系統磁盤這幾個部分,接下來我們梳理一下這個過程。

  階段1:

    這個階段很簡單,一個document文檔第一步會同時被寫進內存緩沖區buffer和translog。此時文檔還不能被檢索。

  階段2:

    refresh:內存緩沖區的documents每隔一秒會被refresh(刷新)到filesystem cache中的一個新的segment中,segment就是索引的最小單位,此時segment將會被打開供檢索。也就是說一旦文檔被刷新到文件系統緩存中,其就能被檢索使用了。這也是es近實時性(NRT)的關鍵。后面會詳細介紹。

  階段3:

    merge:每秒都會有新的segment生成,這將意味着用不了多久segment的數量就會爆炸,每個段都將十分消耗文件句柄、內存、和cpu資源。這將是系統無法忍受的,所以這時,我們急需將零散的segment進行合並。ES通過后台合並段解決這個問題。小段被合並成大段,再合並成更大的段。然后將新的segment打開供搜索,舊的segment刪除。

  階段4:

    flush:經過階段3合並后新生成的更大的segment將會被flush到系統磁盤上。這樣整個過程就完成了。但是這里留一個包袱就是flush的時機。在后面介紹translog的時候會介紹。

2  近實時花搜索(NRT)

  因為 per-segment search 機制,索引和搜索一個文檔之間是有延遲的。新的文檔會在幾分鍾內可以搜索,但是這依然不夠快。磁盤是瓶頸。提交一個新的段到磁盤需要 fsync 操作,確保段被物理地寫入磁盤,即時電源失效也不會丟失數據。但是 fsync 是昂貴的,它不能在每個文檔被索引的時就觸發。

  所以需要一種更輕量級的方式使新的文檔可以被搜索,這意味這移除 fsync 。

  位於Elasticsearch和磁盤間的是文件系統緩存。如前所說,在內存索引緩存中的文檔被寫入新的段,但是新的段首先寫入文件系統緩存,這代價很低,之后會被同步到磁盤,這個代價很大。但是一旦一個文件被寫入文件系統緩存,它也可以被打開和讀取,就像其他文件一樣。

  在es中每隔一秒寫入內存緩沖區的文檔就會被刷新到filesystem cache中的新的segment,也就意味着可以被搜索了。這就是ES的NRT——近實時性搜索。

  簡單介紹一下refresh API

  如果你遇到過你新增了doc,但是沒檢索到,很可能是因為還未自動進行refresh,這是你可以嘗試手動刷新:

  POST /student/_refresh

  性能優化

  在這里我們需要知道一點refresh過程是很消耗性能的。如果你的系統對實時性要求不高,可以通過API控制refresh的時間間隔,但是如果你的新系統很要求實時性,那你就忍受它吧。

  如果你對系統的實時性要求很低,我們可以調整refresh的時間間隔,調大一點將會在一定程度上提升系統的性能。

PUT /student
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

相關閱讀:https://www.cnblogs.com/hello-shf/p/11553317.html

七  ES存儲原理

  上面介紹了在ES內部索引的寫處理流程,這個流程是在ES的內存中執行的,數據被分配到特定的分片和副本上之后,最終是存儲到磁盤上的,這樣在斷電的時候就不會丟失數據。具體的存儲路徑可在配置文件 ../config/elasticsearch.yml中進行設置,默認存儲在安裝目錄的data文件夾下。建議不要使用默認值,因為若ES進行了升級,則有可能導致數據全部丟失。     

path.data:/path/to/data  //索引數據
path.logs:/path/to/logs  //日志記錄

1  分段存儲

  索引文檔以段的形式存儲在磁盤上,何為?索引文件被拆分為多個子文件,則每個子文件叫作, 每一個段本身都是一個倒排索引,並且段具有不變性,一旦索引的數據被寫入硬盤,就不可再修改。在底層采用了分段的存儲模式,使它在讀寫時幾乎完全避免了鎖的出現,大大提升了讀寫性能。

  段被寫入到磁盤后會生成一個提交點,提交點是一個用來記錄所有提交后段信息的文件。一個段一旦擁有了提交點,就說明這個段只有讀的權限,失去了寫的權限。相反,當段在內存中時,就只有寫的權限,而不具備讀數據的權限,意味着不能被檢索。

  段的概念提出主要是因為:在早期全文檢索中為整個文檔集合建立了一個很大的倒排索引,並將其寫入磁盤中。如果索引有更新,就需要重新全量創建一個索引來替換原來的索引。這種方式在數據量很大時效率很低,並且由於創建一次索引的成本很高,所以對數據的更新不能過於頻繁,也就不能保證時效性。

  索引文件分段存儲並且不可修改,那么新增、更新和刪除如何處理呢?

  1. 新增,新增很好處理,由於數據是新的,所以只需要對當前文檔新增一個段就可以了。

  2. 刪除,由於不可修改,所以對於刪除操作,不會把文檔從舊的段中移除而是通過新增一個 .del文件,文件中會列出這些被刪除文檔的段信息。這個被標記刪除的文檔仍然可以被查詢匹配到, 但它會在最終結果被返回前從結果集中移除。

  3. 更新,不能修改舊的段來進行反映文檔的更新,其實更新相當於是刪除和新增這兩個動作組成。會將舊的文檔在 .del文件中標記刪除,然后文檔的新版本被索引到一個新的段中。可能兩個版本的文檔都會被一個查詢匹配到,但被刪除的那個舊版本文檔在結果集返回前就會被移除。

  段被設定為不可修改具有一定的優勢也有一定的缺點,優勢主要表現在:

  1. 不需要鎖。如果你從來不更新索引,你就不需要擔心多進程同時修改數據的問題。

  2. 一旦索引被讀入內核的文件系統緩存,便會留在哪里,由於其不變性。只要文件系統緩存中還有足夠的空間,那么大部分讀請求會直接請求內存,而不會命中磁盤。這提供了很大的性能提升。

  3. 其它緩存(像filter緩存),在索引的生命周期內始終有效。它們不需要在每次數據改變時被重建,因為數據不會變化。

  4. 寫入單個大的倒排索引允許數據被壓縮,減少磁盤 I/O 和 需要被緩存到內存的索引的使用量。

  段的不變性的缺點如下:

  1. 當對舊數據進行刪除時,舊數據不會馬上被刪除,而是在 .del文件中被標記為刪除。而舊數據只能等到段更新時才能被移除,這樣會造成大量的空間浪費。

  2. 若有一條數據頻繁的更新,每次更新都是新增新的標記舊的,則會有大量的空間浪費。

  3. 每次新增數據時都需要新增一個段來存儲數據。當段的數量太多時,對服務器的資源例如文件句柄的消耗會非常大。

  4. 在查詢的結果中包含所有的結果集,需要排除被標記刪除的舊數據,這增加了查詢的負擔。

2  延遲寫策略

  介紹完了存儲的形式,那么索引是寫入到磁盤的過程是這怎樣的?是否是直接調 fsync 物理性地寫入磁盤?

  答案是顯而易見的,如果是直接寫入到磁盤上,磁盤的I/O消耗上會嚴重影響性能,那么當寫數據量大的時候會造成ES停頓卡死,查詢也無法做到快速響應。如果真是這樣ES也就不會稱之為近實時全文搜索引擎了。

  為了提升寫的性能,ES並沒有每新增一條數據就增加一個段到磁盤上,而是采用延遲寫的策略。

  每當有新增的數據時,就將其先寫入到內存中,在內存和磁盤之間是文件系統緩存,當達到默認的時間(1秒鍾)或者內存的數據達到一定量時,會觸發一次刷新(Refresh),將內存中的數據生成到一個新的段上並緩存到文件緩存系統 上,稍后再被刷新到磁盤中並生成提交點

  這里的內存使用的是ES的JVM內存,而文件緩存系統使用的是操作系統的內存。新的數據會繼續的被寫入內存,但內存中的數據並不是以段的形式存儲的,因此不能提供檢索功能。由內存刷新到文件緩存系統的時候會生成了新的段,並將段打開以供搜索使用,而不需要等到被刷新到磁盤。

  在 Elasticsearch 中,寫入和打開一個新段的輕量的過程叫做 refresh (即內存刷新到文件緩存系統)。默認情況下每個分片會每秒自動刷新一次。這就是為什么我們說 Elasticsearch 是近實時搜索,因為文檔的變化並不是立即對搜索可見,但會在一秒之內變為可見。我們也可以手動觸發 refresh, POST/_refresh 刷新所有索引, POST/nba/_refresh刷新指定的索引。

  雖然通過延時寫的策略可以減少數據往磁盤上寫的次數提升了整體的寫入能力,但是我們知道文件緩存系統也是內存空間,屬於操作系統的內存,只要是內存都存在斷電或異常情況下丟失數據的危險。

  為了避免丟失數據,Elasticsearch添加了事務日志(Translog),事務日志記錄了所有還沒有持久化到磁盤的數據。添加了事務日志后整個寫索引的流程如下圖所示。

 

  1. 一個新文檔被索引之后,先被寫入到內存中,但是為了防止數據的丟失,會追加一份數據到事務日志中。不斷有新的文檔被寫入到內存,同時也都會記錄到事務日志中。這時新數據還不能被檢索和查詢。

  2. 當達到默認的刷新時間或內存中的數據達到一定量后,會觸發一次 refresh,將內存中的數據以一個新段形式刷新到文件緩存系統中並清空內存。這時雖然新段未被提交到磁盤,但是可以提供文檔的檢索功能且不能被修改。

  3. 隨着新文檔索引不斷被寫入,當日志數據大小超過512M或者時間超過30分鍾時,會觸發一次 flush。內存中的數據被寫入到一個新段同時被寫入到文件緩存系統,文件系統緩存中數據通過 fsync 刷新到磁盤中,生成提交點,日志文件被刪除,創建一個空的新日志。

3  段合並

  由於自動刷新流程每秒會創建一個新的段 ,這樣會導致短時間內的段數量暴增。而段數目太多會帶來較大的麻煩。每一個段都會消耗文件句柄、內存和cpu運行周期。更重要的是,每個搜索請求都必須輪流檢查每個段然后合並查詢結果,所以段越多,搜索也就越慢。

  Elasticsearch通過在后台定期進行段合並來解決這個問題。小的段被合並到大的段,然后這些大的段再被合並到更大的段。段合並的時候會將那些舊的已刪除文檔從文件系統中清除。被刪除的文檔不會被拷貝到新的大段中。合並的過程中不會中斷索引和搜索。

 

  段合並在進行索引和搜索時會自動進行,合並進程選擇一小部分大小相似的段,並且在后台將它們合並到更大的段中,這些段既可以是未提交的也可以是已提交的。合並結束后老的段會被刪除,新的段被 flush 到磁盤,同時寫入一個包含新段且排除舊的和較小的段的新提交點,新的段被打開可以用來搜索。

  段合並的計算量龐大, 而且還要吃掉大量磁盤 I/O,段合並會拖累寫入速率,如果任其發展會影響搜索性能。Elasticsearch在默認情況下會對合並流程進行資源限制,所以搜索仍然有足夠的資源很好地執行。

八  ES使用中的坑

1  實時性要求高的查詢走DB

  對於ES寫入機制的有了解的同學可能會知道,新增的文檔會被收集到Indexing Buffer,然后寫入到文件系統緩存中,到了文件系統緩存中就可以像其他的文件一樣被索引到。

  然而默認情況文檔從Indexing Buffer到文件系統緩存(即Refresh操作)是每秒分片自動刷新,所以這就是我們說ES是近實時搜索而非實時的原因:文檔的變化並不是立即對搜索可見,但會在一秒之內變為可見。

  當前訂單系統ES采用的是默認Refresh配置,故對於那些訂單數據實時性比較高的業務,直接走數據庫查詢,保證數據的准確性。

  

2  避免深分頁查詢

  ES集群的分頁查詢支持from和size參數,查詢的時候,每個分片必須構造一個長度為from+size的優先隊列,然后回傳到網關節點,網關節點再對這些優先隊列進行排序找到正確的size個文檔。

  假設在一個有6個主分片的索引中,from為10000,size為10,每個分片必須產生10010個結果,在網關節點中匯聚合並60060個結果,最終找到符合要求的10個文檔。

  由此可見,當from足夠大的時候,就算不發生OOM,也會影響到CPU和帶寬等,從而影響到整個集群的性能。所以應該避免深分頁查詢,盡量不去使用。

  比如執行如下查詢:

 GET /student/student/_search
 {
  "query":{
     "match_all": {}
  },
   "from":5000,
   "size":10
 }

  意味着 es 需要在各個分片上匹配排序並得到5010條數據,協調節點拿到這些數據再進行排序等處理,然后結果集中取最后10條數據返回。

  我們會發現這樣的深度分頁將會使得效率非常低,因為我只需要查詢10條數據,而es則需要執行from+size條數據然后處理后返回。

  其次:es為了性能,限制了我們分頁的深度,es目前支持的最大的 max_result_window = 10000;也就是說我們不能分頁到10000條數據以上。 

  參考:https://www.cnblogs.com/hello-shf/p/11543453.html

  深度分頁之scroll

  在es中如果我們分頁要請求大數據集或者一次請求要獲取較大的數據集,scroll都是一個非常好的解決方案。

  使用scroll滾動搜索,可以先搜索一批數據,然后下次再搜索一批數據,以此類推,直到搜索出全部的數據來。

  scroll搜索會在第一次搜索的時候,保存一個當時的視圖快照,之后只會基於該舊的視圖快照提供數據搜索,如果這個期間數據變更,是不會讓用戶看到的。

  每次發送scroll請求,我們還需要指定一個scroll參數,指定一個時間窗口,每次搜索請求只要在這個時間窗口內能完成就可以了。

  一個滾屏搜索允許我們做一個初始階段搜索並且持續批量從Elasticsearch里拉取結果直到沒有結果剩下。這有點像傳統數據庫里的cursors(游標)。

  滾屏搜索會及時制作快照。這個快照不會包含任何在初始階段搜索請求后對index做的修改。它通過將舊的數據文件保存在手邊,所以可以保護index的樣子看起來像搜索開始時的樣子。這樣將使得我們無法得到用戶最近的更新行為。

  scroll的使用很簡單

  執行如下curl,每次請求兩條。可以定制 scroll = 5m意味着該窗口過期時間為5分鍾。

GET /student/student/_search?scroll=5m
 {
   "query": {
     "match_all": {}
   },
   "size": 2
 }

  search_after

  from + size的分頁方式雖然是最靈活的分頁方式,但是當分頁深度達到一定程度將會產生深度分頁的問題。scroll能夠解決深度分頁的問題,但是其無法實現實時查詢,即當scroll_id生成后無法查詢到之后數據的變更,因為其底層原理是生成數據的快照。這時 search_after應運而生。其是在es-5.X之后才提供的。

  search_after 是一種假分頁方式,根據上一頁的最后一條數據來確定下一頁的位置,同時在分頁請求的過程中,如果有索引數據的增刪改查,這些變更也會實時的反映到游標上。為了找到每一頁最后一條數據,每個文檔必須有一個全局唯一值,官方推薦使用 _uid 作為全局唯一值,但是只要能表示其唯一性就可以。

  為了演示,我們需要給上文中的student索引增加一個uid字段表示其唯一性。

GET /student/student/_search
 {
   "query":{
      "match_all": {}
    },
    "size":2,
    "sort":[
      {
        "uid": "desc"
     }  
   ]
 }

假設返回結果如下:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 6,
    "max_score" : null,
    "hits" : [
      {
        "_index" : "student",
        "_type" : "student",
        "_id" : "6",
        "_score" : null,
        "_source" : {
          "uid" : 1006,
          "name" : "dehua",
          "age" : 27,
          "class" : "3-1"
        },
        "sort" : [
        ]
      },
      {
        "_index" : "student",
        "_type" : "student",
        "_id" : "5",
        "_score" : null,
        "_source" : {
          "uid" : 1005,
          "name" : "fucheng",
          "age" : 23,
          "class" : "2-3"
        },
        "sort" : [
        ]
      }
    ]
  }
}
View Code

下一次分頁,需要將上述分頁結果集的最后一條數據的值帶上。

GET /student/student/_search
{
  "query":{
    "match_all": {}
  },
  "size":2,
  "search_after":[1005],
  "sort":[
    {
      "uid": "desc"
    }  
  ]
}

  這樣我們就使用search_after方式實現了分頁查詢。

  

3  FieldData與Doc Values

(1)FieldData

  線上查詢出現偶爾超時的情況,通過調試查詢語句,定位到是跟排序有關系。排序在es1.x版本使用的是FieldData結構,FieldData占用的是JVM Heap內存,JVM內存是有限,對於FieldData Cache會設定一個閾值。

  如果空間不足時,使用最久未使用(LRU)算法移除FieldData,同時加載新的FieldData Cache,加載的過程需要消耗系統資源,且耗時很大。所以導致這個查詢的響應時間暴漲,甚至影響整個集群的性能。針對這種問題,解決方式是采用Doc Values。

(2)Doc Values

  Doc Values是一種列式的數據存儲結構,跟FieldData很類似,但其存儲位置是在Lucene文件中,即不會占用JVM Heap。隨着ES版本的迭代,Doc Values比FieldData更加穩定,Doc Values在2.x起為默認設置。

  

   一方面,doc_values比fielddata慢一點,大概10-25%,但是具有更好的穩定性。

  另一方面,doc_values寫入磁盤文件中,OS Cache先進行緩存,以提升訪問doc value正排索引的性能,如果OS Cache內存大小不足夠放得下整個正排索引,doc value,就會將doc value的數據寫入磁盤文件中。

九  ES寫一致性保障

  首先需要說明的一點,增刪改其實都是一個寫操作,所以這里的寫指的是增刪改三個操作。

  這里我們所說的寫一致性指的是primary shard和replica shard上數據的一致性。es API為我們提供了一個可自定的參數consistency。該參數可以讓我們自定義處理一次增刪改請求,是不是必須要求所有分片都是active的才會執行。

  該參數可選的值有三個:oneallquorum(default,默認)。

  one:要求我們這個寫操作,只要有一個primary shard是active活躍可用的,就可以執行。
  all:要求我們這個寫操作,必須所有的primary shard和replica shard都是活躍的,才可以執行這個寫操作
  quorum:默認的值,要求所有的shard中,必須是大部分的shard都是活躍的,可用的,才可以執行這個寫操作

  這里着重介紹一下quorum,quorum機制,寫之前必須確保大多數shard都可用,下面有個公式,當集群中的active(可用)分片數量達到如下公式結果時寫操作就是可以執行的。否則該操作將無法進行:

int( (primary + number_of_replicas) / 2 ) + 1

  假設我們創建了一個student索引,並且設置primary shard為3個,replica shard有1個(這個1個是相對於索引來說的,對於主分片該數字1意味着每個primary shard都對應的存在一個副本)。也就意味着primary=3,number_of_replicas=1(依然是相對於索引)。shard總數為6。

  此時計算上面公式可知:

  int((3+1)/2) + 1 = 3
  也就是說當集群中可用的shard數量>=3寫操作就是可以執行的。
  PUT /index/type/id?consistency=quorum

  但是這里我們要注意一點,舉例說明:比如新建一個索引,有一個主分片,一個副分片,那(1 + 1/2) + 1 = 2,那就必須要有兩個節點活躍才能執行寫操作,那咱們要是只有單節點集群,這就無法玩了,所以es對這種特殊情況,做了處理,就是說當number_of_replicas>1時才生效

  另外quorum不齊全時,不會立即判定寫入失敗,而是進入wait狀態,默認1分鍾,等待期間,期望活躍的shard數量可以增加,最后實在不行,就會timeout。我們其實可以在寫操作的時候,加一個timeout參數,比如說

  put /index/type/id?timeout=30

  這個就是說自己去設定quorum不齊全的時候,es的timeout時長,可以縮短,也可以增長

 

參考:

  搜索引擎索引之索引基礎  https://blog.csdn.net/malefactor/article/details/7256305

  ES索引和分片  https://www.cnblogs.com/1234AAA/p/9380791.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM