MongoDB 复制(副本集)

    复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。

    复制还允许您从硬件故障和服务中断中恢复数据。

    • 为了让数据安全
    • 高(24* 7)数据可用性
    • 灾难恢复
    • 无停机维护(如备份,索引重建,压实)
    • 读缩放(额外的副本读取)
    • 副本集对应用程序是透明

    MongoDB中复制的工作原理

    MongoDB的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。

    MongoDB各个节点常见的搭配方式为:一主一从、一主多从。

    主节点记录在其上的所有操作oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

    MongoDB复制结构图如下所示:

    以上结构图中,客户端在主节点读取数据,在客户端写入数据到主节点,主节点与从节点进行数据交互保障数据的一致性

    • N 个节点的集群
    • 任何节点可作为主节点
    • 所有写入操作都在主节点上
    • 自动故障转移
    • 自动恢复

    设置一个副本集

    将mongod实例转换成独立的副本集。要转换到副本设置遵循以下步骤:

    关闭停止已经运行的MongoDB服务器

    —replSet 基本语法如下:

    1. 用适当的选项启动副本集的每个成员

    启动副本集名称

    sudo rm -rf /MongoDB/node1 /MongoDB/node2 /MongoDB/node3sudo mkdir -p /MongoDB/node1 /MongoDB/node2 /MongoDB/node3sudo mongod —bind_ip 192.168.17.129 —port 27020 —dbpath "/MongoDB/node1" —replSet rs0sudo mongod —bind_ip 192.168.17.129 —port 27021 —dbpath "/MongoDB/node2" —replSet rs0sudo mongod —bind_ip 192.168.17.129 —port 27022 —dbpath "/MongoDB/node3" —replSet rs0</div>还可以通过配置文件中指定副本集名称。启动mongod使用配置文件,与配置选项指定的文件:

    2. mongo shell连接副本集

    1. mongo -port 27020 --host 192.168.17.129

    3. 初始化initiate副本集

    利用rs.initiate()在副本集的一个成员上:

    1. rs.initiate()

    MongoDB会初始化一个默认的复制集配置。

    4. 验证初始副本集配置

    使用 rs.conf() 显示副本集配置对象:

    1. rs.conf()

    副本集配置对象如下:

    1. rs0:OTHER> rs.conf()
    2. {
    3. "_id" : "rs0",
    4. "version" : 1,
    5. "protocolVersion" : NumberLong(1),
    6. "members" : [
    7. {
    8. "_id" : 0,
    9. "host" : "192.168.17.129:27020",
    10. "arbiterOnly" : false,
    11. "buildIndexes" : true,
    12. "hidden" : false,
    13. "priority" : 1,
    14. "tags" : {
    15. },
    16. "slaveDelay" : NumberLong(0),
    17. "votes" : 1
    18. }
    19. ],
    20. "settings" : {
    21. "chainingAllowed" : true,
    22. "heartbeatIntervalMillis" : 2000,
    23. "heartbeatTimeoutSecs" : 10,
    24. "electionTimeoutMillis" : 10000,
    25. "getLastErrorModes" : {
    26. },
    27. "getLastErrorDefaults" : {
    28. "w" : 1,
    29. "wtimeout" : 0
    30. },
    31. "replicaSetId" : ObjectId("579b3500299da8059cc5fb99")
    32. }
    33. rs0:PRIMARY>

    复制(副本集)当前的状态 rs.status()

    此输出反映了副本集的当前状态,使用来自副本集的其他成员发送的心跳数据包的数据。

    1. rs0:PRIMARY> rs.status()
    2. {
    3. "set" : "rs0",
    4. "date" : ISODate("2016-07-29T11:09:58.433Z"),
    5. "myState" : 1,
    6. "term" : NumberLong(1),
    7. "heartbeatIntervalMillis" : NumberLong(2000),
    8. "members" : [
    9. {
    10. "_id" : 0,
    11. "name" : "192.168.17.129:27020",
    12. "health" : 1,
    13. "state" : 1,
    14. "stateStr" : "PRIMARY",
    15. "uptime" : 1200,
    16. "optime" : {
    17. "ts" : Timestamp(1469789441, 1),
    18. },
    19. "optimeDate" : ISODate("2016-07-29T10:50:41Z"),
    20. "electionTime" : Timestamp(1469789440, 2),
    21. "electionDate" : ISODate("2016-07-29T10:50:40Z"),
    22. "configVersion" : 1,
    23. "self" : true
    24. }
    25. ],
    26. "ok" : 1
    27. }
    28. rs0:PRIMARY>

    5. 将剩下的成员添加到副本集

    必须连接到副本集primary主节点, 才能使用rs.add()添加剩余的成员。

    下面的示例添加了两个成员:

    添加节点 192.168.17.129:27021

    1. rs0:PRIMARY> rs.add('192.168.17.129:27021')
    2. { "ok" : 1 }
    3. rs0:PRIMARY>
    1. rs0:PRIMARY> rs.add('192.168.17.129:27022')
    2. { "ok" : 1 }
    3. rs0:PRIMARY> rs.status()
    4. {
    5. "set" : "rs0",
    6. "date" : ISODate("2016-07-29T11:17:28.721Z"),
    7. "myState" : 1,
    8. "term" : NumberLong(1),
    9. "heartbeatIntervalMillis" : NumberLong(2000),
    10. "members" : [
    11. {
    12. "_id" : 0,
    13. "name" : "192.168.17.129:27020",
    14. "health" : 1,
    15. "state" : 1,
    16. "stateStr" : "PRIMARY",
    17. "uptime" : 1650,
    18. "optime" : {
    19. "ts" : Timestamp(1469791047, 1),
    20. "t" : NumberLong(1)
    21. },
    22. "optimeDate" : ISODate("2016-07-29T11:17:27Z"),
    23. "electionTime" : Timestamp(1469789440, 2),
    24. "electionDate" : ISODate("2016-07-29T10:50:40Z"),
    25. "configVersion" : 3,
    26. "self" : true
    27. },
    28. {
    29. "_id" : 1,
    30. "name" : "192.168.17.129:27021",
    31. "health" : 1,
    32. "state" : 2,
    33. "stateStr" : "SECONDARY",
    34. "optime" : {
    35. "ts" : Timestamp(1469790932, 1),
    36. "t" : NumberLong(1)
    37. },
    38. "optimeDate" : ISODate("2016-07-29T11:15:32Z"),
    39. "lastHeartbeat" : ISODate("2016-07-29T11:17:27.171Z"),
    40. "lastHeartbeatRecv" : ISODate("2016-07-29T11:17:27.159Z"),
    41. "pingMs" : NumberLong(0),
    42. "configVersion" : 3
    43. {
    44. "_id" : 2,
    45. "name" : "192.168.17.129:27022",
    46. "health" : 1,
    47. "state" : 0,
    48. "stateStr" : "STARTUP",
    49. "uptime" : 1,
    50. "optime" : {
    51. "ts" : Timestamp(0, 0),
    52. "t" : NumberLong(-1)
    53. },
    54. "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
    55. "lastHeartbeat" : ISODate("2016-07-29T11:17:27.168Z"),
    56. "lastHeartbeatRecv" : ISODate("2016-07-29T11:17:28.182Z"),
    57. "pingMs" : NumberLong(1),
    58. "configVersion" : -2
    59. }
    60. ],
    61. "ok" : 1
    62. }
    63. rs0:PRIMARY>

    当完成时,您有一个完整功能的副本集。新的副本集将选出一个primary主节点。

    6. 检查副本集的状态

    使用rs.status() 操作:

    1. rs.status()

    7. 删除副本

    1. rs.remove("192.168.17.129:27021")

    副本集成员

    副本集的每个成员都有一个反映它在集合中的配置的状态。

    批量入库

    1. db.eval(function, arguments)
    ParameterTypeDescription
    functionfunction要执行的一个JavaScript函数
    argumentslist参数列表传递给JavaScript函数。省略函数如果不带参数。
    • 查看一下状态
    1. rs0:PRIMARY> rs.status()
    • 查看从节点数据情况

    MongoDB 复制(副本集) - 图1

    1. mongo --port 27021 --host 192.168.17.129
    2. rs0:SECONDARY> show dbs
    3. 2016-09-14T23:02:41.228+0800 E QUERY [thread1] Error: listDatabases failed:{ "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435 } :
    4. _getErrorWithCode@src/mongo/shell/utils.js:25:13
    5. Mongo.prototype.getDBs@src/mongo/shell/mongo.js:62:1
    6. shellHelper.show@src/mongo/shell/utils.js:760:19
    7. shellHelper@src/mongo/shell/utils.js:650:15
    8. @(shellhelp2):1:1
    9. rs0:SECONDARY> rs.slaveOk()
    10. rs0:SECONDARY> show dbs
    11. example 0.000GB
    12. local 0.000GB
    13. rs0:SECONDARY> db.mycol.count()
    14. 10000

    注意: 主从启动之后,连接slave可以成功连上,但是在slave中执行 show dbs 的时候就报错了:

    1. QUERY Error: listDatabases failed:{ "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" }

    解决方法:

    在报错的slave机器上执行 rs.slaveOk() 方法即可。

    解释一下具体slaveOk方法是什么意思?

    1. Provides a shorthand for the following operation:
    2. db.getMongo().setSlaveOk()
    3. This allows the current connection to allow read operations to run on secondary members. See the readPref() method for more fine-grained control over read preference in the mongo shell.

    测试

    测试一下集群是否可用:

    关掉主节点,模拟主节点宕机的情景

    1. python@ubuntu:~$ ps -ef | grep mongod
    2. python 6731 2119 0 913 ? 00:01:02 /opt/sublime_text/sublime_text /var/log/mongodb/mongod.log
    3. root 37130 6046 0 22:31 pts/22 00:00:00 sudo mongod --bind_ip 192.168.17.129 --port 27020 --dbpath /MongoDB/node1 --replSet rs0
    4. root 37131 37130 0 22:31 pts/22 00:00:21 mongod --bind_ip 192.168.17.129 --port 27020 --dbpath /MongoDB/node1 --replSet rs0
    5. root 37180 37153 0 22:31 pts/12 00:00:00 sudo mongod --bind_ip 192.168.17.129 --port 27021 --dbpath /MongoDB/node2 --replSet rs0
    6. root 37181 37180 0 22:31 pts/12 00:00:15 mongod --bind_ip 192.168.17.129 --port 27021 --dbpath /MongoDB/node2 --replSet rs0
    7. root 37229 37202 0 22:31 pts/21 00:00:00 sudo mongod --bind_ip 192.168.17.129 --port 27022 --dbpath /MongoDB/node3 --replSet rs0
    8. root 37230 37229 0 22:31 pts/21 00:00:15 mongod --bind_ip 192.168.17.129 --port 27022 --dbpath /MongoDB/node3 --replSet rs0
    9. python 41115 28977 0 23:08 pts/6 00:00:00 grep --color=auto mongod