
一、业务场景描述
在某大型电商平台中,商品及订单数据量已经突破亿级规模,读写压力持续攀升。为了满足海量数据的存储与高并发访问需求,平台团队选择基于MongoDB的分片集群方案,实现水平扩展和负载分摊。然而在实际生产环境中,我们遇到了分片热区、数据迁移阻塞、Balancer抖动等多种棘手问题。本文将从架构设计、优化思路、故障排查等方面,分享我们在生产环境中积累的实战经验。
二、技术选型过程
- 数据库产品:MongoDB 4.4+(已原生支持事务与复杂聚合)
- 分片策略:使用基于复合字段的Hash分片Key,兼顾写入均衡与查询效率
- 集群部署 :
- Config Server:3 副本集
- Shard Server:每个分片 3 副本,3 个分片共同承担读写
- Mongos 路由层:部署 2 台负载均衡
- 运维监控:Prometheus + Grafana + MongoDB Exporter
- 数据迁移:开启自动 Balancer,结合 Zone Sharding 实现业务分区
在此方案中,通过合理选择分片键并结合 Zone,实现了跨机房数据隔离和读写均衡。但在长时间运行后,依然出现单个 Chunk 热点、迁移卡顿等问题,需要持续调优。
三、实现方案详解
3.1 分片键设计
核心表 order
结构:
{
_id: ObjectId,
userId: String,
orderId: String,
createTime: ISODate,
status: String,
totalAmount: Double,
...
}
我们选择 _id
和 createTime
组合为复合分片键:
js
sh.shardCollection("ecom.order", { _id: "hashed", createTime: 1 });
理由:
- hashed
_id
可将写入均匀分散到所有分片 - 按
createTime
范围查询时性能更优
3.2 Zone Sharding 配置
按地域分区(如华东、华南、华北),将各区域热点写入相应分片机房:
js
// 定义区域范围
sh.addShardTag("shard0000", "east");
sh.addShardTag("shard0001", "south");
sh.addShardTag("shard0002", "north");
// 对 createTime 设置 Zone
sh.updateZoneKeyRange(
"ecom.order",
{ _id: MinKey, createTime: ISODate("2021-01-01T00:00:00Z") },
{ _id: MaxKey, createTime: ISODate("2022-01-01T00:00:00Z") },
"east"
);
// 依次为 south, north 设置范围
Zone Sharding 保证各地域数据写入到最靠近用户的机房,减小跨机房延迟。
3.3 Java 应用接入示例
java
// pom.xml 依赖
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.4.0</version>
</dependency>
// 连接配置
String uri = "mongodb://mongos1:27017,mongos2:27017/?replicaSet=rs0&readPreference=primaryPreferred";
MongoClient mongoClient = MongoClients.create(uri);
MongoDatabase db = mongoClient.getDatabase("ecom");
MongoCollection<Document> orderCol = db.getCollection("order");
// 范围查询示例
FindIterable<Document> docs = orderCol.find(
Filters.and(
Filters.gte("createTime", start),
Filters.lt("createTime", end)
)
).sort(Sorts.descending("createTime")).limit(50);
for (Document doc : docs) {
// 处理结果
}
3.4 Balancer 调优
-
默认 Balancer 会定期扫描并迁移 Chunk,长表场景下会阻塞写入。
-
调整 Balancer 周期:
shellcfg.settings.update({ _id: "balancer" }, { $set: { "balancerIntervalMS": 300000 }});
-
迁移窗口:设置工作时间外执行,使用
startBalancer()
/stopBalancer()
脚本结合 Cron 调度。
四、踩过的坑与解决方案
4.1 热 Chunk 无法均匀分布
现象:某个分片内部分片键热点写入量过高,导致节点 IO 满载。
定位 :通过监控 mongos
插件指标,发现单个 Chunk 写入占比 > 50%。
解决:
-
临时禁用当前 Zone
-
手动
split
热点 Chunk:jssh.splitAt("ecom.order", { _id: hashedValue, createTime: ISODate("2021-06-01T00:00:00Z") });
-
将分片键进一步细化,如增加
userId
三级分片。
4.2 Balancer 抖动导致迁移阻塞
现象 :Balancer 多次启动/停止,Chunk 迁移反复失败,日志提示 LockTimeout
。
定位:Config Server 集群网络抖动,Balancer 进程拿不到锁。
解决:
-
升级 Config Server 网络拓扑
-
增大
lockTimeout
:jscfg.settings.update({ _id: "balancer" }, { $set: { "lockTimeout": 600000 }});
4.3 OOM 导致 Secondary 崩溃
现象:Secondary 节点在 Chunk 迁移归档时内存暴涨,触发 OOM。
定位:Chunk 大小超过 64MB,迁移采用一次性载入数据方式。
解决:
-
限制最大 Chunk 大小:
yamlnet: maxMessageSizeBytes: 48000000
-
升级硬件并监控迁移内存消耗。
五、总结与最佳实践
- 分片键选择 :尽量选用具有随机属性的字段,如 hashed
_id
,并结合业务字段提高范围查询性能。 - Zone Sharding:针对跨机房访问场景,结合地理分区减小延迟。
- Balancer 调度:生产环境建议在夜间或低峰期执行,提高迁移稳定性。
- 监控预警 :使用 Prometheus + Grafana 监控
mongos
QPS、Chunk 分布、网络状况,并配置告警。 - 容量规划:定期评估集群容量,及时扩容分片与硬件。
通过以上经验,团队成功支撑亿级数据量的高并发读写,系统稳定性提升 30%。希望本文能为你的 MongoDB 分片实践提供参考价值。