MongoDB副本集

备注:
MongoDB 4.2 版本

一.复制简介

我们使用的是单台服务器 ,一个 mongod 服务器进程 。如果只 是用作学习和开发,这是可以的,但是如果用到生产环境中,风险会很高:如果服务器崩溃了或者不可访问了怎么办 ?数据库至少会有 一段时间不可用 。如果是硬件出了问题,可能需要将数据转移到另一个机器上。在最坏的情况下,磁盘或者网络 问题可能会导致数据损坏或者数据不可访问 。
使用复制可以将数据副本保存到多台服务器上,建议在所有的生产环境中都要使用 。使用 MongoDB 的复制功能 ,即使一台或多台服务器出错 ,也可以保证应用程序 正 常运行和数据安全 。
在MongoDB中,创建一个副本集之后就可以使用复制功能了。副本集是一组服务器 ,其中有一个主服务器 (primary) ,用于处理客户端请求, 还有多个备份服务器 ( secondary ),用于保存主服务器 的数据副本。如果主服务器崩溃了 ,备份服务器会自动将其中一个成员升级为新的主服务器 。
使用复制功能时 ,如果有一台服务器宕机了 ,仍然可以从副本集的其他服务器上访问数据。如果服务器上的数据损坏或者不可访问,可以从副本集的某个成员中创建 一份新的数据副本 。

副本集是一种创建多个MongoDB实例的方式,这些实例将拥有相同的数据(冗余)和其它相关设置。主从复制、主主复制、复制对等方法都被副本集的概念所取代。在MongoDB中,副本集由一个主节点以及多个辅助或仲裁节点组成,一个副本集最少应该有3个成员。在MongoDB 3.0中,副本集最多可以有50个被动成员和7个主动成员。通常建议副本集有奇数个成员,这条规则主要是为了避免“脑裂”(split brain)问题,也就是说当网络出现问题时,有两台服务器成为主服务器的情况。

1.1 主动成员与被动成员

副本集提供了主动成员与被动成员。当前的主服务器不可用时,被动服务器不会参与新的主服务器的选举,但它们可投票否决某个成员的主服务器资格。

1.2 master

在副本集术语中,主服务器是在特定时间内副本集的数据来源。它是副本集中唯一可以写入的节点。所有其它节点都将从主服务器复制出它们的数据。主服务器由所有主动成员中的大多数投票产生,这被称为法定人数(quorum)。

主服务器的概念是(并且应该是)短暂的。理想情况下不应该固定地认为哪个节点是主服务器。

1.3 secondary

辅助服务器成员是具有数据的非主服务器成员,理论上它可以成为主服务器。它是一个只读节点,同时它将以尽可能接近于实时的方式从主服务器复制数据。默认情况下,如果连接到辅助服务器但不使用任何读偏好,就不能执行读操作。这是因为读取非主服务器时,如果复制过程中存在延迟,读取的可能是旧数据。可以使用rs.slaveOk()将当前连接设置为可从辅助服务器读取数据。或者如果使用的是某种语言的MongoDB驱动,那么也可以设置读偏好。

MongoDB的读偏好是它选择从哪个副本集成员读取数据的方式。通过为驱动指定一个读偏好,它将知道应该在副本集的哪个成员上执行查询。如果设置了读偏好,就意味着可能从辅助服务器读取数据。必须注意,得到的数据可能不是最新的。

1.4 arbiter

仲裁服务器是不含数据的节点,如果副本集中的主动成员是偶数,它就用于提供额外的主动成员,决定哪个节点成为主服务器。仲裁服务器用于帮助避免“脑裂”。

1.5 oplog

oplog(操作日志)是一个固定大小的集合,保存主服务器实例对数据库做出修改的记录,目的是在辅助服务器重做这些操作,保证数据库处于一致状态。副本集的每个成员维护自己的oplog,并且辅助服务器将查询主服务器(或者用过复制链进行其它数据更新的辅助服务器)的oplog,从而获得新条目,并应用到自己的数据库副本中。

oplog将为每个条目创建一个时间戳。通过这种方式,辅助服务器可以记录从上一次读取开始过去了多久,以及有多少oplog需要读取。可将oplog看成主服务器实例最近活动的窗口:如果窗口太小,那么记录中的某些操作可能在被应用到辅助服务器之前丢失。如果当前实例的oplog尚未创建,那么使用--oplogSize启动选项可以设置oplog的大小(以MB为单位)。在64位Linux系统中,oplogSize默认设置为可以磁盘空间的5%,最小为1GB,最大为50GB。

二.副本集维护

环境如下:

IP 主机名 类别
10.31.1.126 10-31-1-126 主动成员1
10.31.1.125 10-31-1-125 主动成员2
10.31.1.124 10-31-1-124 被动成员1
10.31.1.123 10-31-1-123 仲裁
vi /etc/hosts
10.31.1.123             10-31-1-123
10.31.1.124             10-31-1-124
10.31.1.125             10-31-1-125
10.31.1.126             10-31-1-126

2.1 创建副本集

2.1.1 启动副本集成员

以下命令在126上执行:

# 建立autokey文件
mkdir -p /usr/local/mongodb/
cd /usr/local/mongodb/
openssl rand -base64 756 > autokey
# 修改读写模式
chmod 400 autokey
# 复制到副本集成员节点
scp autokey 10.31.1.125:/usr/local/mongodb/
scp autokey 10.31.1.124:/usr/local/mongodb/
scp autokey 10.31.1.123:/usr/local/mongodb/

修改配置文件

mkdir -p /usr/local/mongodb/data/db
mkdir -p /usr/local/mongodb/data/logs
-- 清空配置文件
>/etc/mongod.conf
vi /etc/mongod.conf

如下为配置文件内容:

processManagement:
   fork: true

storage:
  dbPath: "/usr/local/mongodb/data/db"

systemLog:
  destination: file
  path: "/usr/local/mongodb/data/logs/mongodb.log"
  logAppend: true

net:
  port: 27017
  bindIp: 0.0.0.0

security:
  authorization: enabled
  keyFile: /usr/local/mongodb/autokey

replication:
   replSetName: rs

重启mongod服务

pkill mongod
mongod -f /etc/mongod.conf 

2.1.2 初始化副本集

在126实例中执行:

-- root用户才有权限进行下列操作,其它用户会报错
use admin
db.auth("root","123456")

-- 添加集群用户
db.getSiblingDB("admin").createUser(
{
"user" : "clusteradmin",
"pwd" : "123456",
roles: [ { "role" : "clusterAdmin", "db" : "admin" },{ role: "userAdminAnyDatabase", db: "admin" } ]
}
)

> rs.initiate();
{
        "info2" : "no configuration specified. Using a default configuration for the set",
        "me" : "10-31-1-126:27017",
        "ok" : 1
}
rs:SECONDARY> 

2.2 向副本集添加服务器

在126实例中执行:

rs.add("10.31.1.125:27017");
rs.add("10.31.1.124:27017");

测试记录:

rs:PRIMARY> rs.add("10.31.1.125:27017");
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1604991804, 1),
                "signature" : {
                        "hash" : BinData(0,"i5uD9w9i5OH20FxMY9+ZXv2fe8Q="),
                        "keyId" : NumberLong("6893333011551485956")
                }
        },
        "operationTime" : Timestamp(1604991804, 1)
}
rs:PRIMARY> 
rs:PRIMARY> rs.add("10.31.1.124:27017");
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1604992027, 1),
                "signature" : {
                        "hash" : BinData(0,"eF7nAViPRSGL8QMOpX0PZJS1gek="),
                        "keyId" : NumberLong("6893333011551485956")
                }
        },
        "operationTime" : Timestamp(1604992027, 1)
}
rs:PRIMARY> 

2.3 设置辅助服务器

在126实例中执行以下命令,将124设置为隐藏,并且优先级为0:

rs:PRIMARY> conf = rs.conf();
{
        "_id" : "rs",
        "version" : 3,
        "protocolVersion" : NumberLong(1),
        "writeConcernMajorityJournalDefault" : true,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "10-31-1-126:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 1,
                        "host" : "10.31.1.125:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 2,
                        "host" : "10.31.1.124:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                }
        ],
        "settings" : {
                "chainingAllowed" : true,
                "heartbeatIntervalMillis" : 2000,
                "heartbeatTimeoutSecs" : 10,
                "electionTimeoutMillis" : 10000,
                "catchUpTimeoutMillis" : -1,
                "catchUpTakeoverDelayMillis" : 30000,
                "getLastErrorModes" : {

                },
                "getLastErrorDefaults" : {
                        "w" : 1,
                        "wtimeout" : 0
                },
                "replicaSetId" : ObjectId("5faa09da100d84a061f33286")
        }
}
rs:PRIMARY> conf.members[2].hidden = true
true
rs:PRIMARY> conf.members[2].priority = 0
0
rs:PRIMARY> rs.reconfig(conf);
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1604992141, 1),
                "signature" : {
                        "hash" : BinData(0,"C7QDx9sWsMwGSeZYMZUf+8hWFL0="),
                        "keyId" : NumberLong("6893333011551485956")
                }
        },
        "operationTime" : Timestamp(1604992141, 1)
}
rs:PRIMARY> 

这样124不会被选举为主服务器。

2.4 向副本集添加仲裁服务器

在126实例中执行:

rs:PRIMARY> rs.addArb("10.31.1.123:27017");
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1604992252, 1),
                "signature" : {
                        "hash" : BinData(0,"BLF8bW3Ug2/g6Cjz8W+j9Yf95AI="),
                        "keyId" : NumberLong("6893333011551485956")
                }
        },
        "operationTime" : Timestamp(1604992252, 1)
}
rs:PRIMARY> 

2.5 设置被动服务器

现在副本集中有4个节点,需要使主动成员数为奇数,在126实例中执行:

rs:PRIMARY> conf = rs.conf()
{
        "_id" : "rs",
        "version" : 5,
        "protocolVersion" : NumberLong(1),
        "writeConcernMajorityJournalDefault" : true,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "10-31-1-126:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 1,
                        "host" : "10.31.1.125:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 2,
                        "host" : "10.31.1.124:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : true,
                        "priority" : 0,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                },
                {
                        "_id" : 3,
                        "host" : "10.31.1.123:27017",
                        "arbiterOnly" : true,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 0,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                }
        ],
        "settings" : {
                "chainingAllowed" : true,
                "heartbeatIntervalMillis" : 2000,
                "heartbeatTimeoutSecs" : 10,
                "electionTimeoutMillis" : 10000,
                "catchUpTimeoutMillis" : -1,
                "catchUpTakeoverDelayMillis" : 30000,
                "getLastErrorModes" : {

                },
                "getLastErrorDefaults" : {
                        "w" : 1,
                        "wtimeout" : 0
                },
                "replicaSetId" : ObjectId("5faa09da100d84a061f33286")
        }
}
rs:PRIMARY> conf.members[2].votes = 0
0
rs:PRIMARY> rs.reconfig(conf)
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1604992330, 1),
                "signature" : {
                        "hash" : BinData(0,"k8bFCCLaGDXGQmBIgN7r2VFobjA="),
                        "keyId" : NumberLong("6893333011551485956")
                }
        },
        "operationTime" : Timestamp(1604992330, 1)
}
rs:PRIMARY> 

124的votes值设置为0,现在hdp2完全变成被动服务器,它被客户端看成副本集的一部分,并且不会参与选举,也永远不会变成主服务器。

2.6 管理副本集

命令 描述
rs.help() 返回命令列表。
rs.status() 返回副本集当前的状态信息。该命令列出了每个成员服务器及其状态信息,包括最后联系时间。该调用可被用于提供整个集群的简单健康检查。
rs.initiate() 使用默认参数初始化副本集。
rs.initiate(replSetcfg) 使用配置描述初始化副本集。
rs.add("host:port") 使用含有主机名和特定端口(可选)的简单字符串向副本集中添加成员服务器。
rs.add(membercfg) 使用配置描述向副本集中添加成员服务器。如果希望指定特定的属性(如设置新成员服务器的优先级),那么必须使用这种方法。
rs.addArb("host:port") 添加新的成员服务器作为仲裁者。该成员不需要使用—replSet选项:任何运行在可达机器上的mongod实例都可以执行该任务。注意该服务器必须对副本集中的所有成员可达。
rs.stepDown() 在副本集的主服务器成员中使用该命令时,将使主服务器放弃它的角色,并且在集群中重新选举新的主服务器。注意只有主动辅助服务器可用作主服务器的候选,并且在60秒(缺省)之内如果没有出现其它可用的成员,那么原有的主服务器将重新成为主服务器。
rs.syncFrom("host:port") 使辅助服务器从指定的成员同步数据,可用于组成同步链。
rs.freeze(secs) 冻结指定的成员,并使它在指定秒数内无法成为主服务器。
rs.remove("host:port") 从副本集中删除指定成员。
rs.secondaryOk()() 通过该选项,可以允许从辅助服务器读取数据。
rs.conf() 重新显示当前副本集的配置结构。改配置结构可以被修改,然后用作rs.reconfig()的参数,从而修改结构的配置。
db.isMaster() 该函数不只可作用于副本集:它是一个通用的复制支持函数。通过它,应用或驱动可以判断出被连接的特定实例在复制拓扑结构中是否是主服务器。

rs.status字段的值:

描述
_id 副本集中该成员的ID
Name 该成员的主机名
Health replSet的健康值
State 状态数值
StateStr 副本集状态的字符串表示
Uptime 该成员的运行时间
optime 应用在该成员上最后一个操作的时间,格式为一个时间戳和一个整数值
optimeDate 最后一个被应用操作的日期
lastHeartbeat 最后一次发送心跳的日期
lastHeartbeatRecv 最后一次收到心跳的日期
configVersion 这个成员使用的副本集配置的版本
syncingTo 使用哪个副本集成员同步数据

FAQ

1.重新初始化

cfg = rs.conf()
cfg.members[0].host = "你的IP 或者域名"
rs.reconfig(cfg)

测试记录:

rs:PRIMARY> cfg = rs.conf()
{
        "_id" : "rs",
        "version" : 9,
        "protocolVersion" : NumberLong(1),
        "writeConcernMajorityJournalDefault" : true,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "10-31-1-126:27017",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {

                        },
                        "slaveDelay" : NumberLong(0),
                        "votes" : 1
                }
        ],
        "settings" : {
                "chainingAllowed" : true,
                "heartbeatIntervalMillis" : 2000,
                "heartbeatTimeoutSecs" : 10,
                "electionTimeoutMillis" : 10000,
                "catchUpTimeoutMillis" : -1,
                "catchUpTakeoverDelayMillis" : 30000,
                "getLastErrorModes" : {

                },
                "getLastErrorDefaults" : {
                        "w" : 1,
                        "wtimeout" : 0
                },
                "replicaSetId" : ObjectId("5faa09da100d84a061f33286")
        }
}
rs:PRIMARY> cfg.members[0].host = "10.31.1.126"
10.31.1.126
rs:PRIMARY> rs.reconfig(cfg)
{
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1605085418, 1),
                "signature" : {
                        "hash" : BinData(0,"hSupkyIZ07GWPVflfMd4M4iHV6c="),
                        "keyId" : NumberLong("6893333011551485956")
                }
        },
        "operationTime" : Timestamp(1605085418, 1)
}

2.删除副本集

主节点是删除不了的,至少要有一个主节点要存在。
另外,其它节点虽然从副本集退出了,但是旧的配置依旧存在,如果要配置其它的,会影响到

-- 125 被清理出副本集之后,重新配置分片无法成功,清理这个表之后成功
-- 如果有授权问题,先忽略授权,删除了之后,再调整配置文件
> use local
switched to db local
> 
> 
> db.dropDatabase();
{ "dropped" : "local", "ok" : 1 }
> 

参考

1.MongoDB权威指南(第二版)
2.https://blog.csdn.net/wzy0623/article/details/83113823
3.https://www.cnblogs.com/s6-b/p/11288583.html
4.https://blog.csdn.net/yuanwei1144/article/details/103840914

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 一.什么是MongoDB副本集? 副本集是一组mongod维护相同数据集的实例,它提供了数据的冗余备份,并在多个服...
    会九卦的兔子阅读 6,400评论 0 1
  • 1. 副本集概述 某些情况下,副本可以提供更高的读取容量,就像客户端可以发送读操作到不同的服务器。在不同数据中心维...
    LLLeon阅读 10,456评论 2 7
  • MongoDB 中的副本集是一组mongod进程,它们维护相同的数据集。副本集提供冗余和高可用性,并且是所有生产部...
    桥头放牛娃阅读 881评论 0 4
  • MongoDB的复制主要分2种,一种是Master-Salve(主从复制),一种是副本集(replica sets...
    缘来是你ylh阅读 766评论 0 4
  • MongoDB之副本集 一、简介 MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 ...
    牛初九阅读 370评论 0 1