一、为什么 TongSearch 必须引入"分片"这一抽象
在理解 TongSearch 分片机制之前,必须先回到它最底层的事实:TongSearch 并不是一个"原生分布式存储引擎",而是一个分布式的 Lucene 管理系统。
Lucene 的核心设计决定了一个非常重要的前提条件:
一个 Lucene Index 在物理上并不是无限可扩展的。
Lucene 的索引文件需要维护倒排表、词典、段(Segment)以及大量随机访问的数据结构。当索引规模不断扩大时,无论是文件句柄数量、内存映射压力,还是段合并成本,都会呈现出明显的非线性增长。Lucene 官方长期以来的工程经验都表明,单个索引做到"无限大"在工程上不可行。
TongSearch 在设计之初就接受了这一现实,并选择了一条非常明确的路线:
与其让一个 Lucene Index 无限膨胀,不如从逻辑层面把一个索引拆成多个 Lucene Index,并在系统层统一管理。
于是,"Shard(分片)"这一抽象诞生了。
在 TongSearch 中,每一个 Primary Shard 或 Replica Shard,本质上就是一个独立的 Lucene Index。分片并不是逻辑概念,而是物理存在的索引实体。
从这个角度看,分片并不是为了"分布式而分布式",而是为了控制 Lucene 的工程复杂度,并在此基础上实现横向扩展能力。
二、索引创建阶段:分片数量是如何被"固化"的
在 TongSearch 中,分片的生命周期起点,永远发生在索引创建这一刻。
当用户通过 PUT /index_name 创建索引时,请求最终会被路由到当前集群的主节点。主节点并不会立刻去创建任何物理分片,而是先完成一件非常关键的事情:
生成索引级别的元数据(IndexMetadata)并写入集群状态。
在这个元数据中,有一个字段具有决定性意义:
number_of_shards
一旦索引被创建成功,这个值就会被永久写入集群状态,并且在 TongSearch 中不可更改。这是一个非常典型的"设计即约束"的工程决策。
原因并不复杂:
分片数量直接参与 document → shard 的路由计算
分片 ID 会被写入倒排结构、事务日志和 translog
Lucene 并不支持在不重写索引的情况下重新切分索引
因此,TongSearch 选择了一条相对保守但极其稳定的道路:
分片数一旦确定,就成为索引的"物理边界条件"。
这也是为什么在工程实践中,分片设计往往被称为"索引设计中最重要的一次性决策"。
三、Shard ID 的来源:分片并不是随机编号
在索引元数据被写入集群状态后,TongSearch 会为这个索引生成一组固定的 Primary Shard。
每一个 Primary Shard 都有一个不可变的整数 ID:
ShardId = [indexUUID, shardNumber]
其中:
indexUUID 是索引级别的唯一标识
shardNumber 的取值范围是 [0, number_of_shards - 1]
这里有一个非常重要但容易被忽略的事实:
Shard ID 的编号顺序,与节点、磁盘、甚至是否真的创建完成都没有关系。
Shard ID 是一个纯逻辑编号空间,它只用于路由和一致性计算,而不是用于表达"第几个被创建"。
这为后续的路由算法提供了一个稳定、可预测的映射基础。
四、Document 到 Shard 的路由:一次确定、终身有效
当一个文档被写入 TongSearch 时,它必须被路由到某一个确定的 Primary Shard。这一过程在 中遵循一个极其稳定的计算链路。
- routing 值的确定
每一个文档在写入时,都会先确定一个 routing 值:
如果用户显式指定了 _routing,则使用该值
否则默认使用 _id
这一步的设计目标非常明确:确保同一 routing 值的文档,永远落到同一个分片上。
- hash 计算
TongSearch 使用 Murmur3 哈希函数对 routing 值进行计算:
hash = murmur3(routing)
Murmur3 的特点在于:
速度快
分布均匀
跨版本稳定
这保证了分片负载在统计意义上的均衡性。
- 分片定位公式
最终的分片编号通过取模计算得到:
shard = hash % number_of_shards
这一行代码,几乎决定了 TongSearch 分片模型的全部工程约束。
因为:
number_of_shards 一旦变化
所有历史文档的路由结果都会发生改变
这也是为什么 TongSearch 禁止在线修改主分片数量 的根本原因。
五、分片信息如何进入集群状态
当索引创建完成后,分片并不会立刻出现在某个节点上。
主节点首先会在内存中构建一个新的 ClusterState,其中包含:
IndexMetadata(索引元数据)
RoutingTable(分片路由表)
在这个 RoutingTable 中,每一个 Primary Shard 会被标记为:
未分配(UNASSIGNED)
此时,"分片"在逻辑上已经存在,但在物理上尚未落地。
只有在后续的 Shard Allocation 阶段,主节点根据节点信息、磁盘水位、分配规则计算出目标节点后,分片才会真正被创建为 Lucene Index。
这意味着:
分片的"存在"是先于其物理文件的。
这是 TongSearch 能够实现复杂重分配、延迟分配和失败恢复的基础。
六、总结
在 TongSearch 中:
分片不是运行时随意切分的单元,而是索引创建时确定的物理边界
分片 ID 是稳定的、与节点无关的逻辑编号
document → shard 的映射在第一次写入时就已经被永久确定
集群状态是分片存在的第一载体,而不是磁盘文件
这也直接解释了很多现象,例如:
为什么分片设计错误只能通过重建索引修复
为什么 routing 使用不当会导致严重的数据倾斜
为什么索引数量和分片数量会直接影响主节点稳定性