-
MongoDB
是一个开源、高性能、无模式的文档型数据库,是NoSQL
产品中的一种。
-
MongoDB
中记录的是一个文档,所谓文档就是一种类似于JSON
的格式叫BSON
,它是一种由字段和值对组成的数据结构。字段的数据类型是字符型,它的值除了使用基本的一些类型外,还可包括其他文档、普通数组和文档数组。
- 应用场景:MongoDB可应对“三高”(
高并发
、高性能
、高可用
)需求。如:
①社交场景:用于存储用户个人信息;用户发表的朋友圈信息;通过地理位置索引实现附近的人、地点等功能。
②游戏场景:用于存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。
③物流场景:用于存储订单信息,订单状态在运送过程中会不断更新,以内嵌数组的形式来存储,一次查询就能将该订单所有的变更读取出来。
④物联网场景:用于存储所有接入的智能设备信息,设备汇报的日志信息,并对这些信息进行多维度的分析。
⑤视频直播:用于存储用户信息、点赞互动信息等。
- 以上这些应用场景中,数据操作方面的共同特点是:①数据量大;②读写操作都很频繁;③数据价值较低,对事务性要求不高。
SQL术语 |
MongoDB术语 |
解释 |
database |
database |
数据库 |
table |
collection |
数据库表/集合 |
row |
document |
数据记录行/文档 |
column |
field |
数据字段/域 |
index |
index |
索引 |
table joins |
- |
表连接/MongoDB不支持 |
- |
嵌入式文档 |
MongoDB通过嵌入式文档来替代多表连接 |
primary key |
primary key |
主键/MongoDB自动将_id 字段设置为主键 |
- MongoDB的最小存储单位是文档(
document
)对象,对应关系型数据库的行(row
)。数据在MongoDB中以BSON(Binary-JSON
)文档的格式存储在磁盘上。
- BSON和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如
Date
和BinData
类型。
- BSON采用了类似于 C 语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是
空间利用率不高
。
- BSON中,除了基本的JSON类型:
string
、integer
、boolean
、double
、null
、array
和object
,MongoDB还使用了特殊的数据类型:date
、object id
、binary data
、regular expression
和code
。
- BSON数据类型参考列表:
数据类型 |
描述 |
举例 |
字符串 |
UTF-8字符串都可表示为字符串类型的数据 |
{"x" : "foobar"} |
对象id |
文档的12字节唯一ID,类似UUID |
{"X" :ObjectId() } |
布尔值 |
真或假:true或false |
{"x": true} |
数组 |
值的集合或列表可以表示成数组 |
{"x" : ["a", "b", "c"]} |
32位整数 |
类型不可用。JavaScript仅支持64位浮点数,所以32位整数会被自动转换 |
shell是不支持该类型的,shell中默认会转换成64位浮点数 |
64位整数 |
不支持这个类型。shell会使用一个特殊的内嵌文档来显示64位整数 |
shell是不支持该类型的,shell中默认会转换成64位浮点数 |
64位浮点数 |
shell中的数字就是这一种类型 |
{"x": 3.14159,"y": 3} |
null |
表示空值或未定义的对象 |
{"x": null} |
undefined |
文档中可以使用未定义类型 |
{"x": undefined} |
符号 |
shell不支持,shell会将数据库中带符号类型的数据自动转换成字符串 |
- |
正则表达式 |
文档中可以包含正则表达式,采用JavaScript的正则表达式语法 |
{"x" :/foobar/i} |
代码 |
文档中还可以包含JavaScript代码 |
{"x" : function() { /* …… */ }} |
二进制数据 |
二进制数据可以由任意字节的数字串组成,不过在shell中无法使用 |
- |
最大值/最小值 |
BSON包括一个特殊类型,表示可能的最大值/最小值。shell中没有这个类型。 |
- |
- 选择和创建数据库:
use 数据库名称
(有则进入,无则创建)
- 查看(有权限查看)的所有数据库:
show dbs
或show databases
- 查看当前正在使用的数据库:
db
- 删除当前正在使用的数据库:
db.dropDatabase()
- MongoDB 中默认的数据库为
test
,若没有选择数据库,则集合存放在 test 数据库中。
- 数据库名可以是满足以下条件的任意UTF-8字符串:
1、不能是空字符串(""
);
2、不得含有' '
(空格)、.
、$
、/
、\
和\0
(空字符);
3、应该全部小写;
4、最多为64个字节。
- 有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库:
1、admin
:从权限的角度来看,这是root
数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
2、local
:此数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。
3、config
:当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
- 显示创建集合:
db.createCollection(name)
,创建成功则返回{ "ok" : 1 }
- 查看当前数据库的所有集合(表):
show collections
或show tables
- 删除当前数据库的某个集合:
db.集合名.drop()
,删除成功则返回true,否则返回false。
- 集合的命名规范:
1、集合名不能是空字符串""
。
2、集合名不能含有 \0
字符(空字符),这个字符表示集合名的结尾。
3、集合名不能以system.
开头,这是为系统集合保留的前缀。
4、用户创建的集合名字不能含有保留字符,有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$
。
- 使用
insert()
或save()
方法向集合中插入一个或多个文档,语法如下:
#单条插入
db.collection.insert(
,
{
writeConcern: ,
ordered:
}
)
#批量插入
db.collection.insertMany(
[, , ...],
{
writeConcern: ,
ordered:
}
)
参数 |
类型 |
描述 |
document |
document or array |
要插入到集合中的文档或文档数组(json格式) |
writeConcern |
document |
Optional. A document expressing the write concern. Omit to use the default write concern. See Write Concern. Do not explicitly set the write concern for the operation if run in a transaction. To use write concern with transactions, see Transactions and Write Concern |
ordered |
boolean |
可选。如果为真,则按顺序插入数组中的文档,如果其中一个文档出现错误,MongoDB将返回而不处理数组中的其余文档。如果为假,则执行无序插入,如果其中一个文档出现错误,则继续处理数组中的主文档。在版本2.6+中默认为true |
- 如向
comment
的集合(表)中插入一条测试数据,执行后,若返回此结果:WriteResult({ "nInserted" : 1 })
,则表示插入成功。
db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null})
- 向
comment
的集合(表)中批量插入多条测试数据:
db.comment.insertMany([
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},
{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},
{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},
{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},
{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);
{
"acknowledged" : true,
"insertedIds" : [
"1",
"2",
"3",
"4",
"5"
]
}
1、若comment集合不存在,则会隐式创建。
2、mongo中的数字,默认情况下是double类型,若要存储整型数据,必须使用函数NumberInt(整型数字),否则取值时就会出错。
3、插入当前日期用new Date()
。
4、若插入的数据没有指定_id
,则会自动生成主键值,否则主键就是该指定的值。
5、若某字段没值,则可以赋值为null,或不写该字段。
6、文档中的键/值对是有序的。
7、文档中的值不仅可以是在双引号里面的字符串,还可以是其它几种数据类型(甚至可以是整个嵌入的文档)。
8、MongoDB区分类型和大小写。
9、MongoDB的文档不能有重复的键。
10、文档的键是字符串,除了少数例外情况,键可以使用任意UTF-8字符。
1、键不能含有\0
(空字符),这个字符用来表示键的结尾。
2、.
和$
有特别的意义,只有在特定环境下才能使用。
3、以下划线_
开头的键是保留的(不是严格要求的)。
- 在批量插入文档时,若某个文档插入失败,则将会终止插入操作,但已插入成功的数据不会回滚掉,可使用
try-catch
语句来处理异常,测试时可不用处理:
try {
db.comment.insertMany([
{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},
{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},
{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},
{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},
{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);
} catch (e) {
print (e);
}
- 查询文档的语法:
db.集合名.find(, [projection])
参数 |
类型 |
描述 |
query |
document |
可选。使用查询运算符指定选择筛选器。若要返回集合中的所有文档,请省略此参数或传递空文档{} 。 |
projection |
document |
可选。指定要在与查询筛选器匹配的文档中返回的字段(投影)。若要返回匹配文档中的所有字段,则省略此参数。 |
- 查询
comment
集合的所有文档:db.comment.find()
或db.comment.find({})
。
- 查询userid为1003的文档:
db.comment.find({userid: '1003'})
或db.comment.findOne({userid: '1003'})
,其中findOne()
方法返回多个文档中的第一个文档。
- 若要查询结果返回部分字段,则需要使用
投影查询
(不显示所有字段,只显示指定的字段)。如:
db.comment.find({userid:"1003"},{userid:1,nickname:1}) #默认显示主键_id
db.comment.find({userid:"1003"},{userid:1,nickname:1,_id:0}) #不显示主键_id
db.collection.update(
,
,
{
upsert: ,
multi: ,
writeConcern: ,
collation: ,
arrayFilters: [ , ... ],
hint:
}
)
参数 |
类型 |
描述 |
query |
document |
update的查询条件 |
update |
document or pipeline |
update的对象和一些更新的操作等 |
upsert |
boolean |
可选。若不存在update的记录,是否插入objNew -> true为插入,默认是false不插入 |
multi |
boolean |
可选。默认值为false,即只更新找到的第一个文档。若这个参数值为true,则把按条件查出来多个文档全部更新 |
writeConcern |
document |
可选。表示写问题的文档。抛出异常的级别。 |
collation |
document |
可选。指定要用于操作的校对规则。 |
arrayFilters |
array |
可选。一个筛选文档数组,用于确定要为数组字段上的更新操作修改哪些数组元素。 |
hint |
document or string |
可选。指定用于支持查询谓词索引的文档或字符串。 |
- 覆盖修改文档:修改
_id=1
的文档为点赞量为1001,执行后会发现这条文档除了 likenum字段其它字段都不见了。
> db.comment.update({_id:"1"},{likenum:NumberInt(1001)})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
- 局部修改文档,使用修改器
$set
来实现,如修改_id=2
的文档中浏览量为889。
> db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
- 批量修改文档:更新所有用户为1003 的用户的昵称为 凯撒大帝 。
#默认只更新符合条件的第一条记录
> db.comment.update({userid:"1003"},{$set:{nickname:"凯撒2"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
#修改所有符合条件的数据
> db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})
WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 2 })
- 列值增长地修改文档:使用
$inc
运算符来实现,如每次对_id=3
的文档中点赞数加1。
> db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
- 删除文档的语法:
db.集合名.remove(条件)
,如删除_id=1
的文档,或者删除comment
集合中的全部文档:
> db.comment.remove({_id:"1"})
WriteResult({ "nRemoved" : 1 })
> db.comment.remove({})
WriteResult({ "nRemoved" : 4 })
- 统计集合中文档数量的语法:
db.collection.count(query, options)
参数 |
类型 |
描述 |
query |
document |
查询选择条件 |
options |
document |
可选。用于修改计数的额外选项 |
- 统计集合中的文档数量:
db.comment.count()
- 统计userid为1003的文档个数:
db.comment.count({userid:"1003"})
- 分页查询文档的语法:
db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)
,其中limit()
方法用来读取指定数量的文档,skip()
方法用来跳过指定数量的文档。
- 返回
comment
集合中前4个文档:db.comment.find().limit(4)
- 返回
comment
集合中第3和第4个文档:db.comment.find().skip(2).limit(2)
- 排序查询文档:用
sort()
方法对文档进行排序,通过参数指定排序的字段,使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。语法为:db.集合名.find().sort(排序方式)
,如对userid降序排列,并对访问量进行升序排列:
db.comment.find({},{userid:1}).sort({userid:-1,likenum:1})
- 注意:
skip()
,limilt()
,sort()
三个方法放在一起执行时,执行的顺序是先 sort()
,然后是skip()
,最后是limit()
,和命令编写顺序无关。
- MongoDB的模糊查询是通过正则表达式的方式来实现的。格式为:
db.集合名.find({字段:/正则表达式/})
- 查询评论内容中包含“开水”的所有文档:
db.comment.find({content:/开水/})
- 查询评论内容中以 “专家”开头的所有文档:
db.comment.find({content:/^专家/})
- 比较查询文档:
1、db.集合名.find({ "field" : { $gt: value }})
// 大于: field > value
2、db.集合名.find({ "field" : { $lt: value }})
// 小于: field < value
3、db.集合名.find({ "field" : { $gte: value }})
// 大于等于: field >= value
4、db.集合名.find({ "field" : { $lte: value }})
// 小于等于: field <= value
5、db.集合名.find({ "field" : { $ne: value }})
// 不等于: field != value
- 查询评论点赞数量大于 700的所有文档:
db.comment.find({likenum:{$gt:NumberInt(700)}})
- 包含使用
$in
操作符来查询文档,如查询评论的集合中userid字段值为1003或1004的所有文档:db.comment.find({userid:{$in:["1003","1004"]}})
- 不包含使用
$nin
操作符来查询文档,如:查询评论集合中userid字段值不为1003和1004的所有文档:db.comment.find({userid:{$nin:["1003","1004"]}})
。
- 条件连接查询文档:①若要查询同时满足两个以上条件,则需使用
$and
操作符将条件进行关联,语法为:$and:[{ },{ },{}]
。如查询评论集合中 likenum大于等于700 且小于2000的所有文档:db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})
- 若两个以上条件之间是或者的关系,则使用
$or
操作符进行关联,语法为:$or:[{ },{ },{}]
。如查询评论集合中 userid为1003,或者点赞数小于1000的所有文档:db.comment.find({$or:[ {userid:"1003"}, {likenum:{$lt:1000} }]})
- MongoDB的索引使用
B-Tree
,而MySQL的索引使用的是B+Tree
这种数据结构。
- MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为
单字段索引
(Single Field Index)。
- MongoDB还支持多个字段的用户定义索引,即
复合索引
(Compound Index)。复合索引中列出的字段顺序具有重要意义:若复合索引由{ userid: 1, score: -1 }
组成,则索引首先按userid正序排序,然后在相同userid的文档中,再在按score倒序排序。
-
地理空间索引
(Geospatial Index):为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
-
文本索引
(Text Indexes):MongoDB支持在集合中搜索字符串内容,这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”),而将集合中的词作为词干,只存储根词。
-
哈希索引
(Hashed Indexes):为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询。
- 返回一个集合中的所有索引的数组:
db.集合名.getIndexes()
> db.comment.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "articledb.comment"
}
]
- 默认
_id
索引:MongoDB在创建集合的过程中,在_id
字段上创建一个唯一的索引,默认名字为_id_
,该索引可防止客户端插入两个具有相同值的文档,不能在_id
字段上删除此索引。注意:该索引是唯一索引,因此值不能重复,即_id
值不能重复。在分片集群中,通常使用_id
作为片键。
- 在集合上创建索引的语法:
db.集合名.createIndex(keys, options)
参数 |
类型 |
描述 |
keys |
document |
包含字段和值对的文档,其中字段是索引键,值描述该字段的索引类型。对于字段上的升序索引,请指定值为1;对于降序索引,请指定值为-1。如:{ 字段:1或-1} |
options |
document |
可选。包含一组控制索引创建的选项的文档。有关详细信息,请参见选项详情列表。 |
参数 |
类型 |
描述 |
background |
Boolean |
建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加"background" 可选参数。 "background" 默认值为false。 |
unique |
Boolean |
建立的索引是否唯一,指定为true时创建唯一索引。默认值为false。 |
name |
string |
若未指定索引的名称,则通过连接索引的字段名和排序顺序值生成一个索引名称。 |
dropDups |
Boolean |
3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定为true时创建唯一索引。默认值为false。 |
sparse |
Boolean |
对文档中不存在的字段数据不启用索引。注意:若将其设置为true,则在索引字段中不会查询出不包含对应字段的文档。默认值为 false. |
expireAfterSeconds |
integer |
设定集合的生存时间(单位:秒/s)。 |
v |
index version |
索引的版本号。默认的索引版本号取决于mongod创建索引时运行的版本号。 |
weights |
document |
索引权重值,数值在 1 到 99999 之间,表示该索引相对于其它索引字段的得分权重。 |
default_language |
string |
对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语。 |
language_override |
string |
对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为language。 |
- 单字段索引示例:对 userid 字段的值按升序建立索引:
> db.comment.createIndex({userid:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
- 复合索引:对 userid 和 nickname 同时建立复合(Compound)索引:
> db.comment.createIndex({userid:1,nickname:-1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
> db.comment.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "articledb.comment"
},
{
"v" : 2,
"key" : {
"userid" : 1
},
"name" : "userid_1",
"ns" : "articledb.comment"
},
{
"v" : 2,
"key" : {
"userid" : 1,
"nickname" : -1
},
"name" : "userid_1_nickname_-1",
"ns" : "articledb.comment"
}
]
- 移除集合中指定的索引,语法为:
db.集合名.dropIndex(索引名称)
- 删除
comment
集合中 userid 字段上的升序索引:
> db.comment.dropIndex({userid:1})
{ "nIndexesWas" : 3, "ok" : 1 }
- 移除集合中所有的索引,语法为:
db.集合名.dropIndexes()
。注意:只能删除非_id
字段的索引。如:删除comment
集合中所有的索引:
> db.comment.dropIndexes()
{
"nIndexesWas" : 2,
"msg" : "non-_id indexes dropped for collection",
"ok" : 1
}
> db.comment.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "articledb.comment"
}
]
- 查看执行计划,语法为:
db.集合名.find(query, options).explain(options)
,如:查看根据userid查询数据的情况:
> db.comment.find({userid:"1003"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "articledb.comment",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1003"
}
},
"queryHash" : "37A12FC3",
"planCacheKey" : "7FDF74EC",
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"userid" : 1
},
"indexName" : "userid_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"userid" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"userid" : [
"[\"1003\", \"1003\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "DESKTOP-NIFA5F0",
"port" : 27017,
"version" : "4.2.8-54-g9bffa93",
"gitVersion" : "9bffa938ea7e8a18ba0e5508f126f6e723d063a9"
},
"ok" : 1
}
- 关键点看:
"stage" : "COLLSCAN"
,表示全集合扫描; "stage" : "IXSCAN"
,基于索引进行扫描。
-
覆盖查询
:当查询条件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。
- MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。副本集类似于有自动故障恢复功能的主从集群。
- 冗余和数据可用性:复制提供冗余并提高数据可用性。通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止单个数据库服务器丢失数据。
- MongoDB中的复制:副本集是一组维护相同数据集的mongod实例。 副本集包含多个数据承载节点和可选的一个仲裁节点。在承载数据的节点中,有且仅有一个成员被视为主节点,其余被视为从节点。主节点接收所有的写操作,即记录操作日志中的数据集的所有更改:
oplog
。
- 从节点复制主节点的
oplog
并将操作应用于其数据集,以使从节点的数据集反映主节点的数据集。
- 主从复制和副本集区别:副本集没有固定的"主节点",整个集群会选出一个"主节点",当其挂掉后,又在剩余的从节点中选举某个节点作为"主节点",副本集总有一个活跃点(主、primary)和一个或多个备份节点(从、secondary)。
- 副本集有两种类型三种角色:
- 两种类型:
- 主节点(Primary)类型:数据操作的主要连接点,可读写。
- 从节点(Secondaries)类型:数据冗余备份节点,可以读或选举。
- 三种角色:
- 主节点(Primary):主要接收所有写操作。
- 从节点(Replicate):从主节点中通过复制操作以维护相同的数据集,即备份数据,不可进行写操作,但可以读操作(需要配置)。
- 仲裁者(Arbiter):不保留任何数据的副本,只具有投票选举作用。可以将仲裁服务器维护为副本集的一部分,即副本成员同时也可以是仲裁者,也是一种从节点类型。
1、可以将额外的mongod实例添加到副本集作为仲裁者。仲裁者不维护数据集,仲裁者的目的是通过响应其它副本集成员的心跳和选举请求来维护副本集中的仲裁。因为它们不存储数据集,所以仲裁器可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。
2、若副本集中有偶数个成员,则需添加仲裁者以获得主要选举中的"大多数"投票。 仲裁者不需要专用硬件。仲裁者将永远是仲裁者,而主要人员可能会退出并成为次要人员,而次要人员可能成为选举期间的主要人员。
3、若副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数个,容易满足大多数的投票。
4、若副本+主节点的个数是奇数,可以不加仲裁者。
副本集的创建
创建主节点
mkdir -p /opt/mongodb/replica_sets/myrs_27017/logs \ &
mkdir -p /opt/mongodb/replica_sets/myrs_27017/data/db \ &
mkdir -p /opt/mongodb/replica_sets/myrs_27017/conf
- 新建配置文件:
vim /opt/mongodb/replica_sets/myrs_27017/conf/mongod.conf
。注意空格!
systemLog:
destination: file
path: /opt/mongodb/replica_sets/myrs_27017/logs/mongod.log
logAppend: true
storage:
dbPath: /opt/mongodb/replica_sets/myrs_27017/data/db
journal:
enabled: true
processManagement:
fork: true
pidFilePath: /opt/mongodb/replica_sets/myrs_27017/logs/mongod.pid
net:
bindIp: localhost,192.168.0.128
port: 27017
replication:
replSetName: myrs
[root@dev mongodb]# /opt/mongodb/bin/mongod -f /opt/mongodb/replica_sets/myrs_27017/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 22336
child process started successfully, parent exiting
创建副本节点
mkdir -p /opt/mongodb/replica_sets/myrs_27018/logs \ &
mkdir -p /opt/mongodb/replica_sets/myrs_27018/data/db \ &
mkdir -p /opt/mongodb/replica_sets/myrs_27018/conf
- 新建配置文件:
vim /opt/mongodb/replica_sets/myrs_27018/conf/mongod.conf
。注意空格!
systemLog:
destination: file
path: /opt/mongodb/replica_sets/myrs_27018/logs/mongod.log
logAppend: true
storage:
dbPath: /opt/mongodb/replica_sets/myrs_27018/data/db
journal:
enabled: true
processManagement:
fork: true
pidFilePath: /opt/mongodb/replica_sets/myrs_27018/logs/mongod.pid
net:
bindIp: localhost,192.168.0.128
port: 27018
replication:
replSetName: myrs
[root@dev mongodb]# /opt/mongodb/bin/mongod -f /opt/mongodb/replica_sets/myrs_27018/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 22382
child process started successfully, parent exiting
创建仲裁节点
mkdir -p /opt/mongodb/replica_sets/myrs_27019/logs \ &
mkdir -p /opt/mongodb/replica_sets/myrs_27019/data/db \ &
mkdir -p /opt/mongodb/replica_sets/myrs_27019/conf
- 新建配置文件:
vim /opt/mongodb/replica_sets/myrs_27019/conf/mongod.conf
。注意空格!
systemLog:
destination: file
path: /opt/mongodb/replica_sets/myrs_27019/logs/mongod.log
logAppend: true
storage:
dbPath: /opt/mongodb/replica_sets/myrs_27019/data/db
journal:
enabled: true
processManagement:
fork: true
pidFilePath: /opt/mongodb/replica_sets/myrs_27019/logs/mongod.pid
net:
bindIp: localhost,192.168.0.128
port: 27019
replication:
replSetName: myrs
[root@dev mongodb]# /opt/mongodb/bin/mongod -f /opt/mongodb/replica_sets/myrs_27019/conf/mongod.conf
about to fork child process, waiting until server is ready for connections.
forked process: 22429
child process started successfully, parent exiting
初始化配置副本集和主节点
- 使用客户端命令连接任意一个节点,但尽量要连接主节点(27017节点):
/opt/mongodb/bin/mongo --port=27017
,结果连接上之后会发现很多命令无法使用,比如show dbs
等,必须初始化副本集才行。
- 准备初始化新的副本集,语法为:
rs.initiate(configuration)
参数 |
类型 |
描述 |
configuration |
document |
Optional. A document that specifies configuration for the new replica set. If a configuration is not specified,MongoDB uses a default replica set configuration. |
- 使用默认的配置来初始化副本集:
rs.initiate()
- 若"ok"的值为1,则说明创建成功。
- 命令执行后命令行提示符发生变化,变成了一个从节点角色,此时默认不能读写,稍等片刻后回车,变成主节点。
> rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "192.168.0.128:27017",
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610359342, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610359342, 1)
}
myrs:SECONDARY>
myrs:PRIMARY>
- 查看副本集的配置内容,语法:
rs.conf(configuration)
。rs.config()
是该方法的别名。参数configuration
可选,若没有配置,则使用默认主节点的配置。
myrs:PRIMARY> rs.conf()
{
"_id" : "myrs",
"version" : 1,
"term" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "192.168.0.128: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("5ffc222e8f297cc24be48002")
}
}
- 说明:①
"_id" : "myrs"
:副本集的配置数据存储的主键值,默认就是副本集的名字;②"members": []
:副本集成员数组,此时只有一个:"host" : "192.168.0.128:27017"
;该成员不是仲裁节点:"arbiterOnly" : false
;优先级(权重值):"priority" : 1
;③"settings"
:副本集的参数配置。
- 查看副本集状态,语法为:
rs.status()
。说明:此输出使用从副本集的其它成员发送的心跳包中获得的数据反映副本集的当前状态。
myrs:PRIMARY> rs.status()
{
"set" : "myrs",
"date" : ISODate("2021-01-11T10:11:06.345Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 1,
"writeMajorityCount" : 1,
"votingMembersCount" : 1,
"writableVotingMembersCount" : 1,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1610359862, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2021-01-11T10:11:02.751Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1610359862, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2021-01-11T10:11:02.751Z"),
"appliedOpTime" : {
"ts" : Timestamp(1610359862, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1610359862, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2021-01-11T10:11:02.751Z"),
"lastDurableWallTime" : ISODate("2021-01-11T10:11:02.751Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1610359822, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2021-01-11T10:02:22.706Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1610359342, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2021-01-11T10:02:22.725Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2021-01-11T10:02:22.758Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.0.128:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 4143,
"optime" : {
"ts" : Timestamp(1610359862, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-01-11T10:11:02Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1610359342, 2),
"electionDate" : ISODate("2021-01-11T10:02:22Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610359862, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610359862, 1)
}
- 说明:①
"set" : "myrs"
:副本集的名字;②"myState" : 1
:说明状态正常;③"members"
:副本集成员数组,此时只有一个:"name" : "192.168.0.128:27017"
,该成员的角色是"stateStr" : "PRIMARY"
,该节点是健康的:"health" : 1
。
添加副本从节点
- 在主节点中添加从节点,将其它成员加入到副本集,语法为:
rs.add(host, arbiterOnly)
参数 |
类型 |
描述 |
host |
string or document |
要添加到副本集的新成员,指定为字符串或配置文档:①若是一个字符串,则需指定新成员的主机名和可选的端口号;②若是一个文档,则指定在members数组中找到的副本集成员配置文档。必须在成员配置文档中指定主机字段。有关文档配置字段的说明,详见下方文档:"主机成员的配置文档" |
arbiterOnly |
boolean |
可选。 仅在 值为字符串时适用。若为true,则添加的主机是仲裁者。 |
{
_id: ,
host: , #required
arbiterOnly: ,
buildIndexes: ,
hidden: ,
priority: ,
tags: ,
slaveDelay: ,
votes:
}
- 将27018的副本节点添加到副本集中,
"ok" : 1
:说明添加成功。
myrs:PRIMARY> rs.add("公网ip:27018")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610365858, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610365858, 1)
}
添加仲裁从节点
- 添加一个仲裁节点到副本集,语法:
rs.addArb(host)
- 将27019的仲裁节点添加到副本集中:
myrs:PRIMARY> rs.addArb("公网ip:27019")
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610366033, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610366033, 1)
}
myrs:PRIMARY> rs.status()
{
"set" : "myrs",
"date" : ISODate("2021-01-11T11:54:52.719Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"votingMembersCount" : 3,
"writableVotingMembersCount" : 2,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1610366082, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2021-01-11T11:54:42.875Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1610366082, 1),
"t" : NumberLong(1)
},
"readConcernMajorityWallTime" : ISODate("2021-01-11T11:54:42.875Z"),
"appliedOpTime" : {
"ts" : Timestamp(1610366082, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1610366082, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2021-01-11T11:54:42.875Z"),
"lastDurableWallTime" : ISODate("2021-01-11T11:54:42.875Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1610366062, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2021-01-11T10:02:22.706Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1610359342, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2021-01-11T10:02:22.725Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2021-01-11T10:02:22.758Z")
},
"members" : [
{
"_id" : 0,
"name" : "192.168.0.128:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 10369,
"optime" : {
"ts" : Timestamp(1610366082, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-01-11T11:54:42Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1610359342, 2),
"electionDate" : ISODate("2021-01-11T10:02:22Z"),
"configVersion" : 3,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "公网ip:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 234,
"optime" : {
"ts" : Timestamp(1610366082, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1610366082, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-01-11T11:54:42Z"),
"optimeDurableDate" : ISODate("2021-01-11T11:54:42Z"),
"lastHeartbeat" : ISODate("2021-01-11T11:54:51.055Z"),
"lastHeartbeatRecv" : ISODate("2021-01-11T11:54:51.038Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "192.168.0.128:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3,
"configTerm" : 1
},
{
"_id" : 2,
"name" : "公网ip:27019",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 59,
"lastHeartbeat" : ISODate("2021-01-11T11:54:51.085Z"),
"lastHeartbeatRecv" : ISODate("2021-01-11T11:54:51.090Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 3,
"configTerm" : 1
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1610366082, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1610366082, 1)
}
副本集的数据读写操作
myrs:PRIMARY> use articledb
switched to db articledb
myrs:PRIMARY> db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date()})
WriteResult({ "nInserted" : 1 })
myrs:PRIMARY> db.comment.find()
{ "_id" : ObjectId("5ffc3daf321ee60a406090d4"), "articleid" : "100000", "content" : "今天天气真好,阳光明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2021-01-11T11:59:43.728Z") }
- 登录从节点27018:
/opt/mongodb/bin/mongo --port 27018
[root@dev ~]# /opt/mongodb/bin/mongo --port 27018
myrs:SECONDARY> show dbs
uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("5ffc14e97ffa677c852d618a"),
"counter" : NumberLong(4)
},
"operationTime" : Timestamp(1610366712, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotPrimaryNoSecondaryOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1610366712, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
shellHelper.show@src/mongo/shell/utils.js:937:13
shellHelper@src/mongo/shell/utils.js:819:15
@(shellhelp2):1:1
- 执行
show dbs
后发现不能读取集合的数据,默认情况下,从节点是没有读写权限的,可以增加读的权限,语法:rs.secondaryOk()
或rs.secondaryOk(true)。
- 在27018上设置作为从节点权限,具备
读权限
,但不允许插入文档:
myrs:SECONDARY> rs.secondaryOk()
myrs:SECONDARY> show dbs
admin 0.000GB
articledb 0.000GB
config 0.000GB
local 0.000GB
myrs:SECONDARY> use articledb
switched to db articledb
myrs:SECONDARY> db.comment.find()
{ "_id" : ObjectId("5ffc3daf321ee60a406090d4"), "articleid" : "100000", "content" : "今天天气真好,阳光明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2021-01-11T11:59:43.728Z") }
myrs:SECONDARY> db.comment.insert({"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,k一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})
WriteCommandError({
"topologyVersion" : {
"processId" : ObjectId("5ffc14e97ffa677c852d618a"),
"counter" : NumberLong(4)
},
"operationTime" : Timestamp(1610367532, 1),
"ok" : 0,
"errmsg" : "not master",
"code" : 10107,
"codeName" : "NotWritablePrimary",
"$clusterTime" : {
"clusterTime" : Timestamp(1610367532, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
})
myrs:SECONDARY> rs.secondaryOk(false)
myrs:SECONDARY> show dbs
uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("5ffc14e97ffa677c852d618a"),
"counter" : NumberLong(4)
},
"operationTime" : Timestamp(1610367382, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotPrimaryNoSecondaryOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1610367382, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
shellHelper.show@src/mongo/shell/utils.js:937:13
shellHelper@src/mongo/shell/utils.js:819:15
@(shellhelp2):1:1
[root@dev ~]# /opt/mongodb/bin/mongo --port 27019
myrs:ARBITER> rs.slaveOk()
WARNING: slaveOk() is deprecated and may be removed in the next major release. Please use secondaryOk() instead.
myrs:ARBITER> rs.secondaryOk()
myrs:ARBITER> show dbs
uncaught exception: Error: listDatabases failed:{
"topologyVersion" : {
"processId" : ObjectId("5ffc14f0bb6f0453b549db22"),
"counter" : NumberLong(1)
},
"ok" : 0,
"errmsg" : "node is not in primary or recovering state",
"code" : 13436,
"codeName" : "NotPrimaryOrSecondary"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:147:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:99:12
shellHelper.show@src/mongo/shell/utils.js:937:13
shellHelper@src/mongo/shell/utils.js:819:15
@(shellhelp2):1:1
- 主节点的选举原则:MongoDB在副本集中,会自动进行主节点的选举,主节点选举的触发条件:①主节点故障;②主节点网络不可达(默认心跳信息为10秒);③人工干预(
rs.stepDown(600)
)。一旦触发选举,就要根据一定规则来选主节点。
- 选举规则是根据票数来决定谁获胜:
- 票数最高且获得了"大多数"成员投票支持的节点获胜。"大多数"的定义为:假设复制集内投票成员数量为N,则大多数为 N/2 + 1。例如:3个投票成员,则大多数的值是2。当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。
- 若票数相同且都获得了"大多数"成员的投票支持,数据新的节点获胜。数据的新旧是通过操作日志oplog来对比的。
- 在获得票数时,优先级(priority)参数影响重大。优先级即权重,取值为0-1000,相当于可额外增加0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成为主要成员,更低的值可使成员更不符合条件。默认情况下,优先级的值是1。
myrs:PRIMARY> rs.conf()
{
"_id" : "myrs",
"version" : 3,
"term" : 1,
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "192.168.0.128:27017",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "公网ip:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "公网ip:27019",
"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("5ffc222e8f297cc24be48002")
}
}
- 从执行结果可以看出,主节点和副本节点的优先级各为 1,即默认已有一票,但仲裁节点的优先级是0。(注意:其不能是别的值,即不具备选举权,但具有投票权)
故障测试
- 副本节点故障测试:关闭27018副本节点后发现主节点和仲裁节点对27018的心跳失败。因为主节点还在,所以没有触发投票选举。若此时在主节点写入数据,再启动从节点后发现主节点写入的数据会自动同步给从节点。
db.comment.insert({"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,k一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})
- 主节点故障测试:关闭27017节点后发现从节点和仲裁节点对27017的心跳失败,当失败超过10秒,因为没有了主节点,所以会自动发起投票。而副本节点只有一个(27018),因此候选人只有一个就是27018,开始投票:
- 27019向27018投了一票,27018本身自带一票,总共两票,超过了"大多数";
- 27019是仲裁节点,没有选举权,27018不向其投票,其票数是0;
- 最终结果:27018成为主节点,其具备读写功能;在27018写入数据查看。
- 再启动 27017节点,发现27017变成了从节点,而27018仍保持主节点。
- 登录27017节点,发现是从节点了,其数据自动从27018进行同步,从而实现了高可用。
- 仲裁节点和主节点故障:先关掉仲裁节点27019,再关掉主节点27018,登录27017后,发现27017仍然是从节点,副本集中没有主节点了,导致此时的副本集是只读状态,无法写入。
- 为啥不选举了?因为从节点27017没有获得大多数的票数,即小于2,它只有默认的一票(优先级是1)。
- 若要触发选举,随便加入一个成员即可。
- 若只加入27019仲裁节点成员,则主节点一定是27017,因为没得选了,且仲裁节点不参与选举,但参与投票。
- 若只加入 27018节点,则会发起选举,因为27017和27018都是两票,此时按照谁数据新,谁当主节点。
- 仲裁节点和从节点故障:先关掉仲裁节点27019,再关掉副本节点27018,10秒后,27017主节点自动降级为副本节点。(服务降级)副本集不可写入数据了,已经故障了。