首页 > 基础资料 博客日记

ElasticSearch主分片和副本分片概念详解

2026-04-17 11:30:01基础资料围观1

极客资料网推荐ElasticSearch主分片和副本分片概念详解这篇文章给大家,欢迎收藏极客资料网享受知识的乐趣

主分片概述

ES 主分片的本质是独立的 Lucene 实例,主分片负责数据的写入、修改、删除等操作,可以理解成凡是写入操作一定会走主分片。
ES中有一个文档(doc)的概念,每一个文档只会属于一个主分片.

例如一个ES索引有三个主分片分别在A、B、C三个集群节点上,当一个user的doc需要写入时,那么这个user的doc只会在其中一个主分片上,而不是在每个集群节点上都有。

主分片数量在ES索引创建时指定,当索引创建完成之后,则不允许再修改主分片的数量,例如下面的案例:

# 创建一个索引,指定主分片数量为3
PUT /test01
{
  "settings": {
    "number_of_shards": 3
  }
}

# 然后再尝试修改其主分片数量
PUT /test01/_settings
{
  "settings": {
    "number_of_shards": 3
  }
}

# 报错如下:
{
  "error" : {
    "root_cause" : [
      {
        "type" : "illegal_argument_exception",
        "reason" : "Can't update non dynamic settings [[index.number_of_shards]] for open indices [[test01/FPlsR0XQQba41Fr_2_8m7g]]"
      }
    ],
    "type" : "illegal_argument_exception",
    "reason" : "Can't update non dynamic settings [[index.number_of_shards]] for open indices [[test01/FPlsR0XQQba41Fr_2_8m7g]]"
  },
  "status" : 400
}

当我们指定主分片数量时,主分片数量并不是越多越好,在不考虑机器性能的情况下,主分片数量一般是根据其ES集群节点数量及索引存储的数据量来决定,有几个参考值:

  • 单个主分片大小控制在 20GB ~ 50GB 范围内,性能最优。
  • 主分片数量一般是集群节点的1-3倍。例如3节点的集群,一个索引的主分片数量可以为3/6/9。

当然这些仅仅只是参考,例如一个索引的存储的数据量仅仅只有几个G,那么此时只需要给一个主分片即可。

为什么呢?
上文说过 ES 主分片的本质是独立的 Lucene 实例,每个分片都要占用独立的系统资源、元数据开销、调度开销,数据量小的时候多分片的弊端会被无限放大,收益为负,具体体现在5个维度:

1、堆内存资源严重浪费(最直观的影响),每个 Lucene 实例都要占用固定的堆内存来存储:索引元数据(段信息、倒排词典、字段缓存、过滤器缓存等),写入/查询线程上下文、连接资源,分片级别的监控、统计数据。在小分片场景下,这些固定开销的占比会极高。
例如:1GB 索引用 1 个分片,元数据开销约 10~30MB,占比 1%~3%,而1GB 索引用 10 个分片,元数据总开销约 100~300MB,占比 10%~30%

2、查询性能不升反降(最影响业务的问题)
ES 查询的标准流程是:协调节点接收请求 → 路由到所有相关分片 → 每个分片独立执行查询 → 协调节点聚合所有分片的结果 → 返回给用户。
当主分片越多,调度开销+聚合开销越大。
比如 1GB 索引用 1 个分片:只需要执行1次查询,无需聚合,延迟 10ms 以内
比如 1GB 索引用 10 个分片:需要执行10次查询,还要合并10份结果,延迟会变成 30~50ms,是单分片的 3~5 倍
高并发场景下还会放大问题:协调节点要同时处理大量分片级别的请求,很容易出现CPU占满、OOM等问题。

3、集群元数据管理压力指数级上升(影响集群稳定性)
ES Master 节点需要管理全集群所有分片的元数据,分片越多,会导致集群状态(Cluster State)体积越大,Master 同步元数据到所有节点的耗时越长,创建/删除索引、分片分配、节点上下线等集群操作的执行速度越慢,极端情况会导致 Master 卡顿、集群无响应,元数据写入持久化的频率越高,Master 磁盘IO压力越大。
生产经验:总分片数超过 1 万的集群,稳定性会明显下降,很多操作都会变慢。

4、写入性能不会提升反而下降
很多人误以为「分片越多写入越快」,但小数据量场景下完全相反,当写入时需要计算路由决定写到哪个分片,分片越多路由开销越大。每个分片都要独立执行段合并、刷新磁盘等操作,小分片会产生大量小的段文件,合并开销翻倍。单分片写入性能本身就可以达到 5000~20000 TPS,小数据量(日写入低于 100GB)完全不需要多分片来扛写入压力。

5、运维复杂度大幅提升
分片分配、迁移、恢复的成本更高:集群扩容、节点故障时,大量小分片需要在节点之间移动,总耗时比少分片更长。
问题排查更难:分片越多,出现未分配分片、分片损坏的概率越高,定位问题更麻烦。
快照备份/恢复速度变慢:快照需要遍历所有分片的元数据,分片越多备份耗时越长。

然后ES的读写并行能力是由主分片数量来进行控制的,当主分片数量越多,那么读写并发支持的越多,但是读写不一定更快。

这里简单说一下,当一台32C/64G 节点,NVMe SSD,单分片大小 20~30GB支持500-25000左右的QPS,这里根据写入的场景来划分、例如写入一个极为复杂的文档,且写入的数据量超过10KB,那么QPS可能只有500 ~ 3000,当写入一个简单的文档,例如日志等,那么QPS可能为8000-25000左右的QPS

主分片存储数据量大小控制

上文有说过,主分片存储数据量大小推荐为20-50GB,那么怎么控制主分片存储的数据量大小呢?

  • 方式一:预估主分片数量
    在创建索引之前,需要预估一下该索引可能会存储多少数据?例如该索引会存储100GB数据,那么有3个集群节点,则这个索引给3个主分片即可

  • 方式二:使用Rollover + ILM自动滚动(推荐)
    最适合日志/指标/订单等按时间生成的时序数据,ES自动监控分片大小/时间,超过阈值自动生成新索引,永远不会出现超大分片:

示例:

PUT _ilm/policy/log_ilm_policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_size": "30GB",  // 单分片到30G自动滚动
            "max_age": "7d",     // 或者满7天自动滚动,哪个条件先到就触发
            "max_docs": 10000000 // 或者满1000万条自动滚动
          }
        }
      },
      "delete": {
        "min_age": "30d", // 超过30天自动删除
        "actions": { "delete": {} }
      }
    }
  }
}

当索引的分片存储数据量太大怎么解决呢?

方案A:Split拆分(推荐,速度快,不需要全量reindex)

适用条件:分片数只能拆成原分片数的整数倍,比如原4分片可以拆成8/12/16个,不能拆成6个

# 1. 先把索引设为只读
PUT /超大索引名/_settings
{ "index.blocks.write": true }

# 2. 拆分,原4分片拆成8分片(必须是整数倍)
POST /超大索引名/_split/拆分后的新索引名
{
  "settings": {
    "index.number_of_shards": 8
  }
}

方案B:Reindex重建(通用,任何情况都适用)

适用条件:任意调整分片数,没有倍数限制,缺点是需要全量同步数据,数据量大的话耗时久

# 1. 创建新索引,设置合适的分片数
PUT /新索引名
{
  "settings": {
    "number_of_shards": 10, // 改成你需要的分片数
    "number_of_replicas": 1
  }
}
# 2. 全量同步数据
POST _reindex?wait_for_completion=true
{
  "source": { "index": "超大索引名" },
  "dest": { "index": "新索引名" }
}

当索引分片太小(碎片太多)时怎么解决呢?

方案A:Shrink合并(推荐,速度快,不需要全量reindex)

适用条件:分片数只能合并成原分片数的约数,比如原8分片可以合并成4/2/1个,不能合并成3个

限制:Shrink 时不能指定 mapping,只能沿用源索引 mapping
源索引必须所有分片 结构一致、正常 green、只读

# 1. 先把索引设为只读,并且所有分片移到同一个节点
PUT /小分片索引名/_settings
{
  "index.blocks.write": true,
  "index.routing.allocation.require._name": "你的节点名称"
}
# 2. 合并,原8分片合并成2分片(必须是约数)
POST /小分片索引名/_shrink/合并后的新索引名
{
  "settings": {
    "index.number_of_shards": 2
  }
}

方案B:多个小索引合并成一个大索引

适用条件:比如把7天的日索引(log-2026.04.10 ~ log-2026.04.16)合并成一个周索引,减少总分片数

_reindex 合并多个 mapping 不同的索引时,需要下面几点注意:
1、新索引必须提前创建好统一的 mapping
2、所有旧索引的数据都会往这个新 mapping 上靠
3、类型冲突时:旧数据类型 vs 新 mapping 类型不兼容 → 报错,兼容 → 自动转换
当旧索引有、新索引没有的字段 → 被丢弃(不写入)
旧索引没有、新索引有的字段 → 写入 null / 默认值

// 1. 创建新的周索引,设置合适的分片数
PUT /log-2026.04.week2
{ "settings": { "number_of_shards": 6 } }
// 2. 批量同步所有日索引数据
POST _reindex?wait_for_completion=true
{
  "source": { "index": ["log-2026.04.10","log-2026.04.11","log-2026.04.12","log-2026.04.13","log-2026.04.14","log-2026.04.15","log-2026.04.16"] },
  "dest": { "index": "log-2026.04.week2" }
}

文档的写入流程

ES中文档的写入和主分片有关系,我们可以来看下整个文档的写入流程

ES文档写入涉及到下面几个组件:

  • Client:客户端(REST/Java API)
  • Coordinating Node:协调节点(接收请求、路由、返回结果)
  • Primary Shard:主分片(写入核心,数据强一致)
  • Replica Shard:副本分片(异步 / 同步复制,高可用)
  • Memory Buffer(Index Buffer):内存缓冲(高速写入,不可搜索)
  • Translog:事务日志(断电恢复,数据不丢)
  • Segment(Lucene):段文件(倒排索引,可搜索)
  • Disk:持久化磁盘

其写入流程主要分为7个步骤:

  • 1、客户端发起请求->ES协调节点写入
    • 客户端发送:PUT /my_index/_doc/1
    • 请求到达任意节点,该节点成为 Coordinating Node(协调节点)
  • 2、路由计算 → 确定主分片
    • 默认按照_id哈希,当用户指定id时,会按照id字段哈希
    • 然后协调节点查询集群元数据,找到该主分片所在节点
      _id哈希公式:
shard_num = hash(_id) % number_of_primary_shards
  • 3、转发请求 → 主分片节点

    • 协调节点把请求转发给 主分片所在的数据节点
  • 4、主分片写入(核心!内存 + Translog)
    主分片所在节点执行:

    • 写入 Memory Buffer(Index Buffer)
      • 数据进内存,此时不可搜索
      • 写入速度极快(内存级)
    • 同步写入 Translog(事务日志)
      • 每条写入都追加到 Translog,并刷到磁盘(默认 request 模式,强一致)
      • 作用:宕机后从 Translog 恢复内存数据,防止丢失
  • 5、主分片 → 副本分片 同步

    • 主分片并行把请求发给其对应的所有副本分片
    • 副本分片执行和主分片一样的逻辑:写内存 + 写 Translog
    • 副本全部成功后,主分片才标记写入成功
  • 6、返回结果 → 客户端

    • 主分片向协调节点返回成功
    • 协调节点向客户端返回 201 Created
  • 7、后台异步(Refresh + Flush)
    7.1 Refresh(默认 1 秒,近实时搜索)

    • 触发:refresh_interval: 1s 或内存满
    • 动作:
      • Memory Buffer → 生成 新 Lucene Segment(倒排索引)
      • Segment 写入 OS Cache(文件系统缓存),不直接落盘
      • 此时文档可被搜索(NRT 近实时)
      • 清空 Memory Buffer
        7.2 Flush(默认 30 分钟,持久化)
    • 触发:30 分钟、Translog 达 512MB、手动 flush
    • 动作:
      • 所有内存 Segment → 强制刷到磁盘
      • 执行 Lucene Commit,生成 commit 点
      • 清空 Translog,释放空间

副本分片概述

ES中的副本分片是主分片的完全拷贝,数据上和主分片完全一致。

可以看上面文档的写入流程,当文档完全写入主分片和副本分片时才会标记文档写入成功

副本分片是实现ES高可用的基础,当主分片挂了之后,副本分片会自动升级为主分片。

此时有一个问题,当主分片挂了之后,副本分片升级为主分片之后,那么还会有主分片吗?
主分片挂了之后,可以理解成当前主分片的节点挂了,副本分片升级为主分片之后,此时是集群为yellow,是没有副本分片的。
主要当原主分片所在的节点恢复之后,此时整个集群变为green,副本分片才会恢复。

并且副本分片可以承载ES中读的请求,提高ES集群查询的吞吐量。

ES中同一份数据的主分片和副本分片永远不会分配在同一个节点上(ES内置反亲和规则,避免单节点故障同时丢主副)

例如当前集群有三个节点,主分片在A节点上,那么副本分片永远不会在A节点上,只会在B或者C节点中。

副本数可以动态调整,随时修改,不需要重建索引
示例:

# 创建一个索引,指定副本分片为1
PUT /test01
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}
# 查看一下
GET /test01/_settings
# 返回结果
{
  "test01" : {
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "3",
        "provided_name" : "test01",
        "creation_date" : "1776394680601",
        "number_of_replicas" : "1", # 副本分片数量
        "uuid" : "FSLjaib0Rv2JXvd_wlg2MQ",
        "version" : {
          "created" : "7172699"
        }
      }
    }
  }
}


# 修改副本分片数量
PUT /test01/_settings
{
  "settings": {
    "number_of_replicas": 2 #修改为2
  }
}

# 查看一下
GET /test01/_settings
{
  "test01" : {
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "3",
        "provided_name" : "test01",
        "creation_date" : "1776394680601",
        "number_of_replicas" : "2", # 主分片为2
        "uuid" : "FSLjaib0Rv2JXvd_wlg2MQ",
        "version" : {
          "created" : "7172699"
        }
      }
    }
  }
}

副本分片设置注意事项

  • 不要把副本数设为0(临时导入数据除外):设0主分片挂了直接丢数据,导入完成后立刻改回1
  • 不要设超过2副本:3副本及以上性价比极低,写性能下降明显(写请求要同步到所有副本),存储成本过高,除非是金融级核心业务

可以给一个设置的参考

集群规模 业务等级 推荐副本数 说明
1节点测试集群 测试 0 单节点设副本也分配不了,浪费空间
2节点生产集群 普通业务 1 必须设1副本,任意节点挂了数据不丢
3节点及以上生产集群 普通业务 1 性价比最高,兼顾高可用和存储成本
3节点及以上生产集群 核心业务(不能停机) 2 允许同时挂2个节点还能正常提供服务,存储成本是1副本的1.5倍

文档的读取流程

文档的读取流程分为两种,一种是GET流程,一种是SEARCH流程。

文档的GET读取流程较于写入流程比较简单,其流程如下:

  • 客户端发起GET请求
GET /my_index/_doc/100
  • 请求到达协调节点
    任何一个节点都可以当协调节点,只负责路由 + 转发 + 聚合。

  • 协调节点 计算文档所在分片
    根据公式计算完成之后协调节点立刻知道:文档 100 只在 主分片 P2 及其副本 R2 上
    计算公式:

shard = hash(_id) % 主分片数
  • 协调节点 选择分片(负载均衡)
    它会在 主分片 + 所有副本分片中进行轮询(round-robin) 选一个:

    • 可能选 P2
    • 可能选 R2
  • 协调节点转发请求到选中的分片节点上
    该分片本地读取文档,直接返回。

  • 分片返回文档 → 协调节点 → 客户端

文档的SEARCH读取流程较于写入流程比较复杂

search分为 2 个阶段:

  • Query 阶段(分片本地查,只返回 id + 排序值)
  • Fetch 阶段(协调节点拉取真实文档)

第 1 阶段:Query 阶段(分散收集)

  • 客户端发起请求到协调节点
  • 协调节点把查询广播到 所有主分片 + 副本分片
  • 每个分片开始本地执行搜索
    • 匹配文档
    • 按相关性分数排序
  • 每个分片只返回:doc id + 分数 + 排序值
    • 注意:不返回完整文档! 轻量级
  • 查询结果全部返回给协调节点

Fetch 阶段(聚合拉取)

  • 协调节点把所有分片返回的 id进行:
    • 全局排序
    • 合并
  • 协调节点取最终 top N(如前 10 条)
  • 协调节点根据这 最终 topN 的 id,向对应分片发起获取真实文档请求
  • 对应分片返回完整文档内容
  • 协调节点封装结果 → 返回给客户端

search读取文档的关键点:

  • 搜索 = 查所有分片
    • 不管数据在哪,必须广播所有分片
  • 分片越多,搜索压力越大
  • 副本可以分担搜索压力
    • 协调节点会轮询主 / 副,实现搜索负载均衡
  • Query 只传 id,Fetch 才取文档
    • 减少网络传输,这是 ES 搜索快的原因之一
  • from + size 越深,性能越差,因为要合并的结果越多

超级汇总:主分片和副本分片的区别

对比项 主分片(Primary Shard) 副本分片(Replica Shard)
数据角色 原始数据、权威数据源 主分片的完整备份拷贝
是否接受写入 接受写入(唯一入口) 不接受写入,仅同步数据
数量是否可修改 创建后不可修改 可动态修改(随时增删)
故障影响 挂掉会导致分片不可用,需副本切换 挂掉不影响写入,仅降低可用性
高可用作用 提供完整数据,被副本依赖 主分片挂掉可升级为主分片
查询作用 提供查询服务 分担查询,提升并发能力
同节点分配 与自己的副本不能在同一节点 与对应主分片不能在同一节点

文章来源:https://www.cnblogs.com/huangSir-devops/p/19879328
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云