Elasticsearch的架構原理剖析

Elasticsearch 是最近兩年異軍突起的一個兼有搜索引擎和NoSQL數據庫功能的開源系統,基於Java/Lucene構建。
Elasticsearch 看名字就能大概瞭解下它是一個彈性的搜索引擎。首先彈性隱含的意思是分佈式,單機系統是無法彈起來的,而後加上靈活的伸縮機制,就是這裏的 Elastic 包含的意思。它的搜索存儲功能主要是 Lucene 提供的,Lucene 至關於其存儲引擎,它在之上封裝了索引,查詢,以及分佈式相關的接口。java

Elasticsearch 中的幾個概念
集羣(Cluster)一組擁有共同的 cluster name 的節點。
節點(Node) 集羣中的一個 Elasticearch 實例。
索引(Index) 至關於關係數據庫中的database概念,一個集羣中能夠包含多個索引。這個是個邏輯概念。
主分片(Primary shard) 索引的子集,索引能夠切分紅多個分片,分佈到不一樣的集羣節點上。分片對應的是 Lucene 中的索引。
副本分片(Replica shard)每一個主分片能夠有一個或者多個副本。
類型(Type)至關於數據庫中的table概念,mapping是針對 Type 的。同一個索引裏能夠包含多個 Type。
Mapping 至關於數據庫中的schema,用來約束字段的類型,不過 Elasticsearch 的 mapping 能夠自動根據數據建立。
文檔(Document) 至關於數據庫中的row。
字段(Field)至關於數據庫中的column。
分配(Allocation) 將分片分配給某個節點的過程,包括分配主分片或者副本。若是是副本,還包含從主分片複製數據的過程。node

分佈式以及 Elastic
分佈式系統要解決的第一個問題就是節點之間互相發現以及選主的機制。若是使用了 Zookeeper/Etcd 這樣的成熟的服務發現工具,這兩個問題都一併解決了。但 Elasticsearch 並無依賴這樣的工具,帶來的好處是部署服務的成本和複雜度下降了,不用預先依賴一個服務發現的集羣,缺點固然是將複雜度帶入了 Elasticsearch 內部。算法

服務發現以及選主 ZenDiscovery
節點啓動後先ping(這裏的ping是 Elasticsearch 的一個RPC命令。若是 discovery.zen.ping.unicast.hosts 有設置,則ping設置中的host,不然嘗試ping localhost 的幾個端口, Elasticsearch 支持同一個主機啓動多個節點)
Ping的response會包含該節點的基本信息以及該節點認爲的master節點。
選舉開始,先從各節點認爲的master中選,規則很簡單,按照id的字典序排序,取第一個。
若是各節點都沒有認爲的master,則從全部節點中選擇,規則同上。這裏有個限制條件就是 discovery.zen.minimum_master_nodes,若是節點數達不到最小值的限制,則循環上述過程,直到節點數足夠能夠開始選舉。
最後選舉結果是確定能選舉出一個master,若是隻有一個local節點那就選出的是本身。
若是當前節點是master,則開始等待節點數達到 minimum_master_nodes,而後提供服務。
若是當前節點不是master,則嘗試加入master。
Elasticsearch 將以上服務發現以及選主的流程叫作 ZenDiscovery 。因爲它支持任意數目的集羣(1-N),因此不能像 Zookeeper/Etcd 那樣限制節點必須是奇數,也就沒法用投票的機制來選主,而是經過一個規則,只要全部的節點都遵循一樣的規則,獲得的信息都是對等的,選出來的主節點確定是一致的。但分佈式系統的問題就出在信息不對等的狀況,這時候很容易出現腦裂(Split-Brain)的問題,大多數解決方案就是設置一個quorum值,要求可用節點必須大於quorum(通常是超過半數節點),才能對外提供服務。而 Elasticsearch 中,這個quorum的配置就是 discovery.zen.minimum_master_nodes 。 說到這裏要吐槽下 Elasticsearch 的方法和變量命名,它的方法和配置中的master指的是master的候選節點,也就是說可能成爲master的節點,並非表示當前的master,我就被它的一個 isMasterNode 方法坑了,開始一直沒能理解它的選舉規則。spring

彈性伸縮 Elastic
Elasticsearch 的彈性體如今兩個方面:
服務發現機制讓節點很容易加入和退出。
豐富的設置以及allocation API。
Elasticsearch 節點啓動的時候只須要配置discovery.zen.ping.unicast.hosts,這裏不須要列舉集羣中全部的節點,只要知道其中一個便可。固然爲了不重啓集羣時正好配置的節點掛掉,最好多配置幾個節點。節點退出時只須要調用 API 將該節點從集羣中排除 (Shard Allocation Filtering),系統會自動遷移該節點上的數據,而後關閉該節點便可。固然最好也將不可用的已知節點從其餘節點的配置中去除,避免下次啓動時出錯。sql

分片(Shard)以及副本(Replica)
分佈式存儲系統爲了解決單機容量以及容災的問題,都須要有分片以及副本機制。Elasticsearch 沒有采用節點級別的主從複製,而是基於分片。它當前還未提供分片切分(shard-splitting)的機制,只能建立索引的時候靜態設置。
好比,開始設置爲5個分片,在單個節點上,後來擴容到5個節點,每一個節點有一個分片。若是繼續擴容,是不能自動切分進行數據遷移的。官方文檔的說法是分片切分紅本和從新索引的成本差很少,因此建議乾脆經過接口從新索引。
Elasticsearch 的分片默認是基於id 哈希的,id能夠用戶指定,也能夠自動生成。但這個能夠經過參數(routing)或者在mapping配置中修改。
Elasticsearch 禁止同一個分片的主分片和副本分片在同一個節點上,因此若是是一個節點的集羣是不能有副本的。數據庫

恢復以及容災
分佈式系統的一個要求就是要保證高可用。前面描述的退出流程是節點主動退出的場景,但若是是故障致使節點掛掉,Elasticsearch 就會主動allocation。但若是節點丟失後馬上allocation,稍後節點恢復又馬上加入,不會形成浪費。Elasticsearch的恢復流程大體以下:
集羣中的某個節點丟失網絡鏈接
master提高該節點上的全部主分片的在其餘節點上的副本爲主分片
cluster集羣狀態變爲 yellow ,由於副本數不夠
等待一個超時設置的時間,若是丟失節點回來就能夠當即恢復(默認爲1分鐘,經過 index.unassigned.node_left.delayed_timeout 設置)。若是該分片已經有寫入,則經過translog進行增量同步數據。
不然將副本分配給其餘節點,開始同步數據。
但若是該節點上的分片沒有副本,則沒法恢復,集羣狀態會變爲red,表示可能要丟失該分片的數據了。
分佈式集羣的另一個問題就是集羣整個重啓後可能致使預期的分片從新分配(部分節點沒有啓動完成的時候,集羣覺得節點丟失),浪費帶寬。因此 Elasticsearch 經過如下靜態配置(不能經過API修改)控制整個流程,以10個節點的集羣爲例:
gateway.recover_after_nodes: 8
gateway.expected_nodes: 10
gateway.recover_after_time: 5m
好比10個節點的集羣,按照上面的規則配置,當集羣重啓後,首先系統等待 minimum_master_nodes(6)個節點加入纔會選出master, recovery操做是在 master節點上進行的,因爲咱們設置了 recover_after_nodes(8),系統會繼續等待到8個節點加入, 纔開始進行recovery。當開始recovery的時候,若是發現集羣中的節點數小於expected_nodes,也就是還有部分節點未加入,因而開始recover_after_time 倒計時(若是節點數達到expected_nodes則馬上進行 recovery),5分鐘後,若是剩餘的節點依然沒有加入,則會進行數據recovery。
—————————————————————————————————————————————————————————
搜索引擎 Search
Elasticsearch 除了支持 Lucene 自己的檢索功能外,在之上作了一些擴展。express

腳本支持
Elasticsearch 默認支持groovy腳本,擴展了 Lucene 的評分機制,能夠很容易的支持複雜的自定義評分算法。它默認只支持經過sandbox方式實現的腳本語言(如lucene expression,mustache),groovy必須明確設置後才能開啓。Groovy的安全機制是經過java.security.AccessControlContext設置了一個class白名單來控制權限的。
Suggester Elasticsearch 經過擴展的索引機制,能夠實現像google那樣的自動完成suggestion以及搜索詞語錯誤糾正的suggestion。json

NoSQL 數據庫
Elasticsearch 能夠做爲數據庫使用,主要依賴於它的如下特性:
默認在索引中保存原始數據,並可獲取。這個主要依賴 Lucene 的store功能。
實現了translog,提供了實時的數據讀取能力以及完備的數據持久化能力(在服務器異常掛掉的狀況下依然不會丟數據)。Lucene 由於有 IndexWriter buffer, 若是進程異常掛掉,buffer中的數據是會丟失的。因此 Elasticsearch 經過translog來確保不丟數據。同時經過id直接讀取文檔的時候,Elasticsearch 會先嚐試從translog中讀取,以後才從索引中讀取。也就是說,即使是buffer中的數據還沒有刷新到索引,依然能提供實時的數據讀取能力。Elasticsearch 的translog 默認是每次寫請求完成後統一fsync一次,同時有個定時任務檢測(默認5秒鐘一次)。若是業務場景須要更大的寫吞吐量,能夠調整translog相關的配置進行優化。
dynamic-mapping 以及 schema-free
Elasticsearch 的dynamic-mapping至關於根據用戶提交的數據,動態檢測字段類型,自動給數據庫表創建表結構,也能夠動態增長字段,因此它叫作schema-free,而不是schema-less。這種方式的好處是用戶能必定程度享受schema-less的好處,不用提早創建表結構,同時由於其實是有schema的,能夠作查詢上的優化,檢索效率要比純schema-less的數據庫高許多。但缺點就是已經建立的索引不能變動數據類型(Elasticsearch 寫入數據的時候若是類型不匹配會自動嘗試作類型轉換,若是失敗就會報錯,好比數字類型的字段寫入字符串」123」是能夠的,但寫入」abc」就不能夠。),要損失必定的自由度。
另外 Elasticsearch 提供的index-template功能方便用戶動態建立索引的時候預先設定索引的相關參數以及type mapping,好比按天建立日誌庫,template能夠設置爲對 log-* 的索引都生效。安全

豐富的QueryDSL功能
Elasticsearch 的query語法基本上和sql對等的,除了join查詢,以及嵌套臨時表查詢不能支持。不過 Elasticsearch 支持嵌套對象以及parent外部引用查詢,因此必定程度上能夠解決關聯查詢的需求。另外group by這種查詢能夠經過其aggregation實現。Elasticsearch 提供的aggregation能力很是強大,其生態圈裏的 Kibana 主要就是依賴aggregation來實現數據分析以及可視化的。服務器

系統架構
Elasticsearch 的依賴注入用的是guice,網絡使用netty,提供http rest和RPC兩種協議。
Elasticsearch 之因此用guice,而不是用spring作依賴注入,關鍵的一個緣由是guice能夠幫它很容易的實現模塊化,經過代碼進行模塊組裝,能夠很精確的控制依賴注入的管理範圍。好比 Elasticsearch 給每一個shard單獨生成一個injector,能夠將該shard相關的配置以及組件注入進去,下降編碼和狀態管理的複雜度,同時刪除shard的時候也方便回收相關對象。
ClusterState
前面咱們分析了 Elasticsearch 的服務發現以及選舉機制,它是內部本身實現的。服務發現工具作的事情其實就是跨服務器的狀態同步,多個節點修改同一個數據對象,須要有一種機制將這個數據對象同步到全部的節點。Elasticsearch 的ClusterState 就是這樣一個數據對象,保存了集羣的狀態,索引/分片的路由表,節點列表,元數據等,還包含一個ClusterBlocks,至關於分佈式鎖,用於實現分佈式的任務同步。
主節點上有個單獨的進程處理 ClusterState 的變動操做,每次變動會更新版本號。變動後會經過PRC接口同步到其餘節點。主節知道其餘節點的ClusterState 的當前版本,發送變動的時候會作diff,實現增量更新。

Rest 和 RPC

Elasticsearch 的rest請求的傳遞流程如上圖(這裏對實際流程作了簡化):
用戶發起http請求,Elasticsearch 的9200端口接受請求後,傳遞給對應的RestAction。
RestAction作的事情很簡單,將rest請求轉換爲RPC的TransportRequest,而後調用NodeClient,至關於用客戶端的方式請求RPC服務,只不過transport層會對本節點的請求特殊處理。
這樣作的好處是將http和RPC兩層隔離,增長部署的靈活性。部署的時候既能夠同時開啓RPC和http服務,也能夠用client模式部署一組服務專門提供http rest服務,另一組只開啓RPC服務,專門作data節點,便於分擔壓力。
Elasticsearch 的RPC的序列化機制使用了 Lucene 的壓縮數據類型,支持vint這樣的變長數字類型,省略了字段名,用流式方式按順序寫入字段的值。每一個須要傳輸的對象都須要實現:
void writeTo(StreamOutput out)
T readFrom(StreamInput in)
兩個方法。雖然這樣實現開發成本略高,增刪字段也不太靈活,但對 Elasticsearch 這樣的數據庫系統來講,不用考慮跨語言,增刪字段確定要考慮兼容性,這樣作效率最高。因此 Elasticsearch 的RPC接口只有java client能夠直接請求,其餘語言的客戶端都走的是rest接口。

網絡層
Elasticsearch 的網絡層抽象很值得借鑑。它抽象出一個 Transport 層,同時兼有client和server功能,server端接收其餘節點的鏈接,client維持和其餘節點的鏈接,承擔了節點之間請求轉發的功能。Elasticsearch 爲了不傳輸流量比較大的操做堵塞鏈接,因此會按照優先級建立多個鏈接,稱爲channel。
recovery: 2個channel專門用作恢復數據。若是爲了不恢復數據時將帶寬佔滿,還能夠設置恢復數據時的網絡傳輸速度。
bulk: 3個channel用來傳輸批量請求等基本比較低的請求。
regular: 6個channel用來傳輸通用正常的請求,中等級別。
state: 1個channel保留給集羣狀態相關的操做,好比集羣狀態變動的傳輸,高級別。
ping: 1個channel專門用來ping,進行故障檢測。
(3個節點的集羣鏈接示意,來源 Elasticsearch 官方博客)
每一個節點默認都會建立13個到其餘節點的鏈接,而且節點之間是互相鏈接的,每增長一個節點,該節點會到每一個節點建立13個鏈接,而其餘每一個節點也會建立13個連回來的鏈接。

線程池
因爲java不支持綠色線程(fiber/coroutine),線程池裏保留多少線程合適?如何避免慢的任務佔用線程池,致使其餘比較快的任務也得不到執行?不少應用系統裏,爲了不這種狀況,會隨手建立線程池,最後致使系統裏充塞了大的量的線程池,浪費資源。而 Elasticsearch 的解決方案是分優先級的線程池。它默認建立了10多個線程池,按照不一樣的優先級以及不一樣的操做進行劃分。而後提供了4種類型的線程池,不一樣的線程池使用不一樣的類型:
CACHED 最小爲0,無上限,無隊列(SynchronousQueue,沒有緩衝buffer),有存活時間檢測的線程池。通用的,但願能儘量支撐的任務。
DIRECT 直接在調用者的線程裏執行,其實這不算一種線程池方案,主要是爲了代碼邏輯上的統一而創造的一種線程類型。
FIXED 固定大小的線程池,帶有緩衝隊列。用於計算和IO的耗時波動較小的操做。
SCALING 有最小值,最大值的伸縮線程池,隊列是基於LinkedTransferQueue 改造的實現,和java內置的Executors生成的伸縮線程池的區別是優先增長線程,增長到最大值後纔會使用隊列,和java內置的線程池規則相反。用於計算和IO耗時都不太穩定,須要限制系統承載最大任務上限的操做。
這種解決方案雖然要求每一個用到線程池的地方都須要評估下執行成本以及應該用什麼樣的線程池,但好處是限制了線程池的泛濫,也緩解了不一樣類型的任務互相之間的影響。

Elasticsearch 如今主要的應用場景有三塊。站內搜索,主要和 Solr 競爭,屬於後起之秀。NoSQL json文檔數據庫,主要搶佔 Mongo 的市場,它在讀寫性能上優於 Mongo(見文末比較連接),同時也支持地理位置查詢,還方便地理位置和文本混合查詢,屬於歪打正着。監控,統計以及日誌類時間序的數據的存儲和分析以及可視化,這方面是引領者。

內容來源:http://jolestar.com/elasticsearch-architecture/#rd?sukey=3997c0719f1515201d882aeeb35124573c66a29fa2f0bb6ba2023993366bae7928cfbdab55cb22992f061648df3fce69

相關文章
相關標籤/搜索