← 返回文章列表

Elasticsearch 底层原理系列(七):分片与路由机制

2020-12-13·5 分钟阅读

前言

分片(Shard)是 Elasticsearch 分布式能力的核心。理解分片路由机制,对于数据分布优化、查询性能调优至关重要。

本章将深入解析分片路由算法、分片规划原则以及分片重平衡策略。

技术亮点

技术点难度面试价值本文覆盖
分片路由算法⭐⭐⭐高频考点
自定义路由⭐⭐⭐⭐进阶考点
分片数量规划⭐⭐⭐⭐高频考点
分片重平衡⭐⭐⭐实战价值

面试考点

  1. Elasticsearch 的分片路由算法是怎样的?
  2. 如何使用自定义路由优化查询?
  3. 分片数量应该如何规划?
  4. 什么是分片重平衡?如何配置?

分片基础

分片的本质

┌─────────────────────────────────────────────────────────────────────────┐
│                        分片的本质                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  分片 = 独立的 Lucene 索引                                               │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  ES Index (my_index)                                            │   │
│  │       │                                                         │   │
│  │       ├── Primary Shard 0 ──────► Lucene Index                  │   │
│  │       │                         ├── Segment 1                   │   │
│  │       │                         ├── Segment 2                   │   │
│  │       │                         └── ...                         │   │
│  │       │                                                         │   │
│  │       ├── Primary Shard 1 ──────► Lucene Index                  │   │
│  │       │                         └── ...                         │   │
│  │       │                                                         │   │
│  │       └── Primary Shard 2 ──────► Lucene Index                  │   │
│  │                                 └── ...                         │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  关键特性:                                                             │
│  • 分片数量在创建索引时确定,之后不可更改                                │
│  • 每个分片是一个独立的工作单元                                          │
│  • 查询并行执行于所有相关分片                                            │
│  • 分片可以分布在集群中的任意节点                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

主分片与副本分片

┌─────────────────────────────────────────────────────────────────────────┐
│                    主分片与副本分片                                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Primary Shard(主分片)                                        │   │
│  │  ┌───────────────────────────────────────────────────────────┐ │   │
│  │  │ • 负责写入操作                                             │ │   │
│  │  │ • 数据权威来源                                             │ │   │
│  │  │ • 数量创建时确定,不可更改                                 │ │   │
│  │  └───────────────────────────────────────────────────────────┘ │   │
│  │                                                                 │   │
│  │  Replica Shard(副本分片)                                      │   │
│  │  ┌───────────────────────────────────────────────────────────┐ │   │
│  │  │ • 主分片的完整复制                                         │ │   │
│  │  │ • 负责读取操作(负载均衡)                                 │ │   │
│  │  │ • 提供数据冗余(高可用)                                   │ │   │
│  │  │ • 主分片故障时提升为新主分片                               │ │   │
│  │  │ • 数量可动态调整                                           │ │   │
│  │  └───────────────────────────────────────────────────────────┘ │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  示例架构:                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Index: number_of_shards=3, number_of_replicas=1               │   │
│  │                                                                 │   │
│  │  ┌──────┐       ┌──────┐       ┌──────┐                        │   │
│  │  │ P0   │       │ P1   │       │ P2   │     主分片             │   │
│  │  └──┬───┘       └──┬───┘       └──┬───┘                        │   │
│  │     │              │              │                             │   │
│  │     ▼              ▼              ▼                             │   │
│  │  ┌──────┐       ┌──────┐       ┌──────┐                        │   │
│  │  │ R0   │       │ R1   │       │ R2   │     副本分片           │   │
│  │  └──────┘       └──────┘       └──────┘                        │   │
│  │                                                                 │   │
│  │  总分片数 = 3 (主) + 3 (副本) = 6 个分片                        │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

分片路由算法

默认路由算法

┌─────────────────────────────────────────────────────────────────────────┐
│                        默认路由算法                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  路由公式:                                                              │
│                                                                         │
│  shard_num = hash(_routing) % num_primary_shards                       │
│                                                                         │
│  默认情况下:_routing = _id                                             │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  示例:3 个主分片的索引                                         │   │
│  │                                                                 │   │
│  │  Document ID    hash(_id)    % 3    目标分片                   │   │
│  │  ─────────────────────────────────────────────                 │   │
│  │  "doc_001"      12345678     0      Shard 0                    │   │
│  │  "doc_002"      23456789     1      Shard 1                    │   │
│  │  "doc_003"      34567890     2      Shard 2                    │   │
│  │  "doc_004"      45678901     0      Shard 0                    │   │
│  │  ...                                                            │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  特点:                                                                  │
│  • 文档均匀分布在所有分片上                                              │
│  • 同一 ID 的文档总是路由到同一分片                                      │
│  • 查询时可以根据 ID 快速定位分片                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

为什么分片数不能改

┌─────────────────────────────────────────────────────────────────────────┐
│                    分片数不可更改的原因                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  假设场景:从 3 个分片改为 4 个分片                                      │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  原:hash(_routing) % 3                                         │   │
│  │  新:hash(_routing) % 4                                         │   │
│  │                                                                 │   │
│  │  Document "doc_001":                                            │   │
│  │  原:hash("doc_001") % 3 = 0  → Shard 0                         │   │
│  │  新:hash("doc_001") % 4 = 2  → Shard 2  ❌ 位置变化!          │   │
│  │                                                                 │   │
│  │  结果:                                                         │   │
│  │  • 几乎所有文档的分片位置都会改变                                │   │
│  │  • 需要重新索引所有数据                                         │   │
│  │  • ES 选择不允许,让用户手动 Reindex                            │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  解决方案:                                                              │
│  1. 创建新索引(新分片数)                                              │
│  2. 使用 Reindex API 迁移数据                                           │
│  3. 删除旧索引                                                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

自定义路由

使用场景

┌─────────────────────────────────────────────────────────────────────────┐
│                    自定义路由使用场景                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  场景:多租户系统,按用户 ID 路由                                        │
│                                                                         │
│  好处:                                                                  │
│  • 查询时只需访问一个分片                                                │
│  • 减少查询开销                                                          │
│  • 提高缓存命中率                                                        │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  默认路由(访问所有分片):                                     │   │
│  │                                                                 │   │
│  │  查询 user_id=123 的数据:                                      │   │
│  │  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐                │   │
│  │  │ S0   │ │ S1   │ │ S2   │ │ S3   │ │ S4   │                │   │
│  │  │ ✓?   │ │ ✓?   │ │ ✓!   │ │ ✓?   │ │ ✓?   │  ← 需要查询所有 │   │
│  │  └──────┘ └──────┘ └──────┘ └──────┘ └──────┘                │   │
│  │                                                                 │   │
│  │  自定义路由(只访问一个分片):                                  │   │
│  │                                                                 │   │
│  │  按 user_id 路由后:                                            │   │
│  │  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐                │   │
│  │  │ S0   │ │ S1   │ │ S2   │ │ S3   │ │ S4   │                │   │
│  │  │      │ │      │ │ ✓!   │ │      │ │      │  ← 只查询一个  │   │
│  │  └──────┘ └──────┘ └──────┘ └──────┘ └──────┘                │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

自定义路由实现

// 1. 设置文档时指定 routing
PUT /my_index/_doc/1?routing=user_123
{
  "user_id": "user_123",
  "title": "Document for user 123"
}

// 2. 查询时指定 routing(只查询特定分片)
GET /my_index/_search?routing=user_123
{
  "query": {
    "match": { "title": "document" }
  }
}

// 3. Mapping 中设置 routing 必需
PUT /my_index
{
  "mappings": {
    "_routing": {
      "required": true
    }
  }
}

自定义路由注意事项

┌─────────────────────────────────────────────────────────────────────────┐
│                    自定义路由注意事项                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ⚠️ 数据倾斜问题:                                                       │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  如果某些 routing 值的数据量特别大:                            │   │
│  │                                                                 │   │
│  │  ┌──────────────────────────────────────────────────────┐      │   │
│  │  │ S0        │ S1        │ S2        │ S3        │ S4   │      │   │
│  │  │ 100GB     │ 100GB     │ 500GB     │ 100GB     │ 100GB│      │   │
│  │  │           │           │ ↑         │           │      │      │   │
│  │  │           │           │ 过大!    │           │      │      │   │
│  │  └──────────────────────────────────────────────────────┘      │   │
│  │                                                                 │   │
│  │  解决方案:                                                     │   │
│  │  • 使用 routing + partition                                    │   │
│  │  • 同一 routing 可以分布到多个分片                              │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  配置示例:                                                              │
│  PUT /my_index                                                         │
│  {                                                                      │
│    "settings": {                                                        │
│      "number_of_shards": 6,                                            │
│      "number_of_routing_shards": 30                                    │
│    }                                                                    │
│  }                                                                      │
│                                                                         │
│  routing=user_123 可能分布到 Shard 0, 6, 12, 18, 24                     │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

分片数量规划

规划原则

┌─────────────────────────────────────────────────────────────────────────┐
│                    分片规划原则                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  黄金法则:单分片大小 10GB - 50GB                                       │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  计算公式:                                                     │   │
│  │                                                                 │   │
│  │  分片数 = 预计数据量 / 单分片目标大小                           │   │
│  │                                                                 │   │
│  │  示例:                                                         │   │
│  │  • 预计数据量:500GB                                           │   │
│  │  • 单分片目标:25GB                                            │   │
│  │  • 分片数 = 500 / 25 = 20 个主分片                              │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  考虑因素:                                                              │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │                                                                │    │
│  │  1. 数据量                                                     │    │
│  │     • 现有数据量                                               │    │
│  │     • 增长速度                                                 │    │
│  │                                                                │    │
│  │  2. 查询模式                                                   │    │
│  │     • 查询复杂度                                               │    │
│  │     • 并发查询量                                               │    │
│  │                                                                │    │
│  │  3. 节点数量                                                   │    │
│  │     • 分片不应过于集中                                         │    │
│  │     • 每个节点建议 < 20 个分片/GB堆内存                        │    │
│  │                                                                │    │
│  │  4. 恢复速度                                                   │    │
│  │     • 分片越小,恢复越快                                       │    │
│  │     • 分片越大,恢复越慢                                       │    │
│  │                                                                │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

分片数量建议

┌─────────────────────────────────────────────────────────────────────────┐
│                    分片数量建议                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  数据量              主分片数         副本数         总分片数           │
│  ─────────────────────────────────────────────────────────────          │
│  < 10GB             1                1              2                   │
│  10GB - 100GB       2-5              1              4-10                │
│  100GB - 1TB        5-20             1-2            10-60               │
│  1TB - 10TB         20-100           1-2            40-300              │
│  > 10TB             100+             2+             300+                │
│                                                                         │
│  注意事项:                                                              │
│  • 过少:无法利用分布式并行能力                                          │
│  • 过多:每个分片消耗内存和 CPU,影响性能                                │
│  • 建议每个节点分片数 ≤ (堆内存GB × 20)                                 │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

分片重平衡

重平衡触发条件

┌─────────────────────────────────────────────────────────────────────────┐
│                    分片重平衡触发条件                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  自动触发:                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  1. 新节点加入集群                                              │   │
│  │  2. 节点离开集群                                                │   │
│  │  3. 分片分配失败                                                │   │
│  │  4. 手动触发重平衡                                              │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  新节点加入示例:                                                        │
│                                                                         │
│  Before:                                                               │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │ Node 1        │ Node 2        │ Node 3                         │    │
│  │ P0, P1, R2   │ P2, R0, R1   │                                │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                      │                                                  │
│                      ▼ 新节点加入                                       │
│  After (自动重平衡):                                                    │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │ Node 1   │ Node 2   │ Node 3   │ Node 4                        │    │
│  │ P0, R1   │ P1, R2   │ P2, R0   │ R0, R1, R2                    │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

重平衡配置

// 集群级别的重平衡配置
PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.rebalance.enable": "all",           // 启用所有重平衡
    "cluster.routing.allocation.allow_rebalance": "indices_all_active",
    "cluster.routing.allocation.cluster_concurrent_rebalance": 2  // 并发重平衡数
  }
}

// 索引级别的重平衡配置
PUT /my_index/_settings
{
  "index.routing.rebalance.enable": "all"
}

分片分配规则

┌─────────────────────────────────────────────────────────────────────────┐
│                    分片分配规则                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  分片分配感知(Shard Allocation Awareness):                            │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  1. 节点属性感知                                                │   │
│  │                                                                 │   │
│  │     节点配置:                                                  │   │
│  │     node.attr.rack_id: rack_one                                │   │
│  │     node.attr.rack_id: rack_two                                │   │
│  │                                                                 │   │
│  │     集群配置:                                                  │   │
│  │     cluster.routing.allocation.awareness.attributes: rack_id   │   │
│  │                                                                 │   │
│  │     效果:主分片和副本分片分布在不同 rack                        │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  2. 强制感知(Forced Awareness)                                        │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │     cluster.routing.allocation.awareness.force.rack_id.values:  │   │
│  │       - rack_one                                               │   │
│  │       - rack_two                                               │   │
│  │                                                                 │   │
│  │     效果:如果某个 rack 不可用,副本不会被分配                   │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

监控与诊断

分片状态查看

# 查看分片分布
GET /_cat/shards?v&h=index,shard,prirep,state,docs,store,node

# 查看分片分配情况
GET /_cat/allocation?v

# 查看未分配分片
GET /_cat/shards?v&state=UNASSIGNED

# 查看分片分配解释
GET /_cluster/allocation/explain
{
  "index": "my_index",
  "shard": 0,
  "primary": true
}

分片健康检查

┌─────────────────────────────────────────────────────────────────────────┐
│                    分片状态说明                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  状态           说明                                                    │
│  ─────────────────────────────────────────────────────────              │
│  STARTED       分片已启动,正常工作                                     │
│  INITIALIZING  分片正在初始化                                           │
│  RELOCATING    分片正在迁移到其他节点                                    │
│  UNASSIGNED    分片未分配,需要关注                                      │
│                                                                         │
│  常见 UNASSIGNED 原因:                                                  │
│  • 节点故障导致分片丢失                                                  │
│  • 分配规则限制(磁盘空间、属性感知等)                                  │
│  • 副本数超过节点数                                                     │
│                                                                         │
│  解决方案:                                                              │
│  POST /_cluster/reroute                                                │
│  {                                                                      │
│    "commands": [                                                        │
│      { "allocate_stale_primary": {                                     │
│          "index": "my_index",                                          │
│          "shard": 0,                                                   │
│          "node": "node_1",                                             │
│          "accept_data_loss": true                                      │
│        }                                                                │
│      }                                                                  │
│    ]                                                                    │
│  }                                                                      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

总结

本章深入解析了分片路由机制:

要点说明
路由算法hash(_routing) % num_shards
自定义路由按 routing 值分布,减少查询分片数
分片规划单分片 10-50GB,每节点 ≤ 20分片/GB堆内存
重平衡自动/手动触发,支持感知规则

参考资料

下一章预告

下一章将探讨 分布式一致性,包括:

  • Master 选举机制详解
  • 脑裂问题与解决方案
  • 故障检测与恢复
分享: