💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 主从复制 1.这种架构有如下的好处: - 数据备份 - 数据恢复 - 读写分离 <!--more--> 2.下面我们就一一实践 实际应用中我们肯定是多服务器部署,限于自己懒的装虚拟机,就在一台机器上实践了。 第一步:我们把mongodb文件夹放在D盘和E盘,模拟放在多服务器上。 第二步:启动D盘上的mongodb,把该数据库指定为主数据库,其实命令很简单: ``` >mongodb --dbpath='XXX' --master ``` 端口还是默认的27017. 开启主服务器: ``` D:\Projects\mongodb\bin>mongod --dbpath=D:\Projects\mongodb\db --master 2015-07-02T22:53:13.738+0800 I CONTROL [initandlisten] options: { master: true, storage: { dbPath: "D:\Projects\mongodb\db" } } 2015-07-02T22:53:13.814+0800 I REPL [initandlisten] ****** 2015-07-02T22:53:13.814+0800 I REPL [initandlisten] creating replication oplog of size: 50MB... 2015-07-02T22:53:13.921+0800 I REPL [initandlisten] ****** 2015-07-02T22:53:13.924+0800 I NETWORK [initandlisten] waiting for connections on port 27017 ``` 第三步:同样的方式启动E盘上的mongodb,指定该数据库为从属数据库,命令也很简单,当然我们要换一个端口,比如:8888。 source 表示主数据库的地址。 ``` >mongod --dbpath=xxxx --port=8888 --slave --source=127.0.0.1:27017 ``` 开启从服务器: ``` D:\Projects\mongodb2\bin>mongod --dbpath=D:\Projects\mongodb2\db --slave --port 8888 --source=17.0.0.1:27017 ``` 报错了: ``` 2015-07-02T23:07:44.152+0800 I REPL [replslave] repl: --source 127.0.0.1:2222 != 17.0.0.1:27017 from local.sources collection ``` 这是由于之前误操作,127.0.0.1:2222 != 17.0.0.1:27017,IP地址错了。可以删掉D:\Projects\mongodb2\db的db文件夹新建。尝试修改主服务器的端口。 一般还可能有下面两个错误: (1)D:\Projects\mongodb2\db的db文件夹不存在导致的错误;创建该文件夹即可。 (2)D:\Projects\mongodb2\db下存在mongod.lock文件。删掉即可。 重新来: 开主服务器: ``` D:\Projects\mongodb\bin>mongod --dbpath=D:\Projects\mongodb\db --master --port 2222 ``` 提示: ``` 2015-07-02T23:10:04.004+0800 I CONTROL [initandlisten] options: { master: true, net: { port: 2222 }, storage: { dbPath: "D:\Proje cts\mongodb\db" } } 2015-07-02T23:10:04.063+0800 I NETWORK [initandlisten] waiting for connections on port 2222 ``` 成功。 开启从服务器: ``` D:\Projects\mongodb2\bin>mongod --dbpath=D:\Projects\mongodb2\db --slave --port 3333 --source=127.0.0.1:2222 ``` 提示: ``` 2015-07-02T23:26:44.602+0800 I CONTROL [initandlisten] allocator: tcmalloc 2015-07-02T23:26:44.603+0800 I CONTROL [initandlisten] options: { net: { port: 3333 }, slave: true, source: "127.0.0.1:2222", sto rage: { dbPath: "D:\Projects\mongodb2\db" } } 2015-07-02T23:26:44.621+0800 I NETWORK [initandlisten] waiting for connections on port 3333 2015-07-02T23:26:53.106+0800 I REPL [replslave] repl: sleep 1 sec before next pass 2015-07-02T23:26:54.109+0800 I REPL [replslave] repl: syncing from host:127.0.0.1:2222 2015-07-02T23:27:03.104+0800 I REPL [replslave] repl: applied 1 operations 2015-07-02T23:27:03.106+0800 I REPL [replslave] repl: end sync_pullOpLog syncedTo: Jul 02 23:26:58 55955842:1 ``` 成功。从中我们发现了一条:“applied 1 operations"这样的语句,并且发生的时间相隔10s,也就说明从属数据库每10s就向主数据库同步数据,同步依据也就是寻找主数据库的”OpLog“日志,可以在图中红色区域内发现”sync_pullOpLog“字样。 接下来,我们可以打开客户端,连接从服务器进行测试了。再打开一个命令行窗口: ``` D:\Projects\mongodb2\bin>mongo 127.0.0.1:3333 connecting to: 127.0.0.1:3333/test > db.user.find() { "_id" : ObjectId("55935f703cb05382291e9132"), "name" : "yjc", "age" : 22 } { "_id" : ObjectId("559360ae3cb05382291e9134"), "name" : "hxc", "birthday" : "1989-2-2" } { "_id" : ObjectId("559360bd3cb05382291e9135"), "name" : "jack", "birthday" : "1989-3-2" } { "_id" : ObjectId("559360d13cb05382291e9136"), "name" : "joe", "birthday" : "1989-2-22" } { "_id" : ObjectId("559360df3cb05382291e9137"), "name" : "mary", "birthday" : "1989-3-12" } { "_id" : ObjectId("559360e93cb05382291e9138"), "name" : "jr", "birthday" : "1989-3-2" } > db.person.find() { "_id" : ObjectId("558fd39d02d9b5bcf004aaf5"), "name" : "yjc", "age" : 5, "address" : { "city" : "beijing" } } { "_id" : ObjectId("5590ac85c8e4762462ebbab2"), "name" : "liyi", "age" : 20, "address" : { "province" : "hubei", "city" : "wuhan" }, "favourite" : [ "music", "movie" ] } { "_id" : ObjectId("5590adecc8e4762462ebbab3"), "name" : "lier", "age" : 10, "adress" : { "province" : "hubei", "city" : "yichang" } } { "_id" : ObjectId("5590b92aeb60c2633d9cfa24"), "name" : "yjc2", "age" : 25 } { "_id" : ObjectId("55926102f431047603d95187"), "name" : "yjc", "age" : 22 } ``` 好家伙,数据全都有了!数据已经同步更新成功! 3.如果我还想增加一台从属数据库,但是我不想在启动时就指定,而是后期指定,那么mongodb可否做的到呢?答案肯定是可以的。我们的主或者从属数据库中都有一个叫做local的集合,主要是用于存放内部复制信息。 主服务器不用关,我们可以再新增一台或者关掉从服务器,重新开启从服务器: ``` D:\Projects\mongodb2\bin>mongod --dbpath=D:\Projects\mongodb2\db --slave --port 3333 2015-07-03T09:50:18.777+0800 I CONTROL [initandlisten] allocator: tcmalloc 2015-07-03T09:50:18.777+0800 I CONTROL [initandlisten] options: { net: { port: 3333 }, slave: true, storage: { dbPath: "D:\Projec ts\mongodb2\db" } } 2015-07-03T09:50:18.780+0800 I INDEX [initandlisten] allocating new ns file D:\Projects\mongodb2\db\local.ns, filling with zero es... 2015-07-03T09:50:19.129+0800 I STORAGE [FileAllocator] allocating new datafile D:\Projects\mongodb2\db\local.0, filling with zero es... 2015-07-03T09:50:19.130+0800 I STORAGE [FileAllocator] creating directory D:\Projects\mongodb2\db\_tmp 2015-07-03T09:50:19.542+0800 I STORAGE [FileAllocator] done allocating datafile D:\Projects\mongodb2\db\local.0, size: 64MB, too k 0.406 secs 2015-07-03T09:50:19.695+0800 I NETWORK [initandlisten] waiting for connections on port 3333 2015-07-03T09:50:20.692+0800 I REPL [replslave] no source given, add a master to local.sources to start replication 2015-07-03T09:50:20.692+0800 I REPL [replslave] repl: sleep 20 sec before next pass ``` 看上面的log,提示没有主数据库,没关系,某一天我们良心发现,给他后期补贴一下,哈哈,再开一个cmd窗口,打开客户端,语句也就是在sources中add一个host地址: ``` D:\Projects\mongodb2\bin>mongo 127.0.0.1:3333 connecting to: 127.0.0.1:3333/test ``` 使用local集合 ``` > use local switched to db local ``` 从主服务器127.0.0.1:2222复制数据 ``` > db.sources.insert({"host":"127.0.0.1:2222"}) WriteResult({ "nInserted" : 1 }) > db.sources.find() { "_id" : ObjectId("5595ecd1143aa60c5492e195"), "host" : "127.0.0.1:2222", "source" : "main", "syncedTo" : Timestamp(1435888931, 1) } ``` 使用test集合 ``` > use test; switched to db test > db.user.find() { "_id" : ObjectId("55935f703cb05382291e9132"), "name" : "yjc", "age" : 22 } { "_id" : ObjectId("559360ae3cb05382291e9134"), "name" : "hxc", "birthday" : "1989-2-2" } { "_id" : ObjectId("559360bd3cb05382291e9135"), "name" : "jack", "birthday" : "1989-3-2" } { "_id" : ObjectId("559360d13cb05382291e9136"), "name" : "joe", "birthday" : "1989-2-22" } { "_id" : ObjectId("559360df3cb05382291e9137"), "name" : "mary", "birthday" : "1989-3-12" } { "_id" : ObjectId("559360e93cb05382291e9138"), "name" : "jr", "birthday" : "1989-3-2" } ``` 最后发现数据也同步到127.0.0.1:3333这台后期同步的从属数据库中.... 客户端插入一条数据试试: ``` > db.user.insert({"name":"mary", "age": 23}) WriteResult({ "writeError" : { "code" : undefined, "errmsg" : "not master" } }) ``` 切换连接到主服务器就可以新增了: ``` D:\Projects\mongodb2\bin>mongo 127.0.0.1:2222 > db.user.insert({"name":"mary", "age": 23}) WriteResult({ "nInserted" : 1 }) ``` 再连接到从服务器发现数据也同步了,即不用重新去追加数据。 4.读写分离 这种手段在大一点的架构中都有实现,在mongodb中其实很简单,在默认的情况下,从属数据库不支持数据的读取,但是没关系,在驱动中给我们提供了一个叫做“slaveOkay"(?)来让我们可以显示的读取从属数据库来减轻主数据库的性能压力。 主从复制的选项: ``` --only 在从节点上指定只复制特定的某个数据库(默认是复制所有数据库) --slavedelay 用在从节点上,当应用主节点的操作时,从节点增加延时复制(单位秒).这样就能轻松设置延时从节点,这种节点对用户 无意中删除重要文档或者插入垃圾数据等有防护作用,这些不良操作都会被复制到所有的从节点上,通过延时执行操作, 可以有个恢复的时间差. --fastsync 以主节点的数据快照为基础启动从节点.如果数据目录一开始是主节点的数据快照,从节点用这个选项启动要比 做完整的同步快的多. --autoresync 如果从节点与主节点不同步了,则自动重新同步 --oplogsize 主节点oplog的大小(单位MB) ``` ## 副本集 这个也是很牛X的主从集群,不过跟上面的集群还是有两点区别的。 1 该集群没有特定的主数据库。 2 如果哪个主数据库宕机了,集群中就会推选出一个从属数据库作为主数据库顶上,这就具备了自动故障恢复功能。 好,我们现在就来试一下,首先把所有的cmd窗口关掉重新来,清掉db下的所有文件(不清除后续操作可能会报错)。 第一步: 既然我们要建立集群,就得取个集群名字,这里就取名shopex, --replSet表示让服务器知道shopex下还有其他数据库, 这里就把D盘里面的mongodb程序打开,端口为2222。指定端口为3333是shopex集群下的另一个数据库服务器。 ``` D:\Projects\mongodb\bin>mongod --dbpath=D:\Projects\mongodb\db --port 2222 --replSet shopex/127.0.0.1:3333 2015-07-04T09:05:37.568+0800 I CONTROL [initandlisten] allocator: tcmalloc 2015-07-04T09:05:37.568+0800 I CONTROL [initandlisten] options: { net: { port: 2222 }, replication: { replSet: "shopex/127.0.0.1: 3333" }, storage: { dbPath: "D:\Projects\mongodb\db" } } 2015-07-04T09:05:37.601+0800 I NETWORK [initandlisten] waiting for connections on port 2222 ``` 第二步: 既然上面说3333是另一个数据库服务器,不要急,现在就来开,这里把另外一个目录mongodb2的mongodb程序打开。 ``` mongod --dbpath=D:\Projects\mongodb2\db --port 3333 --replSet shopex/127.0.0.1:2222 2015-07-04T09:08:11.815+0800 I CONTROL [initandlisten] allocator: tcmalloc 2015-07-04T09:08:11.815+0800 I CONTROL [initandlisten] options: { net: { port: 3333 }, replication: { replSet: "shopex/127.0.0.1: 2222" }, storage: { dbPath: "D:\Projects\mongodb2\db" } } ingDocument Did not find replica set configuration document in local.system.replset 2015-07-04T09:08:12.905+0800 I NETWORK [initandlisten] waiting for connections on port 3333 ``` 第三步: ok,看看上面的日志,似乎我们还没有做完,是的,log信息告诉我们要初始化一下“副本集“,既然日志这么说,那我也就这么做,随便连接一下哪个服务器都行,不过一定要进入admin集合。 ``` D:\Projects\mongodb2\bin>mongo 127.0.0.1:2222/admin db.runCommand({"replSetInitiate":{ "_id":"shopex", "members" :[ { "_id" :1 , "host":"127.0.0.1:2222", }, { "_id" :2 , "host":"127.0.0.1:3333", }, ] }}) ``` 第一次设置报告了一个错误: ``` "errmsg" : "'127.0.0.1:3333' has data already, cannot initiate set." ``` 原因是我没有清除`D:\Projects\mongodb2\`下的db。关闭`D:\Projects\mongodb2\`服务器并删除db下的文件后重新打开mongodb2服务器,并再次设置初始化:显示ok。 第四步: 开启成功后,我们要看看谁才能成为主数据库服务器,可以看到端口为2222的已经成为主数据库服务器。 ``` mongodb1---cmd: 2015-07-04T09:16:02.925+0800 I REPL [ReplicationExecutor] replSet election succeeded, assuming primary role 2015-07-04T09:16:02.928+0800 I REPL [ReplicationExecutor] transition to PRIMARY mongodb2---cmd: 2015-07-04T09:16:07.059+0800 I REPL [ReplicationExecutor] transition to RECOVERING 2015-07-04T09:16:07.062+0800 I REPL [ReplicationExecutor] transition to SECONDARY ``` 第五步:我们知道sql server里面有一个叫做仲裁服务器,那么mongodb中也是有的,跟sql server一样,仲裁只参与投票选举,这里我们把mongodb3的mongodb作为仲裁服务器,然后指定shopex集群中的任一个服务器端口,这里就指定2222。 ``` mongod --dbpath=D:\Projects\mongodb3\db --port 4444 --replSet shopex/127.0.0.1:2222 ``` 然后我们在admin集合中使用rs.addArb()追加即可。 ``` > rs.addArb("127.0.0.1:4444") { "ok" : 1 } ``` 追加好了之后,我们使用rs.status()来查看下集群中的服务器状态,图中我们可以清楚的看到谁是主,还是从,还是仲裁。 ``` shopex:PRIMARY> rs.status() { "set" : "shopex", "date" : ISODate("2015-07-04T01:30:29.593Z"), "myState" : 1, "members" : [ { "_id" : 1, "name" : "127.0.0.1:2222", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 1492, "optime" : Timestamp(1435973390, 1), "optimeDate" : ISODate("2015-07-04T01:29:50Z"), "electionTime" : Timestamp(1435972562, 2), "electionDate" : ISODate("2015-07-04T01:16:02Z"), "configVersion" : 2, "self" : true }, { "_id" : 2, "name" : "127.0.0.1:3333", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 870, "optime" : Timestamp(1435973390, 1), "optimeDate" : ISODate("2015-07-04T01:29:50Z"), "lastHeartbeat" : ISODate("2015-07-04T01:30:28.854Z"), "lastHeartbeatRecv" : ISODate("2015-07-04T01:30:29.147Z"), "pingMs" : 0, "syncingTo" : "127.0.0.1:2222", "configVersion" : 2 }, { "_id" : 3, "name" : "127.0.0.1:4444", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 38, "lastHeartbeat" : ISODate("2015-07-04T01:30:28.855Z"), "lastHeartbeatRecv" : ISODate("2015-07-04T01:30:29.175Z"), "pingMs" : 0, "configVersion" : 2 } ], "ok" : 1 } ``` 上面的结果显示很清楚。 不是说该集群有自动故障恢复吗?那么我们就可以来试一下,在2222端口的cmd服务器按Ctrl+C来KO掉该服务器,立马我们发现在3333端口的从属服务器即可顶上,最后大家也可以再次使用rs.status()来看下集群中服务器的状态。 其它: 在从服务器是无法查看数据: ``` > use test; switched to db test > db.user.find() Error: error: { "$err" : "not master and slaveOk=false", "code" : 13435 } ``` 切换到主服务器就可以了 ``` shopex:PRIMARY> db.user.find() { "_id" : ObjectId("55935f703cb05382291e9132"), "name" : "yjc", "age" : 22 } { "_id" : ObjectId("559360ae3cb05382291e9134"), "name" : "hxc", "birthday" : "1989-2-2" } { "_id" : ObjectId("559360bd3cb05382291e9135"), "name" : "jack", "birthday" : "1989-3-2" } { "_id" : ObjectId("559360d13cb05382291e9136"), "name" : "joe", "birthday" : "1989-2-22" } { "_id" : ObjectId("559360df3cb05382291e9137"), "name" : "mary", "birthday" : "1989-3-12" } { "_id" : ObjectId("559360e93cb05382291e9138"), "name" : "jr", "birthday" : "1989-3-2" } { "_id" : ObjectId("5595f1500369349a9f73e947"), "name" : "mary", "age" : 23 } shopex:PRIMARY> db.user.insert({"name":"tt"}) WriteResult({ "nInserted" : 1 }) shopex:PRIMARY> ``` 当然也可以使用下rs.slaveOk() 命令,使其可以查询数据(还是不能更新之类的) 拓展: rs相关命令 ``` shopex:PRIMARY> rs.help() rs.status() { replSetGetStatus : 1 } checks repl set status rs.initiate() { replSetInitiate : null } initiates set with default settings rs.initiate(cfg) { replSetInitiate : cfg } initiates set with configuration cfg rs.conf() get the current configuration object from local.system.replset rs.reconfig(cfg) updates the configuration of a running replica set with cfg (disconnects) rs.add(hostportstr) add a new member to the set with default attributes (disconnects) rs.add(membercfgobj) add a new member to the set with extra attributes (disconnects) rs.addArb(hostportstr) add a new member which is arbiterOnly:true (disconnects) rs.stepDown([stepdownSecs, catchUpSecs]) step down as primary (disconnects) rs.syncFrom(hostportstr) make a secondary sync from the given member rs.freeze(secs) make a node ineligible to become primary for the time specified rs.remove(hostportstr) remove a host from the replica set (disconnects) rs.slaveOk() allow queries on secondary nodes rs.printReplicationInfo() check oplog size and time range rs.printSlaveReplicationInfo() check replica set members and replication lag db.isMaster() check who is primary reconfiguration helpers disconnect from the database so the shell will display an error, even if the command succeeds. ``` 后记: 有些问题,关掉2222端口的服务器,3333和4444都提示: ``` couldn't connect to server 127.0.0.1:2222(127.0.0.1) ``` 然后再打开2222端口的服务器,现在3333的变成主服务器了。 再关掉3333提示, `couldn't connect to server 127.0.0.1:3333 (127.0.0.1)`,且使用`rs.status()`显示3333的状态是 `stateStr" : "(not reachable/healthy)`。