国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化

這篇具有很好參考價(jià)值的文章主要介紹了Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

1. 背景

????????Elasticsearch 是一個(gè)實(shí)時(shí)的分布式搜索分析引擎,簡稱 ES。一個(gè)集群由多個(gè)節(jié)點(diǎn)組成,節(jié)點(diǎn)的角色可以根據(jù)用戶的使用場景自由配置,集群可以以節(jié)點(diǎn)為單位自由擴(kuò)縮容,數(shù)據(jù)以索引、分片的形式散列在各個(gè)節(jié)點(diǎn)上。本文介紹 ES 分布式架構(gòu)基礎(chǔ)原理,剖析分布式元數(shù)據(jù)管理模型,并介紹騰訊云 ES 在分布式擴(kuò)展性層面相關(guān)的優(yōu)化,解析代碼基于 8.5 版本。

2. 分布式架構(gòu)

????????我們首先來看看 ES 的分布式架構(gòu)。一個(gè) ES 集群包含多個(gè)節(jié)點(diǎn),節(jié)點(diǎn)的類型有多種,最新版本主要的角色類型包括:

  • master 節(jié)點(diǎn):可設(shè)置專屬 master 節(jié)點(diǎn),也可以和其它節(jié)點(diǎn)角色共享。
  • 數(shù)據(jù)節(jié)點(diǎn): data、data_hot、data_warm、data_cold、data_frozen 等。
  • 功能節(jié)點(diǎn): ingest、ml、remote_cluster_client、transform 等。

????????節(jié)點(diǎn)角色分為三類:主節(jié)點(diǎn)(master)用于管理集群;數(shù)據(jù)節(jié)點(diǎn)(data)用于存儲(chǔ)管理數(shù)據(jù),數(shù)據(jù)節(jié)點(diǎn)按照數(shù)據(jù)特點(diǎn)分為多種類型,冷溫?zé)岬鹊?;功能?jié)點(diǎn),包括 ETL、機(jī)器學(xué)習(xí)、遠(yuǎn)程管理等等。

下面我們拿一種典型的大規(guī)模集群場景架構(gòu)來舉例說明。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

ES 分布式架構(gòu)

????????上圖所示的分布式架構(gòu)中,上面部分是專屬 master 節(jié)點(diǎn),負(fù)責(zé)管理集群的元數(shù)據(jù),他們之間通過基于類 Raft 的分布式一致性協(xié)議進(jìn)行選主、元數(shù)據(jù)同步。下面部分是專屬 data 節(jié)點(diǎn),data 節(jié)點(diǎn)上包含索引分片。索引對應(yīng)傳統(tǒng)關(guān)系數(shù)據(jù)庫的表,是邏輯概念,一個(gè)索引包含多個(gè)分片,分片是節(jié)點(diǎn)上的數(shù)據(jù)存儲(chǔ)單元,它按照主鍵 hash 或用戶自定義數(shù)據(jù)路由(routing)均勻分布到各個(gè)節(jié)點(diǎn)。分片一般包含一主(primary)、多從(replica)分片,一個(gè)索引主從分片之間的數(shù)據(jù)復(fù)制模型基于微軟提出的 PacificA 協(xié)議。

3. 讀寫模型

3.1 寫入模型

????????ES 的任意節(jié)點(diǎn)可作為寫入請求的協(xié)調(diào)節(jié)點(diǎn),接收用戶請求,協(xié)調(diào)節(jié)點(diǎn)先將寫入請求 hash 至分片粒度并先轉(zhuǎn)發(fā)對應(yīng)主分片寫入,主分片寫入成功再轉(zhuǎn)發(fā)至從分片,主從分片均寫入完畢經(jīng)協(xié)調(diào)節(jié)點(diǎn)返回客戶端成功。

????????騰訊云 ES 內(nèi)核團(tuán)隊(duì)實(shí)現(xiàn)了寫入分組定向路由、主從分片物理復(fù)制能力,可以減少從分片的寫入棧計(jì)算開銷,寫入性能提升50%+。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

分布式寫入模型

????????本文不詳細(xì)解析寫入過程,有興趣同學(xué)可參考寫入流程解析:https://cloud.tencent.com/developer/article/1370501

3.2 查詢模型

????????和寫入一樣,ES 的任意節(jié)點(diǎn)可以作為查詢請求的協(xié)調(diào)節(jié)點(diǎn),請求轉(zhuǎn)發(fā)至對應(yīng)一個(gè)或多個(gè)(取決于路由規(guī)則,不指定路由默認(rèn)索引所有分片均查詢)數(shù)據(jù)分片的主或者從分片進(jìn)行查詢,查詢根據(jù)復(fù)雜度分不同類型,QUERY_THEN_FETCH(兩階段),QUERY_AND_FETCH(一階段),各個(gè)分片查詢結(jié)果最后在協(xié)調(diào)節(jié)點(diǎn)匯聚,返回最終結(jié)果給客戶端。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

分布式查詢模型

????????本文不詳細(xì)解析查詢過程,有興趣同學(xué)可參考查詢流程解析:https://cloud.tencent.com/developer/article/1154813

4. 分布式架構(gòu)元數(shù)據(jù)模型分類

????????在大規(guī)模分布式存儲(chǔ)架構(gòu)中,元數(shù)據(jù)模型主要分為中心化架構(gòu)對等架構(gòu)兩類。中心化架構(gòu)的典型代表包括 HDFS、ES、BigTable 等,對等架構(gòu)典型的系統(tǒng)包括 Cassandra、Dynamo 等。

4.1 中心化架構(gòu)

????????中心化架構(gòu)的特點(diǎn)是專有的主節(jié)點(diǎn)管理集群元數(shù)據(jù),HDFS 中 Namenode 統(tǒng)一管理元數(shù)據(jù),data node 不單獨(dú)維護(hù),客戶端找 Namenode 獲取路由信息。中心化架構(gòu)的優(yōu)勢是在大規(guī)模集群場景下,元數(shù)據(jù)同步范圍收斂,效率更高。由于主節(jié)點(diǎn)提供路由查詢信息,因此其主要缺點(diǎn)是主節(jié)點(diǎn)易成為瓶頸,一般通過 Federation 機(jī)制優(yōu)化,或者將元數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)表中分布在部分節(jié)點(diǎn)上管理。ES 也采用了中心化架構(gòu),稍后我們展開介紹。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

中心化架構(gòu)

4.2 對等架構(gòu)

????????對等架構(gòu)的特點(diǎn)是去中心化,沒有獨(dú)立的主節(jié)點(diǎn)。節(jié)點(diǎn)之間通過 Gossip 協(xié)議傳播同步元數(shù)據(jù),所有節(jié)點(diǎn)保存全量的元數(shù)據(jù)。這種方式架構(gòu)簡單清晰,沒有中心化瓶頸。缺點(diǎn)是通過 Gossip 協(xié)議收斂元數(shù)據(jù)效率偏低,受節(jié)點(diǎn)數(shù)量限制,擴(kuò)展性弱。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

對等架構(gòu)

5. 元數(shù)據(jù)模型

????????前面我們介紹了 ES 的分布式架構(gòu)基礎(chǔ)原理及讀寫基本流程,也了解了業(yè)內(nèi)常用的分布式架構(gòu)元數(shù)據(jù)管理模型。下面我們來看看 ES 是如何管理集群的,其核心元數(shù)據(jù)模型是如何運(yùn)作的。

????????ES 的元數(shù)據(jù)由 master 節(jié)點(diǎn)維護(hù)管理,同時(shí)其它節(jié)點(diǎn)也維護(hù)著全量的元數(shù)據(jù),目的是為了確保每個(gè)節(jié)點(diǎn)都能承擔(dān)數(shù)據(jù)路由的能力。元數(shù)據(jù)包括節(jié)點(diǎn)信息、索引信息、分片路由信息、配置信息等等,下面我們先揭開元數(shù)據(jù)在內(nèi)存中的神秘面紗,然后再看看元數(shù)據(jù)是如何持久化的。

5.1 內(nèi)存結(jié)構(gòu)

????????ES 元數(shù)據(jù)的內(nèi)存數(shù)據(jù)結(jié)構(gòu)對象是 ClusterState,代碼層面對應(yīng)的是 ClusterState.java。其主要的成員包含:

成員

類型

描述

version

long

元數(shù)據(jù)版本。由 master 控制,每次元數(shù)據(jù)變更加1。

clusterName

String

集群名稱。

nodes

DiscoveryNodes

節(jié)點(diǎn)信息,包含不同角色的節(jié)點(diǎn)。

metaData

MetaData

集群的配置信息。包含 _cluster/settings 設(shè)定的動(dòng)態(tài)配置、各個(gè)索引配置信息、模板配置信息等。

routingTable

RoutingTable

索引分片到節(jié)點(diǎn)的映射關(guān)系,是主要的數(shù)據(jù)路由結(jié)構(gòu)。

routingNodes

RoutingNodes

節(jié)點(diǎn)到索引分片的映射關(guān)系,主要用于分片分配、均衡決策。各個(gè)節(jié)點(diǎn)自己維護(hù),每次需要時(shí)基于 routingTable 全量構(gòu)建而來,不會(huì)在節(jié)點(diǎn)之間發(fā)布。

blocks

ClusterBlocks

集群的 block 信息。例如集群整體只讀、單個(gè)索引只讀等信息。

customs

Map

其它自定義的元數(shù)據(jù),例如 snapshot 自定義配置、xpack 自定義配置等。

????????上面的內(nèi)存結(jié)構(gòu)中,對象占比最大的是 nodes、metaData、routingTable 和 routingNodes,這四塊是元數(shù)據(jù)的核心組成部分:

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

元數(shù)據(jù)核心結(jié)構(gòu)

????????接下來對這四個(gè)部分的內(nèi)部結(jié)構(gòu)進(jìn)行展開分析。

5.1.1 DiscoveryNodes

DiscoveryNodes 對象描述的是集群的節(jié)點(diǎn)信息。里面包含如下主要的成員信息:

// 當(dāng)前 master
private final String masterNodeId;
// 本地節(jié)點(diǎn)
private final String localNodeId;
// 完整的節(jié)點(diǎn)列表
private final ImmutableOpenMap<String, DiscoveryNode> nodes;

// 其它分類型節(jié)點(diǎn)列表
private final ImmutableOpenMap<String, DiscoveryNode> dataNodes;
private final ImmutableOpenMap<String, DiscoveryNode> masterNodes;
private final ImmutableOpenMap<String, DiscoveryNode> ingestNodes;

復(fù)制

????????從變量名稱很容易理解,其包含了本地節(jié)點(diǎn)、當(dāng)前 master 節(jié)點(diǎn)的 id 信息,以及集群全量節(jié)點(diǎn)列表(nodes),并將這個(gè)列表分類為不同類型的節(jié)點(diǎn)列表。

5.1.2 MetaData

MetaData 包含集群全局的配置信息。主要的成員包括:

// 集群的動(dòng)態(tài)設(shè)定,transient 全量重啟后就沒有了,persistent 會(huì)持久化
private final Settings transientSettings;
private final Settings persistentSettings;

// 索引、模板信息
private final ImmutableOpenMap<String, IndexMetaData> indices;
private final ImmutableOpenMap<String, IndexTemplateMetaData> templates;

// 集群選舉管理信息
private final CoordinationMetaData coordinationMetaData;

// raft 選舉使用的 term
private final long term;

// 最近提交的選舉節(jié)點(diǎn)列表,內(nèi)部是一個(gè) Set<String> nodeIds
private final VotingConfiguration lastCommittedConfiguration;

// 最近接收的選舉節(jié)點(diǎn)列表
private final VotingConfiguration lastAcceptedConfiguration;

// 用戶通過 _cluster/voting_config_exclusions 接口設(shè)定的選舉排除節(jié)點(diǎn)列表
private final Set<VotingConfigExclusion> votingConfigExclusions;

復(fù)制

5.1.3 RoutingTable

????????RoutingTable 包含主要的數(shù)據(jù)路由信息,在查詢、寫入請求的時(shí)候提供分片到節(jié)點(diǎn)的路由,動(dòng)態(tài)構(gòu)建不會(huì)持久化。我們先來通過一張圖了解這個(gè)數(shù)據(jù)路由結(jié)構(gòu)的包含關(guān)系:

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

分片到節(jié)點(diǎn)的映射(路由表)

????????一個(gè) RoutingTable 包含集群的所有索引,一個(gè)索引包含多個(gè)分片,一個(gè)分片包含一個(gè)主、多個(gè)從本分片,最底層的 ShardRouting 描述具體的某個(gè)副本分片所在的節(jié)點(diǎn)以及正在搬遷的目標(biāo)節(jié)點(diǎn)信息。用戶請求時(shí)只指定索引信息,請求到達(dá)協(xié)調(diào)節(jié)點(diǎn),由協(xié)調(diào)節(jié)點(diǎn)根據(jù)該路由表來獲取底層分片所在節(jié)點(diǎn)并轉(zhuǎn)發(fā)請求。

????????接下來看看對應(yīng)的數(shù)據(jù)結(jié)構(gòu),RoutingTable 頂層主要的成員只有一個(gè):

// key 為索引名,IndexRoutingTable 為該索引包含的分片信息
private final ImmutableOpenMap<String, IndexRoutingTable> indicesRouting;

復(fù)制

????????IndexRoutingTable 對象:

// 索引信息,包含索引名稱、uuid 等。
private final Index index;

// 索引的分片列表,key 為分片的編號(hào),例如 2 號(hào)分片,3 號(hào)分片
private final ImmutableOpenIntMap<IndexShardRoutingTable> shards;

復(fù)制

????????一套分片信息包含一個(gè)主、多個(gè)從分片,其對象結(jié)構(gòu) IndexShardRoutingTable:

// 當(dāng)前分片信息,主要包含分片的編號(hào)、分片對應(yīng)的索引信息
final ShardId shardId;

// 當(dāng)前分片的主分片信息
final ShardRouting primary;

// 當(dāng)前分片的多個(gè)從分片列表
final List<ShardRouting> replicas;

// 當(dāng)前分片全量的分片列表,包含主、副本
final List<ShardRouting> shards;

// 當(dāng)前分片已經(jīng) started 的活躍分片列表
final List<ShardRouting> activeShards;

// 當(dāng)前分片已經(jīng)分配了節(jié)點(diǎn)的分片列表
final List<ShardRouting> assignedShards;

復(fù)制

分片最底層的數(shù)據(jù)結(jié)構(gòu)是 ShardRouting ,它描述這個(gè)分片的狀態(tài)、節(jié)點(diǎn)歸屬等信息:

private final ShardId shardId;

// 分片所在的當(dāng)前節(jié)點(diǎn) id
private final String currentNodeId;

// 如果分片正在搬遷,這里為目標(biāo)節(jié)點(diǎn) id
private final String relocatingNodeId;

// 是否是主分片
private final boolean primary;

// 分片的狀態(tài),UNASSIGNED/INITIALIZING/STARTED/RELOCATING
private final ShardRoutingState state;

// 每一個(gè)分片分配后都有一個(gè)唯一標(biāo)識(shí)
private final AllocationId allocationId;

復(fù)制

5.1.4 RoutingNodes

????????該對象為節(jié)點(diǎn)到分片的映射關(guān)系。主要用于統(tǒng)計(jì)每個(gè)節(jié)點(diǎn)的分片分配情況,在分片分配、均衡的時(shí)候使用,需要時(shí)根據(jù) RoutingTable 動(dòng)態(tài)構(gòu)建,不會(huì)持久化。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

節(jié)點(diǎn)到分片的映射

RoutingNodes 的主要成員包括:

// 節(jié)點(diǎn)到分片的映射,key 為 nodeId,value 為一個(gè)包含分片列表的節(jié)點(diǎn)信息
private final Map<String, RoutingNode> nodesToShards = new HashMap<>();

// 未分配的分片列表,每次 reroute 從這里獲取分片分配
private final UnassignedShards unassignedShards = new UnassignedShards(this);

// 已經(jīng)分配的分片列表
private final Map<ShardId, List<ShardRouting>> assignedShards = new HashMap<>();

// 當(dāng)前分片遠(yuǎn)程恢復(fù)的統(tǒng)計(jì)數(shù)據(jù) incoming/outcoming
private final Map<String, Recoveries> recoveriesPerNode = new HashMap<>();

復(fù)制

上面 RoutingNode 的主要結(jié)構(gòu)包括:

private final String nodeId;

// 該節(jié)點(diǎn)上的分片列表
private final LinkedHashMap<ShardId, ShardRouting> shards; 

// 該節(jié)點(diǎn)上的初始化中的分片列表
private final LinkedHashSet<ShardRouting> initializingShards;

// 該節(jié)點(diǎn)上的正在搬遷(搬走)的分片列表
private final LinkedHashSet<ShardRouting> relocatingShards;

復(fù)制

????????前面就是所有元數(shù)據(jù)在內(nèi)存中的結(jié)構(gòu),最后我們借助這張 UML 圖來看元數(shù)據(jù)的整體結(jié)構(gòu)和關(guān)系:

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

元數(shù)據(jù)類結(jié)構(gòu)

5.2 元數(shù)據(jù)持久化

????????前面介紹的元數(shù)據(jù)內(nèi)存結(jié)構(gòu)中,只有部分信息會(huì)被持久化存儲(chǔ)到磁盤上,其它結(jié)構(gòu)都是在節(jié)點(diǎn)啟動(dòng)后動(dòng)態(tài)構(gòu)建或者直接在內(nèi)存中動(dòng)態(tài)維護(hù)的。持久化的內(nèi)容包含兩部分,索引元數(shù)據(jù)集群元數(shù)據(jù)也叫全局元數(shù)據(jù),下面我們分別來介紹這兩部分持久化的內(nèi)容。

5.2.1 索引元數(shù)據(jù)(index metadata)

索引元數(shù)據(jù)維護(hù)的是各個(gè)索引獨(dú)有的配置信息,持久化的內(nèi)容主要包括:

  • in_sync_allocations:每個(gè)分片分配之后都有一個(gè)唯一的 allocationId,該列表是主分片用來維護(hù)被認(rèn)為跟自己保持?jǐn)?shù)據(jù)一致的副本列表。從這個(gè)列表中踢出的分片不會(huì)接收新的寫入,需要走分片恢復(fù)流程將數(shù)據(jù)追齊之后才能再被放入該隊(duì)列。
  • mappings:索引的 mapping 信息,定義各個(gè)字段的類型、屬性。
  • settings:索引自己的配置信息。
  • state:索引狀態(tài),OPEN/CLOSED。
  • aliases:索引別名信息。
  • routing_num_shards:索引分片數(shù)量。
  • primary_terms:每一輪分片切主都會(huì)產(chǎn)生新的 primary term,用于保持主從分片之間的一致性。
5.2.2 全局元數(shù)據(jù)(global metadata)

????????全局元數(shù)據(jù)持久化的內(nèi)容主要是前面描述的 MetaData 對象中去除 indices 索引部分。包括動(dòng)態(tài)配置、模板信息、選舉信息等。主要包含三部分:

  • manifest-數(shù)字.st:該文件是一個(gè)磁盤元數(shù)據(jù)管理入口,主要管理元數(shù)據(jù)的版本信息。
  • global-數(shù)字.st:MetaData 中的全局元數(shù)據(jù)的主要信息就是持久化到這個(gè)文件,包括動(dòng)態(tài)配置、模板信息等。
  • node-數(shù)字.st:當(dāng)前節(jié)點(diǎn)的元數(shù)據(jù)信息,包括節(jié)點(diǎn)的 nodeId 和版本信息。
5.2.3 元數(shù)據(jù)文件分布

????????在 7.6.0 版本之前,元數(shù)據(jù)直接存放在每個(gè)節(jié)點(diǎn)磁盤上,且在專有 master 節(jié)點(diǎn)上每個(gè)索引一個(gè)元數(shù)據(jù)目錄。在 7.6.0 版本之后,ES 將元數(shù)據(jù)放到節(jié)點(diǎn)本地獨(dú)立的 lucene 索引中保存。7.6.0 之前持久化的數(shù)據(jù)目錄包含的內(nèi)容:

├── indices
│   ├── 00IUbRWZSzGsN71k0TlPmA
│   │   └── _state
│   │   └── state-8.st
│   ├── 01RTGqCbRe-9PYO55j8zAQ
│   │   └── _state
│   │   └── state-10.st
│   ├── 02DSrGNzRzizuI42Yf6aJg
│   │   └── _state
│   │   └── state-17.st

├── node.lock
└── _state
├── global-4928.st
└── node-0.st

復(fù)制

????????上圖中上半部分是每個(gè)索引的元數(shù)據(jù),它們位于 indices 目錄下,一個(gè)索引對應(yīng)一個(gè) uuid 目錄,下面有一個(gè) _state 文件夾,索引自己的元數(shù)據(jù)就放在這個(gè) _state 下面。下半部分就是我們上面描述的全局元數(shù)據(jù)持久化的文件。

????????7.6.0 之后 data/nodes/0 里面保存的是 segment 文件,元數(shù)據(jù)以本地 Lucene index 方式持久化,收斂文件數(shù)量。其中 node-0.st 是舊版升級后的兼容文件:

├── node.lock
└── _state
├── _3q.cfe
├── _3q.cfs
├── _3q.si
├── _3r.cfe
├── _3r.cfs
├── _3r.si
├── node-0.st
├── segments_6i
└── write.lock

復(fù)制

元數(shù)據(jù)索引同時(shí)保存全局、索引元數(shù)據(jù),用 type 字段區(qū)分,索引包含三個(gè)字段:

  • type:cluster 級別元數(shù)據(jù) “global”;索引級別元數(shù)據(jù) “index”。
  • index_uuid:索引元數(shù)據(jù)對應(yīng)索引的 UUID。
  • data:metadata 元數(shù)據(jù)內(nèi)容。

同時(shí),在每次提交保存的時(shí)候,會(huì)存一份 commit user data,包括選舉的 term,最新的版本、node id、node version 等信息:

* +------------------------------+-----------------------------+----------------------------------------------+
* | "type" (string field)        | "index_uuid" (string field) | "data" (stored binary field in SMILE format) |
* +------------------------------+-----------------------------+----------------------------------------------+
* | GLOBAL_TYPE_NAME == "global" | (omitted)                   | Global metadata                              |
* | INDEX_TYPE_NAME  == "index"  | Index UUID                  | Index metadata                               |
* +------------------------------+-----------------------------+----------------------------------------------+
*
* Additionally each commit has the following user data:
*
* +---------------------------+-------------------------+-------------------------------------------------------------------------------+
* |        Key symbol         |       Key literal       |                                     Value                                     |
* +---------------------------+-------------------------+-------------------------------------------------------------------------------+
* | CURRENT_TERM_KEY          | "current_term"          | Node's "current" term (≥ last-accepted term and the terms of all sent joins)  |
* | LAST_ACCEPTED_VERSION_KEY | "last_accepted_version" | The cluster state version corresponding with the persisted metadata           |
* | NODE_ID_KEY               | "node_id"               | The (persistent) ID of the node that wrote this metadata                      |
* | NODE_VERSION_KEY          | "node_version"          | The (ID of the) version of the node that wrote this metadata                  |
* +---------------------------+-------------------------+-------------------------------------------------------------------------------+

復(fù)制

6. 元數(shù)據(jù)管理

????????ES 的索引創(chuàng)建、刪除、mapping 更新、template 更新、節(jié)點(diǎn)加入脫離等 DDL 操作都會(huì)涉及到元數(shù)據(jù)變更。前面我們從內(nèi)存、持久化層面介紹了 ES 元數(shù)據(jù)的組成部分,接下來我們看看 ES 是如何對元數(shù)據(jù)進(jìn)行管理的。

6.1 元數(shù)據(jù)初始化

????????ES 節(jié)點(diǎn)的啟動(dòng)入口在 Node.java 的 start 函數(shù)中,里面會(huì)初始化各種 service。元數(shù)據(jù)管理相關(guān)的 service 也是在這個(gè)環(huán)節(jié)進(jìn)行初始化。GatewayMetaState 對象負(fù)責(zé)元數(shù)據(jù)的存取,在節(jié)點(diǎn)啟動(dòng)過程中會(huì)根據(jù)節(jié)點(diǎn)的類型,確定元數(shù)據(jù)的存取方式。分為以下幾種場景:

  • master 節(jié)點(diǎn)。基于 LucenePersistedState 對象同步落盤,每當(dāng)節(jié)點(diǎn)收到新的元數(shù)據(jù)的時(shí)候會(huì)馬上保存到磁盤。并更新緩存,讀取的時(shí)候優(yōu)先讀取緩存對象,節(jié)點(diǎn)啟動(dòng)的時(shí)候緩存會(huì)從磁盤加載。
  • data 節(jié)點(diǎn)。包括前述 data 前綴的 role,基于 AsyncPersistedState 對象異步落盤,對應(yīng)異步線程名稱是 AsyncLucenePersistedState#updateTask。
  • 其它功能節(jié)點(diǎn)。非 master 屬性且不保存數(shù)據(jù)的節(jié)點(diǎn),例如 ingest、ml、transform 等。基于 InMemoryPersistedState 對象直接保存到內(nèi)存不做持久化。無論是在內(nèi)存中的還是持久化的元數(shù)據(jù)對象,它們都對外暴露一個(gè) PersistedState 接口。提供保存最后接收的元數(shù)據(jù)( setLastAcceptedState),以及獲取最后接收的元數(shù)據(jù)(getLastAcceptedState)。

GatewayMetaState.java 代碼片段:

if (DiscoveryNode.isMasterNode(settings) || DiscoveryNode.canContainData(settings)) {
    ......
    if (DiscoveryNode.isMasterNode(settings)) {
        persistedState = new LucenePersistedState(persistedClusterStateService, currentTerm, clusterState);
    } else {
        persistedState = new AsyncPersistedState(
            settings,
            transportService.getThreadPool(),
            new LucenePersistedState(persistedClusterStateService, currentTerm, clusterState)
        );
    }        
    ......
    this.persistedState.set(persistedState);
} else {
    ......
    persistedState.set(new InMemoryPersistedState(currentTerm, clusterState));
}

復(fù)制

6.2 發(fā)布流程

????????接下來我們以最典型的 DDL 創(chuàng)建索引操作為例,介紹整個(gè)元數(shù)據(jù)發(fā)布流程。ES 的 DDL 操作都是通過元數(shù)據(jù)變更推導(dǎo)模式實(shí)現(xiàn)的,例如創(chuàng)建索引,首先 master 會(huì)產(chǎn)生一版帶新增索引的元數(shù)據(jù),并將該新版元數(shù)據(jù)發(fā)布至各個(gè)節(jié)點(diǎn),各個(gè)節(jié)點(diǎn)和自己上一個(gè)持久化的元數(shù)據(jù)版本進(jìn)行比對,產(chǎn)生差異化的索引執(zhí)行創(chuàng)建索引、分片的任務(wù)。

下圖是一張宏觀的索引創(chuàng)建元數(shù)據(jù)變更流程,方便大家從整體架構(gòu)上有個(gè)初步的了解。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

索引創(chuàng)建元數(shù)據(jù)變更流程

????????接下來分別從 master、data 節(jié)點(diǎn)維度分別看著兩階段的處理流程。下面是兩個(gè)階段的各個(gè)狀態(tài):

enum PublicationTargetState {
    NOT_STARTED,
    FAILED,
    SENT_PUBLISH_REQUEST, // 已發(fā)送 publish 請求
    WAITING_FOR_QUORUM,   // 等待大多數(shù)節(jié)點(diǎn)響應(yīng)
    SENT_APPLY_COMMIT,    // 已發(fā)送 commit 請求
    APPLIED_COMMIT,       // 元數(shù)據(jù)應(yīng)用完畢
}

復(fù)制

6.2.1 Master publish 階段

????????用戶發(fā)起 create index 請求,會(huì)先到達(dá) rest 層,由 RestCreateIndexAction 解析請求,并產(chǎn)生 transport 層的請求轉(zhuǎn)發(fā)給 master 處理。調(diào)用鏈:

RestCreateIndexAction -> TransportCreateIndexAction -> MetadataCreateIndexService

所有參數(shù)解析完畢之后,就進(jìn)入提交集群元數(shù)據(jù)變更任務(wù)流程:

private void onlyCreateIndex(final CreateIndexClusterStateUpdateRequest request, final ActionListener<AcknowledgedResponse> listener) {
    normalizeRequestSetting(request);
    //提交元數(shù)據(jù)變更任務(wù),索引創(chuàng)建優(yōu)先級為 URGENT
    submitUnbatchedTask(
        "create-index [" + request.index() + "], cause [" + request.cause() + "]",
        new AckedClusterStateUpdateTask(Priority.URGENT, request, listener) {

            @Override
            public ClusterState execute(ClusterState currentState) throws Exception {
                // 分配分片產(chǎn)生新版本的元數(shù)據(jù)
                return applyCreateIndexRequest(currentState, request, false);
            }

            @Override
            public void onFailure(Exception e) {
                ......
            }
        }
    );
}

復(fù)制

????????代碼中 applyCreateIndexRequest 主要負(fù)責(zé)索引創(chuàng)建過程中,分配分片產(chǎn)生新版本的元數(shù)據(jù),里面涉及復(fù)雜的分片分配、均衡策略流程,騰訊云 ES 內(nèi)核結(jié)合單個(gè)索引分片數(shù)、節(jié)點(diǎn)主分片數(shù)、節(jié)點(diǎn)總分片數(shù)、節(jié)點(diǎn)存儲(chǔ)空間多個(gè)維度深度定制優(yōu)化了均衡策略,徹底解決社區(qū)版各個(gè)維度負(fù)載不均的問題,這里不展開,以后再單獨(dú)介紹。

????????上述任務(wù)經(jīng)過 MasterService.submitStateUpdateTask 包裝之后提交給線程池執(zhí)行:

public void submitTask(BatchedTask task, @Nullable TimeValue timeout) throws EsRejectedExecutionException {
    tasksPerBatchingKey.compute(task.batchingKey, (k, existingTasks) -> {
        if (existingTasks == null) {
            existingTasks = Collections.synchronizedSet(new LinkedHashSet<>());
        } else {
            assert assertNoDuplicateTasks(task, existingTasks);
        }
        // 按 executor 匯總 task
        existingTasks.add(task);
        return existingTasks;
    });

    if (timeout != null) {
        // 指定超時(shí)時(shí)間,不指定默認(rèn)元數(shù)據(jù)變更 30s 超時(shí)
        threadExecutor.execute(task, timeout, () -> onTimeoutInternal(task, timeout));
    } else {
        threadExecutor.execute(task);
    }
}

復(fù)制

????????元數(shù)據(jù)任務(wù)支持 batch 方式執(zhí)行,相同類別的任務(wù)例如 template 變更任務(wù)可以批量變更,參考 MetadataIndexTemplateService.TEMPLATE_TASK_EXECUTOR。創(chuàng)建索引任務(wù)不能批量執(zhí)行。元數(shù)據(jù)任務(wù)執(zhí)行采用的是單線程多任務(wù)按優(yōu)先級串行執(zhí)行的模式,線程名稱為 masterService#updateTask,上述 threadExecutor 的初始化過程:

protected PrioritizedEsThreadPoolExecutor createThreadPoolExecutor() {
    return EsExecutors.newSinglePrioritizing(
        // masterService#updateTask,我們經(jīng)常在 jstack 堆棧中看到的 master 節(jié)點(diǎn)元數(shù)據(jù)變更線程
        nodeName + "/" + MASTER_UPDATE_THREAD_NAME,
        daemonThreadFactory(nodeName, MASTER_UPDATE_THREAD_NAME),
        threadPool.getThreadContext(),
        threadPool.scheduler(),
        ......
    );
}

復(fù)制

元數(shù)據(jù)變更的優(yōu)先級:

public enum Priority {
    IMMEDIATE((byte) 0), // 節(jié)點(diǎn)加入、脫離等
    URGENT((byte) 1), // 索引、template 創(chuàng)建等
    HIGH((byte) 2),
    NORMAL((byte) 3),
    LOW((byte) 4),
    LANGUID((byte) 5);
}

復(fù)制

元數(shù)據(jù)變更任務(wù)產(chǎn)生了新的元數(shù)據(jù)之后,就會(huì)進(jìn)入元數(shù)據(jù) publish 階段,調(diào)用棧:

MasterService.publishClusterStateUpdate() -> MasterService.publish() -> Coordinator.publish()

核心邏輯在 Coordinator 中處理,該類負(fù)責(zé)管理元數(shù)據(jù)的 publish、commit 流程。publish 函數(shù)的代碼片段:

@Override
public void publish(
    ClusterStatePublicationEvent clusterStatePublicationEvent,
    ActionListener<Void> publishListener,
    AckListener ackListener
) {
    try {
        synchronized (mutex) {
           ......
            try {
                ......
                // 獲取目標(biāo)發(fā)布節(jié)點(diǎn)
                final DiscoveryNodes publishNodes = publishRequest.getAcceptedState().nodes();
                // leader checker 在數(shù)據(jù)節(jié)點(diǎn)上用于追蹤 master,和 master 保持心跳
                leaderChecker.setCurrentNodes(publishNodes);
                // 設(shè)置當(dāng)前 leader 需要追蹤的 follower 節(jié)點(diǎn),主要是用于心跳保持
                followersChecker.setCurrentNodes(publishNodes);
                // lag 探測器,如果某個(gè)節(jié)點(diǎn)超過指定時(shí)間默認(rèn) 90s 沒有 publish 成功則踢出
                lagDetector.setTrackedNodes(publishNodes);
                // 啟動(dòng) publish 任務(wù)
                publication.start(followersChecker.getFaultyNodes());
            } finally {
                publicationContext.decRef();
            }
        }
    } catch (Exception e) {
       ......
    }
}

復(fù)制

依次發(fā)送給各節(jié)點(diǎn):

public void start(Set<DiscoveryNode> faultyNodes) {
    logger.trace("publishing {} to {}", publishRequest, publicationTargets);
    publicationTargets.forEach(PublicationTarget::sendPublishRequest);
}
void sendPublishRequest() {
    Publication.this.sendPublishRequest(discoveryNode, publishRequest, new PublishResponseHandler());
}

復(fù)制

????????sendPublishRequest 內(nèi)部會(huì)分全量和增量兩種發(fā)送方式,在節(jié)點(diǎn)脫離之后重新加入或者有版本落后的情況下元數(shù)據(jù)會(huì)全量發(fā)送。至此 master 節(jié)點(diǎn)的發(fā)送流程就結(jié)束了,接下來就會(huì)在上面的 PublishResponseHandler 中等待多數(shù)節(jié)點(diǎn)響應(yīng)后發(fā)起 commit 請求。

6.2.2 數(shù)據(jù)節(jié)點(diǎn)接收 publish

????????實(shí)際處理 publish 的節(jié)點(diǎn)不止數(shù)據(jù)節(jié)點(diǎn),還包括不帶數(shù)據(jù)屬性的所有節(jié)點(diǎn),也包括當(dāng)前 master 節(jié)點(diǎn)本身。只是我們?yōu)榱朔奖忝枋觯詳?shù)據(jù)節(jié)點(diǎn)處理 publish 邏輯為主。數(shù)據(jù)節(jié)點(diǎn)接收 publish 請求的入口在 PublicationTransportHandler.handleIncomingPublishRequest(),接收的 ClusterState 分為全量和增量兩種,用一個(gè) boolean 區(qū)分,代碼片段:

private PublishWithJoinResponse handleIncomingPublishRequest(BytesTransportRequest request) throws IOException {
    StreamInput in = request.bytes().streamInput();
    try {
        ......
        // 全量或增量標(biāo)記
        if (in.readBoolean()) {
            final ClusterState incomingState;
            ......
            final PublishWithJoinResponse response = acceptState(incomingState);
            // 這里 lastSeenClusterState 的目的就是為了下次接收增量之后應(yīng)用 diff
            lastSeenClusterState.set(incomingState);
            return response;
        } else {
            final ClusterState lastSeen = lastSeenClusterState.get();
            if (lastSeen == null) {
                throw new IncompatibleClusterStateVersionException("have no local cluster state");
            } else {
                // 基于 lastSeen 應(yīng)用增量 diff 產(chǎn)生新的完整的 incomingState
                ClusterState incomingState;
                try {
                    final Diff<ClusterState> diff;
                    // Close stream early to release resources used by the de-compression as early as possible
                    try (StreamInput input = in) {
                        diff = ClusterState.readDiffFrom(input, lastSeen.nodes().getLocalNode());
                    }
                    incomingState = diff.apply(lastSeen); // might throw IncompatibleClusterStateVersionException
                } catch (Exception e) {
                    throw e;
                }
                ......
                final PublishWithJoinResponse response = acceptState(incomingState);
                // 本地變量 CAS 替換成全局
                lastSeenClusterState.compareAndSet(lastSeen, incomingState);
                return response;
            }
        }
    } finally {
        IOUtils.close(in);
    }
}

復(fù)制

????????序列化完畢后進(jìn)入本地 accept 階段。上述片段中 acceptState 函數(shù)內(nèi)部先會(huì)做元數(shù)據(jù)的各種校驗(yàn),比如 term、clusterUUID 等最終更新 lastAcceptedState 并持久化,在元數(shù)據(jù)未 commit 之前元數(shù)據(jù)的 metaData 部分的 clusterUUIDCommitted 屬性為 false。元數(shù)據(jù)的標(biāo)識(shí)是通過 clusterUUID 來區(qū)分的,判斷一個(gè)集群的多個(gè)節(jié)點(diǎn)具備相同的元數(shù)據(jù)版本可根據(jù)此屬性,并結(jié)合 clusterUUIDCommitted 屬性確定元數(shù)據(jù)是已經(jīng) commit 的版本。處理接收請求代碼片段:

public PublishResponse handlePublishRequest(PublishRequest publishRequest) {
    final ClusterState clusterState = publishRequest.getAcceptedState();
    if (clusterState.term() != getCurrentTerm()) {
       ......
    }
    if (clusterState.term() == getLastAcceptedTerm() && clusterState.version() <= getLastAcceptedVersion()) {
       ......
    }
    // 更新 lastAcceptedState
    persistedState.setLastAcceptedState(clusterState);
    return new PublishResponse(clusterState.term(), clusterState.version());
}

復(fù)制

6.2.3 Master 節(jié)點(diǎn)發(fā)起 commit

????????在 master 節(jié)點(diǎn)發(fā)送 publish 請求給各個(gè)節(jié)點(diǎn)后,會(huì)在等待半數(shù)以上節(jié)點(diǎn)響應(yīng)才會(huì)進(jìn)入 commit 流程。代碼片段:

public Optional<ApplyCommitRequest> handlePublishResponse(DiscoveryNode sourceNode, PublishResponse publishResponse) {
    ......
    publishVotes.addVote(sourceNode);
    if (isPublishQuorum(publishVotes)) {
        return Optional.of(new ApplyCommitRequest(localNode, publishResponse.getTerm(), publishResponse.getVersion()));
    }
    return Optional.empty();
}

復(fù)制

一旦達(dá)到多數(shù)節(jié)點(diǎn)響應(yīng)后,master 發(fā)起 apply commit 請求:

@Override
protected void sendApplyCommit(
    DiscoveryNode destination,
    ApplyCommitRequest applyCommit,
    ActionListener<Empty> responseActionListener
) {
    transportService.sendRequest(
        destination,
        COMMIT_STATE_ACTION_NAME,
        applyCommit,
        COMMIT_STATE_REQUEST_OPTIONS,
        new ActionListenerResponseHandler<>(wrapWithMutex(responseActionListener), in -> Empty.INSTANCE, Names.CLUSTER_COORDINATION)
    );
}

復(fù)制

ApplyCommitRequest 請求比較簡單,僅包含 sourceNode、term、version 三個(gè)字段。

6.2.4 數(shù)據(jù)節(jié)點(diǎn)處理 commit

????????數(shù)據(jù)節(jié)點(diǎn)處理 commit 請求的入口在 Coordinator.java。一方面標(biāo)記接收的元數(shù)據(jù)為 commited 狀態(tài),另外進(jìn)行元數(shù)據(jù)應(yīng)用。代碼片段:

private void handleApplyCommit(ApplyCommitRequest applyCommitRequest, ActionListener<Void> applyListener) {
    synchronized (mutex) {
        // 將我們之前接收到的 acceptedState 的元數(shù)據(jù)改為 commit 狀態(tài)
        coordinationState.get().handleCommit(applyCommitRequest);
        ......
        if (applyCommitRequest.getSourceNode().equals(getLocalNode())) {
            // master node applies the committed state at the end of the publication process, not here.
            applyListener.onResponse(null);
        } else {
            // 元數(shù)據(jù)接收完畢,進(jìn)入應(yīng)用環(huán)節(jié)
            clusterApplier.onNewClusterState(applyCommitRequest.toString(), () -> applierState, applyListener.map(r -> {
                onClusterStateApplied();
                return r;
            }));
        }
    }
}

復(fù)制

????????數(shù)據(jù)節(jié)點(diǎn) commit 元數(shù)據(jù)之后,元數(shù)據(jù)的發(fā)布流程就完畢了,之后數(shù)據(jù)節(jié)點(diǎn)進(jìn)入異步應(yīng)用環(huán)節(jié)。異步單線程處理,線程名稱是 clusterApplierService#updateTask 我們在很多 jstack 中看到該線程名稱即為數(shù)據(jù)節(jié)點(diǎn)的元數(shù)據(jù)應(yīng)用線程。

????????元數(shù)據(jù)應(yīng)用流程核心邏輯在 ClusterApplierService.java 的 applyChanges 函數(shù)中,主要是處理一些列元數(shù)據(jù)變更附屬的任務(wù),例如創(chuàng)建、刪除索引、template 維護(hù)等,另外 master 節(jié)點(diǎn)上還會(huì)回調(diào)一些元數(shù)據(jù)變更完成后關(guān)聯(lián)的 listener。

private void applyChanges(ClusterState previousClusterState, ClusterState newClusterState, String source, Recorder stopWatch) {
    ......
    logger.debug("apply cluster state with version {}", newClusterState.version());
    // 進(jìn)行元數(shù)據(jù)應(yīng)用,會(huì)按優(yōu)先級分為 low/normal/high 來應(yīng)用,例如創(chuàng)建索引屬于 high
    callClusterStateAppliers(clusterChangedEvent, stopWatch);

    logger.debug("set locally applied cluster state to version {}", newClusterState.version());
    // 更新本地最新 commited 的 clusterState
    state.set(newClusterState);

    // 這里一般是 master 節(jié)點(diǎn)發(fā)起元數(shù)據(jù) commit 結(jié)束后,再回調(diào)相應(yīng)的 listener
    callClusterStateListeners(clusterChangedEvent, stopWatch);
}

復(fù)制

????????我們主要以創(chuàng)建索引來介紹元數(shù)據(jù)變更主流程,上述 callClusterStateAppliers apply 元數(shù)據(jù)的環(huán)節(jié)會(huì)進(jìn)到 IndicesClusterStateService.java applyClusterState 函數(shù),該函數(shù)有一連串應(yīng)用最新 state 的流程:

@Override
public synchronized void applyClusterState(final ClusterChangedEvent event) {
    // 最新 commit 的元數(shù)據(jù)
    final ClusterState state = event.state();
    ......
    // 刪除索引、分片,清理磁盤分片目錄
    deleteIndices(event); // also deletes shards of deleted indices

    // 刪除索引、分片,只是清理內(nèi)存對象,主要是針對 Close/Open 操作
    removeIndicesAndShards(event); // also removes shards of removed indices

    // 更新索引 settings、mapping 等
    updateIndices(event); // can also fail shards, but these are then guaranteed to be in failedShardsCache

    // 創(chuàng)建索引、分片
    createIndicesAndUpdateShards(state);
}

復(fù)制

????????createIndicesAndUpdateShards 主要處理索引創(chuàng)建任務(wù),根據(jù)接收到的元數(shù)據(jù),對比內(nèi)存最新的索引列表,找出需要?jiǎng)?chuàng)建的索引列表進(jìn)行創(chuàng)建,同時(shí)創(chuàng)建缺失的分片信息:

private void createIndicesAndUpdateShards(final ClusterState state) {
    DiscoveryNodes nodes = state.nodes();
    // 通過節(jié)點(diǎn)到分片的映射,獲取屬于該節(jié)點(diǎn)的索引分片信息
    RoutingNode localRoutingNode = state.getRoutingNodes().node(nodes.getLocalNodeId());
  
    // 對比接收到的元數(shù)據(jù)和本節(jié)點(diǎn)內(nèi)存的索引列表,找出新增的需要?jiǎng)?chuàng)建的索引
    final Map<Index, List<ShardRouting>> indicesToCreate = new HashMap<>();
    for (ShardRouting shardRouting : localRoutingNode) {
        ShardId shardId = shardRouting.shardId();
        if (failedShardsCache.containsKey(shardId) == false) {
            final Index index = shardRouting.index();
            final var indexService = indicesService.indexService(index);
            if (indexService == null) {
                // 不存在的索引就是需要?jiǎng)?chuàng)建的
                indicesToCreate.computeIfAbsent(index, k -> new ArrayList<>()).add(shardRouting);
            } else {
                // 索引存在看看有沒需要?jiǎng)?chuàng)建或更新的分片
                createOrUpdateShard(state, nodes, routingTable, shardRouting, indexService);
            }
        }
    }

    // 過濾出來的待創(chuàng)建索引列表,遍歷依次創(chuàng)建
    for (Map.Entry<Index, List<ShardRouting>> entry : indicesToCreate.entrySet()) {
        final Index index = entry.getKey();
        final IndexMetadata indexMetadata = state.metadata().index(index);
        logger.debug("[{}] creating index", index);

        try {
            // 創(chuàng)建索引
            indicesService.createIndex(indexMetadata, buildInIndexListener, true);
        } catch (Exception e) {
            ......
        }
        // 創(chuàng)建索引對應(yīng)的本地分片
        for (ShardRouting shardRouting : entry.getValue()) {
            createOrUpdateShard(state, nodes, routingTable, shardRouting, indexService);
        }
    }
}

復(fù)制

????????至此,一個(gè)完整的元數(shù)據(jù)變更流程就介紹完了??傮w來看,master 經(jīng)過兩階段提交元數(shù)據(jù)后,進(jìn)入元數(shù)據(jù)應(yīng)用流程,各個(gè)節(jié)點(diǎn)對比自己本地的信息和接收的元數(shù)據(jù),根據(jù)差異處理相關(guān)流程。

7. 擴(kuò)展性優(yōu)化

????????前面我們介紹了 ES 元數(shù)據(jù)管理模型,接下來我們結(jié)合元數(shù)據(jù)管理模型看看 ES 分布式架構(gòu)存在的擴(kuò)展性瓶頸及優(yōu)化措施。

7.1 擴(kuò)展性瓶頸

????????社區(qū)版本建議控制整個(gè)集群分片數(shù)在 3 萬以下,節(jié)點(diǎn)數(shù)不超過100,超過之后在創(chuàng)建、刪除索引、維護(hù) mapping、template 等元數(shù)據(jù)變更操作時(shí)可能出現(xiàn)較嚴(yán)重的卡頓。例如 ES 在寫入觸發(fā)創(chuàng)建的場景,大批量 bulk 請求在索引創(chuàng)建完畢之前會(huì)堆積內(nèi)存,節(jié)點(diǎn)有被打垮的風(fēng)險(xiǎn)。其次,因?yàn)閱渭褐С值姆制瑪?shù)、節(jié)點(diǎn)數(shù)有限,導(dǎo)致用戶大規(guī)模數(shù)據(jù)場景下,需要建大量的小規(guī)格集群滿足業(yè)務(wù)需求,從而導(dǎo)致集群碎片化資源嚴(yán)重,整體 TCO 偏高。從前面元數(shù)據(jù)整體的變更流程中我們總結(jié)出如下主要的瓶頸:

  • master 構(gòu)建新元數(shù)據(jù),例如節(jié)點(diǎn)分片分配、均衡策略時(shí),會(huì)基于 RoutingTable 全量構(gòu)建 RoutingNodes。
  • master 節(jié)點(diǎn)元數(shù)據(jù)序列化,全量比對新舊元數(shù)據(jù),構(gòu)建 diff 并序列化。
  • data 節(jié)點(diǎn)元數(shù)據(jù) diff 推導(dǎo),基于 RoutingTable 全量構(gòu)建 RoutingNodes,多次全量遍歷本節(jié)點(diǎn)分片和 diff 比對。
  • 單個(gè)任務(wù)多次元數(shù)據(jù)變更,例如創(chuàng)建一個(gè)索引,會(huì)先創(chuàng)建主分片再創(chuàng)建從分片,導(dǎo)致單任務(wù)多次元數(shù)據(jù)變更
  • 路由全節(jié)點(diǎn)分發(fā),單次元數(shù)據(jù)變更全節(jié)點(diǎn)分發(fā),GC、網(wǎng)絡(luò)抖動(dòng)等長尾節(jié)點(diǎn)影響變更。
  • 部分場景元數(shù)據(jù)同步落盤,HDD 場景性能影響嚴(yán)重。

???????總結(jié)來說,分片到節(jié)點(diǎn)的映射(RoutingTable)和節(jié)點(diǎn)到分片的映射(RoutingNodes)兩者之間的全量構(gòu)建,以及新舊元數(shù)據(jù)全量對比、全節(jié)點(diǎn)分發(fā)、同步落盤等是影響擴(kuò)展性的主要瓶頸。

7.2 優(yōu)化措施

????????結(jié)合前面分析的瓶頸點(diǎn),優(yōu)化措施主要包括映射增量維護(hù)、收斂元數(shù)據(jù)變更范圍、重啟性能優(yōu)化等方向。

7.2.1 映射增量維護(hù)

????????RoutingTable 和 RoutingNodes 全量構(gòu)建的問題,在分片總數(shù)達(dá)到 5 萬以上時(shí),多處這種相互構(gòu)建的性能瓶頸明顯,我們可以采用兩者增量維護(hù)的方式,避免全量相互構(gòu)建:

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

映射增量維護(hù)

????????元數(shù)據(jù)發(fā)送前序列化階段也不需要全量對比,因?yàn)槲覀兩厦嬉呀?jīng)維護(hù)了一個(gè) diff 的增量,可以省去一些全量對比的環(huán)節(jié),直接將產(chǎn)生好的 diff 發(fā)送給數(shù)據(jù)節(jié)點(diǎn)即可。數(shù)據(jù)節(jié)點(diǎn)上的元數(shù)據(jù)推導(dǎo)也不需要再進(jìn)行全量對比,直接拿增量的 diff 進(jìn)行應(yīng)用。對于部分持久化存在同步刷盤的情況改為異步刷盤,緩解 HDD 盤場景的性能瓶頸。整體的優(yōu)化方向是流程增量、異步化:

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

元數(shù)據(jù)變更增量、異步化改造

7.2.2 收斂變更范圍

????????對于元數(shù)據(jù)發(fā)布全節(jié)點(diǎn)的瓶頸,在節(jié)點(diǎn)數(shù)多了例如數(shù)百個(gè)之后,容易因個(gè)別節(jié)點(diǎn)的抖動(dòng)受影響。我們可以控制元數(shù)據(jù)發(fā)布的范圍,只發(fā)生在 master 節(jié)點(diǎn)之間,索引、分片的創(chuàng)建采用定向節(jié)點(diǎn)下發(fā)的方式避免請求扇出嚴(yán)重,而數(shù)據(jù)節(jié)點(diǎn)可以通過學(xué)習(xí)的方式動(dòng)態(tài)構(gòu)建元數(shù)據(jù)。

Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化,elasticsearch,分布式,架構(gòu)

收斂元數(shù)據(jù)變更范圍

7.2.3 重啟性能優(yōu)化

????????在集群的總分片數(shù)和節(jié)點(diǎn)數(shù)達(dá)到一定規(guī)模后,重啟恢復(fù)的性能會(huì)成為瓶頸,時(shí)間會(huì)比較長,也是我們需要主要優(yōu)化的方向。因?yàn)橹貑?huì)涉及到大量的分片分配、恢復(fù),主要的優(yōu)化思路包括:

  • 排序粗化:分片恢復(fù)有優(yōu)先級,按優(yōu)先級排序時(shí),我們可以從分片維度優(yōu)化到索引維度。上面就是整體的擴(kuò)展性優(yōu)化的主要方向,還有其它方面的優(yōu)化,例如統(tǒng)計(jì)接口層面的優(yōu)化,主要思路是構(gòu)建緩存、低頻的方式提升接口性能。
  • 批量 fetch:在恢復(fù)之前,master 會(huì)先獲取所有數(shù)據(jù)節(jié)點(diǎn)上分片最新的狀態(tài),可以從單分片請求優(yōu)化為按節(jié)點(diǎn)批量獲取。
  • 遍歷裁剪:在分片恢復(fù)過程中,每分配一批(并發(fā)恢復(fù)分片數(shù)控制)分片,都需要全量遍歷所有分片,遍歷過程中可以根據(jù)并發(fā)控制的分片數(shù)對遍歷進(jìn)行高效裁剪,去掉不需要分配的無用分片的遍歷過程。
7.2.4 優(yōu)化效果

????????在經(jīng)過一系列全方位擴(kuò)展性瓶頸優(yōu)化之后,我們將集群的分片數(shù)擴(kuò)展至百萬級,節(jié)點(diǎn)數(shù)擴(kuò)展至數(shù)百上千級。于此同時(shí),我們也將部分熱點(diǎn)瓶頸優(yōu)化反饋給了社區(qū)。附部分已合并 PR:

  • https://github.com/elastic/elasticsearch/pull/87723
  • https://github.com/elastic/elasticsearch/pull/64753
  • https://github.com/elastic/elasticsearch/pull/46520
  • https://github.com/elastic/elasticsearch/pull/65045
  • https://github.com/elastic/elasticsearch/pull/56870
  • https://github.com/elastic/elasticsearch/pull/65172
  • https://github.com/elastic/elasticsearch/pull/60564

8 總結(jié)

????????本文通過介紹 ES 分布式架構(gòu),對比業(yè)界主流的分布式元數(shù)據(jù)管理模式,之后剖析了 ES 核心元數(shù)據(jù)管理模型。最后結(jié)合集群擴(kuò)展性的瓶頸介紹了騰訊在 ES 擴(kuò)展性方面所做的相關(guān)優(yōu)化,現(xiàn)階段重點(diǎn)解決了元數(shù)據(jù)的擴(kuò)展性,節(jié)點(diǎn)的擴(kuò)展性后續(xù)還需持續(xù)優(yōu)化以支持更大規(guī)模,目前騰訊云 ES 內(nèi)核已能滿足絕大部分用戶的擴(kuò)展性需求。文章來源地址http://www.zghlxwxcb.cn/news/detail-810793.html

參考文獻(xiàn)

  • https://www.elastic.co/guide/en/elasticsearch/guide/index.html
  • https://hadoop.apache.org/docs/r3.3.4/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html
  • https://cassandra.apache.org/_/cassandra-basics.html
  • https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf

到了這里,關(guān)于Elasticsearch 分布式架構(gòu)剖析及擴(kuò)展性優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深度剖析 Apache EventMesh 云原生分布式事件驅(qū)動(dòng)架構(gòu)

    深度剖析 Apache EventMesh 云原生分布式事件驅(qū)動(dòng)架構(gòu)

    近年來,隨著微服務(wù)、云原生和 Serverless 概念的普及以及容器化技術(shù)的發(fā)展,事件驅(qū)動(dòng)也再次成為熱點(diǎn),引起 IT 界廣泛的關(guān)注。事件驅(qū)動(dòng)架構(gòu)是一種用于設(shè)計(jì)應(yīng)用的軟件架構(gòu)和模型。對于事件驅(qū)動(dòng)系統(tǒng)而言,事件的捕獲、通信、處理和持久保留是解決方案的核心結(jié)構(gòu)。事件驅(qū)

    2024年02月09日
    瀏覽(28)
  • Elasticsearch分布式一致性原理剖析(三)-Data篇

    Elasticsearch分布式一致性原理剖析(三)-Data篇

    本文首發(fā)于云棲社區(qū)( Elasticsearch分布式一致性原理剖析(三)-Data篇-博客-云棲社區(qū)-阿里云 ),由原作者轉(zhuǎn)載。 “Elasticsearch分布式一致性原理剖析”系列將會(huì)對Elasticsearch的分布式一致性原理進(jìn)行詳細(xì)的剖析,介紹其實(shí)現(xiàn)方式、原理以及其存在的問題等(基于6.2版本)。前兩篇文章

    2024年01月24日
    瀏覽(22)
  • Elasticsearch分布式一致性原理剖析(二)-Meta篇

    Elasticsearch分布式一致性原理剖析(二)-Meta篇

    Elasticsearch分布式一致性原理剖析(二)-Meta篇 - 知乎 本文首發(fā)于云棲社區(qū)(Elasticsearch分布式一致性原理剖析(二)-Meta篇-博客-云棲社區(qū)-阿里云 ),由原作者轉(zhuǎn)載。 “Elasticsearch分布式一致性原理剖析”系列將會(huì)對Elasticsearch的分布式一致性原理進(jìn)行詳細(xì)的剖析,介紹其實(shí)現(xiàn)方式、原

    2024年01月24日
    瀏覽(38)
  • 分布式搜索引擎elasticsearch搜索功能介紹及實(shí)際案例剖析

    分布式搜索引擎elasticsearch搜索功能介紹及實(shí)際案例剖析

    1.1.1 DSLQuery的分類 Elasticsearch提供了基于JSON的DSL(Domain Specific? Language)來定義查詢。常見的查詢類型包括: 查詢所有:查詢出所有數(shù)據(jù),一般測試用。例如:match_all 全文檢索(full text)查詢:利用分詞器對用戶輸入內(nèi)容分詞,然后去倒排索引庫中匹配。例如: match_query mu

    2024年02月20日
    瀏覽(28)
  • 分布式存儲(chǔ)系統(tǒng)舉例剖析(elasticsearch,kafka,redis-cluster)

    分布式存儲(chǔ)系統(tǒng)舉例剖析(elasticsearch,kafka,redis-cluster)

    1. 概述 對于分布式系統(tǒng),人們首先對現(xiàn)實(shí)中的分布式系統(tǒng)進(jìn)行高層抽象,然后做出各種假設(shè),發(fā)展了諸如CAP, FLP 等理論,提出了很多一致性模型,Paxos 是其中最璀璨的明珠。我們對分布式系統(tǒng)的時(shí)序,復(fù)制模式,一致性等基礎(chǔ)理論特別關(guān)注。 在共識(shí)算法的基礎(chǔ)上衍生了選舉

    2024年02月12日
    瀏覽(25)
  • 分布式技術(shù)剖析

    分布式技術(shù)剖析

    隨著企業(yè)數(shù)字化進(jìn)程的進(jìn)一步深入,企業(yè)為了解決大數(shù)據(jù)的“4個(gè)V”問題,往往需要構(gòu)建多個(gè)不同技術(shù)棧的大數(shù)據(jù)平臺(tái),其中不乏會(huì)使用到分布式相關(guān)的存儲(chǔ)、計(jì)算、資源管理技術(shù)。分布式系統(tǒng)的出現(xiàn)解決了單機(jī)系統(tǒng)無法解決的成本、效率和高可用問題。那么什么是分布式技

    2023年04月10日
    瀏覽(30)
  • Zookeeper分布式一致性協(xié)議ZAB源碼剖析

    Zookeeper分布式一致性協(xié)議ZAB源碼剖析

    ZAB 協(xié)議全稱:Zookeeper Atomic Broadcast(Zookeeper 原子廣播協(xié)議)。 Zookeeper 是一個(gè)為分布式應(yīng)用提供高效且可靠的分布式協(xié)調(diào)服務(wù)。在解決分布式一致性方面,Zookeeper 并沒有使用 Paxos ,而是采用了 ZAB 協(xié)議,ZAB是Paxos算法的一種簡化實(shí)現(xiàn)。 ZAB 協(xié)議定義:ZAB 協(xié)議是為分布式協(xié)調(diào)服

    2024年02月07日
    瀏覽(34)
  • 架構(gòu)09- 理解架構(gòu)的模式3-性能和可擴(kuò)展性

    架構(gòu)09- 理解架構(gòu)的模式3-性能和可擴(kuò)展性

    一、緩存輔助模式:根據(jù)需要將數(shù)據(jù)從數(shù)據(jù)存儲(chǔ)加載到緩存中,以提高讀取性能和響應(yīng)速度。 1、查緩存,不存在則查庫,并更新緩存:應(yīng)用程序首先嘗試從緩存中獲取所需數(shù)據(jù),如果緩存中不存在,則從數(shù)據(jù)庫中獲取并更新緩存。這種方式可以提高讀取性能和命中率。 2、直

    2024年01月18日
    瀏覽(26)
  • 【分布式】分布式存儲(chǔ)架構(gòu)

    【分布式】分布式存儲(chǔ)架構(gòu)

    說到分布式存儲(chǔ),我們先來看一下傳統(tǒng)的存儲(chǔ)是怎么個(gè)樣子。 傳統(tǒng)的存儲(chǔ)也稱為集中式存儲(chǔ), 從概念上可以看出來是具有集中性的,也就是整個(gè)存儲(chǔ)是集中在一個(gè)系統(tǒng)中的,但集中式存儲(chǔ)并不是一個(gè)單獨(dú)的設(shè)備,是集中在一套系統(tǒng)當(dāng)中的多個(gè)設(shè)備,比如下圖中的 EMC 存儲(chǔ)就需

    2024年02月10日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包