一、从"已存在"到"可工作":分片真正开始运行的时刻
在以前文章中已经明确过一个容易被忽略的事实:分片在集群状态中被创建,并不意味着它已经可以对外提供服务。
在 Tongsearch 中,一个分片从逻辑诞生到真正可用,必须经历完整的运行时生命周期,而这一切的调度核心,全部集中在主节点。
当索引创建完成后,主节点在新的集群状态中会生成对应的 RoutingTable。此时,每一个 Primary Shard 都处于 UNASSIGNED 状态。这个状态并不是异常,而是一种明确的设计表达:
分片已被定义,但尚未选择其运行载体。
也正是在这一刻,分片正式进入了"运行时管理系统"的控制范围。
二、Shard Routing Table:分片运行时的核心数据结构
要理解分片在运行时的行为,必须先理解 RoutingTable 的存在意义。
在 Tongsearch中,RoutingTable 并不是一个简单的"分片在哪个节点"的映射,而是一个分片状态机的集中表达。
对于每一个 shard,RoutingTable 中都会记录:
-
分片 ID(index + shard number)
-
分片角色(Primary / Replica)
-
当前状态(UNASSIGNED / INITIALIZING / STARTED / RELOCATING)
-
目标节点与源节点信息
这些信息并不存放在节点本地配置中,而是全部属于集群状态的一部分。这意味着:
分片的运行状态,是一个被主节点强一致管理的全局事实。
任何节点都不能"自行决定"某个分片的角色或位置。
三、分片分配的触发时机:不是只有建索引
很多开发者以为,分片分配只发生在索引创建时。实际上,在 Tongsearch 中,分片分配是一种持续存在的调度行为。
以下事件都会触发主节点重新计算分片分配:
-
新索引创建
-
节点加入或离开集群
-
磁盘水位变化
-
手动触发 reroute
-
分片启动或失败
每一次触发,主节点都会基于当前集群状态,重新执行一次完整的分配决策流程。
四、分片分配决策是如何计算出来的
分片分配的计算过程,集中发生在主节点的 AllocationService 中。这不是一个简单的"找一个空节点"的逻辑,而是一个多阶段、多约束的决策过程。
从宏观上看,分配过程可以理解为:
在所有可用节点中,为每一个未分配或需要迁移的分片,寻找一个"代价最低且合法"的目标节点。
这里的"合法",包含了大量工程约束:
-
节点角色是否允许存储数据
-
磁盘水位是否满足要求
-
是否违反同一分片副本不共存规则
-
是否满足强制感知(awareness)或机架规则
Tongsearch 并不会一次性决定所有分片的位置,而是采用增量评估与打分机制,逐个分片计算最优解。
这也是为什么在分片数量巨大时,主节点的 CPU 压力会明显上升。
五、Primary 与 Replica:并非简单的主从关系
在运行时,Primary Shard 与 Replica Shard 承担着不同职责。
Primary 是写入路径的唯一入口,所有写请求必须先成功作用于 Primary,才能被复制到 Replica。Replica 的存在,主要用于:
-
提供查询并发能力
-
在 Primary 失效时快速接管
但在分配和迁移过程中,两者并非完全对等。
在 Tongsearch 中,系统始终优先保证 Primary 的可用性。一个典型表现是:
如果 Primary 未分配,对应的 Replica 永远不会被分配。
这是为了避免出现"没有主分片却存在副本"的无意义状态。
六、分片启动流程:从 INITIALIZING 到 STARTED
当主节点决定将某个分片分配到目标节点后,该分片会进入 INITIALIZING 状态。
此时,目标节点会开始执行一系列实际操作:
-
创建 Lucene 索引目录
-
打开或恢复 translog
-
如果是 Replica,则从 Primary 进行数据恢复
这个过程是真正消耗 IO 和网络资源的阶段。
只有当目标节点明确向主节点汇报"分片已完全就绪"后,主节点才会发布新的集群状态,将该分片标记为 STARTED。
这一步非常关键,因为:
分片状态的变更,永远以主节点发布的集群状态为准,而不是节点自我感知。
七、分片迁移(Relocation):不停服的数据移动
当集群发生节点变动或负载不均衡时,主节点可能会决定迁移某些分片。
迁移在 RoutingTable 中表现为:
-
原分片进入
RELOCATING -
目标节点生成一个新的
INITIALIZING副本
在迁移过程中:
-
写请求仍然发送到原 Primary
-
数据通过后台恢复同步到新节点
当新分片完全同步完成后,主节点会一次性切换路由,并发布新的集群状态。这一切对客户端是透明的。
八、分片失败与自动恢复机制
分片在运行过程中并不是永远稳定的。磁盘异常、节点宕机、进程崩溃,都会导致分片失败。
在 Tongsearch 中,一旦节点检测到分片异常,会立刻上报主节点。主节点随后会:
-
将该分片标记为 FAILED
-
在新的集群状态中移除其运行实例
-
尝试重新分配新的分片副本
这一过程完全自动完成,不依赖人工介入。
九、分片生命周期中的关键时间窗口与隐性成本
在真实生产环境中,分片的生命周期并不是一个线性、平滑推进的过程,而是充满了时间窗口与资源竞争。这些细节往往不会直接体现在 API 或日志的第一层信息中,却决定了集群在压力场景下是否稳定。
一个典型被忽略的时间窗口,出现在 分片从 INITIALIZING 切换为 STARTED 之前。在这一阶段,分片的数据已经在目标节点上基本可用,但在集群状态层面仍然被视为"不可服务"。
这意味着:
-
查询层不会将请求路由到该分片
-
写入层仍然集中在旧 Primary 或其他副本
-
主节点需要持续跟踪该分片的恢复进度
如果恢复过程因为磁盘抖动、网络带宽受限或 JVM 压力而被拉长,这个"看似短暂"的窗口就会被无限放大,进而导致:
-
查询热点集中
-
Primary 写入压力异常
-
集群状态更新频率被迫提高
十、磁盘水位与分片迁移的连锁反应
在 Tongsearch 中,磁盘水位是分片迁移最常见、也是最容易引发误判的触发因素之一。
当某个节点超过高水位时,主节点会尝试将该节点上的分片迁移出去。但在工程实践中,迁移并不一定真的"降低风险"。
原因在于:
-
分片迁移本身需要大量磁盘 IO
-
迁移目标节点往往也处于接近水位阈值的状态
-
大规模迁移会同时触发多个 shard 的 INITIALIZING
这在 RoutingTable 层面表现为:
大量分片同时处于 RELOCATING 与 INITIALIZING 状态
RoutingTable 的膨胀,会直接增加集群状态对象的体积,进而放大:
-
集群状态序列化成本
-
主节点发布状态的 CPU 压力
-
非主节点应用状态的延迟
这也是为什么在工程上经常看到这样一种现象:
明明磁盘问题已经缓解,但集群却在较长时间内持续不稳定。
十一、分片失败后的重建:并非"立刻恢复"
当一个分片失败并被主节点标记为 FAILED 后,系统会尝试自动重建该分片。但这里存在一个重要的工程事实:
重建并不是即时发生的,而是受多重条件约束。
例如:
-
是否存在可用的节点
-
是否满足分配规则
-
是否会导致副本不一致风险
在这些条件不满足时,分片会长时间停留在 UNASSIGNED 状态。这并不是 Bug,而是 Tongsearch 在"可用性与一致性"之间做出的主动权衡。
工程上真正危险的情况,并不是 UNASSIGNED 本身,而是:
- 大量分片频繁 FAILED → UNASSIGNED → INITIALIZING 的抖动
这种抖动会导致主节点不断生成新的集群状态,最终演变为集群协调层面的压力问题。
十二、分片运行时对主节点稳定性的真实影响
在第二篇中,需要明确指出一个经常被低估的事实:
分片运行时的复杂度,最终都会汇聚到主节点。
无论是分配、迁移、失败还是恢复,这些事件都会导致:
-
RoutingTable 发生变化
-
新的 ClusterState 被构建
-
状态被序列化并广播
当分片数量较少时,这一切几乎是"无感的"。但当集群进入以下状态之一时,问题就会被放大:
-
分片数量巨大
-
节点频繁进出集群
-
磁盘或网络处于临界状态
此时,分片运行时行为就不再是"数据层问题",而是直接演变为集群协调与稳定性问题。
十三、分片不是静态资源,而是动态系统
在 Tongsearch中,分片并不是一个静态存在的存储单元,而是一个持续参与集群运行调度的动态对象。
它的每一次状态变化,都会影响:
-
集群状态大小
-
主节点的计算与发布负载
-
整个系统的恢复节奏