Mongodb的增删改查(二)
一、创建、更新和删除文档操作
官网文档链接:https://docs.mongodb.com/manual/crud/ #下拉选项里面插入更新什么的例子都已经写的很详细了
1.1 常用帮助及基本命令
> help #这是MongoDB最顶层的命令列表,主要告诉我们管理数据库相关的一些抽象的范畴:数据库操作帮助、集合操作帮助、管理帮助。 > db.help() #了解数据库操作更详细的帮助命令,可以直接使用db.help()获取对数据库进行管理和操作的基本命令 > db.listCommands() #打印每个命令的详细用法 > db.testdb.help() #比较基础的是对指定数据库的集合进行操作、管理和监控,testdb是数据库的名称 > show dbs #显示当前数据库服务器上的数据库 > use testdb #切换到指定数据库 > show collections #显示数据库中的集合 > db.serverStatus() #查看数据库服务器的状态 > db.stats() #查看当前数据库的统计信息 > db.getCollectionNames() #查询指定数据库包含的集合名称列表
1.2 插入文档
> db.col.insert({"name":"chaishao"}) #在当前库下面的col集合下面插入一个文档,key是name,value是chaishao。
WriteResult({ "nInserted" : 1 })
> document=({name: 'csp',by: '51niux.com', url: 'www.51niux.com'}) #这是另外一种形式,先定义一个变量
> db.col.insert(document) #通过变量插入
> db.col.insertMany([{name:"A"},{name:"B"},{name:"C"}]) #这是批量插入多条文档
#这是三次插入的结果
1.3 更新文档
https://docs.mongodb.com/manual/tutorial/update-documents/
#其他的也是如此,点击页面最下面,右击就能看到这个此操作详细的参数说明。
updateOne() 的参数:
db.collection.updateOne( <filter>, <update>, { upsert: <boolean>, writeConcern: <document>, collation: <document> })
上面参数的说明:
filter #update的查询条件,类似sql update查询内where后面的。 update #update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的 upsert #可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。 multi #可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。 writeConcern #可选,抛出异常的级别。
替换单个文档:
> db.col.update({'name':'B'},{$set:{'name':'zouni'}}) #替换name是B的改为zouni,$set修饰器的作用是指定一个字段的值,如果这个字段不存在,则创建它。这对更新模式或者增加用户定义的键来说非常方便。
#_id是没有变化的,只是里面的值发生了改变
#除了$set以外还有其他的修饰器,如rename:官方文档:https://docs.mongodb.com/manual/reference/operator/update-field/
求证不加参数的update:
> db.col.update({"name":"chaishao"},{"name":"chaishaopeng"}) #默认情况下,MongoDB只会更新一个文档。
> db.col.find({"name":"chaishaopeng"}) #从结果可以看出只把第一个chaishao更新了
{ "_id" : ObjectId("5989da9ec90b5b3ff6b9a039"), "name" : "chaishaopeng" }
批量更新:
> db.col.update({"name":"chaishao"},{$set:{"name":"chaishaopeng"}},{multi:true}) #加{multi:true}就是批量更新
> db.col.updateMany({"name":"haha"},{$set:{"name":"zou8"}}) #当然直接用updateMany()方法也可以
{ "acknowledged" : true, "matchedCount" : 3, "modifiedCount" : 3 }
upsert的用法:
> db.col.update({"name":"chaishao"},{$set:{"name":"shuaiqila"}},true,true) #上面那个操作我们name已经没有chaishao值了,第一个true代表upsert默认是false,这里设置为true如果没有此键就创建,第二个true表示multi也为true。
用点filter:
上面记录的都是比较简单的,精确匹配的更新,这里那一个正则的方式来举例:
> db.col.find({"name":/^shu.*i$/}) #先验证下我们的filter语句写的没问题,这是一个正则,将以shu开头以i结尾的name值find出来,用//要把正则括起来
{ "_id" : ObjectId("598b1bf29082b7d2b70e0cf1"), "name" : "shuaiqi" }
> db.col.update({"name":/^shu.*i$/},{$set:{"name":"jiushi6"}},{multi:true}) #这就是filter正则并更新
#从上图可以看出过滤成功并更新了。
1.4 删除文档
db.collection.deleteOne()方法的基本语法格式如下所示:
db.collection.deleteOne( <filter>, { writeConcern: <document>, collation: <document> })
> db.col.deleteOne({"name":"A"}) #删除单个文档,删除name是A的文档
db.collection.deleteMany() #是删除多个文档的方法
> db.col.deleteMany({"name":/^zou/}) #从下面的结果看是删除了四个文档
{ "acknowledged" : true, "deletedCount" : 4 }
博文来自:www.51niux.com
二、查找操作
2.1 find简介
MongoDB中使用find来进行查询。查询就是返回一个集合中文档的子集,子集的范围从0个文档到整个集合。find的第一个参数决定了要返回哪些文档,这个参数是一个文档,用于指定查询条件。空的查询文档(例如{}会匹配集合的全部内容。要是不指定查询文档,默认就是{})。
可以向查询文档加入多个键/值对,将多个查询条件组合在一起,这样多个查询条件就是AND的形式。
> db.user.insertMany([{name:"chai",age:10},{name:"chaishao",age:15},{name:"chaishaopen",age:15}]) #插入个测试集合
2.2 指定需要返回的键
可以通过find(或者findOne)的第二个参数来指定想要的键。这样做既会节省传输的数据量,又能节省客户端解码文档的时间和内存消耗。
> db.user.find({},{"age":0}) #不打印age那一列。
> db.user.find({},{"name":1,"_id":0}) #_id总是默认打印并返回的,也可以将_id剔除掉。
> db.user.find({},{"name":1,"_id":0}) #如果age不指定为1,也就显示不出来了
2.3 查询条件
比较操作符
$lt,$lte,$gt,$gte,$ne,$eq,$in,$nin就是全部的比较操作符,分别对应<,<=,>,>=,不等于,等于,匹配任何一个数组中指定的值,匹配数组中指定的值。可以将其组合起来以便查找一个范围的值。
#像$lt这种比较规则什么,官网文档:https://docs.mongodb.com/manual/reference/operator/query/
> db.user.find() #现在有个样例
{ "_id" : ObjectId("598b2ad4de6df0a0823607a4"), "name" : "chai", "age" : 10 } { "_id" : ObjectId("598b2ad4de6df0a0823607a5"), "name" : "chaishao", "age" : 15 } { "_id" : ObjectId("598b2ad4de6df0a0823607a6"), "name" : "chaishaopen", "age" : 15 } { "_id" : ObjectId("598c6f10be8d075f59f22d89"), "name" : "A", "age" : 11 } { "_id" : ObjectId("598c6f10be8d075f59f22d8a"), "name" : "B", "age" : 12 } { "_id" : ObjectId("598c6f10be8d075f59f22d8b"), "name" : "C", "age" : 24 }
> db.user.find({"age":{"$gte":11,"$lte":15}}) #查找年龄大于等于11岁,然后小于等于15岁的,下面是结果
{ "_id" : ObjectId("598b2ad4de6df0a0823607a5"), "name" : "chaishao", "age" : 15 }
{ "_id" : ObjectId("598b2ad4de6df0a0823607a6"), "name" : "chaishaopen", "age" : 15 }
{ "_id" : ObjectId("598c6f10be8d075f59f22d89"), "name" : "A", "age" : 11 }
{ "_id" : ObjectId("598c6f10be8d075f59f22d8a"), "name" : "B", "age" : 12 }
> db.user.find({age:{$nin:[11,15]}}) #查找年龄不是11和15的文档
{ "_id" : ObjectId("598b2ad4de6df0a0823607a4"), "name" : "chai", "age" : 10 }
{ "_id" : ObjectId("598c6f10be8d075f59f22d8a"), "name" : "B", "age" : 12 }
{ "_id" : ObjectId("598c6f10be8d075f59f22d8b"), "name" : "C", "age" : 24 }
逻辑查询运算符
$and #使用逻辑AND连接查询子句返回与两个子句的条件相匹配的所有文档。
$not #反转查询表达式的效果,并返回与查询表达式不匹配的文档。
$nor #使用逻辑NOR连接查询子句返回所有不匹配两个子句的文档。
$or #使用逻辑OR连接查询子句返回与任一子句的条件相匹配的所有文档。
> db.user.find({$and:[{name:/en$/},{age:{$gt:10}}]}) #查找name以en结尾,并且age大于10的文档
{ "_id" : ObjectId("598b2ad4de6df0a0823607a6"), "name" : "chaishaopen", "age" : 15 }
> db.user.find({$or:[{name:/chai/},{age:{$gt:16}}]}) #$in智能是判断单个键,$or就可以匹配多个键这里是查找name里面带有chai或者age大于16的文档
{ "_id" : ObjectId("598b2ad4de6df0a0823607a4"), "name" : "chai", "age" : 10 }
{ "_id" : ObjectId("598b2ad4de6df0a0823607a5"), "name" : "chaishao", "age" : 15 }
{ "_id" : ObjectId("598b2ad4de6df0a0823607a6"), "name" : "chaishaopen", "age" : 15 }
{ "_id" : ObjectId("598c6f10be8d075f59f22d8b"), "name" : "C", "age" : 24 }
> db.user.find({name:{$not:/^([B-Z]|[a-z]).*/}}) #这是是取非[B-Z]或者a-z开头的文档,$not是元条件句,在与正则表达式联合使用时很有用
{ "_id" : ObjectId("598c6f10be8d075f59f22d89"), "name" : "A", "age" : 11 }
元素查询运算符
$exists #匹配具有指定字段的文档。主要用来匹配null,官网例子:https://docs.mongodb.com/manual/reference/operator/query/exists/#op._S_exists
$type #字段是指定类型
$type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。
类型 | 数字 | 备注 |
Double | 1 | |
String | 2 | |
Object | 3 | |
Array | 4 | |
Binary data | 5 | |
Undefined | 6 | 弃用 |
ObjectId | 7 | |
Boolean | 8 | |
Date | 9 | |
Null | 10 | |
Regular Expression | 11 | |
DBPointer | 12 | 弃用 |
JavaScript | 13 | |
Symbol | 14 | 弃用 |
JavaScript (with scope) | 15 | |
32-bit integer | 16 | |
Timestamp | 17 | |
64-bit integer | 18 | |
Decimal128 | 19 | 弃用 |
Min key | -1 | |
Max key | 127 |
> db.user.find({"name":{$type:2}}) #查找name里面值得类型是string(字符串)的文档
评估查询运算符
$mod #对字段的值执行模运算,并选择具有指定结果的文档。
$regex #选择其中值与指定正则表达式匹配的文档。
$text #执行文本搜索。
$where #匹配满足JavaScript表达式的文档。
> db.user.find({age:{$mod:[4,3]}}) #$mod取摸,这里就是age的值%4然后余数是3的文档,注意这里不能是空数组[]也不能是一个单值
{ "_id" : ObjectId("598b2ad4de6df0a0823607a5"), "name" : "chaishao", "age" : 15 }
{ "_id" : ObjectId("598b2ad4de6df0a0823607a6"), "name" : "chaishaopen", "age" : 15 }
{ "_id" : ObjectId("598c6f10be8d075f59f22d89"), "name" : "A", "age" : 11 }
$regex正则表达式:
要使用$ regex,请使用以下语法之一:
{ <field>: { $regex: /pattern/, $options: '<options>' } } { <field>: { $regex: 'pattern', $options: '<options>' } } { <field>: { $regex: /pattern/<options> } }
以下<options>可用于正则表达式:
i #大小写不匹配 m #对于包括锚点的模式(即开始的^,结束的$),在每行的开始或结尾匹配具有多行值的字符串。 没有这个选项,这些锚在字符串的开始或结尾匹配。 #如果模式不包含锚点,或者如果字符串值没有换行符(例如\ n),则m选项不起作用。 x #扩展“功能可以忽略$ regex模式中的所有空白字符,除非转义或包含在字符类中。 s #允许点字符(即)匹配包括换行符的所有字符。
> db.user.find({name:{$regex: /^b/,$options:'i'}}) #加上$options:‘i’ 不区分大小写,所以^b开头也是^B的文档。
{ "_id" : ObjectId("598c6f10be8d075f59f22d8a"), "name" : "B", "age" : 12 }
https://docs.mongodb.com/manual/reference/operator/query/regex/#op._S_regex #这里还有一些例子
$text
$ text对文本索引编制的字段的内容执行文本搜索。针对建立了全文索引的字段,实施全文检索匹配。 $ text表达式具有以下语法:
{ $text: { $search: <string>, $language: <string>, $caseSensitive: <boolean>, $diacriticSensitive: <boolean> }}
$where
使用$where操作符将包含JavaScript表达式或完整JavaScript函数的字符串传递给查询系统。 $where提供更大的灵活性,但要求数据库处理集合中每个文档的JavaScript表达式或函数。 使用this或obj在JavaScript表达式或函数中引用该文档。
用法示例(返回满足传入的js函数的文档,函数表示文档中只要任意字段的值为"steven"则返回)
db.op_test.find({"$where":function(){ for(var index in this) { if(this[index] == "steven") { return true; } } return false; }})
#不是非常必要时,尽量避免使用$where查询,因为他们在速度上要比常规查询慢很多。每个文档都要从BSON转换成JavaScript对象,然后通过$where表达式来运行。而且$where语句不能使用索引,所以只能在没办法的时候才考虑这种用法。
数组查询运算符
$all #匹配包含查询中指定的所有元素的数组。
$elemMatch #选择文档,如果数组中的元素字段匹配所有指定的$elemMatch条件。
$size #如果阵列字段是指定的大小,请选择文档。
> db.user.find({"namearry":{$all:["xiaoqiang","shuaige"]}}) #查找namearry键里面既有xiaoqiang又有shuaige的数组的文档
{ "_id" : ObjectId("598dc8fdebf54081b19de9ac"), "namearry" : [ "csp", "xiaoqiang", "shuaige" ] }
{ "_id" : ObjectId("598dc906ebf54081b19de9ad"), "namearry" : [ "dage", "xiaoqiang", "shuaige" ] }
{ "_id" : ObjectId("598dc911ebf54081b19de9ae"), "namearry" : [ "xiaoqiang", "shuaige" ]
> db.user.find({"namearry":{$size:2}}) #查找namearry键里面数组里面有两个元素的文档
{ "_id" : ObjectId("598dc911ebf54081b19de9ae"), "namearry" : [ "xiaoqiang", "shuaige" ] }
博文来自:www.51niux.com
三、副本集
上面只是简单的记录了一些文档的增删改查的操作以及一些运算符来方便我们后面的测试,像索引、分片之类的等详细的使用方式可参考官网。
复制的官网链接:https://docs.mongodb.com/manual/replication/
3.1 复制简介
mysql有主从复制,mongodb也有,使用复制可以将数据副本保存到多台服务器上。在MongoDB中,创建一个副本集之后就可以使用复制功能了。副本集是一组服务器,其中有一个主服务器(primary),用于处理客户端请求;还有多个备份服务器(secondary)用于保存主服务器的数据副本。
#副本集的所有成员都可以接受读取操作。 但是,默认情况下,应用程序将其读操作定向到主成员。副本集最多只能有一个主服务器,如果当前主要不可用,则从从服务器中选举确定新的主服务器。如下图:
您可以向副本集添加一个额外的mongod实例作为仲裁器。 仲裁者不保留数据集。 仲裁者的目的是通过响应其他副本集成员的心跳和选举请求来维护副本集中的法定人数。 因为它们不存储数据集,所以与具有数据集的完全功能的副本集成员相比,仲裁器可以是以更便宜的资源成本提供副本集仲裁功能的好方法法如果您的副本组成员数量相当,则添加一个仲裁器以获得大部分选票。 仲裁员不需要专用硬件。 有关仲裁器的更多信息,请参阅副本集仲裁器。如下图:
#从上图可以看出,仲裁者允许该组对于选举有奇数票,当通过授权运行时,仲裁者将通过密钥文件与集合中的其他成员交换证书进行身份验证。 MongoDB加密身份验证过程。 MongoDB认证交换密码安全。因为仲裁者不存储数据,所以它们不具有用于认证的用户和角色映射的内部表。 因此,登录授权激活的仲裁器的唯一方法是使用localhost异常。仲裁员和其他成员之间唯一的沟通是:选举期间的投票,心跳和配置数据。 这些交换没有加密。但是如果MongoDB部署使用TLS / SSL,MongoDB将加密副本集成员之间的所有通信
3.2 主从复制
MongoDB最初支持一种比较传统的主从模式(master-slave),在这种模式下,MongoDB不会做自动故障迁移,而且需要明确声明主节点和从节点。如果需要多于11个备份节点,或者是需要复制单个数据库才会用到主从复制,但是一般还是使用副本集的形式。主从模式主挂了只能手工来了。
主:192.168.1.108,从:192.168.1.111-192.168.1.112
主节点上面的操作:
# /usr/local/mongodb/bin/mongod --dbpath=/data/mongodb/master --logpath=/mongodb/logs/mongodb.log --logappend --bind_ip=0.0.0.0 --port=27017 --master --fork #主要加了一个--master,还可以加--noly dbname 来限定只同步哪个数据库
2017-08-12T11:32:39.441+0800 I CONTROL [initandlisten] options: { master: true, net: { bindIp: "0.0.0.0", port: 27017 }, processManagement: { fork: true }, storage: { dbPath: "/data/mongodb/master" }, systemLog: { destination: "file", logAppend: true, path: "/mongodb/logs/mongodb.log" } }
#然后查看日志里面也有master:true
从节点上面的操作:
# /usr/local/mongodb/bin/mongod --dbpath=/data/mongodb/slave --logpath=/mongodb/logs/mongodb.log --logappend --httpinterface --slave --source 192.168.1.108:27017 --fork #主要加了一个--slave 并且--source指向了master的IP和端口
2017-08-12T11:41:10.332+0800 I CONTROL [initandlisten] options: { net: { http: { enabled: true } }, processManagement: { fork: true }, slave: true, source: "192.168.1.108:27017", storage: { dbPath: "/data/mongodb/slave" }, systemLog: { destination: "file", logAppend: true, path: "/mongodb/logs/mongodb.log" } }
#然后查看日志里面有salve:true,并有master的IP和端口
主从同步测试:
可以先查看下master的日志:
2017-08-12T11:41:11.808+0800 I NETWORK [conn4] received client metadata from 192.168.1.111:41610 conn4: { driver: { name: "MongoDB Internal Client", version: "3.4.6" }, os: { type: "Linux", name: "CentOS release 6.4 (Final)", architecture: "x86_64", version: "Kernel 2.6.32-504.12.2.el6.x86_64" } } 2017-08-12T11:47:57.123+0800 I NETWORK [thread1] connection accepted from 192.168.1.112:56759 #5 (2 connections now open) 2017-08-12T11:47:57.137+0800 I NETWORK [conn5] received client metadata from 192.168.1.112:56759 conn5: { driver: { name: "MongoDB Internal Client", version: "3.4.6" }, os: { type: "Linux", name: "CentOS release 6.4 (Final)", architecture: "x86_64", version: "Kernel 2.6.32-358.el6.x86_64" } }
#从日志里面可以看出已经有两个IP连过来了。
#查看下端口也有两个IP在连接27017端口。
> use dbtest; switched to db dbtest > db.col.insert({"name":"chaishao"}) WriteResult({ "nInserted" : 1 })
#然后再主服务器上面创建一个dbtest库,并且创建一个col集合,往里面插入一个name:chaishaopeng的文档
下面我们在从服务器上面查看一下:
> show dbs 2017-08-12T11:59:22.692+0800 E QUERY [thread1] Error: listDatabases failed:{ "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435, "codeName" : "NotMasterNoSlaveOk" } : _getErrorWithCode@src/mongo/shell/utils.js:25:13 Mongo.prototype.getDBs@src/mongo/shell/mongo.js:62:1 shellHelper.show@src/mongo/shell/utils.js:769:19 shellHelper@src/mongo/shell/utils.js:659:15 @(shellhelp2):1:1
#报错了,不能查看,是因为主从的一种保护机制,备份节点可能会落后于主节点,可能没有最新的写入的数据,所以备份节点在默认情况下会拒绝读取请求,以防止应用程序意外拿到过期的数据,因为在备份节点上面查询的时候会有这个错误的提示,说当前节点不是master节点并且slaveOk=false,
> db.getMongo().setSlaveOk() #那就先加上这句话,设置slaveOk=true > show dbs #再次查看当前的数据库,发现除了两个本地库多了一个dbtest库 admin 0.000GB dbtest 0.000GB local 0.000GB > use dbtest #进入到dbtest库 switched to db dbtest > show collections #展示当前库有哪些集合 col > db.col.find() #查看col集合里面的所有文档,正式我们刚才在192.168.1.108上面插入的文档 { "_id" : ObjectId("598e7beba4c5fb8fec4b9a71"), "name" : "chaishao" }
#下面分别在主从服务器上面运行名称查看复制状态:
从服务器:
> db.printSlaveReplicationInfo() #打印从数据库的复制状态信息 source: 192.168.1.108:27017 syncedTo: Sat Aug 12 2017 14:47:04 GMT+0800 (CST) 11 secs (0 hrs) behind the freshest member (no primary available at the moment)
主服务器:
> db.printReplicationInfo() #打印主数据库的复制状态信息 configured oplog size: 990MB log length start to end: 11715secs (3.25hrs) oplog first event time: Sat Aug 12 2017 11:32:39 GMT+0800 (CST) oplog last event time: Sat Aug 12 2017 14:47:54 GMT+0800 (CST) now: Sat Aug 12 2017 14:48:00 GMT+0800 (CST)
博文来自:www.51niux.com
2.3 设置副本集
初始化mongodb
每台服务器都运行下面的命令:
# /usr/local/mongodb/bin/mongod --dbpath=/data/mongodb --logpath=/mongodb/logs/mongodb.log --logappend --replSet repset --fork
2017-08-12T15:22:58.876+0800 I CONTROL [initandlisten] options: { processManagement: { fork: true }, replication: { replSet: "repset" }, storage: { dbPath: "/data/mongodb" }, systemLog: { destination: "file", logAppend: true, path: "/mongodb/logs/mongodb.log" } }
#日志里面出现了上面一句话
初始化副本集
> use admin #使用admin库 switched to db admin > config = { _id:"repset", members:[ #定义副本集配置变量,这里的 _id:”repset” 和上面命令参数“ –replSet repset” 要保持一样。 ... {_id:0,host:"192.168.1.108:27017"}, ... {_id:1,host:"192.168.1.111:27017"}, ... {_id:2,host:"192.168.1.112:27017"}] ... } #下面是输出 { "_id" : "repset", "members" : [ { "_id" : 0, "host" : "192.168.1.108:27017" }, { "_id" : 1, "host" : "192.168.1.111:27017" }, { "_id" : 2, "host" : "192.168.1.112:27017" } ] }
> rs.initiate(config); #初始化副本集配置
{ "ok" : 1 }
#如果你现在查看日志信息可以看到投票选举主服务器的过程,这里我们看一下服务器集群现在的状态:
repset:PRIMARY> rs.status(); #选举出主节点之后,通过mongo shell登录,这里会自动变成repset:PRIMARY>,rs.status();是查看集群状态 "members" : [ #只截取部分信息,这里可以看到主节点是哪个服务器 { "_id" : 0, "name" : "192.168.1.108:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", ... }, { "_id" : 1, "name" : "192.168.1.111:27017", #从节点有哪些以及基本信息 "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 292,
测试副本集的复制功能:
主服务器上面插入数据:
repset:PRIMARY> use fbjtest repset:PRIMARY> db.user.insert({"name":"csp","age":16}) repset:PRIMARY> db.user.insert({"name":"xiaoqiang","age":22})
从服务器上面的查看:
repset:SECONDARY> show dbs #还是存在哪个问题的,主从复制的时候已经说了一种保护机制 2017-08-12T15:47:53.126+0800 E QUERY [thread1] Error: listDatabases failed:{ "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435, "codeName" : "NotMasterNoSlaveOk" } : _getErrorWithCode@src/mongo/shell/utils.js:25:13 Mongo.prototype.getDBs@src/mongo/shell/mongo.js:62:1 shellHelper.show@src/mongo/shell/utils.js:769:19 shellHelper@src/mongo/shell/utils.js:659:15 @(shellhelp2):1:1 repset:SECONDARY> rs.slaveOk(); #这种方式有个问题,再次登录时候还会限制。解决办法就是:把rs.slaveOk();写入到~/.mongorc.js repset:SECONDARY> show dbs #查看库已经同步过来了 admin 0.000GB fbjtest 0.000GB local 0.000GB repset:SECONDARY> use fbjtest switched to db fbjtest repset:SECONDARY> db.user.find() #数据已经同步过来了 { "_id" : ObjectId("598eb1fcc489fdbbcbc6e000"), "name" : "csp", "age" : 16 } { "_id" : ObjectId("598eb25ac489fdbbcbc6e001"), "name" : "xiaoqiang", "age" : 22 }
现在让主节点挂机看是否能重新选取主节点:
把192.168.1.108上面的mongodb服务关掉
查看从服务器192.168.1.111上面的日志:
2017-08-12T16:02:59.066+0800 I REPL [ReplicationExecutor] Starting an election, since we've seen no PRIMARY in the past 10000ms 2017-08-12T16:02:59.078+0800 I REPL [ReplicationExecutor] conducting a dry run election to see if we could be elected 2017-08-12T16:02:59.078+0800 I ASIO [NetworkInterfaceASIO-Replication-0] Connecting to 192.168.1.108:27017 2017-08-12T16:02:59.078+0800 I ASIO [NetworkInterfaceASIO-Replication-0] Failed to connect to 192.168.1.108:27017 - HostUnreachable: Connection refused 2017-08-12T16:02:59.078+0800 I ASIO [NetworkInterfaceASIO-Replication-0] Dropping all pooled connections to 192.168.1.108:27017 due to failed operation on a connection 2017-08-12T16:02:59.079+0800 I REPL [ReplicationExecutor] VoteRequester(term 1 dry run) failed to receive response from 192.168.1.108:27017: HostUnreachable: Connection refused 2017-08-12T16:02:59.083+0800 I REPL [ReplicationExecutor] VoteRequester(term 1 dry run) received a yes vote from 192.168.1.112:27017; response message: { term: 1, voteGranted: true, reason: "", ok: 1.0 }
#第一条是10000ms还连接不到PROMARY,开始发起选举。
#第二条是复制从服务器进行选择
#最后一条是收到192.168.1.112发过来的一条,因为我们只有两个从节点嘛,然后当选。
然后再192.168.1.111上面查看:
repset:PRIMARY> rs.status(); #下面是部分信息 "members" : [ { "_id" : 0, "name" : "192.168.1.108:27017", "health" : 0, "state" : 8, "stateStr" : "(not reachable/healthy)", "uptime" : 0, ... }, { "_id" : 1, "name" : "192.168.1.111:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 2677, #然后再把192.168.1.108的mongodb启动起来,就自动变成"stateStr" : "SECONDARY",
修改副本集设置
接着上面的继续操作,现在我们192.168.1.108上面的mongodb关闭了,然后现在集群里面还有它呢,然后你看日志就会在狂刷
2017-08-12T16:37:37.940+0800 I ASIO [NetworkInterfaceASIO-Replication-0] Failed to connect to 192.168.1.108:27017 - HostUnreachable: Connection refused 2017-08-12T16:37:37.940+0800 I ASIO [NetworkInterfaceASIO-Replication-0] Dropping all pooled connections to 192.168.1.108:27017 due to failed operation on a connection 2017-08-12T16:37:37.940+0800 I REPL [ReplicationExecutor] Error in heartbeat request to 192.168.1.108:27017; HostUnreachable: Connection refused
那么我们就要把192.168.1.108上面踢出去:
repset:PRIMARY> rs.remove("192.168.1.108:27017") #这个要在PRIMARY节点上面做 { "ok" : 1 }
然后查看日志,也不狂刷连不上192.168.1.108了,然后查看下集群状态:
repset:PRIMARY> rs.status(); { "set" : "repset", "date" : ISODate("2017-08-12T08:44:08.307Z"), "myState" : 1, "term" : NumberLong(2), "heartbeatIntervalMillis" : NumberLong(2000), "optimes" : { "lastCommittedOpTime" : { "ts" : Timestamp(1502527440, 1), "t" : NumberLong(2) }, "appliedOpTime" : { "ts" : Timestamp(1502527440, 1), "t" : NumberLong(2) }, "durableOpTime" : { "ts" : Timestamp(1502527440, 1), "t" : NumberLong(2) } }, "members" : [ #发现192.168.1.108已经不在了,但是192.168.1.111的_id号并没有发送变化 { "_id" : 1, "name" : "192.168.1.111:27017", "health" : 1, "state" : 1, #1对应的就是下面的PRIMARY "stateStr" : "PRIMARY",
注:这里提一下state:num对应的stateStr状态:
其他状态: 0==》STARTUP #副本集的每个成员启动在STARTUP状态。 mongod然后加载该成员的副本集配置,并将成员的状态转换为STARTUP2。 STARTUP中的成员不符合投票资格,因为它们尚未成为任何副本集的公认会员。 5==》STARTUP2 #一旦mongod完成加载该成员的配置,副本集的每个成员将进入STARTUP2状态,此时它将成为副本集的活动成员。 然后,会员决定是否进行初始同步。 如果成员开始初始同步,则成员保留在STARTUP2中,直到所有数据被复制并且构建所有索引。 之后,成员转换到RECOVERING。 3==》RECOVERING #副本集的成员在尚未准备好接受读取的情况下进入RECOVERING状态。 恢复状态可以在正常操作期间发生,并不一定反映错误状况。 RECOVERING州的成员有资格在选举中投票,但不符合进入PRIMARY状态的资格。 #在复制足够的数据后,成员将从RECOVERING转换到SECONDARY,以确保客户端读取的数据的一致视图。 RECOVERING和SECONDARY状态之间的唯一区别是RECOVERING禁止客户端读取,SECONDARY允许它们。 SECONDARY状态并不保证有关数据相对于主要数据的数据的任何数据。 #由于过载,从服务器可能落后于复制集合的其他成员之后,使得它可能需要与集合的其余部分重新同步。 当这种情况发生时,成员进入RECOVERING状态,需要手动干预。 核心状态: 1==》PRIMARY #PRIMARY状态的成员接受写操作。 一个副本集最多只有一个主服务器的。 2==》SECONDARY #SECONDARY状态下的成员复制主数据集,并可配置为接受读取操作。 7==》ARBITER #仲裁员不会复制数据,只能存在参与选举。 错误状态: 6==》UNKNOWN #从来没有将状态信息传送给副本集的成员处于UNKNOWN状态。 8==》DOWN #失去与副本集的连接的成员被该集合的其余成员看作是DOWN。 10==》REMOVED #该成员曾经在一个副本集,但后来被删除。 9==》ROLLBACK #该成员正在积极地执行回滚。 数据不可用于读取。
好了现在192.168.1.108的问题解决了,我们再把它加回来:
repset:PRIMARY> rs.add("192.168.1.108:27017") { "ok" : 1 } repset:PRIMARY> rs.config() #打印下配置信息,下面是部分信息,可以看到已经加入集群,_id是往后递增的 { "_id" : "repset", #副本集的名称。 一旦设置,将无法更改副本集的名称。_id必须与replication.replSetName相同,或者在命令行上指定为mongod的-replSet的值。 "version" : 10, #每次修改为集群配置这里的num是递增的,初始值是1。 "protocolVersion" : NumberLong(1), #从MongoDB 3.2开始,新的副本集默认使用protocolVersion:1。 MongoDB的以前版本使用协议的版本0,不能作为指定protocolVersion 1的副本集配置的成员运行。 "members" : [ #一组成员配置文件,一个用于副本集的每个成员。 成员数组是零索引数组。 { "_id" : 3, #复制集中每个成员的整数标识符。 值必须在0和255之间,包括0和255。 每个副本集成员必须具有唯一的_id <members [n] ._ id>。 一旦设置,您将无法更改成员的_id。 "host" : "192.168.1.108:27017", #主机名,如果指定,则为成员的端口号。主机名必须能够针对副本集中的每个主机进行解析。 "arbiterOnly" : false, #一个标识仲裁器的布尔值。 值为true表示该成员是仲裁者。当使用rs.addArb()方法添加仲裁器时,该方法会自动将添加成员的[n] .arbiterOnly成员设置为true。 "buildIndexes" : true, #对于从客户端接收查询的mongod实例,不要设置为false。这是一个永久选项,不创建索引,这个选项要求成员优先级设置为0,单纯只备份的数据库可以这样设置。 #如果满足以下所有条件,将buildIndexes设置为false可能会很有用:您只使用此实例使用mongodump执行备份,以及这个成员将不会收到任何查询索引创建和维护使主机系统负担过重。 "hidden" : false, #当此值为true时,副本集隐藏此实例,并且不包括db.isMaster()或isMaster的输出中的成员。 这样可以防止读操作(即查询)通过辅助读优先级到达此主机。 "priority" : 1, #一个数字表示成员成为主要成员的相对资格。指定更高的值以使成员更有资格成为主要成员,0就是不会成为主服务器。 "tags" : { #包含任意键和值的映射的标签集文档。 这些文档描述了复制集成员,以便定制写入注意和读取偏好,从而允许可配置的数据中心意识。只有当分配给成员的标签时,此字段才存在。 }, "slaveDelay" : NumberLong(0), #延迟复制的秒数 "votes" : 1 #服务器将在复制集选举中投票的数量。 每个成员的票数为1或0,仲裁员总是只有1票。优先级大于0的会员不能有0票。 #一个副本集可以有多达50个成员,但只有7个有投票权的成员。 如果您在一个副本集中需要超过7个成员,则将成员[n] .votes设置为0,以获得额外的无投票权的成员。 }
如果我们主机加错了,比如说应该加192.168.1.122结果加成了192.168.1.108,出了删除再添加以外,可以动态修改吗:
repset:PRIMARY> var config=rs.config() repset:PRIMARY> config.members[2].host="192.168.1.122:27017" #members[2]就是字典里面members键的数组的第三个元素,也就是192.168.1.108的内嵌字典,然后改为我们要设置的值 192.168.1.122:27017 repset:PRIMARY> rs.reconfig(config) #配置完成修改完成后,需要使用rs.reconfig辅助函数将新的配置文件发送给数据库。 { "ok" : 1 }
3.4 用到的命令方法
官方文档:https://docs.mongodb.com/manual/reference/replication/
rs.add() #将一个成员添加到副本集。 rs.addArb() #向仲裁集添加仲裁器。 rs.conf() #返回副本集配置文档。 rs.freeze() #防止当前成员在一段时间内选举为主要选举。 rs.help() #返回副本集函数的基本帮助文本。 rs.initiate() #初始化一个新的副本集。 rs.printReplicationInfo() #从主数据库角度打印副本集的状态报告。 rs.printSlaveReplicationInfo() #从slave数据库的角度打印副本集的状态的报告。 rs.reconfig() #通过应用新的副本集配置对象来重新配置副本集。 rs.remove() #从副本集中删除成员。 rs.slaveOk() #设置当前连接的slaveOk属性。已过时。使用readPref()和Mongo.setReadPref()设置读取首选项。 rs.status() #返回具有有关副本集状态的信息的文档。 rs.stepDown() #使当前的主要成员成为强制执行选举的仲裁。 rs.syncFrom() #设置此副本集成员将同步的成员,覆盖默认同步目标选择逻辑。
复制数据库相关命令
applyOps #将指定的oplog条目应用于mongod实例。 applyOps命令是一个内部命令。 isMaster #显示有关此成员在副本集中的角色的信息,包括它是否是主控。如:> db.isMaster() replSetFreeze #replSetFreeze命令可以防止副本集成员在指定的秒数内寻求选举。 将此命令与replSetStepDown命令结合使用,以使副本集中的其他节点成为主节点。 #格式:{ replSetFreeze: <seconds> },{ replSetFreeze: 0 }或者重新启动mongod进程也会解冻副本集成员。replSetFreeze是一个管理命令,您必须针对admin数据库发出。 replSetGetConfig #返回副本集的配置对象。> use admin > db.runCommand({replSetGetConfig:1}) #必须在admin库中执行,相当于rs.conf(); replSetGetStatus #replSetGetStatus命令从当前服务器的角度返回副本集的状态。 您必须对管理数据库运行命令。 replSetInitiate #命令初始化一个新的副本集。{ replSetInitiate : <config_document> } replSetMaintenance #命令启用或禁用副本集的辅助成员的维护模式。{ replSetMaintenance: <boolean> }.运行replSetMaintenance命令时,请考虑以下行为: #无法在主节点上运行该命令,必须对管理数据库运行命令,当启用replSetMaintenance:true时,成员进入RECOVERING状态,当是RECOVERING: #该成员不可读取操作,该成员继续同步其主要的oplog。当一个节点收到一个replSetMaintenance:true请求时,它将一个维护模式任务添加到任务队列中。 如果任务队列为空,现在不存在,则节点将转换到RECOVERING状态,并开始拒绝读取请求。 #当节点收到replSetMaintenance:false请求时,它将从队列中删除维护模式任务(即使该任务由不同的客户端启动)。 如果请求清空维护模式任务队列,节点将返回到SECONDARY状态。如果要防止节点读取服务,请考虑使用隐藏的副本集成员。 replSetReconfig #将新配置应用于现有副本集。可以使用此命令添加和删除成员,并更改现有成员上设置的选项。 使用以下语法:{ replSetReconfig: <new_config_document>, force: false },可以使用shell的rs.reconfig()方法运行replSetReconfig。 replSetStepDown #强制复制集的主要成为次要的,触发主要选举。该命令只对主要有效,如果在非主成员上运行,则该错误将是错误的。 replSetStepDown只能在admin数据库中运行,相当于rs.stepDown() replSetSyncFrom #临时覆盖当前mongod的默认同步目标。 resync #强制一个mongodb从主服务器重新同步。 仅适用于主从复制。