# 部署
MongoDB 常见的部署架构:
- 单实例
- 主从集群
- 与 MySQL 的主从集群类似。
- MongoDB v4.0 开始弃用主从集群,要改用副本集群。
- 副本集群
- 分片集群
# 版本
- v1.0 :2009 年发布。
- v2.0 :2011 年发布。
- v3.0 :2015 年发布。
- v4.0 :2018 年发布。
- 原本 mongo 不支持事务操作,只是保证了 CRUD 操作的原子性。现在开始支持事务。客户端可以调用 startTransaction()、commitTransaction() 等函数创建事务,在一个事务中处理多个文档。
- v4.2
- 对于副本集群,函数 rs.slaveOk() 改名为 rs.secondaryOk() 。
- 对于分片集群,支持分布式事务。
- v4.4
- mongod 日志改为 JSON 格式。
- 执行 compact 命令时,不再阻塞当前数据库的所有 CRUD 操作。
- v5.0 :2021 年发布。
- 支持创建时序集合。
- 原本 mongod 重启时,会自动重新执行尚未完成的 index build 任务。现在会根据重启前的进度,继续执行任务。
- 对于分片集群,增加 reshardCollection 命令,对在线数据重新分片。
- v5.2
- 对于分片集群,默认的 chunkSize 从 64MB 改为 128MB 。
- v5.3
- 对于副本集群,默认禁止添加多个仲裁节点。
- v6.0 :2022 年发布。
- 支持查询加密数据:客户端使用密钥创建一个加密集合,该集合内的数据会加密之后存储在服务器,允许对加密数据进行查询。
# 单实例
# 部署
用 yum 安装:
wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-server-4.0.5-1.el7.x86_64.rpm wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-shell-4.0.5-1.el7.x86_64.rpm wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-tools-4.0.5-1.el7.x86_64.rpm wget https://repo.mongodb.org/yum/redhat/7/mongodb-org/4.0/x86_64/RPMS/mongodb-org-mongos-4.0.5-1.el7.x86_64.rpm yum install -y mongodb-org-*.rpm rm -f mongodb-org-*.rpm
然后启动:
mongod # 启动 mongo 服务器 -f /etc/mongod.conf # 使用指定的配置文件
- 服务器启动时,默认不会读取配置文件,在前台运行,监听端口 27017 ,
或者用 docker-compose 部署:
version: "3" services: mongo: container_name: mongo image: mongo:5.0.5 command: [mongod, -f, /etc/mongod.conf] restart: unless-stopped environment: MONGO_INITDB_ROOT_USERNAME: root # 非副本集群时,自动创建管理员用户 MONGO_INITDB_ROOT_PASSWORD: ****** ports: - 27017:27017 volumes: - /etc/localtime:/etc/localtime:ro - ./mongod.conf:/etc/mongod.conf - ./data:/data/db # 官方镜像将 /data/db 目录声明为了 volume ,不挂载到该目录则会自动挂载一个匿名 volume
- 需要调整挂载目录的权限:
chown -R 999 data
- 需要调整挂载目录的权限:
# 管理
执行以下命令,即可终止 mongod 服务器:
mongod -f /etc/mongod.conf --shutdown
或者在客户端执行:
use admin db.shutdownServer()
不要用 kill 命令强制终止 mongod 进程,否则磁盘中的数据文件可能损坏。导致 mongod 启动之后,从磁盘读取某些数据文件时会崩溃退出。
- 可以尝试修复 mongod 的数据文件:
mongod --repair --dbpath /data/db # 直接修复原数据目录 mongod --repair --dbpath /data/db --repairpath /data/db_new # 将修复之后的数据文件保存到另一个目录
- repair 会检查每个 collection-xx.wt 文件中的每个数据页 pages 并修复,然后重建 index-xx.wt 文件。每小时大概处理 150G 的磁盘文件。
- repair 操作不能中断,否则不能启动 mongod 。
- 可以尝试修复 mongod 的数据文件:
升级 mongod 服务器版本时:
- 如果沿用之前的数据目录,则只能升级到最近一个子版本。比如从 v4.0 升级到 V4.2 ,再升级到 v4.4 ,不能跨版本升级。
- 通过 mongodump 迁移数据,则可以跨版本升级。
# 客户端
命令:
mongo [options] [server_uri] [script.js]
-u <username>
-p <password>
- mongo 服务器的 URI 采用以下格式:
mongodb://[username:password@]host1[:port1][,hostN[:portN]]...[/db][?options]]
- 例:
mongodb://127.0.0.1:27017/test # 这是 mongo 客户端的默认配置,会连接到 localhost:6379 地址的服务器,使用 test 数据库 mongodb://root:******@127.0.0.1:27017/test?authSource=admin # 传入用户名、密码,并通过 URI 参数指定验证数据库 mongodb://10.0.0.1:27017,10.0.0.2:27017,10.0.0.3:27017/?replicaSet=rs1 # 连接到副本集群
- 例:
- 启动客户端时,默认是打开一个 JavaScript 终端,因此可以执行 js 命令、定义变量、定义函数。
- 也可以指定一个要执行的 js 脚本,或者从 stdin 传入要执行的 js 命令。如下:
mongo $server_uri test.js echo 'show dbs' | mongo $server_uri
- 也可以指定一个要执行的 js 脚本,或者从 stdin 传入要执行的 js 命令。如下:
# 备份数据
# 导出
mongodump 命令:
mongodump -h <host>[:port] --authenticationDatabase=admin -u <username> -p <password> -d <db> # 只处理指定的一个数据库。如果不指定,则默认会导出所有数据库,除了 local 数据库 -c <collection> # 只处理指定的一个集合。如果不指定,则默认会处理所有集合 -j 4 # 导出多个集合时,同时最多处理几个集合 -q <expression> # --query ,用一个 JSON 格式的表达式筛选文档 -o <path> # 导出之后保存到指定目录,默认为 ./dump --gzip # 导出时进行 gzip 压缩
- 例:
mongodump -h 10.0.0.1:27017 -d db1 -c col1 -q '{"_id": -1}' # 不导出文档,只导出 index 。而 collection 会在导入 index 时自动创建 mongodump -h 10.0.0.1:27017 -d db1 -c col1 -q '{"_id": {"$gte": {"$oid": "62a03eb3ec744e00015b1f26"}}}' # 只导出 _id 大于指定值的文档
- 导出时,会在 -o 目录下按数据库名分别创建子目录,为每个集合保存
<collection>.bson
和<collection>.metadata.json
两个数据文件。 - mongo shell 中的查询表达式,不一定能直接用于 mongodump -q 选项。可能需要转换格式,以符合 JSON 语法。
- 导出时,会在 -o 目录下按数据库名分别创建子目录,为每个集合保存
- mongod 的 WiredTiger 存储引擎默认会将文档、索引压缩之后才存储到磁盘,压缩率大概为 50% 。假设占用磁盘为 50G ,则所有数据加载到内存中大概为 100G 。
- 如果 mongodump 时不启用 gzip 压缩,则导出文件的体积大概为 100G 。
- 如果 mongodump 时启用 gzip 压缩,则导出文件的体积大概为 25G 。不过该压缩文件不支持用 tar 命令解压。
- mongodump 导出的速度大概为每分钟 4G ,mongorestore 导入的速度大概慢一半。
- mongodump 导出数据时,mongod 需要从磁盘读取这些数据,载入内存,可能导致磁盘 IO 速度达到瓶颈、内存使用率过高。
- mongodump 导出大量数据时,建议带上查询条件,分批导出数据。比如根据 oid 范围,每次导出一个月的数据。这样导出第一批数据之后,就可以开始导入,减少迁移数据的耗时。
- mongodump 不适合在线迁移 mongod 的数据。因为在 mongodump 导出数据的过程中,mongod 可能又修改了数据。可采用以下措施:
- 先执行一次 mongodump + mongorestore 进行全量迁移,再执行几次 mongodump ,根据 _id 导出最近几天的新增文档,进行增量同步。但这样不完全可靠,可能存在几天以前的旧文档,_id 不变而其它字段被修改了。
- 先执行一次 mongodump + mongorestore 进行全量迁移,再执行 mongoshake ,基于 oplog 增量同步。
- 例:
mongoexport 命令:
mongoexport # 导出为 JSON 或 CSV 格式。该命令兼容 mongodump 命令的大部分参数 --out out.json # 导出之后保存到指定文件。默认输出到 stdout --type json # 导出格式,默认为 json --pretty # 导出格式为 JSON 时,进行缩进排版。默认是每个文档占一行 --limit <int> # 限制导出的文档数
# 导入
mongodump 导出的数据要用 mongorestore 命令导入。而 mongoexport 导出的数据,以及其它来源的 JSON、CSV 数据,要用 mongoimport 命令导入。
mongorestore 命令:
mongorestore <path> # 指定要导入的 bson 文件。也可以指定目录,这会自动发现该目录下的所有 bson 文件,但不会发现子目录的 -h <host>[:port] --authenticationDatabase=admin -u <username> -p <password> -d <db> -c <collection> # bson 文件中可能存在多个集合的数据,用 -c 选项可以只导入指定一个集合 --nsInclude "db1.*" # 可以用通配符,匹配多个集合,只导入它们 --nsExclude "db1.*" # 可以用通配符,匹配多个集合,不导入它们 --nsFrom "db1.*" --nsTo "db2.*" # 导入时,将集合重命名 -j 4 # 导入多个集合时,同时最多处理几个集合。并行数过多可能导致服务器过载,中断导入 --numInsertionWorkersPerCollection 1 # 导入每个集合时,创建几个线程进行并发 insert --gzip # 导入时进行 gzip 解压 --drop # 导入每个集合时,如果存在同名的集合,则先删除,再导入 --stopOnError # 遇到报错时立即终止该命令(默认会忽略报错,且命令的返回码依然为 0 )
- mongorestore 导入每个集合时,会先 insert 所有文档,然后 build index 也需要一定时间。
- mongorestore 导入每个文档时,如果已存在相同 _id 的文档,则会报错
duplicate key
,跳过导入该文档。而且此时需要从磁盘读取 _id 索引的大量数据,导入速度会慢几倍。 - mongorestore 不支持导入重复的文档,这种问题需要用 mongoimport 命令。
mongoimport 命令:
mongoimport <file> # 指定文件,导入 JSON 或 CSV 格式的数据,兼容 mongorestore 的大部分参数 -d <db> -c <collection> --mode insert # 导入模式,默认为 insert # 如果导入的某个新文档,遇到字段重复(即该字段建立了唯一索引,并且与已有文档的同一字段取值重复),则会根据 mode 决定如何处理 # 有以下几种 mode : # insert :报错说新文档的字段重复,然后继续导入 # upsert :用新文档覆盖已有文档。但不允许修改已有文档的 _id 字段 # merge :合并 # delete :删除与新文档字段重复的所有已有文档 --upsertFields=_id,<filed>,... # 指定一组字段,用于在 upsert、merge、delete 模式下查询字段值重复的已有文档。默认指定 _id 字段
# mongoshake
:一个命令行工具,用于将一个 Mongo 的数据,同步到其它 Mongo ,也可同步到 Kafka 等目的端,属于异步复制。
- GitHub (opens new window)
- 由阿里云开源,采用 Golang 语言开发。
- 用法:
- 下载 mongoshake 。
- 修改配置文件 collector.conf :
mongo_urls = mongodb://username:[email protected]:27017 # 源 Mongo 的地址 # mongo_cs_url = # 源 Mongo 为分片集群时,mongoshake 需要连接 config server ,并分别连接各个 shard server # tunnel = direct # 通道模式。默认为 direct ,表示将数据直接同步到其它 Mongo tunnel.address = mongodb://10.0.0.1:27017 # 目标端的地址 sync_mode = all # 同步模式,有几种取值: # all :表示全量同步 + 增量同步 # full :表示仅全量同步,这需要从源 Mongo 获取各个集合的数据 # incr :表示仅增量同步,这需要从源 Mongo 获取 oplog # filter.namespace.black = db1.col1,db2 # 同步数据时,不同步哪些 db、collection ,用逗号分隔。默认不会同步系统数据库 admin、config、local # filter.namespace.white = # 同步的白名单,不能与黑名单同时配置 # 关于全量同步 # full_sync.reader.collection_parallel = 6 # 并行同步的集合数 # full_sync.reader.write_document_parallel = 8 # 同步一个集合时,创建多少个线程来并发写入目标端 # full_sync.collection_exist_drop = true # 同步一个集合时,如果目的端已存在同名的集合,是否删除它再同步 full_sync.create_index = background # 是否创建索引。background 表示开始同步时,就创建索引 # full_sync.executor.insert_on_dup_update = false # 同步插入一个文档时,如果目的端已存在相同 _id 的文档,是否将 insert 语句改为 update 语句 # 关于增量同步 # incr_sync.mongo_fetch_method = oplog # 从源 Mongo 获取 oplog ,从而实现增量同步 incr_sync.shard_key = id # 从源 Mongo 获取到文档之后,按 _id 进行哈希,将文档分配给各个 worker 线程 incr_sync.worker.worker = 32 # 将文档写入目的端时,创建多少个 worker 线程,进行并发写入 # incr_sync.target_delay = 0 # 源 Mongo 的数据,延迟多久才同步到目标端,单位为秒 # incr_sync.executor.insert_on_dup_update = false # 同步插入一个文档时,如果目的端已存在相同 _id 的文档,是否将 insert 语句改为 update 语句 # incr_sync.executor.upsert = false # 同步更新一个文档时,如果目的端不存在相同 _id 的文档,是否将 update 语句改为 insert 语句 # checkpoint.storage.db = mongoshake # 默认将已成功同步的 oplog 时间戳记录在源 Mongo 里,因此重启 mongoshake 之后也会继续同步 checkpoint.start_position = 1970-01-01T00:00:00Z # 增量同步指定时刻之后的 oplog 。如果源 Mongo 已记录 checkpoint ,则忽略该配置参数
- 执行以下命令,开始同步:查看进度:
./collector.linux -conf collector.conf -verbose 2
curl 127.0.0.1:9101/progress # 查看全量同步的进度 curl 127.0.0.1:9100/repl # 查看增量同步的进度。其中 logs_repl 表示重放了多少条 oplog ,tps 表示每秒同步多少条 oplog ,LSN_ACK 表示已成功同步的 oplog 的 ts 时间戳
# 副本集群
副本集群(Replication)由多个 mongod server 组成,server 的角色分为三种:
- Primary
- :主节点,负责处理客户端的读、写请求。
- 副本集群内,有且仅存在一个 Primary 节点,还可存在任意个(包括零个) Secondary、Arbiter 节点。
- Secondary
- :从节点,负责复制主节点的数据。
- 从节点不能处理客户端的写请求,但能处理读请求,可实现读写分离。
- Arbiter
- :仲裁节点。不同步数据(因此不容易出故障),仅参与投票,不能被选举。
- 仲裁节点不存储数据,而主节点、从节点都属于数据节点。
- Primary
与 mongod 单实例相比,副本集群能大幅提高可用性。当主节点故障时,其它节点会自动选出一个新的主节点,实现主从切换。
- 副本集群内只存在一个节点时,它会担任 Primary 节点,供客户端读写,但不能进行主从切换。
- 副本集群内有超过半数的投票节点在线时,才能开始选举。
- 选举过程有几秒长。
- Mongo 限制了一个副本集群最多存在 7 个投票节点。
- 与 zk 集群类似,投票节点的数量建议为奇数,为偶数时并不会提高可用性。
- 副本集群至少要部署 3 个投票节点,才能进行主从切换。
- 建议至少部署 1 主 + 2 从节点。
- 建议最多部署一个仲裁节点。因为部署多个仲裁节点时,可能投票选出一个尚未完全同步的从节点,担任新的主节点。
# 原理
oplog.rs
- :副本集模式下,会在 local 数据库中创建一个名为 oplog.rs 的集合,采用 capped 类型,用于记录 mongod 执行过的各个写操作,称为 operation log 。
- oplog 中所有写操作按时间戳 ts 排序。每个写操作具有幂等性,例如将
$inc
操作记录成$set
操作。 - 例:oplog 记录的一个写操作
{ "op": "c", // operation 的类型。i 表示 insert ,u 表示 update ,d 表示 delete ,c 表示 db cmd "ns": "config.$cmd", // 命名空间,格式为 <database>.<ollection> "ui": UUID("45f0789d-1de0-96f0-a82a-18c49c625b14"), "o": { // operation 的内容 "create": "transactions", "idIndex": { "v": 2, "key": { "_id": 1 }, "name": "_id_" } }, "ts": Timestamp(1677498832, 2), // 前一个数表示 UNIX 时间戳,单位为秒。后一个数表示在这一秒执行了多个操作时,每个操作的序号 "t": NumberLong(1), // term ,表示副本集群经过了几次选举 "v": NumberLong(2), "wall": ISODate("2023-03-01T12:00:00.000Z") }
从节点的启动流程:
- 从节点已启动,但尚未加入副本集群,此时处于 STARTUP 状态。
- initial sync 阶段:从节点连接到主节点,进行一次全量同步,复制主节点的所有数据库(除了 local 数据库)的文档到本机,然后构建索引。此时处于 STARTUP2 状态。
- 从节点触发 initial sync 的几种情况:
- 从节点的 oplog 为空,说明是一个新节点。
- 从节点的 local 数据库的 replset.minvalid 集合中记录了
"_initialSyncFlag": true
,说明从节点在 initial sync 的过程中重启了。
- 从节点触发 initial sync 的几种情况:
- replication 阶段:从节点通过 oplog 增量同步主节点的数据。此时处于 SECONDARY 状态。
- 流程如下:
- 从节点记录开始 initial sync 的时间戳 t ,作为初次查询 oplog 的起始位置。
- initial sync 完成之后,从节点的后台线程 BackgroundSync 会循环执行:先选取其它一个数据节点作为同步源,然后向它查询在时间戳 t 以后的 oplog ,复制到本机的内存缓冲区 OplogBuffer 。
- BackgroundSync 会循环执行,不断查询是否存在新的 oplog :
db.oplog.rs.find({ts:{$gte: t}})
- 如果某次成功复制 oplog 并重放,则刷新时间戳 t ,作为下一次查询 oplog 的起始位置。
- BackgroundSync 会循环执行,不断查询是否存在新的 oplog :
- 从节点的线程 ReplBatcher 从 OplogBuffer 中获取 oplog ,存入 opQueue 。
- 从节点的线程 OplogApplier 以 batch 为单位从 opQueue 中获取 oplog 。然后创建默认 16 个 writer 线程,重放这些 oplog 中的写操作。
- 多个 writer 线程并行工作,不能保证所有 oplog 都是按时间戳 ts 的顺序执行。但能保证每个 batch 中同一个 collection 的所有 oplog ,会被同一个线程有序执行。
- 从节点将已重放的 oplog 保存到本机的 local 数据库中。
- 如果一个从节点在 SECONDARY 状态终止,过了很长时间才重启。则可能发现自己最新的一条 oplog 的时间戳 ts ,比主节点最旧的一条 oplog 的时间戳 ts 更旧,说明从节点落后了太多数据,不能通过 oplog 正常同步。此时从节点会报错
We are too stale
,阻塞在 RECOVERING 状态,用户可采取以下几种措施:- 清空从节点的数据目录 dbpath ,让它重新 initial sync 。
- 将其它节点的数据目录 dbpath 拷贝到从节点,这包括了普通数据库和 local 数据库。
- 建议不要在 mongod 运行时拷贝 dbpath ,因为数据文件可能正在被修改。
- 增加 oplog 的体积,可容忍从节点落后更多数据。
- 流程如下:
客户端连接副本集群时,写请求只能发送到主节点,而读请求有几种偏好(Read Preference):
primary # 读请求只能发送到主节点。这是默认值 primaryPreferred # 读请求尽量发送到主节点,也可以发送到从节点 secondary # 读请求只能发送到从节点 secondaryPreferred # 读请求尽量发送到从节点,也可以发送到主节点 nearest # 读请求发送给网络延迟较低的节点
- 默认从节点不支持读请求,除非执行
rs.secondaryOk()
。因为从节点采用半同步复制,可能没有最新的数据。 - 数据库负载不是很大的话,没必要读写分离,客户端可以将读、写请求都发给主节点。
- 默认从节点不支持读请求,除非执行
writeConcern
- :Mongo 的一个配置参数,用于控制主从节点之间的数据同步策略。如下:
db.col1.insertOne( {...}, {writeConcern: {w: "majority", j: true, wtimeout: 100} } );
- w :表示每提交一个写操作时,要等待多少个数据节点确认写入,才认为写操作完成,然后才能开始下一个写操作。
- 如果为 1 ,则只要等待一个数据节点确认写入,即主节点自己。属于异步复制。
- 如果为 majority ,则要等待大多数(writeMajorityCount)的数据节点确认写入,属于半同步复制。
- 此时副本集群不能是 1 主 + 1 从 + 1 仲裁节点,否则一个数据节点故障时,剩下一个数据节点不能提交写操作。因为 writeMajorityCount 为 2 ,而仲裁节点不属于数据节点。
- w 的默认值在不同 mongo 版本、不同场景有差异,建议手动查看。
- j :表示怎样才算确认写入。
- 如果为 true ,则等待数据节点将写操作保存到预写日志,才算确认写入。
- 如果为 false ,则在内存中执行了写操作,就算确认写入。此时可能因为断电而丢失数据。
- j 的默认值,取决于 writeConcernMajorityJournalDefault 。
- wtimeout 表示等待写操作完成的超时时间。如果超时,则报错。
- 如果不指定超时时间,则可能一直阻塞等待。
- :Mongo 的一个配置参数,用于控制主从节点之间的数据同步策略。如下:
# 部署
集群节点之间默认采用密钥文件进行认证。执行以下命令,生成密钥文件:
openssl rand -base64 512 > mongo.key chown mongod mongo.key chmod 400 mongo.key
然后将密钥文件拷贝到各个节点。
在各个节点的 mongod.conf 文件中增加配置:
replication: # 让 mongod 工作在副本集群模式。默认为 Standalone 模式 # oplogSizeMB: <int> # oplog 集合的体积。默认为磁盘空间的 5% ,默认值最大为 50GB replSetName: rs0 # 副本集群名称。同一副本集群中的所有节点必须采用相同的 replSetName security: authorization: enabled keyFile: /etc/mongo/mongo.key # 密钥文件的路径
登录一个节点,启动副本集群:
rs.initiate({ _id: "rs0", // 副本集群的名称 members: [ // 副本集群的成员列表 { _id: 0, host: "mongo-1:27017" }, { _id: 1, host: "mongo-2:27017" }, { _id: 2, host: "mongo-3:27017", priority: 0} ] })
- 副本集群必须初始化一次之后才能启动,也只需要初始化一次。
- 副本集群的 members 列表,至少要包含一个 mongo 节点,此时它担任 Primary 角色,作为单节点的副本集群运行。
- 不允许在 priority 为 0 的节点,或仲裁节点上进行初始化。
创建账号:
use admin db.createUser( { user: 'root', pwd: '******', roles: [{role: 'root', db: 'admin'}] } ) db.auth('root', '******')
- 初始化副本集群之前,只允许从本机地址登录 mongod ,比如执行
mongo 127.0.0.1:27017
。初始化之后,才可以创建账号,从其它地址登录。
- 初始化副本集群之前,只允许从本机地址登录 mongod ,比如执行
# 管理
相关命令:
rs.initiate(cfg) // 启动副本集群,可传入一些配置参数进行初始化,其余的配置参数则采用默认值
rs.reconfig(cfg) // 重新配置副本集群,可覆盖已有的配置参数
rs.conf() // 显示副本集群的配置
rs.status() // 显示副本集群的状态
rs.add("mongo-1:27017") // 添加从节点
rs.remove("mongo-1:27017") // 删除从节点
rs.addArb("mongo-4:27017") // 添加仲裁节点
rs.secondaryOk() // 当客户端连接到从节点时,执行该命令,则允许读取从节点的数据,不过有效期仅限当前会话
rs.printReplicationInfo() // 显示当前节点的 oplog 的状态
rs.printSecondaryReplicationInfo() // 显示所有从节点的同步状态
例:查看副本集群的状态
rs0:SECONDARY> rs.status() { "set" : "rs0", "date" : ISODate("2019-12-19T09:22:27.736Z"), "myState" : 1, "term" : NumberLong(1), "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "heartbeatIntervalMillis" : NumberLong(2000), "majorityVoteCount" : 2, "writeMajorityCount" : 2, ... "members" : [ // 所有节点的信息 { "_id" : 0, "name" : "mongo-1:27017", "ip" : "10.244.25.178", "health" : 1, // 0 代表下线,1 代表在线 "state" : 1, // 该节点的状态 "stateStr" : "PRIMARY", // 该节点的状态描述 "uptime" : 1798, // 该节点的在线时长 "optime" : { "ts" : Timestamp(1576746303, 1), "t" : NumberLong(1) }, "optimeDate" : ISODate("2019-12-19T09:05:03Z"), // 该节点最后一次同步 oplog 的时间 "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "could not find member to sync from", "configVersion" : 1, "self" : true, // 该节点是否为当前登录的节点 "lastHeartbeatMessage" : "" }, ...
- 节点的状态分为以下几种,分别对应一个数字编号:
0 STARTUP # 已启动,但尚未加入副本集群 1 PRIMARY 2 SECONDARY 3 RECOVERING # 正在恢复数据,不能接受客户端的读请求 5 STARTUP2 # 已启动,而且加入了某个副本集群,正在进行初始同步。随后会变成 PRIMARY、SECONDARY 等状态 6 UNKNOWN # 其它节点不能判断该节点的状态 7 ARBITER # 仲裁者 8 DOWN # 其它节点不能访问该节点 9 ROLLBACK # 正在回滚 10 REMOVED # 该节点从副本集群中移除了
- 从节点处于 STARTUP2、RECOVERING 状态时,有权投票,但不能被选举为主节点。
- 节点的状态分为以下几种,分别对应一个数字编号:
例:修改副本集群的配置
rs0:PRIMARY> rs.conf() { "_id" : "rs0", "version" : 1, "protocolVersion" : NumberLong(1), "writeConcernMajorityJournalDefault" : true, "members" : [ { "_id" : 0, "host" : "mongo-2:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, // 该节点是否隐藏。隐藏节点的 priority 为 0 ,有权投票,但不能被选举为主节点 "priority" : 1, // 该节点在竞选主节点时的优先级,取值范围为 0.0~1000.0 。取值越大则越容易当选,取值为 0 则不会当选 "tags" : {}, "slaveDelay" : NumberLong(0), "votes" : 1 // 该节点是否有权投票 }, ...
cfg = rs.conf() cfg.members[2].priority = 0 rs.reconfig(cfg)
# 分片集群
分片集群(Shard Cluster):在 MongoDB 中划分多个分片(shard),将数据分散存储到各个分片。
- 优点:方便横向扩容,能大幅提高数据容量、并发读写量。
分片集群需要部署 3 种服务器,每种服务器都可以部署多实例以提高可用性:
- shard server
- :运行 mongod 进程,负责存储分片的数据。
- 每个分片可以部署一个 mongo 单实例,或副本集群。
- config server
- :运行 mongod 进程,并设置
configsvr=true
,负责存储集群的元数据、配置信息。
- :运行 mongod 进程,并设置
- mongos
- :运行 mongos 进程,从 config server 获取集群信息,然后接收客户端的请求、路由转发到各个 shard mongo 。
- shard server
shard mongo 以 chunk 为单位存储数据。
- 默认限制了 chunkSize 为 64MB 。如果写入新文档之后,一个 chunk 超过最大体积,则自动拆分成两个 chunk 。
- config server 会运行一个 balancer 进程,监控各个 shard 的 chunk 数量,自动迁移 chunk 以使得它们的 chunk 数量差不多,实现负载均衡。
- 迁移需要遵循分片规则。
- 如果 chunk size 设置得较小,则容易经常迁移,导致大量读写磁盘、网络传输,增加服务器负载。
几种分片方式:
- 哈希分片(Hash Sharding)
- :选取 Mongo 文档的一个或复合字段作为 shard key ,计算
hash(shard_key) / shard_count
,从而决定将文档分配到第几个分片。 - 优点:分片很均匀,各个分片的文档数量差不多。
- 缺点:范围查询时的效率低。比如要查询一串 key 取值连续的文档时,需要分别读取多个分片,类似顺序读写磁盘与随机读写磁盘的区别。
- :选取 Mongo 文档的一个或复合字段作为 shard key ,计算
- 范围分片(Range Sharding)
- :将 shard key 取值分为几个范围,然后分配到各个分片。比如 key 取值为 0~10000 的文档都分配到第 1 个分片。
- 优点:key 取值连续的一组文档会被保存到同一个分片,因此范围查询时的效率高。
- 缺点:分片不均匀。
- 区域分片(Zone Sharding)
- :将 shard key 取值分为几个区域,每个区域分配一个或多个分片。
- 优点:与范围分片相比,分片更均匀。
- 哈希分片(Hash Sharding)
# 部署
以副本集群模式,部署 config server ,建议部署 3 个节点。
- docker-compose.yml 的配置示例:
version: "3" services: mongo: container_name: mongo-configsvr image: mongo:4.4 command: mongod -f /etc/mongod.conf restart: unless-stopped ports: - 27018:27018 volumes: - /etc/localtime:/etc/localtime:ro - ./mongod.conf:/etc/mongod.conf - ./mongo.key:/etc/mongo/mongo.key - ./data:/data/db
- mongod.conf 的配置示例:
net: port: 27018 bindIp: 0.0.0.0 storage: dbPath: /data/db directoryPerDB: true engine: wiredTiger security: authorization: enabled keyFile: /etc/mongo/mongo.key replication: # 启用副本集群 replSetName: rs-configsvr # 设置副本集群名称。当前分片集群中,所有 config server 必须配置同一个 replSetName sharding: # 启用分片集群 clusterRole: configsvr
- 启动 config server 之后,执行
mongo 127.0.0.1:27018
从本机地址登录,进行初始化:然后用 db.createUser() 创建账号。服务器之间采用 mongo.key 文件进行认证,创建的账号专供用户使用。rs.initiate( { _id: "rs-configsvr", configsvr: true, // 需要声明该副本集群为 config server 类型 members: [ { _id: 0, host: "10.0.0.1:27018"}, { _id: 1, host: "10.0.0.2:27018"}, { _id: 2, host: "10.0.0.3:27018"} ] } ) rs.status()
- docker-compose.yml 的配置示例:
以副本集群模式,部署 shard server 。可以有多个分片,每个分片部署一个 mongod 副本集群。
- docker-compose.yml 的配置与 config server 差不多。
- mongod.conf 的配置示例:
... replication: replSetName: rs-shardsvr-0 sharding: clusterRole: shardsvr
- 启动 config server 之后,执行
mongo 127.0.0.1:27017
从本机地址登录,进行初始化:然后用 db.createUser() 创建账号。rs.initiate( { _id: "rs-shardsvr-0", // 同一个分片中的 shard server 必须配置同一个 replSetName members: [ { _id: 0, host: "10.0.0.1:27017"}, { _id: 1, host: "10.0.0.2:27017"}, { _id: 2, host: "10.0.0.3:27017"} ] } ) rs.status()
- 上述部署了一个分片 rs-shardsvr-0 ,包含 3 个 mongod 节点。还可部署更多分片,比如 rs-shardsvr-1、rs-shardsvr-2 。
以单实例模式,部署 mongos 。
- docker-compose.yml 的配置示例:
version: "3" services: mongo: container_name: mongos image: mongo:4.4 command: mongos -f /etc/mongos.conf # mongos 的启动命令 restart: unless-stopped ports: - 27019:27019 volumes: - /etc/localtime:/etc/localtime:ro - ./mongos.conf:/etc/mongos.conf - ./mongo.key:/etc/mongo/mongo.key # - ./data:/data/db # mongos 运行时会从 config server 读写数据,不会直接写磁盘
- mongos.conf 的配置示例:
net: port: 27019 bindIp: 0.0.0.0 security: # authorization: enabled # mongos 没有 authorization 配置参数 keyFile: /etc/mongo/mongo.key sharding: configDB: rs-configsvr/10.0.0.1:27018,10.0.0.2:27018,10.0.0.3:27018 # config server 的地址
- 启动 mongos 之后,执行
mongo 127.0.0.1:27019
从本机地址登录,进行初始化:然后用 db.createUser() 创建账号。sh.addShard('rs-shardsvr-0/10.0.0.1:27017,10.0.0.2:27017,10.0.0.3:27017') // 添加一组 shard server ,mongos 会将这些地址保存到 config server sh.addShard('rs-shardsvr-1/10.0.0.4:27017,10.0.0.5:27017,10.0.0.6:27017') sh.status()
- 上述部署了一个 mongos 。建议部署 3 个实例,用 nginx 反向代理之后给客户端访问,从而实现负载均衡。各个 mongos 独立工作,只依赖 config server 。
- docker-compose.yml 的配置示例:
部署了分片集群之后,客户端连接到 mongos 的地址,即可使用集群。
- 需要给数据库、集合启用分片模式:
sh.enableSharding("db1") sh.shardCollection("db1.col1", {"_id" : 1}) // 对 db1 数据库中的 col1 集合开启分片模式,以 _id 字段作为分片键
{"_id" : 1}
表示基于范围的分片,而{"_id" : "hashed"}
表示基于哈希的分片。
- 需要给数据库、集合启用分片模式: