CentOS单机伪分布式MongoDB副本集搭建与故障转移实战
1. 为什么要在单机上折腾伪分布式MongoDB很多人第一次听到单机伪分布式MongoDB集群这个词第一反应是一台机器上搞集群这不是自欺欺人吗我一开始也这么想。但实际做过几个项目之后我发现这个方案在特定场景下非常实用甚至可以说是开发阶段的最优解。先把这个概念说清楚。所谓单机伪分布式就是在一台物理机或虚拟机上通过启动多个MongoDB实例每个实例监听不同端口、使用不同数据目录让它们组成一个具备副本集Replica Set或分片Sharding特性的集群。它和真正的分布式集群在架构逻辑上是一致的区别只在于所有节点共享同一台机器的CPU、内存和磁盘。那为什么要在CentOS上搭这个东西我总结了几个真实需求开发环境需要验证副本集逻辑你的代码里写了读写分离、故障转移、事务单实例根本测不出来但又不值得为开发环境申请三台服务器。学习成本最低想搞懂MongoDB的副本集选举机制、Oplog同步原理单机多实例是最直观的教具出问题了直接看日志不用SSH跳来跳去。资源受限但需要高可用特性一些小项目部署在单台高配服务器上用伪分布式副本集能获得自动故障转移能力比单实例靠谱得多。CI/CD流水线集成自动化测试需要快速拉起一个MongoDB集群环境单机伪分布式启动快、清理方便。注意伪分布式集群不能提供真正的高可用。如果这台机器宕机所有节点一起挂。它解决的是逻辑验证和开发测试问题不是生产级容灾问题。这篇文章我会从零开始把CentOS上搭建单机伪分布式MongoDB副本集的全过程拆开讲。包括版本选型、目录规划、配置文件怎么写、副本集怎么初始化、怎么验证故障转移、以及我踩过的那些坑。适合有一定Linux基础、想深入理解MongoDB集群机制的开发者和运维人员。2. 搭建前的整体设计与关键决策2.1 副本集还是分片集群怎么选MongoDB的集群形态主要有两种副本集Replica Set和分片集群Sharded Cluster。单机伪分布式环境下我建议优先选副本集原因如下对比维度副本集分片集群最少节点数3个1主2从至少4个组件config、mongos、2个shard资源占用较低较高每个组件都要独立进程核心价值高可用、读写分离水平扩展、海量数据单机搭建难度中等较高适用场景开发测试、学习原理模拟大数据量分片逻辑副本集的核心机制是一个主节点Primary负责写操作多个从节点Secondary通过Oplog异步复制数据。主节点挂了从节点会自动选举出新的主节点。这个自动故障转移能力是单实例完全不具备的。分片集群在单机上搭建进程数量太多内存消耗大而且分片的核心价值——水平扩展——在单机上根本体现不出来。所以除非你明确要学习分片路由逻辑否则副本集足够了。2.2 节点数量的讲究副本集最少需要3个节点才能实现自动故障转移。为什么因为MongoDB的选举机制要求多数派Majority存活才能选出新主节点。3个节点中多数派是2个。如果只有2个节点1个挂了剩下1个无法形成多数派整个集群就变成只读状态。所以标准配置是3个节点1主2从。在单机上就是启动3个MongoDB实例分别监听27017、27018、27019端口。实操心得如果你的机器内存紧张可以只启动2个数据节点加1个仲裁节点Arbiter。仲裁节点不存数据只参与投票内存占用极低。但仲裁节点不能变成主节点所以实际只有1个数据副本容灾能力有限。开发环境够用但别在生产这么干。2.3 版本选择与系统准备MongoDB的版本迭代很快我建议选5.0或6.0的稳定版。4.x系列已经停止维护7.x虽然新但部分API有变动社区资料还没跟上。5.0和6.0在副本集方面的功能已经非常成熟文档也齐全。CentOS方面7和8都可以但CentOS 7的glibc版本较老安装MongoDB 6.0可能需要额外处理依赖。如果条件允许用CentOS 8或Rocky Linux 8会更省心。系统准备清单关闭SELinux或配置正确的策略否则MongoDB可能无法访问数据目录防火墙开放对应端口27017-27019或者直接关闭firewalld仅限内网开发环境确保磁盘有足够空间每个节点至少预留1GB用于数据和日志调整系统限制ulimit -n建议调到64000以上MongoDB对文件描述符需求较高# 查看当前文件描述符限制 ulimit -n # 临时调整 ulimit -n 64000 # 永久调整编辑 /etc/security/limits.conf echo mongod soft nofile 64000 /etc/security/limits.conf echo mongod hard nofile 64000 /etc/security/limits.conf2.4 目录结构规划单机上跑多个实例目录规划必须清晰否则后期维护会疯掉。我习惯用这样的结构/data/mongodb/ ├── node1/ │ ├── data/ # 数据目录 │ ├── log/ # 日志目录 │ └── conf/ # 配置文件 ├── node2/ │ ├── data/ │ ├── log/ │ └── conf/ └── node3/ ├── data/ ├── log/ └── conf/每个节点独立目录互不干扰。删除某个节点时直接删目录就行不会影响其他节点。这个习惯在后期排查问题时特别有用——你可以单独重启某个节点、单独查看某个节点的日志而不用在一堆混杂的文件里翻找。3. 核心细节解析与实操要点3.1 安装MongoDB的几种方式对比在CentOS上装MongoDB常见的有三种方式YUM源安装、TGZ压缩包手动安装、Docker容器安装。单机伪分布式场景下我强烈推荐TGZ手动安装原因如下YUM安装会把MongoDB注册为系统服务默认只启动一个实例多实例需要额外配置容易和系统服务管理冲突Docker方式虽然隔离性好但多容器网络配置在单机上会增加复杂度而且数据卷映射容易出权限问题TGZ手动安装最灵活解压即用每个实例用独立的二进制文件或共享同一份二进制但指定不同配置完全可控具体操作# 下载MongoDB 6.0的TGZ包以x86_64为例 cd /opt wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-6.0.12.tgz # 解压 tar -zxvf mongodb-linux-x86_64-rhel80-6.0.12.tgz # 重命名方便管理 mv mongodb-linux-x86_64-rhel80-6.0.12 mongodb # 把bin目录加入PATH echo export PATH/opt/mongodb/bin:$PATH /etc/profile source /etc/profile # 验证 mongod --version注意下载地址中的版本号和系统标识要根据实际情况调整。rhel80对应CentOS 8如果是CentOS 7要选rhel70的包。选错了会报glibc版本不兼容的错误。3.2 配置文件的关键参数解读MongoDB支持命令行参数和配置文件两种方式启动。多实例场景下必须用配置文件否则命令行会长得没法维护。每个节点的配置文件我建议包含以下核心参数# /data/mongodb/node1/conf/mongod.conf systemLog: destination: file path: /data/mongodb/node1/log/mongod.log logAppend: true storage: dbPath: /data/mongodb/node1/data journal: enabled: true wiredTiger: engineConfig: cacheSizeGB: 0.5 net: port: 27017 bindIp: 0.0.0.0 replication: replSetName: rs0 processManagement: fork: true pidFilePath: /data/mongodb/node1/mongod.pid逐个解释这些参数背后的逻辑systemLog日志必须独立。三个节点如果写同一个日志文件会互相覆盖排查问题时根本分不清是哪个节点出的错。logAppend: true保证重启时追加而不是覆盖保留历史日志。storage.dbPath数据目录必须独立这个不用多说。journal.enabled: true开启预写日志保证崩溃恢复能力。虽然会稍微影响写入性能但开发环境数据安全更重要。storage.wiredTiger.engineConfig.cacheSizeGB这是最关键的调优参数。WiredTiger引擎默认会使用机器可用内存的50%作为缓存。单机上跑3个实例如果每个都抢50%内存机器直接OOM。所以必须手动限制每个节点给0.5GB到1GB就够了。计算公式是cacheSizeGB 可用内存 / 节点数 * 0.6留40%给系统和其他进程。net.port三个节点分别用27017、27018、27019。bindIp: 0.0.0.0允许外部访问如果只在本机测试改成127.0.0.1更安全。replication.replSetName三个节点必须用同一个副本集名称否则它们无法组成集群。这个名字随便取但要有意义比如rs0、myReplSet等。processManagement.fork让mongod在后台运行。不加这个参数终端一关进程就没了。pidFilePath记录进程ID方便后续用脚本管理。3.3 目录创建与权限处理配置文件写好后要创建对应的目录并设置权限。这里有个坑MongoDB进程需要对数据目录有读写权限如果权限不对启动时会报Permission denied。# 创建三个节点的目录结构 for i in 1 2 3; do mkdir -p /data/mongodb/node$i/{data,log,conf} done # 设置权限假设用mongod用户运行 chown -R mongod:mongod /data/mongodb # 如果没有mongod用户可以创建 useradd -r -s /bin/false mongod实操心得我习惯用专门的mongod用户运行MongoDB而不是root。这样即使MongoDB被攻破攻击者也只能访问mongod用户权限范围内的文件。虽然开发环境安全性要求没那么高但养成好习惯没坏处。3.4 启动脚本的编写手动一个个启动mongod太麻烦写个脚本一键管理#!/bin/bash # /data/mongodb/manage.sh MONGO_HOME/opt/mongodb BASE_DIR/data/mongodb start_node() { local node$1 local port$2 $MONGO_HOME/bin/mongod --config $BASE_DIR/$node/conf/mongod.conf echo Started $node on port $port } stop_node() { local node$1 local pid_file$BASE_DIR/$node/mongod.pid if [ -f $pid_file ]; then kill $(cat $pid_file) echo Stopped $node fi } case $1 in start) start_node node1 27017 start_node node2 27018 start_node node3 27019 ;; stop) stop_node node1 stop_node node2 stop_node node3 ;; *) echo Usage: $0 {start|stop} ;; esac这个脚本虽然简单但比手动敲命令靠谱得多。特别是stop操作通过pid文件精确杀进程不会误杀其他mongod实例。4. 副本集初始化与故障转移验证4.1 初始化副本集三个节点都启动后它们还是独立的实例互相不认识。需要连接到一个节点执行副本集初始化命令。# 连接到第一个节点 mongosh --port 27017 # 在mongosh中执行初始化 rs.initiate({ _id: rs0, members: [ { _id: 0, host: 127.0.0.1:27017 }, { _id: 1, host: 127.0.0.1:27018 }, { _id: 2, host: 127.0.0.1:27019 } ] })执行后会返回{ ok: 1 }表示初始化成功。但这只是开始副本集需要几秒钟到几十秒的时间来完成选举和数据同步。# 查看副本集状态 rs.status()在输出中你会看到三个成员的状态。刚初始化时27017通常是PRIMARY另外两个是SECONDARY。如果看到某个节点状态是STARTUP2说明还在同步数据等一会儿再查。注意host字段必须用其他节点能访问到的地址。如果写127.0.0.1那么所有节点必须在同一台机器上。如果以后想扩展到多机这里要改成实际的IP或主机名。单机伪分布式用127.0.0.1没问题。4.2 验证读写分离副本集默认的读策略是从主节点读。要验证读写分离需要手动设置读偏好// 在mongosh中切换到从节点 mongosh --port 27018 // 设置从节点可读 rs.slaveOk() // 旧版本命令 // 或者 db.getMongo().setReadPref(secondary) // 尝试读取数据 use testdb db.testcollection.find()如果配置正确从节点能读到主节点写入的数据。这里有个细节MongoDB的复制是异步的主节点写入后从节点需要一点时间才能同步。如果立即在从节点读可能读不到最新数据。这是正常现象不是配置错误。4.3 模拟故障转移这是副本集最核心的能力验证。我们手动把主节点干掉看集群是否能自动选出新主节点。# 找到当前主节点的PID cat /data/mongodb/node1/mongod.pid # 杀掉主节点进程 kill $(cat /data/mongodb/node1/mongod.pid) # 连接到剩下的某个节点 mongosh --port 27018 # 查看状态 rs.status()正常情况下几秒钟内27018或27019中的一个会变成PRIMARY。你可以观察rs.status()输出中的electionDate和stateStr字段确认选举发生的时间和新主节点是谁。然后重新启动被杀的节点/opt/mongodb/bin/mongod --config /data/mongodb/node1/conf/mongod.conf重启后它会自动以SECONDARY身份加入集群并从新主节点同步数据。这个过程完全自动不需要人工干预。4.4 数据同步机制解析副本集的数据同步依赖Oplog操作日志。主节点把所有的写操作记录到本地的local.oplog.rs集合中从节点不断拉取这些操作并重放从而实现数据同步。Oplog是一个固定大小的集合Capped Collection默认大小是磁盘空间的5%。如果Oplog写满了旧的记录会被覆盖。如果从节点落后太多需要的Oplog记录已经被覆盖就会同步失败需要重新进行全量同步Initial Sync。查看Oplog状态use local db.oplog.rs.stats().maxSize // 查看Oplog最大大小 db.oplog.rs.stats().size // 查看当前使用大小实操心得开发环境数据量小Oplog默认大小完全够用。但如果做压力测试写入量很大建议手动调大Oplog。在配置文件中加replication.oplogSizeMB: 1024设置为1GB。太小会导致从节点频繁全量同步太大则浪费磁盘空间。5. 常见问题与排查技巧实录5.1 启动失败类问题问题一端口被占用Failed to set up listener: SocketException: Address already in use排查netstat -tlnp | grep 27017查看谁占用了端口。可能是之前启动的mongod没杀干净或者有其他服务占用了这个端口。解决杀掉占用进程或者修改配置文件换端口。问题二数据目录权限不足Permission denied: /data/mongodb/node1/data/WiredTiger排查ls -la /data/mongodb/node1/data查看目录所有者和权限。解决chown -R mongod:mongod /data/mongodb/node1确保运行mongod的用户对数据目录有读写权限。问题三配置文件格式错误Error parsing YAML config file: yaml-cpp: error at line XYAML对缩进极其敏感必须用空格不能用Tab。而且冒号后面必须有空格。我见过太多人因为缩进问题卡半天。解决用yamllint工具检查配置文件或者对照官方示例逐行核对。5.2 副本集状态异常问题四节点一直处于STARTUP2状态这说明节点正在从主节点进行全量同步。如果数据量大这个过程可能持续很久。查看日志确认进度tail -f /data/mongodb/node2/log/mongod.log | grep -i sync如果长时间卡住不动可能是网络问题或Oplog不连续。检查主从节点之间的网络连通性以及Oplog是否被覆盖。问题五无法选举出主节点如果rs.status()显示所有节点都是SECONDARY没有PRIMARY说明选举失败。常见原因多数派节点不可达检查是否有超过半数的节点存活节点配置不一致检查rs.conf()中的members列表是否和实际启动的节点匹配网络分区单机环境下一般不会出现但如果是多机伪分布式要检查防火墙规则问题六从节点数据落后太多// 在主节点查看从节点延迟 rs.printSlaveReplicationInfo()如果延迟很大可能是从节点性能不足或网络带宽受限。单机环境下通常是磁盘IO瓶颈。可以考虑把不同节点的数据目录放在不同的物理磁盘上。5.3 连接与认证问题问题七mongosh连接被拒绝MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017排查确认mongod进程是否在运行ps aux | grep mongod。确认端口是否正确netstat -tlnp | grep 2701。问题八副本集模式下无法写入NotWritablePrimary: not primary这说明你连接的是从节点而从节点默认不允许写入。解决连接到主节点写入或者在连接字符串中指定replicaSetrs0让驱动自动发现主节点。连接字符串示例mongodb://127.0.0.1:27017,127.0.0.1:27018,127.0.0.1:27019/?replicaSetrs0这种写法让驱动自动感知副本集拓扑主节点挂了会自动切换到新主节点应用程序无需修改配置。5.4 常见问题速查表问题现象可能原因排查命令解决方案启动报端口占用端口被其他进程占用netstat -tlnp | grep 端口杀进程或换端口启动报权限拒绝数据目录权限不对ls -la 数据目录chown修改所有者节点状态STARTUP2正在全量同步tail -f 日志 | grep sync等待或检查Oplog无PRIMARY节点多数派不可达rs.status()检查节点存活数从节点延迟大磁盘IO瓶颈rs.printSlaveReplicationInfo()优化磁盘或分离数据目录连接被拒绝进程未启动或端口错ps aux | grep mongod启动进程或修正端口无法写入连接的是从节点db.hello().isWritablePrimary连接主节点或指定replicaSet6. 生产环境迁移与扩展思路6.1 从伪分布式到真分布式的平滑迁移单机伪分布式验证通过后如果项目需要上生产迁移到多机真分布式其实并不复杂。核心步骤在新机器上安装相同版本的MongoDB把单机上的某个从节点数据目录拷贝到新机器或者直接让新节点全量同步修改副本集配置把新机器的地址加入members列表移除单机上的对应节点// 添加新节点 rs.add(新机器IP:27017) // 移除旧节点 rs.remove(127.0.0.1:27019)这个过程是在线操作不需要停机。MongoDB会自动处理数据同步和选举。6.2 安全加固建议开发环境的配置不能直接上生产。生产环境必须做以下加固开启认证配置security.authorization: enabled创建管理员用户和业务用户开启TLS/SSL加密节点间通信和客户端连接限制bindIp不要用0.0.0.0只绑定内网IP使用Keyfile认证副本集内部节点通信需要认证防止非法节点加入Keyfile配置示例security: keyFile: /data/mongodb/keyfile authorization: enabled# 生成keyfile openssl rand -base64 756 /data/mongodb/keyfile chmod 400 /data/mongodb/keyfile chown mongod:mongod /data/mongodb/keyfile注意keyfile的权限必须是400而且所有者必须是运行mongod的用户。权限不对会导致启动失败。6.3 监控与日常维护伪分布式集群虽然简单但基本的监控不能少。我习惯用以下命令做日常巡检// 查看副本集整体状态 rs.status() // 查看Oplog窗口大小还能撑多久 db.getReplicationInfo() // 查看各节点延迟 rs.printSlaveReplicationInfo() // 查看当前操作 db.currentOp() // 查看慢查询 db.setProfilingLevel(1, { slowms: 100 }) db.system.profile.find().sort({ ts: -1 }).limit(10)这些命令覆盖了副本集健康度、复制延迟、性能瓶颈等关键指标。建议写成脚本每天定时执行并输出报告。6.4 资源限制与性能调优单机上跑三个MongoDB实例资源竞争是不可避免的。除了前面提到的cacheSizeGB限制还有几个调优点CPU亲和性用taskset把不同节点绑定到不同CPU核心减少上下文切换磁盘IO隔离如果有多块磁盘把不同节点的数据目录分开存放内存监控用free -h和top观察内存使用避免OOM Killer杀掉mongod进程日志轮转配置logrotate防止日志文件无限增长占满磁盘# logrotate配置示例 /data/mongodb/node*/log/mongod.log { daily rotate 7 compress missingok notifempty copytruncate }copytruncate很关键它先复制日志文件再清空原文件不需要重启mongod进程。如果用默认的create模式需要发信号让mongod重新打开日志文件比较麻烦。7. 我踩过的那些坑与个人体会第一个坑是Oplog大小。刚开始用默认配置做压力测试时写入量太大Oplog很快被覆盖导致从节点同步失败反复触发全量同步。后来把Oplog调到2GB才稳定下来。所以如果你的场景写入频繁一定要提前规划Oplog大小。第二个坑是主机名解析。有次在配置文件里写了主机名而不是IP结果/etc/hosts没配好节点之间互相解析不到副本集一直初始化失败。后来统一用127.0.0.1或者明确的IP地址问题就消失了。单机环境下能用IP就别用主机名。第三个坑是mongosh版本兼容性。老版本的mongo shell和新版本的MongoDB Server之间可能存在命令不兼容。比如rs.slaveOk()在新版本里已经废弃要用db.getMongo().setReadPref()。建议shell版本和server版本保持一致。第四个坑是fork模式下的日志。开启processManagement.fork: true后mongod在后台运行但启动时的错误信息不会输出到终端而是写到日志文件。如果启动失败终端只显示一个模糊的错误必须去翻日志才能找到真正原因。所以每次启动后我都习惯性tail -20看一下日志确认没有报错。关于这个方案的价值我的看法是单机伪分布式MongoDB集群是一个性价比极高的学习和验证环境。它让你用一台机器的成本获得了理解副本集核心机制的机会。你在单机上踩过的坑到了多机环境大概率还会遇到但那时候排查成本会高得多。所以如果你打算在生产环境使用MongoDB副本集强烈建议先在单机上把整套流程跑通把故障转移、数据同步、认证配置这些环节都验证一遍。这个过程花的时间会在后续的生产运维中加倍省回来。最后分享一个小技巧如果你经常需要重建这个环境做测试可以把整个搭建过程写成一个Shell脚本包括目录创建、配置生成、启动、初始化副本集。这样每次测试前跑一遍脚本几分钟就能得到一个干净的集群环境比手动操作快得多也不会漏掉步骤。