elasticsearch 源碼 創建索引
創建索引過程,當我們客戶端提交一個創建索引請求時,之前提到了es的transport模塊,在處理請求時,會將請求分發到對應的TransportRequestHandler,而創建索引的入口就是TransportHandler對象,這個對象對應的類是TransportCreateIndexAction的內部類,而TransportCreateIndexAction的這個內部類繼承自父類TransportMasterNodeOperationAction。
//TransportMasterNodeOperationAction.java
private class TransportHandler extends BaseTransportRequestHandler<Request> {
@Override
public Request newInstance() {
return newRequest();
}
@Override
public String executor() {
return ThreadPool.Names.SAME;
} // 使用的是same,請求不是交由線程池執行
@Override
public void messageReceived(final Request request, final TransportChannel channel) throws Exception {
// we just send back a response, no need to fork a listener
request.listenerThreaded(false);
execute(request, new ActionListener<Response>() {
@Override
public void onResponse(Response response) {
try {
channel.sendResponse(response);
} catch (Throwable e) {
onFailure(e);
}
}
@Override
public void onFailure(Throwable e) {
try {
channel.sendResponse(e);
} catch (Exception e1) {
logger.warn("Failed to send response", e1);
}
}
});
}
}
服務端在接收到創建索引請求時,會調用TransportHandler的messageReceived方法,進而調用execute方法。可以看到ActionListener 封裝了response,讓調用者可以執行調用OnResponse方法來響應,或者調用OnFailure來響應失敗。而後
//TransportMasterNodeOperationAction.java
public void execute(Request request, ActionListener<Response> listener) {
request.listenerThreaded(true);
super.execute(request, listener);
}
//TransportAction.java
public void execute(Request request, ActionListener<Response> listener) {
if (request.listenerThreaded()) {
//對ActionListener 進行封裝ThreadedActionListener 封裝線程池
listener = new ThreadedActionListener<Response>(threadPool, listener, logger);
}
ActionRequestValidationException validationException = request.validate();
if (validationException != null) {
listener.onFailure(validationException);
return;
}
try {
doExecute(request, listener);
} catch (Throwable e) {
logger.trace("Error during transport action execution.", e);
listener.onFailure(e);
}
}
方法調用,對ActionListener進行了封裝,得到一個ThreadedActionListener對象,這個對象繼承自ActionListener,不過他的onResponse方法和onFailure方法中響應處理Response的邏輯是由generic類型的線程池來執行。
public void onResponse(final Response response) {
try {
threadPool.generic().execute(new Runnable() {
@Override
public void run() {
try {
listener.onResponse(response);
} catch (Throwable e) {
listener.onFailure(e);
}
}
});
} catch (EsRejectedExecutionException ex) {
logger.debug("Can not run threaded action, exectuion rejected
try {
listener.onResponse(response);
} catch (Throwable e) {
listener.onFailure(e);
}
}
}
@Override
public void onFailure(final Throwable e) {
try {
threadPool.generic().execute(new Runnable() {
@Override
public void run() {
listener.onFailure(e);
}
});
} catch (EsRejectedExecutionException ex) {
listener.onFailure(e);
}
}
上邊調用了doExecute方法,最終會調用innerExecute方法。這個方法比較長,不貼代碼了說下具體邏輯
1.獲取集羣信息ClusterState,進而獲取到所有的節點信息
2.判斷本節點是否是master節點,若是mater節點則使用線程池調用masterOperation方法,使用的是same類型線程池,否則執行3
3.不是master節點,判斷是否有master節點,若是master節點爲null,則30s後再次執行innerExecute方法,或者集羣狀態更新時調用innerExecute方法,從1開始,只能重新執行一次,若重新執行依然沒有master節點,返回失敗響應。若是master節點不爲null執行4
4.轉發請求到master節點,並new TransportResponseHandler,在接收到master的響應是,再轉發響應到客戶端。master節點接收到請求後,從頭開始執行,不出意外的話,會執行到步驟2,調用masterOperation方法。
masterOperation方法,將request中的信息生成CreateIndexClusterStateUpdateRequest對象,然後調用了MetaDataCreateIndexService中的createIndex方法。主要進行以下操作
1.構建索引的setting
2.根據索引名稱獲取鎖,若是獲取成功則調用重載的createIndex方法,否則new 一個Runnable,其中使用tryAcquire(long timeout, TimeUnit unit)方法來獲取鎖,等待時間是請求的timeout時間。
若是在等待時間內獲取到鎖,則調用重載的createIndex方法。
在createIndex方法中將索引創建任務封裝成一個集羣狀態更新任務,提交到一個優先級線程池中取執行,索引的創建流程如下:
1)驗證索引名稱,判斷路由表中是否含有此索引,元數據中是否含有次索引,索引名稱是否合法等
2)根據索引名稱查找templates,並排序,解析request中的mappings,和customs(暫時不知道這個是什麼)
3)合併mapping,mapping 包含以下幾部分
1.request的mapping
2.匹配到的templates
3.home/config/mappings/indexName 下的mapping
4.home/config/mappings/_default下的mapping
5._default_ 的mapping type (也就是索引類型 也叫mappings類型)
優先級從高到底
3)合併custom,將template中的custom與request中的合併
4)合併索引的setting,主要包含以下幾部分
1.request中的indexSetting
2.templates中的setting
3.ES setting中的配置的默認值
4.默認值
優先級從高到低
5)使用索引名稱和索引配置來創建索引,調用InternalIndicesService中的createIndex方法,初始化索引相關的各個模塊
public synchronized IndexService createIndex(String sIndexName, Settings settings, String localNodeId) throws ElasticsearchException {
if (!lifecycle.started()) { //判斷生命週期 是否是started
throw new ElasticsearchIllegalStateException("Can't create an index [" + sIndexName + "], node is closed");
}
Index index = new Index(sIndexName);// new index
if (indicesInjectors.containsKey(index.name())) {
throw new IndexAlreadyExistsException(index);
}
indicesLifecycle.beforeIndexCreated(index);// 調用生命週期對象中的beforeIndexCreated方法
logger.debug("creating Index [{}], shards [{}]/[{}]", sIndexName, settings.get(SETTING_NUMBER_OF_SHARDS), settings.get(SETTING_NUMBER_OF_REPLICAS));
Settings indexSettings = settingsBuilder()
.put(this.settings)
.put(settings)
.classLoader(settings.getClassLoader())
.build();
ModulesBuilder modules = new ModulesBuilder();
modules.add(new IndexNameModule(index));//索引名稱
modules.add(new LocalNodeIdModule(localNodeId)); //就用來記錄localNodeId
modules.add(new IndexSettingsModule(index, indexSettings)); //索引setting inject IndexSettingsService 和setting
modules.add(new IndexPluginsModule(indexSettings, pluginsService)); // 索引plugin 注入
modules.add(new IndexStoreModule(indexSettings)); // 根據配置 注入不同的store對象
/*
默認engine
public static final Class<? extends Module> DEFAULT_INDEX_ENGINE = InternalIndexEngineModule.class;
public static final Class<? extends Module> DEFAULT_ENGINE = InternalEngineModule.class;
*/
modules.add(new IndexEngineModule(indexSettings));
/*
解析 模塊 各種關鍵字處理 pattern_replace
CharFiltersBindings 字符處理
TokenFiltersBindings 詞元處理
TokenizersBindings 分詞處理
AnalyzersBindings analyzer 分析器 封裝了以上處理單元
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html
*/
modules.add(new AnalysisModule(indexSettings, indicesAnalysisService));
//我們要修改打分機制,就需要自定義similarity BM25Similarity
modules.add(new SimilarityModule(indexSettings));
/*
索引相關 cache
*/
modules.add(new IndexCacheModule(indexSettings));
// fieldData doc_value 聚合功能
modules.add(new IndexFieldDataModule(indexSettings));
// 編解碼模塊 PostingsFormatProvider DocValuesFormatProvider
//PostingsFormat covers the inverted index, including all fields, terms, documents,
// positions, payloads and offsets. Queries use this API to find their matching documents.
modules.add(new CodecModule(indexSettings));
//mapping
modules.add(new MapperServiceModule());
//查詢解析
modules.add(new IndexQueryParserModule(indexSettings));
//別名
modules.add(new IndexAliasesServiceModule());
// gateway full restart 索引恢復
modules.add(new IndexGatewayModule(indexSettings, injector.getInstance(Gateway.class)));
// 索引服務 包含了上邊的各個服務模塊,直接使用indexService
modules.add(new IndexModule(indexSettings));
Injector indexInjector;
try {
// 創建一個子注入器
indexInjector = modules.createChildInjector(injector);
} catch (CreationException e) {
throw new IndexCreationException(index, Injectors.getFirstErrorFailure(e));
} catch (Throwable e) {
throw new IndexCreationException(index, e);
}
// 映射關係
indicesInjectors.put(index.name(), indexInjector);
//// 索引服務 包含了上邊的各個服務模塊,直接使用indexService
IndexService indexService = indexInjector.getInstance(IndexService.class);
// 生命週期
indicesLifecycle.afterIndexCreated(indexService);
indices = newMapBuilder(indices).put(index.name(), indexService).immutableMap();
return indexService;
}
6)將mappings信息添加到MapperService中,然後根據MapperService生成MappingMetaData元數據。每個type對應一個MappingMetaData。
7)根據索引名稱,索引setting,mapping元數據MappingMetaData,custom,和請求中的索引狀態來構建一個索引元數據IndexMetaData。將索引元數據添加到元數據中MetaData
8)若是request中有索引對應的block信息,則添加到ClusterBlocks中,若是request的state等於close(索引關閉),則添加此block到ClusterBlocks中。block用來定義索引的訪問權限
write read metadata 三種類型阻塞
block
index read-only / write metadata
index read / read
index write /write
index metadata /metadata
index close /read write
9)更新metaData 和 block 到ClusterState 中
10)若是request的state等於Open,則構建routingTable,進行分片分配,這個過程由ShardsAllocator接口的實現類和AllocationDecider抽象類的子類來完成。
11)對比創建以後的集羣狀態和前的狀態,有變化則集羣狀態的version,routingTable的version與metadata的version都會加1,構建一個新的ClusterState,發佈集羣狀態到所有節點,將集羣狀態更新事件作爲參數調用各個集羣狀態更新的listener。等到收到所有節點的ack信息,則釋放鎖,返回Response
注
1.源碼閱讀筆記