📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

适用版本: 6.8-8.9+

1. 错误异常的基本描述 #

actual shard is not a primary 是 Elasticsearch 在分片路由或写入操作时抛出的分片状态错误。当你尝试在某个分片上执行只有**主分片(primary shard)才能执行的操作(如某些恢复操作、特定管理操作等),但实际路由到的分片是副本分片(replica shard)**时,就会触发此错误。Elasticsearch 的写入操作必须先到达主分片,然后由主分片同步到副本分片。

常见现象 #

  • Elasticsearch 返回 HTTP 500 Internal Server Error409 Conflict 状态码。
  • 写入请求、恢复操作或管理 API 调用失败。
  • 在 Elasticsearch 服务端日志中会记录 ElasticsearchExceptionReplicationOperation.RetryOnPrimaryException
  • 如果是通过应用程序或自动化脚本发送请求,会在客户端收到异常响应。
  • 可能导致部分写入失败、恢复中断或管理操作无法执行。

典型报错与异常栈 #

该异常的典型日志形态如下:

ElasticsearchException: actual shard is not a primary
    at org.elasticsearch.action.support.ReplicationOperation.RetryOnPrimaryException(ReplicationOperation.java:...)
    at org.elasticsearch.action.support.TransportReplicationAction.RetryOnPrimaryTransportHandler.onFailure(TransportReplicationAction.java:...)

通过 API 请求的响应通常如下:

{
  "error": {
    "root_cause": [
      {
        "type": "exception",
        "reason": "actual shard is not a primary",
        "shard": 1,
        "index": "my_index"
      }
    ],
    "type": "exception",
    "reason": "actual shard is not a primary",
    "status": 500
  }
}

另一种常见形态(恢复操作):

ReplicationOperation.RetryOnPrimaryException: actual shard [0] is not a primary
    at org.elasticsearch.indices.recovery.RecoverySourceHandler.lambda$prepareForTranslog$15(RecoverySourceHandler.java:...)

2. 为什么会发生这个错误 #

Elasticsearch 的分片路由机制会将写入请求路由到索引的主分片。主分片处理完写入后,会将操作复制到副本分片。某些操作(如恢复、特定管理操作)只能在主分片上执行。

源码中的逻辑是:

if (shardRouting.isPrimary() == false) {
    throw new ReplicationOperation.RetryOnPrimaryException(
        "actual shard [" + shardId.getId() + "] is not a primary");
}

这意味着操作被路由到了副本分片,而不是主分片。常见原因包括:

  • 分片状态不一致:集群状态中记录某个分片是主分片,但实际节点上的分片是副本。
  • 主分片正在迁移或重新分配:在分片重新平衡、节点重启或主分片切换期间,分片角色可能短暂不一致。
  • 路由信息过期:请求的路由信息(如 _routing 参数)指向了错误的分片。
  • 节点错误地认为自己持有主分片:由于集群状态更新延迟,节点可能持有过期的分片状态。
  • 并发分片操作:在分片状态变更的同时发送写入请求,导致路由到错误的分片。
  • 索引设置问题index.routing.allocation.enable 等设置可能导致分片分配异常。

3. 如何排查和解决这个异常和解决这个异常 #

排查步骤 #

建议按以下顺序进行排查:

第一步:获取完整的错误响应和请求信息 #

# 重现错误并查看完整响应
curl -X PUT "localhost:9200/my_index/_doc/doc_id?pretty" -H 'Content-Type: application/json' -d @request.json 2>&1 | jq .

# 查看 Elasticsearch 日志中的详细错误
tail -n 500 /var/log/elasticsearch/elasticsearch.log | grep -A 30 "actual shard is not a primary"

第二步:检查分片分配和主分片状态 #

# 查看索引的分片分配状态
curl -X GET "localhost:9200/_cat/shards/my_index?v"

# 查看特定分片的详细信息
curl -X GET "localhost:9200/_cat/shards/my_index?h=index,shard,prirep,state,docs,store,ip,node" | grep "0"

# 使用 allocation explain 查看分片分配原因
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty" -H 'Content-Type: application/json' -d '
{
  "index": "my_index",
  "shard": 0,
  "primary": true
}'

第三步:检查集群健康状态和路由 #

# 查看集群健康状态
curl -X GET "localhost:9200/_cluster/health/my_index?pretty"

# 检查索引的路由配置
curl -X GET "localhost:9200/my_index/_settings?pretty" | jq '.my_index.settings.index.routing'

# 查看当前节点持有的分片
curl -X GET "localhost:9200/_cat/shards?node=node_id&v"

第四步:在测试环境验证 #

# 在测试环境使用相同的操作进行测试
curl -X PUT "localhost:9200/test_index/_doc/doc_id" -H 'Content-Type: application/json' -d '
{
  "field": "value"
}'

排查时需要注意的问题_ #

  • 区分分片角色:确认出错的分片应该是主分片还是副本分片。
  • 检查时间线:确认错误发生时是否有分片重新平衡、节点重启等操作。
  • 查看路由参数:如果请求中使用了 _routing 参数,检查路由值是否正确。
  • 确认集群状态:确保集群状态是最新的,没有过期。

4. 如何解决这个错误 #

常用修复思路_ #

方案一:等待分片状态稳定后重试(推荐) #

# 监控集群健康状态,等待分片分配完成
while true; do
  STATUS=$(curl -s "localhost:9200/_cluster/health/my_index?pretty" | jq -r '.status')
  echo "Current status: $STATUS"
  if [ "$STATUS" = "\"green\" ] || [ "$STATUS" = "\"yellow\" ]; then
    echo "Cluster stable, retrying operation..."
    break
  fi
  sleep 10
done

# 重新执行操作
curl -X PUT "localhost:9200/my_index/_doc/doc_id" -H 'Content-Type: application/json' -d @request.json

方案二:检查并修正路由参数 #

// 修复前:可能使用了错误的 routing 参数
{
  "query": {
    "match_all": {}
  },
  "routing": "wrong_routing_value"  // 可能导致路由到错误的分片
}

// 修复后:移除 routing 或修正为正确值
{
  "query": {
    "match_all": {}
  }
  // 不指定 routing,让 Elasticsearch 自动路由到主分片
}

方案三:调整分片分配设置 #

# 检查索引的分片分配设置
curl -X GET "localhost:9200/my_index/_settings?pretty" | jq '.my_index.settings.index.routing.allocation'

# 如果有问题,重置为默认值
curl -X PUT "localhost:9200/my_index/_settings" -H 'Content-Type: application/json' -d '
{
  "index.routing.allocation.enable": "all"
}'

方案四:重启或重分配问题分片 #

# 如果特定分片持续有问题,可以尝试重分配
curl -X POST "localhost:9200/_cluster/reroute?retry_failed=true" -H 'Content-Type: application/json' -d '
{
  "commands": [
    {
      "allocate_empty_primary": {
        "index": "my_index",
        "shard": 0,
        "node": "target_node_id",
        "accept_data_loss": true
      }
    }
  ]
}'

后续注意事项与推荐建议_ #

  • 建立分片监控:监控分片分配状态、主分片健康度和分片迁移,及时发现分片状态异常。
  • 避免并发分片操作:在分片重新平衡、节点重启期间,避免发送大量写入请求。
  • 使用正确的路由:如果使用了 _routing 参数,确保路由值计算正确且稳定。
  • 监控集群状态:通过 INFINI Console 或 Elasticsearch 监控功能,及时发现分片分配问题。
  • 测试环境验证:在测试环境先验证分片操作,确认无误后再在生产环境执行。

借助 INFINI 产品提升排障效率_ #

  • INFINI Console 提供分片分配的可视化管理界面,可以直观地查看、管理和调试分片状态。通过 Console 的分片管理功能,可以快速发现主分片异常、查看分配原因,并直接执行重分配或恢复操作。

  • INFINI Gateway 可以作为 Elasticsearch 集群的流量治理网关,在写入请求到达 Elasticsearch 之前进行路由检查。Gateway 可以检测路由到错误分片的请求,并根据预定义的策略(如自动重试、路由修正、转发到正确节点等)进行处理。

  • 对于需要频繁管理分片或执行写入操作的团队,建议结合 INFINI Console 的分片监控功能和 INFINI Gateway 的请求治理能力,建立从分片分配、写入、到故障恢复的完整自动化流程,减少因分片状态错误导致的操作失败。

5. 小结_ #

actual shard is not a primary 是一个典型的分片状态错误,根源在于操作被路由到了副本分片而不是主分片。虽然报错信息直接指向分片角色错误,但解决思路需要根据具体情况来决定:是等待分片状态稳定、修正路由参数,还是调整分片分配设置。

在实际工作中,为避免此类问题,建议在开发阶段就使用 INFINI Console 的分片管理工具来监控分片状态,在代码中增加重试逻辑,并使用 INFINI Gateway 作为防护层来检测和修正错误的路由请求。通过工具化和流程化的方式,可以大幅减少因分片状态不一致导致的操作失败。

相关错误_ #

参考文档_ #

附:日志上下文_ #

下面保留当前页面中的源码或日志片段,便于继续结合异常调用栈定位问题:

if (shardRouting.isPrimary() == false) {
    throw new ReplicationOperation.RetryOnPrimaryException(
        "actual shard [" + shardId.getId() + "] is not a primary");
}