一、真实故障:某电商平台大促前MongoDB集群宕机事件
去年618大促前一周,我参与过一个电商平台的MongoDB集群故障复盘,这个案例太典型了,拿出来拆解能帮很多人避坑。当时的场景是:平台为了备战大促,提前3天把商品库存、订单状态的核心业务都切到了一套新搭建的MongoDB分片集群上,结果大促前一天的压力测试还没跑满峰值,集群直接全分片宕机,核心业务中断了27分钟,差点影响大促。
1.1 故障前的集群配置(坑点提前埋)
先给大家说下当时的集群配置,完全是新手容易踩的坑。这套集群的技术栈是MongoDB 4.2(当时的稳定版),配置是:3个分片(每个分片是1主2从的副本集),3台配置服务器(副本集),1台单独的路由节点(mongos)。
先看当时的分片配置文件,核心坑点我加了注释:
# MongoDB分片节点配置(当时的错误配置)
storage:
dbPath: /data/mongodb # 数据目录和系统盘共用,没有单独挂载数据盘
wiredTiger:
engineConfig:
cacheSizeGB: 16 # 服务器内存是32G,给WiredTiger缓存设了16G,看似合理但忽略了其他进程
systemLog:
destination: file
path: /var/log/mongodb/mongod.log
net:
port: 27017
bindIp: 0.0.0.0 # 所有IP都能访问,没做权限限制
security:
authorization: disabled # 完全没开权限认证,任何客户端都能连
1.2 故障发生的完整过程
压力测试当天,模拟10万用户同时下单,集群的CPU、内存、磁盘使用率瞬间飙升,路由节点先开始报“连接被拒绝”,然后各个分片的主节点陆续宕机,最后整个集群连不上。事后查日志,发现了三个致命问题: 第一,数据目录和系统盘共用,压力测试时写入量暴增,系统盘被打满,MongoDB直接停止写入; 第二,WiredTiger缓存设了16G,但是服务器上还跑着监控进程、日志收集进程,这些进程占了8G内存,导致MongoDB的缓存实际可用只有不到8G,频繁触发内存换页(就是把内存里的数据写到磁盘交换分区,再把磁盘数据读到内存,速度慢100倍); 第三,没开权限认证,压力测试时不仅有测试客户端连接,还有很多扫描端口的恶意请求也连到了集群,占用了大量连接数。
二、从故障中拆解的架构优化核心经验
这次故障不是个例,很多中小公司的MongoDB集群都是这么搭的,优化经验是从这次故障里一步步抠出来的,每一条都能直接用。
2.1 存储层:必须做物理隔离,不能省成本
很多人觉得“MongoDB的缓存已经把热点数据放内存了,磁盘慢一点没关系”,这是大错特错。尤其是写密集的场景(比如电商的订单、库存),磁盘的写入速度直接决定集群的吞吐量。 当时的集群数据目录和系统盘共用,不仅磁盘IO慢,系统盘满了还会直接导致MongoDB宕机。正确的做法是:
- 数据目录必须单独挂载一块高速磁盘(比如SSD),不能和系统盘、日志盘共用;
- 配置服务器的磁盘要单独用,因为配置服务器存的是分片集群的元数据,元数据访问慢会导致整个集群的路由异常;
- 磁盘的RAID配置要选RAID10(兼顾读写速度和数据安全),不要选RAID5(写速度慢,适合读密集场景)。
2.2 内存配置:不能只看MongoDB官方建议,要结合服务器实际情况
MongoDB官方建议WiredTiger缓存大小设为服务器内存的50%,但这个建议是在“服务器只跑MongoDB”的前提下才成立的。如果服务器上还跑着其他进程(比如监控、日志、代理),就要把这些进程的内存占用预留出来。 比如当时的服务器内存是32G,正确的缓存配置应该是:
# 优化后的WiredTiger缓存配置
storage:
wiredTiger:
engineConfig:
# 服务器32G内存,预留8G给其他进程,剩余24G的50%给缓存,即12G
cacheSizeGB: 12
另外,还要注意MongoDB的连接数内存占用:每个连接大概占1MB内存,如果集群允许10000个连接,就会占10G内存,这个也要算进去。
2.3 权限与安全:不能为了方便忽略认证
当时的集群没开权限认证,导致恶意请求占用连接数,正确的做法是:
- 必须开权限认证,每个业务端用单独的账号,只给必要的权限(比如库存业务的账号只能操作库存集合,不能操作订单集合);
- 配置网络访问控制,只允许业务服务器的IP连接MongoDB集群,禁止公网直接访问;
- 定期审计MongoDB的连接日志,及时发现异常连接。
权限配置的示例(给库存业务创建专属账号):
// 连接到MongoDB(先开认证)
use admin
// 创建库存业务的账号,密码是stock123,只给stock集合的读写权限
db.createUser({
user: "stock_service",
pwd: "stock123",
roles: [
{ role: "readWrite", db: "ecommerce", collection: "stock" }
]
})
2.4 架构冗余:路由节点不能只有一台
当时的集群只有一台路由节点,路由节点宕机的话,所有业务都连不上集群。正确的做法是:
- 路由节点至少部署2台,前面加负载均衡(比如Nginx),实现高可用;
- 配置服务器必须是3台的副本集(MongoDB官方要求,否则配置服务器宕机集群会出问题);
- 每个分片的副本集必须是3台(1主2从),主节点宕机时,从节点能自动切换为主节点,保证分片的可用性。
路由节点的负载均衡配置(Nginx示例):
# Nginx配置MongoDB路由节点的负载均衡
upstream mongos_cluster {
server 192.168.1.100:27017; # 第一台路由节点
server 192.168.1.101:27017; # 第二台路由节点
}
server {
listen 27017;
location / {
proxy_pass mongos_cluster;
}
}
三、优化后的集群验证:压力测试通过
按照上面的经验优化后,我们重新搭建了一套集群,再次跑压力测试,结果完全不同:
- 磁盘使用率稳定在40%,不会出现磁盘满的问题;
- 内存使用率稳定在70%,不会触发内存换页;
- 连接数稳定在500左右,没有恶意连接占用;
- 压力测试跑满10万用户时,集群的CPU使用率稳定在60%,业务响应时间在100ms以内,完全符合大促的要求。
四、相关技术的详细介绍
为了让大家更清楚,这里再补充两个核心技术的说明:
4.1 WiredTiger缓存
WiredTiger是MongoDB默认的存储引擎,缓存是它的核心性能组件,用来存放热点数据(比如经常被访问的商品信息、库存数据),避免频繁访问磁盘。如果缓存不够,热点数据会被频繁换入换出磁盘,导致性能急剧下降。
4.2 副本集
副本集是MongoDB实现高可用的核心,一个副本集有一个主节点(负责读写)和多个从节点(负责读,同时备份主节点的数据)。主节点宕机时,从节点会自动选举出一个新的主节点,保证服务不中断。
五、应用场景、优缺点与注意事项
5.1 应用场景
本文提到的架构优化经验,适用于所有生产环境的MongoDB集群,尤其是写密集的场景(比如电商的订单、库存、用户行为数据)、高可用要求高的场景(比如在线交易、实时数据处理)。
5.2 技术优缺点
优点
- 架构优化后,集群的可用性、性能、安全性都有大幅提升,能应对大促等峰值场景;
- 配置简单,成本可控(不需要换硬件,只需要调整配置和架构);
- 可扩展性强,后续可以根据业务需求增加分片数量。
缺点
- 路由节点和配置服务器的数量增加,会增加运维成本;
- 权限认证和网络控制会增加业务端的连接复杂度。
5.3 注意事项
- 优化前一定要备份数据,避免配置错误导致数据丢失;
- 压力测试要尽可能模拟真实的业务场景,不能只测单线程的读写;
- 定期监控集群的性能指标(CPU、内存、磁盘、连接数、响应时间),及时发现潜在问题。
六、文章总结
这次MongoDB集群故障,本质上是架构设计时忽略了生产环境的实际需求,只满足了“能跑”的最低要求,没有考虑峰值场景、安全、高可用等核心问题。从这次故障中总结的优化经验,核心是三个原则: 第一,物理隔离(存储、网络),避免一个环节出问题影响整个集群; 第二,配置要结合实际情况,不能照搬官方建议; 第三,安全和高可用是生产环境的底线,不能为了方便或成本而忽略。
生产环境的架构设计,从来不是“能跑就行”,而是要“能扛住峰值、能保证安全、能快速恢复”,只有把这些问题都考虑到,才能避免类似的故障发生。
评论
围绕“MongoDB生产环境故障复盘:从典型事故中总结架构优化的核心经验”参与讨论