MongoDB分片集群在业务高峰期频繁抛出1560错误,同时伴随写入延迟升高、chunk迁移变慢,这通常说明primary shard正在承受超出设计容量的负载。primary shard并不是只负责分片数据,未分片的集合、数据库元数据以及部分路由操作都会落在它上面。当这些操作叠加在一起,主分片的CPU、磁盘I/O和锁等待就会迅速恶化,最终以1560错误码的形式暴露出来。

认识错误码1560:为什么primary shard会成为瓶颈
在MongoDB分片集群中,每个数据库在创建时都会指定一个主分片,默认是集群中最先创建的分片。如果该数据库下有集合没有开启分片,那么这些集合的全部数据都会存储在这个主分片上,包括文档、索引以及后续的写入操作。因此即使数据库已经开启了分片,只要还有未分片的集合存在,主分片的压力就会明显高于其他分片。这也是很多集群在初期运行正常,但随着未分片集合增长而突然出现1560告警的原因。
在部分MongoDB运维体系和诊断工具中,1560被归类为主分片压力过大告警。它并不一定表示磁盘已满或节点宕机,而是说明主分片上的资源竞争已经到了影响正常操作的程度。常见表现包括写操作排队、chunk迁移失败、配置元数据响应变慢,甚至mongos路由层出现超时。这种压力可能来自多个维度:CPU占用率持续高位、磁盘I/O等待、内存换页、锁竞争等。如果不及时干预,主分片可能会进入不可用状态,进而影响整个数据库的写入能力。
造成主分片压力过大的来源通常包括以下几类:未分片集合过多、片键设计不合理导致数据倾斜、频繁的chunk迁移以主分片为源或目标、大量索引创建与维护操作、数据库元数据读写集中在主分片。这些因素叠加会使主分片比普通分片更容易达到瓶颈,因此在排查时需要逐一排除。
排查思路:从哪些指标快速定位主分片压力
首先可以使用mongostat和mongotop观察资源占用。mongostat可以显示insert、query、update、delete每秒操作数、连接数、游标、锁等待比例等关键指标。重点关注主分片节点的locked db百分比,如果该值长时间超过10%,说明锁竞争已经比较严重。mongotop则可以从集合级别查看读写耗时,快速找出热点集合。例如某个未分片集合的读耗时突然飙升,很可能就是主分片压力的直接来源。
接着使用db.serverStatus()和db.currentOp()深入分析。serverStatus返回的opcounters、locks、globalLock、wiredTiger等部分信息能够反映出整体负载情况,其中activeClients和currentOp中running时间过长的操作尤其值得关注。通过currentOp可以列出正在执行的命令,特别是写命令、创建索引命令、moveChunk命令等。如果看到大量操作在等待锁,说明存在锁瓶颈;如果看到某个命令长时间运行,则需要进一步分析该命令的执行计划。
使用sh.status()和getShardDistribution()分析数据分布同样不可或缺。sh.status()可以查看哪些数据库的主分片是哪个分片,以及各集合的分片状态。getShardDistribution()可以直观展示每个分片上该集合的数据量、文档数和比例。如果发现某个分片的数据量占比超过70%,说明存在严重数据倾斜,需要重新设计片键或手动迁移数据。
// 查看各数据库的主分片信息
sh.status()
// 输出片段示例:
// databases:
// "testdb" : { "primary" : "shard0000", "partitioned" : true }
// "appdb" : { "primary" : "shard0000", "partitioned" : true }
// 注意:多个数据库的主分片都是 shard0000,会显著增加该分片压力
// 查看集合的分片数据分布
db.orders.getShardDistribution()
修复方案:如何有效降低primary shard负载
针对未分片集合处理是降低主分片压力最直接的手段。首先要找出所有未分片集合,可以通过查询config数据库的collections集合来获得。然后根据业务场景选择合适的片键开启分片。如果集合数据量不大但写入频繁,可以使用hashed片键均匀分散写入;如果查询模式固定,可以使用复合片键。开启分片后,数据会逐渐从主分片迁移到其他分片,从而降低主分片压力。
如果已经分片但仍然数据倾斜,需要重新设计片键。片键选择要避免低基数字段,例如只有几个可能值的字段会导致chunk无法分裂,数据会高度集中。单调递增字段如时间戳、自增ID会导致写入集中在最后一个chunk,进而集中在一个分片上。此时可以使用hashed索引或组合片键。例如将时间字段与用户ID组合,或者使用hashed字段作为片键。需要注意的是,修改片键需要重建集合或使用db.collection.updateOne更新片键字段,过程复杂,需要提前规划。
手动迁移chunk和使用zone sharding也是常用方法。在balance窗口外,可以手动将部分chunk从主分片迁移走。使用moveChunk命令指定查询条件或chunk范围。也可以通过给集合指定zone和zone range,将一部分数据固定在特定分片。例如将历史数据划分到zone中并关联到多个非主分片,新数据保留在主分片。这样既利用主分片性能,又避免所有数据集中。
// 开启数据库分片
sh.enableSharding("testdb")
// 对集合使用 hashed 片键进行分片
sh.shardCollection("testdb.orders", { "orderId": "hashed" })
// 手动迁移单个 chunk 到指定分片
db.adminCommand({
moveChunk: "testdb.orders",
find: { orderId: 12345 },
to: "shard0001"
})
// 配置 zone sharding 示例:将历史数据固定到非主分片
sh.addShardTag("shard0001", "archive")
sh.addTagRange("testdb.orders", { createTime: MinKey }, { createTime: ISODate("2023-12-31") }, "archive")
资源与配置优化也不能忽略。如果主分片所在副本集硬件配置较低,可以考虑垂直扩容、增加内存和SSD,或增加分片数量。调整write concern降低写确认级别也能缓解主分片压力,但需要权衡数据安全性。还可以调整chunk大小,避免过于频繁的迁移。另外定期清理孤儿文档,减少额外开销,也有助于稳定主分片性能。
预防措施与监控告警建议
建立主分片压力监控体系是预防1560错误再次出现的关键。通过Prometheus、Grafana等工具采集mongostat和serverStatus指标,设置告警规则。监控项包括主分片CPU利用率、磁盘I/O等待、锁等待比例、活跃客户端数量、未分片集合数量、数据倾斜度等。当这些指标超过阈值时提前预警,避免故障码1560再次出现。例如可以设置主分片locked db百分比超过15%时发送告警,及时介入处理。
片键设计的最佳实践需要在分片集合之前进行充分分析。分析字段的基数、查询模式和写入增长趋势。优先选择高基数、分布均匀、查询常用的字段。避免使用单调递增字段。如果业务允许,使用hashed片键可以快速分散写入,但会牺牲范围查询效率。对于范围查询频繁的场景,可以使用范围分片配合zone策略。同时要避免片键字段频繁更新,否则会导致数据在分片间频繁移动。
定期健康检查也不可或缺。定期运行sh.status、db.collection.getShardDistribution和config.collections查询,确认没有未分片的大集合、chunk数量均衡、没有长时间运行的chunk迁移。及时清理无用集合和索引,避免元数据膨胀。同时注意配置服务器的负载,配置服务器也可能影响主分片压力。只有把主分片压力治理纳入日常运维流程,才能从根本上控制故障码1560的发生频率。
总结来说,错误码1560背后反映的是分片集群容量规划和片键设计的综合问题,仅靠临时扩容不能根治;需要从数据分布、片键设计和运维监控三方面入手,建立常态化治理机制。这样即使业务量继续增长,主分片也不会轻易成为可用性短板。

