Mongodb管理与维护

本篇使用Mongodb版本为3.2.6。

Mongodb是json格式的文档数据库,优点是存储灵活、查询语法比较丰富、拥有良好的failover机制和伸缩能力、性能良好、易用。当然也有很多不足之处,比如不支持事务、join查询支持的不好等。这些与其价值定位有很大关系,有得必有失,没有各方面都很极致的产品。只要能在某些方面做的足够好,就能在其适合使用的场景下实现价值。

控制脚本与配置文件

Mongodb集群由分片(shard)、配置中心(config)、路由(mongos)三种节点组成。其中shard存储业务数据,可以配置多个副本;config存储分片表的元数据和块分布信息,可以配置多个副本;mongos自身不存储数据,而是直接与config通信查询或更新config的数据。各自的启动配置文件(YAML格式)示例如下:

shard

systemLog:
   destination: file
   path: "/data/log/mongodb/shard1/shard1.log"
   logAppend: false
storage:
   dbPath: "/data/mongodb/shard1"
   engine: "wiredTiger"
   journal:
      enabled: false
   wiredTiger:
      engineConfig:
         cacheSizeGB: 50
         directoryForIndexes: true
   directoryPerDB: true
processManagement:
   fork: true
net:
   port: 22001
replication:
   replSetName: "shard1"
   oplogSizeMB: 10240
sharding:
   clusterRole: "shardsvr"

config

systemLog:
   destination: file
   path: "/data/log/mongodb/config/config.log"
   logAppend: false
storage:
   dbPath: "/data/mongodb/config"
   engine: "wiredTiger"
   journal:
      enabled: false
   wiredTiger:
      engineConfig:
         cacheSizeGB: 1
         directoryForIndexes: true
   directoryPerDB: true
processManagement:
   fork: true
net:
   port: 21000
sharding:
   clusterRole: "configsvr"
replication:
   replSetName: "configReplSet"

mongos

systemLog:
   destination: file
   path: "/data/log/mongodb/mongos/mongos.log"
   logAppend: false
processManagement:
   fork: true
net:
   port: 20000
sharding:
   configDB: "configReplSet/mongodb-na-c1:21000,mongodb-na-c2:21000,mongodb-na-c3:21000"

为了简化日常操作,用shell脚本结合上面的配置文件,实现了以下功能:

  1. 启动和停止服务。
  2. 日志文件轮转和清理。
  3. 物理备份。

源码 https://github.com/willwang1208/LinuxLearn/tree/master/mongodb/mongodb_ctl

备份与恢复

线上服务采用了三种备份方式。

# Mongodb的副本(Replica)特性

  1. 目的:

    保障生产环境高可用。整个集群由N个分片(Shard)组成,每个分片维护三个副本(1个Primary,2个Secondary)。当Primary节点宕机时,剩余副本节点投票产生新的Primary继续提供服务。

  2. 配置:
  3. 建立Secondary节点的过程:

    首先进行初始同步。当一个空的副本节点启动时,它将自动选择一个可用的副本作为复制源,复制所有数据库和集合的数据到本地,并建立索引。然后持续从复制源拉取同步开始时刻以后的oplog进行回放,使本地数据与源保持同步。

    oplog保存在各副本的local库的oplog.rs集合中,是副本间进行同步的依据。oplog.rs的最大尺寸默认是空闲磁盘空间的5%,当达到最大值时,旧数据会被新数据挤掉。如果最旧的oplog数据的时间戳晚于初始同步的开始时间,那这个备份将无法同步到最新状态,因为缺少了中间那一段时间的oplog。建议明确设置启动参数oplogSizeMB来保证足够大的oplog。

# 基础备份 + oplog

  1. 目的:
    保障在发生误操作或数据损坏后,能够回退到最近的某个时刻。
  2. 备份:

    每隔X小时制作一次基础备份,包括configdb和所有shard,保留最近的N份。基础备份的方式可以是磁盘快照或文件拷贝。一个shard的任意一个可用副本都可以作为该shard的备份源,通常选择hidden或优先级最低的secondary节点,尽量减少对生产环境造成的影响。由于oplog在三个副本上是相同的,天然具备较高的备份水平,所以一般不需要再做额外的备份工作。

    进行基础备份时,首先在复制源的副本上执行db.fsyncLock(),强制mongod将挂起的写操作写入磁盘,并锁住不再继续写盘。然后拷贝数据文件到备份目录。完成后执行db.fsyncUnlock()解除锁定,使复制源恢复原样。

    /opt/mongodb/bin/mongo admin --port 22001 --eval "db.fsyncLock();"
    /opt/mongodb/bin/mongo admin --port 22001 --eval "db.fsyncUnlock();"
    
  3. 恢复:

    如果生产环境发生了意外,需要回退数据,就会用到这些基础备份和足够大的oplog数据。恢复过程参考官方文档

    >> 首先关闭入流量,并禁用mongodb的balancer。

    >> 然后准备oplog数据。以一个shard为例(configdb与其类似),根据案发时间确定一个案发之前距离最近的基础备份。然后选择一个oplog足够大的副本,从这个备份的开始时间导出oplog数据。这个时间不必十分精确,其实为了保证数据完整性,可以选择更早一点的时间,因为oplog是具备幂等性,多一些oplog数据也没关系。一条oplog数据示例如下,

    mongodb-mn-002

    其中,字段ts存储了oplog的时间(精确到秒,通过ISODate(“2015-06-17T16:00:00Z”).getTime()/1000转换,注意时区是UTC)和最小时间精度内的先后顺序;字段ns存储了oplog操作发生的数据库和集合。这两个字段可以作为过滤条件来缩小导出的oplog范围。例如,

    # 导出从备份开始时间Timestamp(1511863647, 1)到还原点Timestamp(1511924863, 123)的,操作在jelly_360.user_data上的oplog
    mongodump --port 22009 -d local -c oplog.rs -q '{ts:{$gte:Timestamp(1511863647, 1),$lt:Timestamp(1511924863, 123)}, ns:"jelly_360.user_data"}' -o ./oplog
    # 导出从备份开始时间Timestamp(1511863647, 1)开始的,操作在其他非jelly_360.user_data上的oplog
    mongodump --port 22009 -d local -c oplog.rs -q '{ts:{$gte:Timestamp(1511863647, 1)}, ns:{$ne:"jelly_360.user_data"}}' -o ./oplog2
    # 导出从备份开始时间Timestamp(1511863647, 1)开始的,操作在数据库jelly_360(以jelly_360.开头的ns)上的oplog
    mongodump --port 22009 -d local -c oplog.rs -q '{ts:{$gte:Timestamp(1511863647, 1)}, ns:/^jelly_360\./}' -o ./oplog3
    # 导出除了操作在config.lockpings、config.mongos、config.changelog、config.locks上的oplog
    mongodump --port 21000 -d local -c oplog.rs -q '{$nor:[{ns:"config.lockpings"}, {ns: "config.mongos"}, {ns: "config.changelog"}, {ns: "config.locks"}]}' -o ./oplog4
    

    通过多次导出结果的组合,可以使数据回退的最小粒度细化到collection层次。导出的bson文件可以用bsondump查看,

    bsondump oplog/local/oplog.rs.bson
    

    另外,存储元数据的config数据库一般只需要关注collections和chunks。collections存储分片表的信息,新建和删除分片表的时候都会更新它。chunks存储块信息,也与分片相关,块创建、分裂和迁移的时候会更新。而不分片的表不会记录到configdb。所以有时候configdb可能不需要回退。

    >> 所有shard和configdb的oplog准备就绪后,停掉Mongodb集群并备份好旧数据文件。然后把基础备份拷贝到primary节点的数据目录下替换旧数据,启动mongod。注意,不要轻易删除旧数据,也不要直接在基础备份上操作,以免还原过程出现错误不能重来。

    >> 然后回放oplog。文件名必须是oplog.bson,

    # 复制oplog.rs.bson文件到restore目录下并重命名
    cp oplog/local/oplog.rs.bson restore/oplog.bson
    # 用mongorestore回放oplog
    /opt/mongodb-3.2.6/bin/mongorestore --host 127.0.0.1:22009 --oplogReplay ./restore
    /opt/mongodb-3.2.6/bin/mongorestore --host 127.0.0.1:22009 --oplogReplay --oplogLimit=1510887602:15 ./restore
    

    >> 最后检查数据是否正确,并恢复balancer。文件名必须是oplog.bson,

  4. 主机节点的hostname发生改变的集群恢复:
    一般情况下尽量不要变更hostname,如果变更则需要修改各节点的副本集配置rs.conf()和config数据库中的shards表。

    >> 修改configdb节点副本集配置。

    # 启动节点
    /opt/mongodb-3.2.6/bin/mongod --config /opt/mongodb_conf/conf/config.mcf
    # 修改副本成员只有自己,且hostname为新名字,reconfig
    config = ...
    rs.reconfig(config,{ force: true })
    # 增加其他成员
    

    >> 修改config数据库的shards表。

    db.shards.update({_id:"shard1"}, {$set:{host:"shard1/mongodb-t-m1:22001"}})
    ...
    

    >> 修改shard节点副本集配置。这种方法也适用于单副本节点状态是primary,但是db.isMaster()是false的异常情况恢复。

    # 启动节点,参数recoverShardingState=false
    /opt/mongodb-3.2.6/bin/mongod --config /opt/mongodb_conf/conf/shard2.mcf --setParameter=recoverShardingState=false
    # 修改副本成员只有自己,且hostname为新名字,reconfig
    config = ...
    rs.reconfig(config, { force: true })
    # 清除minOpTimeRecovery数据
    use admin
    db.system.version.remove({ _id: "minOpTimeRecovery" }, { writeConcern: { w: "majority" }})
    # 去掉recoverShardingState重启,增加其他成员
    
  5. recovering状态节点恢复。
    多副本情况下,某个副本可能因为落后太多,没有足够的oplog来自动同步到最新数据,最终这个副本变为recovering状态。通常只要删除重建即可。但是在某些情况下,我们的基础备份启动后也是recovering状态,由于只有这一份数据,所以只能在此基础上强行恢复成primary状态。方法是,先改回单节点无副本形式,再重新初始化副本集。

    # 删掉启动配置文件中的分片和副本信息,使其以单例形式重启
    replication:
       replSetName: "shard9"
    sharding:
       clusterRole: "shardsvr"
    # drop掉local数据库
    use local
    db.dropDatabase()
    # 改回启动配置文件,重启,重新初始化副本集
    config = {_id: "shard9", members: [
        {_id: 1, host: "mongodb-t-m9:22009", priority: 30}
    ]}
    rs.initiate(config)
    

# 逻辑导出备份

  1. 目的:

    由于导出时间较长且落后较多,这些数据不具备较高的恢复质量,通常用作各种测试目的。

  2. 备份:

    每日定时执行导出脚本。建议在备份服务器本地启动mongos,直接连接本地入口。

    # 导出
    /opt/mongodb-3.2.6/bin/mongodump --host 172.16.0.201:20000 -d config --gzip &
    /opt/mongodb-3.2.6/bin/mongodump --host 172.16.0.201:20000 -d jelly_360 --gzip &
    /opt/mongodb-3.2.6/bin/mongodump --host 172.16.0.201:20000 -d jelly_cs --gzip &
    # 导入
    /opt/mongodb-3.2.6/bin/mongorestore --host 127.0.0.1:20000 -d jelly_cs --drop --gzip --dir dump/jelly_cs/
    

统计与监控

参考 官方文档

mongodb自带的mongostat和mongotop是很方便的工具,mongostat用来监控单个mongod进程或mongos进程在单位时间内的各种请求量和运行情况,mongotop用于观察mongod进程在单位时间内每个集合的读写响应时间。

除此之外,还有一些命令可以获取有用的信息,

  • db.serverStatus() 获取整个服务状态。
  • db.stats() 获取当前数据库状态。
  • db.some_collection.stats() 获取选择的集合状态。
  • rs.status() 获取当前shard或config节点的副本集状态。

芝麻问题

  • 以下异常的解决方法:重启所有,启动顺序 config -> shard -> mongos
    mongos:

    SHARDING [Balancer] caught exception while doing balance: could not get updated shard list from config server due to Operation timed out
    

    config:

    Command on database config timed out waiting for read concern to be satisfied.
    

    shard:

    SHARDING [replSetDistLockPinger] pinging failed for distributed lock pinger :: caused by :: ReplicaSetNotFound: None of the hosts for replica set configReplSet could be contacted.
    
  • 当前版本运行一段时间后,尤其是近期插入大量数据,会导致CPU利用率居高不下,运行不稳定,慢查询增多。怀疑与自动均衡时move chunk有关系。解决办法是重启服务。
  • mongos的bug,应该都跟mongos缓存config数据有关。
    一个是readpref不是primary的时候,mongos可能查不到某些应该能查到的数据。但执行一次从primary查询、或count()等函数、或explain()后,再从secondary查就可以了。此现象出现在海外跨欧美数据中心的集群上,两个中心之间有160毫秒延迟,不知道是否是个诱因。
    另一个是move primary节点后,有的mongos看不到未分片的collection。重启mongos后就正常了。
  • mongos与shard之间的会积累越来越多的空闲连接,只能重启一方来释放掉。这个问题在高版本mongodb中不存在。
  • 3.2.12以上版本,在update请求量大的时候,secondary节点同步oplog出现卡顿、不连续。表现为用mongostat观察update列波动极大,甚至出现0。直接查询local的oplog.rs也能发现数据不是平滑写入。
Creative Commons License

本文基于署名-非商业性使用-相同方式共享 4.0许可协议发布,欢迎转载、使用、重新发布,但请保留文章署名wanghengbin(包含链接:https://wanghengbin.com),不得用于商业目的,基于本文修改后的作品请以相同的许可发布。

发表评论