前言:为什么以及何时增加副本数?
假设你管理着一个包含10个节点的Elasticsearch集群,其中索引 index_a
配置了5个主分片(Primary Shards)和1个副本分片(Replica Shards)。这意味着 index_a
总共有 5 (主) + 5 * 1 (副本) = 10个分片,在理想情况下,这10个分片会均匀分布在10个节点上,每个节点承载一个分片。
现在,出于提高读取吞吐量或增强数据冗余和高可用性的目的,你决定将 index_a
的副本数从1增加到2。这个操作看起来很简单,只需要一条API调用,但ES内部会经历一系列复杂而有序的过程。最终,集群需要容纳 5 (主) + 5 * 2 (副本) = 15个分片。
这篇文章将深入探讨当你执行这个操作时,Elasticsearch内部到底发生了什么?它如何为新增的5个副本分片选择合适的家(节点)?数据是如何从主分片复制到新副本的?这个过程是否会像深夜施工一样干扰到正在进行的读写请求?以及,如果在这个关键时刻有节点发生故障,ES又将如何应对?
这篇文章的目标读者是负责大型ES集群运维和变更管理的工程师,我们将侧重于实际操作(API调用)和内部流程(分片分配、复制过程)的解析,并充分考虑操作期间对集群稳定性和性能的潜在影响。
一、触发变更:增加副本数的API调用
一切始于一个简单的API请求。你可以使用 _settings
API来动态修改索引的 number_of_replicas
参数。针对我们的场景,命令如下:
PUT /index_a/_settings
{
"index": {
"number_of_replicas": 2
}
}
这个请求通常发送给集群中的任何一个节点,该节点会将其转发给当前的Master节点。Master节点是集群状态的管理者,负责处理这类元数据变更请求。
关键点:
- 这是一个动态设置,意味着你不需要停止索引写入或关闭索引即可应用。
- Master节点收到请求后,会验证其合法性(例如,是否有足够的节点来容纳新的副本)。
- 验证通过后,Master节点会更新集群状态(Cluster State),将
index_a
的目标副本数修改为2。集群状态的更新会被发布到集群中的所有节点。
二、集群响应:Master的调度与集群状态更新
当集群状态更新(包含 index_a
新的副本数设置)被所有节点确认后,Master节点的核心任务开始了:为新增的5个副本分片找到合适的节点进行分配(Allocation)。
这不是一个随意的过程,ES拥有一套复杂的分片分配机制,由多个“决策者”(Allocation Deciders)共同决定一个分片应该(或不应该)被分配到哪个节点。
思考过程: Master节点会查看当前的集群拓扑、每个节点的状态(负载、磁盘空间等)以及索引的配置。它知道 index_a
现在需要为每个主分片(P0, P1, P2, P3, P4)再创建并分配一个新的副本分片(我们称之为 R0', R1', R2', R3', R4')。目标是最终每个主分片 P_i 对应两个副本 R_i 和 R_i'。
三、核心流程1:新副本的家在哪里?—— 分片分配(Shard Allocation)
Master节点在为新的副本分片寻找家园时,会遵循一系列规则,这些规则由分片分配决策者(Allocation Deciders)实现。主要考虑因素包括:
Same Shard
Decider: 这是最关键的规则之一。它确保同一个分片的主副本和所有副本不会分配到同一个节点上。这是高可用的基石。在我们的例子中,如果P0在Node1上,R0在Node2上,那么新的R0'绝对不会被分配到Node1或Node2。Awareness
Decider: 如果你配置了机架或区域感知(cluster.routing.allocation.awareness.attributes
),ES会尝试将同一分片的副本分散到不同的感知区域(如不同的机架、可用区)。例如,如果你配置了两个区域zone1
和zone2
,ES会尽量保证P0、R0、R0' 分布在不同的区域或至少两个不同的区域中。Disk Threshold
Decider: 检查目标节点的磁盘使用率。如果节点的磁盘使用率超过了低水位线(cluster.routing.allocation.disk.watermark.low
)或高水位线(cluster.routing.allocation.disk.watermark.high
),ES可能不会将新分片分配到该节点。默认情况下,超过85%(低水位)会阻止分配新分片,超过90%(高水位)会触发将现有分片移走。Filter
Decider: 你可以通过索引设置或集群设置来明确包含或排除某些节点用于特定索引的分片分配(index.routing.allocation.include.*
,index.routing.allocation.exclude.*
)。Shards Limit
Decider: 限制单个节点上可以承载的特定索引的分片总数(index.routing.allocation.total_shards_per_node
)或集群范围内单个节点的最大分片数(cluster.routing.allocation.total_shards_per_node
)。Node Version
Decider: 通常用于滚动升级场景,确保分片不会从新版本节点分配到旧版本节点(除非特殊情况)。- 平衡策略(Balancing): 在满足上述所有硬性规则的前提下,ES会尽可能地将分片均匀分布在所有可用节点上,以平衡负载。它会考虑每个节点上已有的分片数量、特定索引的分片数量等因素。
具体到我们的场景:
- 集群有10个节点。
index_a
有5个主分片。- 增加副本到2,需要新增5个副本分片。
- 最初,10个分片(5主+5副)分布在10个节点上,每个节点1个分片。
- Master节点需要为 R0', R1', R2', R3', R4' 找到位置。
- 根据
Same Shard
规则,R0' 不能放在持有P0或R0的节点上。同理适用于其他分片。 - 假设没有其他复杂的分配规则(如awareness、filter),并且所有节点资源充足,ES会倾向于将这5个新的副本分片分配到当前承载分片数量最少的节点上,同时满足
Same Shard
约束。由于初始状态是每个节点1个分片,那么这5个新副本很可能会被分配到那5个当前只持有主分片(或只持有旧副本分片)的节点上,使得最终每个节点承载1或2个index_a
的分片,总共15个分片分布在10个节点上(5个节点有2个分片,5个节点有1个分片),这是一个相对均衡的状态。
你可以使用 _cluster/allocation/explain
API 来查看为什么某个未分配的分片(unassigned shard)没有被分配,或者为什么它被分配到了特定的节点:
GET /_cluster/allocation/explain
{
"index": "index_a",
"shard": 0,
"primary": false # true for primary, false for replica
}
一旦Master节点为某个新副本分片(例如 R0')选定了目标节点(例如 Node3),它会在集群状态中将 R0' 标记为 INITIALIZING
状态,并将其分配给 Node3。Node3收到更新后的集群状态后,就知道自己需要初始化 R0' 了。
四、核心流程2:数据从哪里来?—— 分片复制(Shard Replication / Recovery)
当一个节点(如 Node3)被告知需要初始化一个新的副本分片(R0')时,它需要从对应的主分片(P0,假设在 Node1 上)获取数据。这个过程被称为分片恢复(Shard Recovery),对于全新的副本分片,这通常是基于网络的文件拷贝过程,也称为对等恢复(Peer Recovery)。
复制过程详解:
- 建立连接: Node3上的 R0' 会与 Node1 上的 P0 建立连接。
- 获取元数据: R0' 从 P0 获取最新的段(Segment)文件列表和提交点(Commit Point)信息。
- 文件拷贝(I/O密集型): 这是最耗时的阶段。R0' 开始从 P0 逐个复制索引段文件。这是一个网络密集型和磁盘I/O密集型的操作。
- 在源节点(Node1):产生大量的磁盘读和网络发送。
- 在目标节点(Node3):产生大量的网络接收和磁盘写。
- ES会对这个过程进行限速,以避免恢复流量压垮集群。这个限速由集群设置
indices.recovery.max_bytes_per_sec
控制(默认为40mbps)。你可以根据集群的网络和磁盘能力动态调整此设置,但需要谨慎,过高的值可能影响集群性能。 - 思考: 如果主分片非常大(几百GB甚至TB级别),这个文件拷贝阶段可能会持续很长时间。
- Translog重放(Catch-up Phase): 在文件拷贝进行的同时,主分片 P0 可能仍在接收新的写入、更新或删除请求。这些操作会被记录在 P0 的事务日志(Transaction Log,简称 translog)中。当 R0' 完成了所有段文件的拷贝后,它需要重放从恢复开始时 P0 记录的所有 translog 操作,以确保自己的数据与 P0 完全同步。
- 成为Active: 一旦 R0' 成功重放了所有必要的 translog 操作,并且与 P0 的数据状态一致,它会通知 Master 节点。Master 节点确认后,会将 R0' 的状态从
INITIALIZING
更新为ACTIVE
。此时,这个新的副本分片就可以对外提供读服务了,并且会被纳入写入操作的同步确认集合(in-sync copies)。
你可以通过 _cat/recovery
API 监控恢复过程的进度:
GET /_cat/recovery?v&active_only=false
这条命令会显示所有正在进行或最近完成的恢复活动,包括索引名、分片ID、源节点、目标节点、恢复阶段(如 index
, translog
)、已传输字节数、百分比等信息。观察 bytes_percent
和 files_percent
可以了解文件拷贝的进度。
五、对集群性能和稳定性的影响
增加副本数的操作虽然是后台异步执行,但并非完全没有影响,尤其是在数据复制阶段:
- 网络带宽: 分片复制会消耗显著的网络带宽,特别是当多个副本同时开始复制时。如果网络带宽成为瓶颈,复制速度会变慢,甚至可能影响到正常的集群通信和客户端请求。
- 应对: 监控网络流量。如果影响过大,可以考虑临时调低
indices.recovery.max_bytes_per_sec
,或者错峰执行(比如在业务低峰期)。
- 应对: 监控网络流量。如果影响过大,可以考虑临时调低
- 磁盘 I/O: 源节点(持有主分片)会有较高的读 I/O,目标节点(接收新副本)会有较高的写 I/O。如果节点的磁盘性能不足,可能导致磁盘延迟升高,影响该节点上所有分片的读写性能。
- 应对: 监控磁盘 I/O 使用率和延迟。确保节点有足够的 I/O 能力。使用SSD通常能显著缓解这个问题。
- CPU 消耗: 数据传输、文件校验、可能的解压缩等操作会消耗一定的CPU资源,但通常不如网络和磁盘影响显著。
- 对读请求的影响:
- 基本无影响: 在新副本初始化期间,读请求仍然由主分片和原有的、处于Active状态的副本分片处理。集群的整体读能力不会下降。
- 潜在提升: 一旦新的副本分片变为Active状态,它们就可以分担读请求压力,从而可能提高索引的整体读吞吐量。
- 对写请求的影响:
- 轻微延迟增加可能: 写入操作发生在主分片上。主分片需要将写入操作(或其结果)复制到所有处于in-sync状态的副本分片。默认情况下,一个写操作需要等待至少法定数量(quorum = (副本数 / 2) + 1) 的分片(包括主分片自身)确认后,才向客户端返回成功。当你增加副本数时,这个法定数量可能会增加(从
(1/2)+1 = 1.5 -> 取整为2
增加到(2/2)+1 = 2
)。更重要的是,如果你的写请求设置了wait_for_active_shards
参数(例如设置为all
),那么主分片需要等待所有(包括正在初始化的,一旦它们进入可以接收translog的阶段)副本确认。即使不设置wait_for_active_shards
,主分片也需要将 translog 操作发送给正在初始化的副本(在其进入 translog 重放阶段后),这会轻微增加主分片的网络发送负担。 - 关键在于in-sync集合: 新副本在完成数据复制和translog重放,并被Master标记为Active之前,通常不被视为“in-sync”或者说不计入需要确认写入的副本集合(除非配置非常特殊)。因此,对写入延迟的显著影响通常发生在副本成功激活之后,因为那时主分片需要同步给更多的副本。
- 结论: 在副本初始化完成前,对写入性能的影响通常很小。完成后,由于需要同步的副本增多,可能会有轻微的写入延迟增加,但这通常是为了更高的数据冗余性而付出的合理代价。
- 轻微延迟增加可能: 写入操作发生在主分片上。主分片需要将写入操作(或其结果)复制到所有处于in-sync状态的副本分片。默认情况下,一个写操作需要等待至少法定数量(quorum = (副本数 / 2) + 1) 的分片(包括主分片自身)确认后,才向客户端返回成功。当你增加副本数时,这个法定数量可能会增加(从
- 集群状态更新: 频繁的分片状态变化(
UNASSIGNED
->INITIALIZING
->ACTIVE
)会导致集群状态的更新和发布。在大型集群中,过于频繁或庞大的集群状态更新本身也可能带来开销。
建议:
- 监控!监控!监控! 密切关注集群健康状态 (
_cat/health
),节点资源使用情况(CPU、内存、磁盘I/O、网络),以及恢复进度 (_cat/recovery
)。 - 分阶段进行: 如果集群规模非常大,或者索引非常大,一次性增加大量副本可能会对集群造成冲击。可以考虑分批次增加副本,或者先增加少量副本观察影响。
- 调整恢复速度: 根据监控结果,适当调整
indices.recovery.max_bytes_per_sec
。不要盲目调高,要在性能和稳定性之间找到平衡。
六、故障处理:如果中途有节点挂了怎么办?
在增加副本的过程中,节点故障是可能发生的情况。ES的设计考虑了这种情况,具有良好的容错性。
持有【新副本】的节点挂了:
- 假设Node3正在初始化R0',但此时Node3宕机。
- Master节点会通过故障检测机制(默认基于心跳)发现Node3离线。
- Master会将Node3上的所有分片(包括正在初始化的R0')标记为丢失。
- 集群状态更新,R0'重新变为
UNASSIGNED
状态。 - Master会重新触发R0'的分片分配流程,尝试在另一个健康的、符合分配规则的节点上(比如Node4)分配R0'。
- 一旦分配成功,Node4会开始从P0(在Node1上)进行数据复制,过程从头开始。
- 影响: 该分片的恢复时间会延长,但整个增加副本的操作目标(最终达到2个副本)不受影响,只是过程会曲折一些。其他正在正常进行的副本复制不受此影响。
持有【主分片】的节点挂了:
- 假设Node1持有P0,并且正在向Node3(初始化R0')和Node2(持有原始副本R0)同步数据。此时Node1宕机。
- Master节点检测到Node1故障。
- 主分片提升(Primary Promotion): Master会立即在Node1上的P0的现有Active副本中选择一个(这里是Node2上的R0)提升为新的主分片(New P0)。这个过程非常快。
- 集群状态更新: R0被标记为P0,状态变为Primary。
- 副本重新分配: 原来的P0(在Node1上)丢失了,集群现在缺少一个P0的副本。Master会尝试在其他可用节点上分配一个新的副本分片(我们叫它R0''),目标仍然是维持总共2个副本(现在是New P0 + R0' + R0'')。
- 正在进行的复制中断与重启: Node3上正在进行的R0'复制会中断,因为它原来的源(Old P0 on Node1)已经没了。一旦新的主分片(New P0 on Node2)准备就绪,Node3上的R0'会重新连接到Node2上的New P0,并重新开始或从某个检查点继续(取决于ES版本和具体情况,但通常是接近重新开始)数据复制过程。
- 影响: 短暂的写入中断(在新主分片选出并准备好接收写入之前)。读请求可以由其他分片或副本处理。受影响分片的副本复制过程会被打断并重启,延长完成时间。但集群的可用性和数据完整性(对于已确认的写入)得以保持。
【Master节点】挂了:
- 如果Master节点在副本增加过程中宕机。
- 集群会触发新的Master选举过程。其他符合条件的节点会参与选举,选出一个新的Master。
- 新Master节点会从集群状态中恢复信息,了解到
index_a
的目标副本数是2,以及当前各个分片的分配状态和正在进行的恢复任务。 - 新Master会接管所有未完成的分片分配和恢复管理工作,从中断的地方继续。例如,如果旧Master刚决定将R0'分配给Node3但还未完成状态发布,新Master可能会重新评估并确认或更改这个分配决策。
- 影响: 集群在选举期间(通常是秒级)会短暂不可用(无法处理集群层面的元数据修改,如创建索引、修改设置等,也可能影响部分依赖Master协调的操作)。但一旦新Master选出,增加副本的操作会继续进行。
核心思想: ES的集群状态管理和故障检测机制确保了即使在变更过程中发生节点故障,集群也能自动尝试恢复到目标状态(即每个主分片有两个Active副本),尽管过程可能会被延长。
七、监控与验证
在整个过程中以及操作完成后,你需要使用以下工具进行监控和验证:
GET /_cat/health?v
: 查看集群状态 (status
应该是green
,表示所有主分片和副本分片都已分配;unassign
应该是0)。在增加副本过程中,状态可能会短暂变为yellow
(表示主分片可用,但不是所有副本都已分配),这是正常的。如果长时间处于yellow
,需要检查原因。GET /_cat/indices/index_a?v
: 查看index_a
的健康状态 (health
)、文档数 (docs.count
)、存储大小 (pri.store.size
,store.size
) 以及分片数量 (pri
,rep
)。确认副本数 (rep
) 是否已达到目标值2。GET /_cat/shards/index_a?v
: 查看index_a
每个分片的详细状态 (state
应为STARTED
)、所在节点 (node
)、是主分片还是副本 (prirep
)。检查是否有UNASSIGNED
或INITIALIZING
状态的分片。确认每个主分片(p)都有两个对应的副本(r)处于STARTED
状态。GET /_cat/recovery?v
: 如前所述,用于监控恢复进度。操作完成后,此API的输出应该为空或只显示最近完成的任务。GET /_cluster/allocation/explain
: 如果有分片长时间处于UNASSIGNED
状态,用此API诊断原因。
八、总结与最佳实践
增加Elasticsearch索引的副本数是一个强大的功能,可以有效提升读取性能和数据冗余。其内部过程涉及Master节点的智能调度、基于规则的分片分配决策、以及消耗资源(网络、磁盘)的数据复制。
关键要点回顾:
- 通过
_settings
API 动态触发。 - Master节点负责更新集群状态并根据分配规则(如
same shard
,awareness
,disk usage
等)为新副本选择节点。 - 数据通过 Peer Recovery 从主分片复制到新副本,这是一个I/O密集型过程,受
indices.recovery.max_bytes_per_sec
限速。 - 过程对读请求影响小,可能轻微增加写请求延迟(尤其在副本激活后)。
- ES具备良好的故障处理能力,节点故障(包括Master)通常只会延迟完成时间,不会破坏最终目标。
- 监控是关键:密切关注集群健康、资源使用和恢复进度。
最佳实践建议:
- 了解你的集群: 清楚节点的硬件配置(特别是磁盘和网络)、当前的负载情况、以及是否有特殊的分配规则(如Awareness配置)。
- 规划容量: 确保集群有足够的节点和磁盘空间来容纳新增的副本分片。
- 选择合适的时间: 如果可能,在业务低峰期执行此操作,以减少对生产环境的潜在影响。
- 循序渐进: 对于非常大的索引或集群,可以考虑先增加一个副本,观察影响,再增加到目标数量。
- 合理配置恢复速度: 不要盲目追求最快的恢复速度。根据实际监控情况调整
indices.recovery.max_bytes_per_sec
,在速度和稳定性间取得平衡。 - 使用Shard Allocation Awareness: 如果你的部署环境支持(如跨机架、跨可用区),强烈建议配置Awareness,以最大化高可用性。
通过理解这些内部机制和潜在影响,你可以更有信心地管理和调整你的Elasticsearch集群,确保其在高可用、高性能的道路上稳健运行。