[8]elasticsearch源碼深刻分析——Node與NodeEnvironment的實例化

本篇爲elasticsearch源碼分析系列文章的第八篇,又到了咱們深扒ElasticSearch源碼的時候了:)java

本篇開始將會詳細解釋Node實例化的過程,從Node實例化這個操做爲源點,瞭解ElasticSearch的編碼思想,因爲Node內容衆多,因此會分篇敘述。node

Node概覽

前不久的分析中說到了,Node是ElasticSearch啓動的重中之重,一個Node表明在一個集羣(cluster.name)中的一個節點。爲了使用客戶端對集羣進行操做,客戶端可使用Node中的client()來取得org.elasticsearch.client.Client的實例。正則表達式

任什麼時候候,啓動一個elasticsearch實例都是啓動Node的一個實例,多個Node實例的集合叫作Clusterapache

集羣中的節點默認均可以使用HTTP和Transport兩種方法通訊。transport的通訊可使用Java TransportClient,而HTTP就只能使用Rest Client了。app

集羣中的Node都能相互發現,並轉發請求到合適節點。並且每一個Node會有如下的一個或多個做用:elasticsearch

  • 經過設定node.master屬性值爲true(true爲默認值)被選舉爲Master節點
  • 經過設定node.Data屬性值爲true(true爲默認值)來充當數據節點,顧名思義,這種節點持有數據且能作數據的關聯操做
  • 經過設定node.ingest屬性值爲true(true爲默認值)來充當ingest node。ingest node是5.0新增的特性,簡單點說就是elasticsearch內置的數據處理器,目前提供了convert,grok之類的操做,相信用過Logstash的同窗必定不會陌生。
  • 經過設置tribe.屬性來使node成爲Tribe node*,它是一個特殊的客戶端,它能夠鏈接多個集羣,在全部鏈接的集羣上執行搜索和其餘操做

Node類首先構造了三個Setting屬性,分別是:函數

屬性名 key值 做用
WRITE_PORTS_FILE_SETTING node.portsfile 用於控制是否將文件寫入到包含給定傳輸類型端口的日誌目錄中
NODE_DATA_SETTING node.data 使該node被選舉爲data節點
NODE_MASTER_SETTING node.master 使該node被選舉爲master節點
NODE_INGEST_SETTING node.ingest 使該node被選舉爲ingest節點
NODE_LOCAL_STORAGE_SETTING local_storage 控制節點是否須要持久化元數據到磁盤,這和data node沒有必然聯繫,可是若是local_storage爲false,node.data和node.master的值必須爲false
NODE_NAME_SETTING node.name 節點名稱
NODE_ATTRIBUTES node.attr. 添加gateway,zone,rack_id等參數key
BREAKER_TYPE_KEY indices.breaker.type 斷路器類型,提供參數有hierarchy,none兩種,主要是防止內存溢出後elasticsearch宕機

Node實例化

三個Node的構造參數:源碼分析

Node的構造參數

最重要的構造方法是:ui

protected Node(final Environment environment, Collection<Class<? extends Plugin>> classpathPlugins)
複製代碼

該構造方法所作的工做:編碼

  • 用當前節點名稱設定臨時Logger(由於後續可能節點名稱會變更因此設定成臨時Logger)
  • 根據參數environment中的settings變量構造新的settings實例,添加默認的CLIENT_TYPE="node"值。
  • 用生成的新的settings實例和environment參數構建新的節點環境(NodeEnvironment
  • 構造plugins
  • 加載LocalNodeFactory
  • 構造ThreadPool,接收參數爲setting和plugins的builder
  • 構造scriptModule,analysisModule,settingsModule
  • 經過pluginsService構造NetworkService
  • 經過pluginsService構造ClusterPugins
  • 構造IngestService
  • 構造DiskThresholdMonitor
  • 構造ClusterInfoService
  • 構造UsageService
  • 實例化ModulesBuilder
  • 經過pluginsService構造SearchModule
  • 經過settingsModule構造CircuitBreakerService
  • 構造ActionModule
  • 構造NamedXContentRegistry
  • 構造MetaStateService
  • 構造IndicesService
  • 構造RestController
  • 構造NetworkModule
  • 構造MetaDataUpgrader
  • 構造TransportService
  • 構造ResponseCollectorService
  • 構造SearchTransportService
  • 構造DiscoveryModule
  • 構造NodeService
  • 向構造好的ModuleBuilder中添加全部須要的服務
  • 經過ModuleBuilder獲得Guice注入類
  • 構件LifecycleComponent集合
  • 初始化NodeClient

咱們的源碼解析也會按照這個流程來開展。

構建默認的Setting

在Node剛開始構造的時候,這個時候Node對象中尚未存在Setting實例的,有的配置只有在BootStrap方法中傳過來的Environment實例,這個Envi的實例(environment)其實就是解析了啓動環境中若干的配置路徑(lib路徑,module路徑,logs路徑),在對environment的setting化後(調用Environment的settings()方法,就是對初始的環境變量標準化爲Settings類型的對象),以下圖:

Environment的settings()方法

在構造完這個最初始版本的Settings後,代碼視圖取得配置中的node.name,爲何會在Node剛開始初始化的時候就去查找node的name呢?在跟進源碼後會知道,ElasticSearch這麼作是爲了給Logger的實例增長marker這個參數,相信對log4j熟悉的同窗會對這個參數很熟悉,merker是log4j中LayoutPattern的參數之一,做用是event元素中的標記元素,這種標記元素僅在日誌消息中使用標記時出現,且具備繼承性。以下圖:

logger中的marker元素

固然若是配置了node.name,且在log4j.properties中配置了屬性appender.console.layout.pattern包含元素**%marker**,那麼在控制檯中會很容易看到形以下圖中的日誌打印,這就能很容易區分出日誌的歸屬Node。

logger中的marker

固然到這裏咱們都還沒給Node設置名稱。

接下來給Node設置了client.type的值爲node,這個也是寫在代碼裏的配置。

private static final String CLIENT_TYPE = "node";
複製代碼

接下來開始就開始構建NodeEnvironment實例了。

NodeEnvironment的實例化

首先說明EnvironmentNodeEnvironment是沒有任何繼承關係的,只是在NodeEnvironment的實例化過程當中,Environment做爲了構建所必需的參數。NodeEnvironment主要是針對單個節點的包含全部數據路徑的構件對象,說白了這個類就是xxx,直接看NodeEnvironment構造函數。構造函數中經過累加possibleLockId的值來新增數據存儲的路徑,這個值是從0開始的,因此纔會在ElasticSearch的數據存儲頁面生成以下圖的文件夾:

數據存儲路徑

接下來使用FSDirectory.open(dir, NativeFSLockFactory.INSTANCE)獲取存儲索引的目錄,FSDirectory是對文件系統目錄的操做

  • 第一個參數java.nio.file.Path:dir這個參數是NIO的一個類Path,接收字符串參數建立的。
  • 第二個參數org.apache.lucene.store.LockFactory:這個參數是Lucene中的索引鎖。由於Lucene必須知道一份索引是否已經被某個IndexWriter打開,因此必須使用鎖的機制來保證寫索引的同步性。首先你們要明確一個問題,在ElasticSearch異常退出,或是JVM異常關閉的狀況下,在下次重啓ElasticSearch,索引依然可以正確讀寫,就是這麼神奇。這是怎麼實現的呢?祕密就在這個NativeFSLockFactory.INSTANCE參數中,他是FSDirectory提供的默認鎖,他的最大優點就是當程序異常退出後,能夠由操做系統負責解除索引的鎖,操做系統會釋放文件上全部的引用,以確保索引能夠正確讀寫。LockFactory還提供了其餘類型的鎖,因爲涉及到Lucene的深層次知識點,這裏就不展開敘述。

經過locks[dirIndex] = luceneDir.obtainLock(NODE_LOCK_FILENAME);取得鎖後生成一個內部類NodePath的實例,到這裏鎖就持久化到磁盤上了。

node.lock

補充一句,這個地方涉及到了ElasticSearch的參數max_local_storage_nodes,這個配置限制了單節點上能夠開啓的ES存儲實例的個數,若是咱們須要開多個實例,就要把這個配置寫到配置文件中,併爲這個配置賦值爲2或者更高,這樣的話ElasticSearch就會用for循環建立多個NodePath,而不僅是建立惟一的那個ID爲0的實例。

在NodeEnvironment中加載或建立Node元數據

接下類是構造NodeMetaData節點元數據,這個元數據有個關鍵數據叫nodeId,構造出來後是形如D2_COg3LTUeQcrYjcj_fQQ這樣的字符串。

程序執行到這個地方,其內部類NodePath的對象裏已經保存了節點目錄xxxx\data\nodes\0和節點索引目錄xxxx\data\nodes\0\indices,以下圖所示:

NodePath實例

程序首先經過DirectoryStream<Path> paths = Files.newDirectoryStream(stateDir)遍歷data\nodes\0_state文件夾下的狀態文件,再經過匹配正則表達式\Qnode-\E(\d+)(.st)?,查找到狀態文件node-xxx.st

注意,若是有多個數據存儲路徑,那麼狀態文件夾下可能會有多個最新狀態版本。這種狀況下,只會取最高的版本。若是至少有一個狀態文件使用了新的格式(format,也就是編碼中的legacy==false),那麼最新的狀態文件確定是最新的的格式(format)。若是不是使用最新的狀態文件,那編碼中的pathAndStateIds值是空的,且會在日誌中報加載狀態文件失敗的錯誤。

狀態文件

最後從node-xxx.st文件中讀出ID,至此NodeMetaData對象的nodeId字段就被賦值了。而這個ID的前綴也被做爲Logger的marker值被注入。

至此nodeEnvironment = new NodeEnvironment(tmpSettings, environment);的工做就結束了,總而言之就是載入了狀態參數到內存中。

下一篇會講述pluginsService相關的內容,但願你們持續關注哦^ _ ^。

相關文章
相關標籤/搜索