本 `AllocationDecider` 根据节点配置中定义的 **感知键值对(awareness key-value pairs)** 来控制分片分配。
**感知机制(awareness)** 的核心目的是:
**明确控制副本应该被分配到哪些位置**,从而避免所有副本扎堆在同一物理位置(如同一个机架、同一个机房、同一个可用区),确保即使某个位置整体故障,仍至少有一个副本可用。
---
### 举个例子:按机架(rack)分配
设置:
```yaml
cluster.routing.allocation.awareness.attributes: rack_id
```
表示:**尽量把同一份分片的主副本和各个副本分配到不同的机架上**。
要让这一规则生效,节点启动时必须带上对应的属性值,例如:
```yaml
node.attr.rack_id: 1
```
这样,ES 在分配时就会统计每个机架上已有的副本数,**避免多个副本落在同一机架**,达到“机架级容灾”的效果。
---
### 再举个例子:按云端“可用区(zone)”分配
设置:
```yaml
cluster.routing.allocation.awareness.attributes: zone
```
再加上强制声明:
```yaml
cluster.routing.allocation.awareness.force.zone.values: zone1, zone2
```
表示:
- 集群当前可能只有 `zone1` 的节点在线,但你告诉 ES **未来一定会有 zone2**。
- 因此 ES 会把副本数按 **总 zone 数 = 2** 来均分计算上限,**不会因为是“当前只有 zone1”就把副本全堆在 zone1**。
- 即使 `zone2` 暂时无节点或部分故障,**也不会出现“zone1 超载”**的情况。
zone2 节点没上线前,**ES 不会把副本硬塞进一个不存在的节点**;
它只会 **“先不分配”**(shard 状态保持 `UNASSIGNED`),直到:
1. 你启动了带有 `node.attr.zone: zone2` 的节点 → 副本立刻被分配到这些节点上。
2. 你主动把 `force.zone.values` 里的 `zone2` 去掉 → ES 重新按 **实际只有 1 个 zone** 计算上限,原来卡住的副本才允许在 `zone1` 补完。
所以 **“不超载” ≠ “强行放过去”**,而是 **“宁可暂时少一份,也不破坏容灾布局”**。
集群表现为 **部分分片处于未分配状态**,直到对应 zone 的节点加入。
节点侧只需照常声明自己所在的 zone:
```yaml
node.attr.zone: zone1
```
---
### 总结一句话
**感知机制 = 让 ES 按“你指定的物理维度”去均分副本,防止扎堆;
`force.values` = 提前声明未来才会有的维度值,避免过渡期超载。**
**只要任意一个 AllocationDecider 返回 NO**,这条分片就 **不会被分配到任何节点**,状态保持 **UNASSIGNED**,直到:
- 你**调整规则/配置**(让 decider 通过),或
- 你**增加/修复节点**(让规则自然满足),或
- 你**手动干预**(比如调低副本数、关闭感知强制值等)。
ES 的分配流程是 **“一票否决”制**:
**所有 decider 都 YES → 才能落地;任何一个 NO → 继续等着。**
下面把 `underCapacity` 的完整流程按“**判断顺序 + 分支走向**”拆开,让你一眼看清每一步在拦什么、怎么走。
--------------------------------------------------
1. **感知功能开没开**
```java
if (awarenessAttributes.isEmpty()) return YES_NOT_ENABLED;
```
- 集群没配 `cluster.routing.allocation.awareness.attributes`
→ **直接放行**,后面逻辑全跳过。
--------------------------------------------------
2. **索引是否“扩到全节点”**
```java
if (indexMetadata.getAutoExpandReplicas().expandToAllNodes()) return YES_AUTO_EXPAND_ALL;
```
- 索引设置了 `"auto_expand_replicas" : "0-all"`
→ **无视感知规则,直接放行**。
--------------------------------------------------
3. **逐个维度循环检查**
对 `awarenessAttributes` 里每一个 `awarenessAttribute`(如 zone、rack)执行下列 **4-8** 步:
--------------------------------------------------
4. **目标节点有没有这个维度**
```java
if (node.node().getAttributes().containsKey(awarenessAttribute) == false)
return Decision.NO; // 或 debugNoMissingAttribute
```
- 节点没写 `node.attr.zone` 之类
→ **拒绝 + 给出 missing 理由**。
--------------------------------------------------
5. **运行时 double-check**(assert,非业务分支)
两句 `assert`:
- 节点该属性值不能为 null
- 该值必须出现在“集群已见值 + 强制值”集合里
**生产默认关闭,失败抛 AssertionError,防止手抖写错。**
--------------------------------------------------
6. **统计“目标区”已有多少份该分片**
6a. **遍历已分配副本**
```java
for (ShardRouting assignedShard : allocation.routingNodes().assignedShards(shardId))
if (assignedShard.started() || assignedShard.initializing())
如果副本所在节点的维度值 == targetAttributeValue → 计数器 +1
```
6b. **moveToNode 场景再补 0/1 份**
- 分片已迁出源节点(relocating)或未分配
→ **跨区移动/新增** 会让目标区“净多”1 份,于是 `+1`;同区移动则不加。
**结果:shardsForTargetAttributeValue = 目标区当前份数 + 未来份数变化**
--------------------------------------------------
7. **计算上限**
```java
valueCount = 实际值 ∪ 强制值 去重后总数
maximumShardsPerAttributeValue = ceil( shardCount / valueCount )
```
- shardCount = 1(主)+ 副本数
- 即“每个感知值最多可放几份”。
--------------------------------------------------
8. **是否超标**
```java
if (shardsForTargetAttributeValue > maximumShardsPerAttributeValue)
return Decision.NO; // 或 debugNoTooManyCopies
```
- 目标区份数 > 上限 → **拒绝**,打出当前区、上限、已份数等详情。
- 未超标 → **继续下一个维度**;全部维度通过 → 返回 `YES_ALL_MET`。
--------------------------------------------------
分支全景图(简化)
```
┌─ YES_NOT_ENABLED
├─ YES_AUTO_EXPAND_ALL
│ ↓
对每个维度 ── 缺属性 ──→ NO
│ ↓
assert 自检(异常抛 AssertionError)
│ ↓
统计目标区份数 + 移动修正
│ ↓
计算 ceil(总分片 / 总区数) 上限
│ ↓
份数>上限 ──→ NO + debug
│ ↓
全部维度通过 ──→ YES_ALL_MET
```
走完这张图,就完成了 **“感知维度层面是否允许把这份分片放到该节点”** 的全部判断。
Elasticsearch感知分片分配原理

2090

被折叠的 条评论
为什么被折叠?



