环境搭建

Replica Set:是有故障恢复功能的主从集群,由一个primary节点和一个或多个secondary节点组成。

环境说明

  • MongoDB shell version v3.4.5
  • MongoDB server version: 3.4.5
  • Centos 7 x86_64

注:在副本集环境中,如果所有的secondary节点都宕机,则primary节点降级为secondary节点,不能提供服务。

ReplicaSet架构图

ReplicaSet架构图

准备Mongo服务器

部署三台装有MongoDB的主机

ip port description
192.168.1.167 27017 primary(主节点)
192.168.1.196 27017 secondary(从节点)
192.168.1.197 27017 secondary or arbiter(仲裁节点)

注:确保所有服务器上的数据库都是空的,并停止所有mongod服务。

创建数据文件夹

分别创建数据和日志文件存放目录

1
2
mkdir -p /home/mongo/data/db
mkdir -p /home/mongo/log

配置mongod服务启动参数

vim /etc/mongod.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
systemLog:
  destination: file
  logAppend: true
  path: /home/mongo/log/mongod.log
storage:
  dbPath: /home/mongo/data/db
  journal:
    enabled: true
processManagement:
  fork: true
  pidFilePath: /var/run/mongodb/mongod.pid
net:
  port: 27017
  bindIp: 192.168.1.167
security:
  #authorization: enabled
  authorization: disabled                 
# replica set
replication:
  replSetName: rs  #指定副本集集群名称

Options: - replication: 副本集配置

注:其他两台服务器配置文件只需要修改 bindIp为自己主机ip即可

配置mongod服务控制脚本

vim /usr/lib/systemd/system/mongod.service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[Unit]
Description=High-performance, schema-free document-oriented database
After=network.target
Documentation=https://docs.mongodb.org/manual
[Service]
User=root
Group=root
Environment="OPTIONS=--quiet -f /etc/mongod.conf"
ExecStart=/usr/bin/mongod $OPTIONS run
ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb
ExecStartPre=/usr/bin/chown root:root /var/run/mongodb
ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb
PermissionsStartOnly=true
PIDFile=/var/run/mongodb/mongod.pid
# file size
LimitFSIZE=infinity
# cpu time
LimitCPU=infinity
# virtual memory size
LimitAS=infinity
# open files
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
# Recommended limits for for mongod as specified in
# http://docs.mongodb.org/manual/reference/ulimit/#recommended-settings
[Install]
WantedBy=multi-user.target

注:主要修改Mongod服务的用户组和所有者:User、Group、 为root

启动mongod服务

  • 首先关闭Centos系统防火墙或在防火墙配置中运许27017端口通过

    • systemctl status firewalld.service

    • active (running):表示运行

    • systemctl stop firewalld.service

    • inactive (dead):表示关闭

  • 先开启服务,并检查服务状态

    1
    2
    
    systemctl start mongod.service
    systemctl status mongod.service
  • 连接数据库,使用 ip:port 方式连接

    1
    
    mongo 192.168.1.167:27017

注:如果没有关闭防火墙或允许27017端口,则会出现集群中的节点间通信失败,导致整个集群无法正常运行。

配置副本集初始化参数

配置初始化参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
cfg = {
  "_id": "rs",
  "members": [
    {
      "_id": 0,
      "host": "192.168.1.167:27017"
    },
    {
      "_id": 1,
      "host": "192.168.1.196:27018"
    },
    {
      "_id": 2,
      "host": "192.168.1.197:27019"
    }
  ]
}          

注:

  • _id:选项必须和服务启动参数的副本集保持一致
  • host:用主机的ip:port代替原有的示例说明

执行初始化命令

可通过mongo shell输入 rs.help() 获取命令帮助

1
2
3
rs.initiate(cfg)  # 初始化
rs.reconfig(cfg)  # 重新初始化
rs.reconfig(cfg, {force: true})  # 强制执行重新初始化 

注:初始化过程出现了一些问题记录在 FAQ

查看初始化后的集群配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
$ rs.conf()   

{
  "_id" : "rs",
  "version" : 2,
  "protocolVersion" : NumberLong(1),
  "members" : [
    {
    	"_id" : 0,
    	"host" : "192.168.1.167:27017",
    	"arbiterOnly" : false,
    	"buildIndexes" : true,
    	"hidden" : false,
    	"priority" : 1,
    	"tags" : {
    		
    	},
    	"slaveDelay" : NumberLong(0),
    	"votes" : 1
    },
    {
    	"_id" : 1,
    	"host" : "192.168.1.196:27017",
    	"arbiterOnly" : false,
    	"buildIndexes" : true,
    	"hidden" : false,
    	"priority" : 1,
    	"tags" : {
    		
    	},
    	"slaveDelay" : NumberLong(0),
    	"votes" : 1
    },
    {
    	"_id" : 2,
    	"host" : "192.168.1.197: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" : 2000,
    "getLastErrorModes" : {
    	
    },
    "getLastErrorDefaults" : {
    	"w" : 1,
    	"wtimeout" : 0
    },
    "replicaSetId" : ObjectId("59424b15ac6e1407caddd807")
  }
}

查看初始化后的集群状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
$ rs.status()

{
  "set" : "rs",
  "date" : ISODate("2017-06-16T01:29:14.626Z"),
  "myState" : 1,
  "term" : NumberLong(27),
  "heartbeatIntervalMillis" : NumberLong(2000),
  "optimes" : {
    "lastCommittedOpTime" : {
    	"ts" : Timestamp(1497576548, 1),
    	"t" : NumberLong(27)
    },
    "appliedOpTime" : {
    	"ts" : Timestamp(1497576548, 1),
    	"t" : NumberLong(27)
    },
    "durableOpTime" : {
    	"ts" : Timestamp(1497576548, 1),
    	"t" : NumberLong(27)
    }
  },
  "members" : [
    {
    	"_id" : 0,
    	"name" : "192.168.1.167:27017",
    	"health" : 1,
    	"state" : 1,
    	"stateStr" : "PRIMARY",
    	"uptime" : 2061,
    	"optime" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDate" : ISODate("2017-06-16T01:29:08Z"),
    	"electionTime" : Timestamp(1497574547, 1),
    	"electionDate" : ISODate("2017-06-16T00:55:47Z"),
    	"configVersion" : 2,
    	"self" : true
    },
    {
    	"_id" : 1,
    	"name" : "192.168.1.196:27017",
    	"health" : 1,
    	"state" : 2,
    	"stateStr" : "SECONDARY",
    	"uptime" : 2007,
    	"optime" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDurable" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDate" : ISODate("2017-06-16T01:29:08Z"),
    	"optimeDurableDate" : ISODate("2017-06-16T01:29:08Z"),
    	"lastHeartbeat" : ISODate("2017-06-16T01:29:13.413Z"),
    	"lastHeartbeatRecv" : ISODate("2017-06-16T01:29:13.313Z"),
    	"pingMs" : NumberLong(0),
    	"syncingTo" : "192.168.1.167:27017",
    	"configVersion" : 2
    },
    {
    	"_id" : 2,
    	"name" : "192.168.1.197:27017",
    	"health" : 1,
    	"state" : 2,
    	"stateStr" : "SECONDARY",
    	"uptime" : 904,
    	"optime" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDurable" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDate" : ISODate("2017-06-16T01:29:08Z"),
    	"optimeDurableDate" : ISODate("2017-06-16T01:29:08Z"),
    	"lastHeartbeat" : ISODate("2017-06-16T01:29:13.387Z"),
    	"lastHeartbeatRecv" : ISODate("2017-06-16T01:29:14.323Z"),
    	"pingMs" : NumberLong(0),
    	"syncingTo" : "192.168.1.196:27017",
    	"configVersion" : 2
    }
  ],
  "ok" : 1
} 

注:可以看到有,1个primary节点,两个secondary节点;以及syncingTo指明的同步到的数据库,lastHeartbeat集群中节点间的心跳包

至此,整个副本集搭建成功。

集群功能测试

主要流程

1
2
3
4
5
6
7
1. 创建测试数据库 rs   
2. 在primary节点插入数据
3. 关闭primary节点mongod服务
4. 切换到其他secondary节点, 可以看到2个secondary节点中的一个会升级为primary节点
5. 然后在剩余两个节点上查询刚才插入的数据
6. 如果在从节点查询首先要执行rs.slaveOk()
7. 可以看到在原来primary节点插入的数据已经同步到了其他节点

注:secondary节点默认是不可读的;如果没有配置slaveOk,则回报如下错误:

1
 error: { "$err" : "not master and slaveOk=false", "code" : 13435 }

复制功能

登陆主节点服务

  • mongo 192.168.1.167:27017
  • 查看并新建测试数据库

  • 查看所有数据库

    • show dbs
  • 新建测试数据库

    • use rstest
  • 插入测试数据

    • db.rstest.insert({“name”:“test data”,“value”:1111})
  • 查看是否插入成功

    • db.rstest.find()

开启另一个shell连接到secondary节点

  • mongo 192.168.1.196:27017
  • use rstest

在从节点不能读写数据,需要设置从节点可读

  • rs.slaveOk()
  • db.rstest.find()

执行完成可看到主节点数据已经同步到从节点了

自动故障转移

关闭primary节点服务

断开与主节点连接,回到shell执行一下命令:

  • systemctl stop mongod.service

登陆到任意一个从节点

  • mongo 192.168.1.196:27017

查看节点最新状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
$ rs.status
{
  "set" : "rs",
  "date" : ISODate("2017-06-16T01:29:14.626Z"),
  "myState" : 1,
  "term" : NumberLong(27),
  "heartbeatIntervalMillis" : NumberLong(2000),
  "optimes" : {
    "lastCommittedOpTime" : {
    	"ts" : Timestamp(1497576548, 1),
    	"t" : NumberLong(27)
    },
    "appliedOpTime" : {
    	"ts" : Timestamp(1497576548, 1),
    	"t" : NumberLong(27)
    },
    "durableOpTime" : {
    	"ts" : Timestamp(1497576548, 1),
    	"t" : NumberLong(27)
    }
  },
  "members" : [
    {
    	"_id" : 0,
    	"name" : "192.168.1.167:27017",
    	"health" : 1,
    	"state" : 1,
    	"stateStr" : "(not reachable/healthy)", #这里变为不可达状态
    	"uptime" : 2061,
    	"optime" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDate" : ISODate("2017-06-16T01:29:08Z"),
    	"electionTime" : Timestamp(1497574547, 1),
    	"electionDate" : ISODate("2017-06-16T00:55:47Z"),
    	"configVersion" : 2,
    	"self" : true
    },
    {
    	"_id" : 1,
    	"name" : "192.168.1.196:27017",
    	"health" : 1,
    	"state" : 2,
    	"stateStr" : "PRIMARY",  # 这个升级为主节点
    	"uptime" : 2007,
    	"optime" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDurable" : {
    		"ts" : Timestamp(1497576548, 1),
    		"t" : NumberLong(27)
    	},
    	"optimeDate" : ISODate("2017-06-16T01:29:08Z"),
    	"optimeDurableDate" : ISODate("2017-06-16T01:29:08Z"),
    	"lastHeartbeat" : ISODate("2017-06-16T01:29:13.413Z"),
    	"lastHeartbeatRecv" : ISODate("2017-06-16T01:29:13.313Z"),
    	"pingMs" : NumberLong(0),
    	"syncingTo" : "192.168.1.167:27017",
    	"configVersion" : 2
    },
    {
      ......
    }
  ],
  "ok" : 1
} 

可以看到,原来的主节点(192.168.1.167:27017)变为不可达状态,而两个从节点中的一个会升级为主节点继续提供服务; 自动故障转移测试完成。

集群维护

增加节点

详细过参考 环境搭建1.1~1.6

  • 创建mongod服务实例
  • 连接到primary节点
  • 添加节点

    • rs.add(“192.168.1.1198:27017”)
    • rs.status()

删除节点

直接删除

  • 删除节点前首先要关闭该节点的mongod服务, 进入该节点的主机,执行

    • systemctl mongod.service
  • 连接到primary节点,执行

    • mongo 192.168.1.169:27017
    • rs.remove(“192.168.1.196:27017”)
    • rs.status()

通过重新配置达到删除目的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 进入主节点Primary,查看配置文档,找到需要删除的节点
$ mongo 192.168.1.169:27017
$ rs.conf()
{
	"_id" : "rs",
	"version" : 1,
	"protocolVersion" : NumberLong(1),
	"members" : [
		{
			"_id" : 0,
			"host" : "192.168.1.167:27017",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 1,
			"host" : "192.168.1.196:27017",
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 1,
			"tags" : {
				
			},
			"slaveDelay" : NumberLong(0),
			"votes" : 1
		},
		{
			"_id" : 2,
			"host" : "192.168.1.197: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" : 2000,
		"getLastErrorModes" : {
			
		},
		"getLastErrorDefaults" : {
			"w" : 1,
			"wtimeout" : 0
		},
		"replicaSetId" : ObjectId("5943e5328053a39fb04bac1c")
	}
}

# 获取到配置对象,删除,重新配置副本集
$ cfg = rs.conf()
$ cfg.members.splice(2,1)    # 删除192.168.1.197:27017
$ rs.reconfig(cfg)

集群其他方面应用

应用方面会随后补充更新…

手动从节点升级为主节点

添加仲裁节点

添加备份节点

读写分离

FAQ

  • ReplicaSet集群节点互联不通,Heartbeat超时,导致链接通道关闭,主要表现为一下两个错误提示:

    • Issue 1:

      connected refused

    • Issue 2

      “stateStr” : “(not reachable/healthy)”

    • Why

      Centos防火墙未关闭,导致三个节点之间连接不通;或27017节点没有被允许通过防火墙

    • How 关闭防火墙或运行27017端口

附录

集群中节点状态

Name Description
STARTUP 没有任何活跃的节点,所有节点在这种状态下启动,解析副本集配置
PRIMARY 副本集的主节点
SECONDARY 副本集从节点,可以读数据
RECOVERING 可以投票,成员执行启动自检,或完成回滚或重新同步。
STARTUP2 节点加入,并运行初始同步
UNKNOWN 从其它节点看来,该节点未知
ARBITER 仲裁者,不复制数据,供投票
DOWN 在其它节点看来,该节点不可达
ROLLBACK 该节点正在执行回滚,不能读取数据
REMOVED 该节点被删除

集群中shell方法

Method Name Description
rs.add() 添加节点到副本集
rs.addArb() 添加仲裁节点到副本集
rs.conf() 获取副本集的配置文档
rs.freeze() 指定一段时间内,当前节点不能竞选主节点Primary
rs.help() 获取副本集的基本方法
rs.initiate() 初始化一个新的副本集
rs.printReplicationInfo() 打印副本集的主节点Primary的状态报告
rs.printSlaveReplicationInfo() 打印副本集的从节点Secondary的状态报告
rs.reconfig() 重新配置副本集
rs.remove() 删除一个节点
rs.slaveOk() 设置当前连接可读,使用readPref() 和 MongosetReadPref()去设置读偏好
rs.status() 返回副本集状态信息的文档
rs.stepDown() 强制当前主节点Primary成为从节点Secondary,并触发投票选举
rs.syncFrom() 设置新的同步目标,覆盖默认的同步目标

See Also

Thanks to the authors 🙂