Elasticsearch 底层原理系列(七):分片与路由机制
2020-12-13·5 分钟阅读
前言
分片(Shard)是 Elasticsearch 分布式能力的核心。理解分片路由机制,对于数据分布优化、查询性能调优至关重要。
本章将深入解析分片路由算法、分片规划原则以及分片重平衡策略。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| 分片路由算法 | ⭐⭐⭐ | 高频考点 | ✅ |
| 自定义路由 | ⭐⭐⭐⭐ | 进阶考点 | ✅ |
| 分片数量规划 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| 分片重平衡 | ⭐⭐⭐ | 实战价值 | ✅ |
面试考点
- Elasticsearch 的分片路由算法是怎样的?
- 如何使用自定义路由优化查询?
- 分片数量应该如何规划?
- 什么是分片重平衡?如何配置?
分片基础
分片的本质
┌─────────────────────────────────────────────────────────────────────────┐
│ 分片的本质 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 分片 = 独立的 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 选举机制详解
- 脑裂问题与解决方案
- 故障检测与恢复
相关文章
Elasticsearch 底层原理系列(六):集群架构与节点角色
2020-11-29·6 分钟阅读
深入解析 Elasticsearch 集群架构,包括 Master、Data、Coordinating 等节点角色的职责、集群状态管理、以及节点角色配置最佳实践。
Elasticsearch 底层原理系列(一):架构概述
2020-10-06·5 分钟阅读
深入理解 Elasticsearch 整体架构设计,包括核心概念、分层架构、Lucene 与 ES 的关系,以及分布式搜索的基本原理。
Elasticsearch 底层原理系列(九):生产实践与性能调优
2020-12-31·6 分钟阅读
深入解析 Elasticsearch 生产环境最佳实践,包括硬件选型、JVM 调优、索引设计、性能监控与调优、常见问题排查。