官方文档链接
想详细了解最好还是读官方文档,有好多东西我没写上,而且我的理解也不一定对
1. Replica Set(复制集)
1.0 定义
多个维护了同一个数据集合的 mongod 进程(可以单服务器),它们为系统提供两天冗余和高可用。
A replica set in MongoDB is a group of mongod
processes that maintain the same data set
1.1 Replica Set Members
Primary(主节点):接收所有的写操作
Secondaries(从节点):为了与主节点的数据集相同,会复制主节点上的写操作
最低配置推荐:一主二从或者一主一从一仲裁(arbiter),其中arbiter只投票不存储数据
- Primary主节点
复制集中唯一执行写操作的成员。MongoDB在主节点执行写操作,并且把写操作记录在oplog里。从节点会复制oplog,然后执行oplog中的操作。
复制集至多有一个主节点。当主节点不可用,会重新选出新的主节点。
- Secondaries从节点
从节点维护了主节点数据集的备份。一个复制集可以有一个以上的从节点。不可以从从节点直接写数据,但是经过配置,客户端可以从从节点读取数据。
从节点可以配置为:
-
Priority 0 Replica Set Members:
不可成为主节点并且不能触发主节点选举。有数据集的备份,接收读操作和为选举投票。
什么样的成员适合被配置成priority 0: 该成员被部署在远离主部署位置的数据中心,网络延迟更高。
Priority 0成员可以作为备份,当一个节点不可用,它可以替代这个节点。
其他成员的priority默认为1。
-
Hidden Replica Set Members:
有主节点的数据集的备份,但是对客户端程序不可见。Hidden成员必须是Priority 0,不能成为主节点,但可以参与主节点的选举。
客户端不会再Hidden成员上进行读操作,因此Hidden成员可以有一些专门的用途:备份和数据分析。
如果复制集包含delayed成员,一定要确保它们hidden并且不可以投票。
write concern?
-
Delayed Replica Set Members:
有主节点的数据集的备份,但是是这个数据集的早期的或者延迟的版本。例如,如果现在9:52,一个成员有一个小时的延迟,这个delayed成员上就没有比8:52更晚的操作。
可以帮助数据恢复。
一定为priority 0, hidden并且不可以给选举投票。
write concern?
- Arbiter仲裁节点
Arbiter参与主节点的选举,但是没有数据集的数据不能成为主节点。
一个Arbiter在主节点的选举中只有一票,它默认为priority 0。
不能在主节点和从节点运行Arbiter
一个复制集至多只有Arbiter
1.2 Replica Set Oplog(operations log)
oplog是一个记录了所有数据库的写操作的特殊集合。
MongoDB会执行主节点上的操作并且将这些操作记录在主节点的oplog中。然后从节点会异步地复制oplog并执行里面的操作。所有的replica set中的成员都有oplog的复制,保存在local.oplog.rs集合中。
不可以删除local.oplog.rs集合。
为了方便复制,所有replica set中的成员会给其他的成员发送心跳。任意的从节点可以从其他成员中引用oplog的条目。
在oplog中,每一个操作都是幂等的。也就是说,在一个数据库中执行一次和执行多次oplog中的操作,结果一样。
-
Oplog的大小与最小保留时间(minimum oplog retention period)
可以指定oplog的大小,可以使用replSetResizeOplog命令在mongd程序运行时动态地改变oplog的大小或保留时间。
mongod会截断一个oplog当:
- oplog的大小到达了配置设定的大小
- oplog的时间到了配置设定的保留时间
-
什么时候需要更大的oplog
- 一次更新多个文档(为了幂等,oplog会把multi-updates拆分成多个单独的更新)
- 删除和插入的数据量一样多
- 大量的更新操作
1.3 Replica Set Data Synchronization
MongoDB有两种形式的同步:initial sync(为了把全部的数据同步到新成员上) 和 replication(同步数据集上的变更)
-
Initial Sync
把复制集中的一个成员中的所有数据复制到另一个成员上。可以使用initialSyncSourceReadPreference来选定initial sync的源,但是只能在开启mongod时设定
- 步骤
- 复制除了local数据库的所有数据库;创建所有的索引;在Initial Sync期间如果有新的数据更改操作,会记录在oplog中。
- 执行源节点上的oplog中的操作。
- 步骤
-
Replication
从节点成员从源节点复制oplog,并且异步地执行这些操作
1.4 Replica Set Deployment Architectures
-
策略
- 决定复制集成员数量:
- 最多的投票成员数量:7个;
- 奇数个成员,凑不够奇数个成员,但是由硬件的限制不能再添加从节点,可以添加一个Arbiter(一个复制集最多有一个Arbiter);
- 考虑容错:Fault tolerance for a replica set is the number of members that can become unavailable and still leave enough members in the set to elect a primary. 当一个复制集一定数量的成员不可用时,这个复制集仍然有足够的成员去选举新的主节点,这些不可用员的数量的最大值就是容错值;
- 启用Hidden和Delayed成员为了其他用途:备份或数据分析;
- 在读压力大的节点上的负载均衡:让其它从节点承载读操作;
- 在添加从节点前要确保有足够的空间
- 在不同的地理位置部署成员
- 决定复制集成员数量:
-
Replica Set 命名
如果一个程序使用了多个复制集,每一个集合都要一个不同的名字。
-
部署的模型
-
三个成员
Primary-Secondary-Secondary(Recommended)
Primary-Secondary-Arbiter
-
部署在两个以上数据中心(地理位置不同)
如果把节点都部署在同一个数据中心,那么当这个数据中心由于意外而不可用时,可能会导致数据的丢失。
如果可以,使用奇数个数据中心
-
成员之间的优先级,priority高的越可能成为主节点
-
例子
-
三个成员:
1). 两个数据中心,两个成员在数据中心1,一个成员在数据中心2,把arbiter放在数据中心1中
2). 三个数据中心:每个数据中心分别有一个成员
-
五个成员:
1). 三个成员在数据中心1,两个个成员在数据中心2
2). 三个数据中心:数据中心1有两个成员,数据中心2有两个成员,数据中心3有两个成员
-
-
-
1.5 Replica Set High Availability
1.5.1 Replica Set Elections
什么时候触发选举:
- 向replica set中添加一个新节点
- 初始化一个replica set
- 执行维护replica set的方法,例如rs.stepDown()(让主节点变成从节点)或rs.reconfig()(重新配置一个已经存在的replica set,必须在主节点执行这个方法)
- 从节点联系不上主节点,时间超过配置的timeout(默认10s)
在选举期间,replica set不可以执行写操作直到选举完成,但是如果Read Preference Mode支持在从节点读仍然可以支持读操作。
选举的时间默认为12秒,可通过settings.electionTimeoutMillis来配置。网络延迟会增加这个时间。
影响选举的因素:
-
Replication Election Protocol
-
心跳:Replica set的成员每2秒向彼此发送心跳。如果一个心跳没有在10秒内返回,其它的成员会把这个成员标记为不可达。
-
成员的优先级:优先级越高的成员越倾向于召集选举,他们更可能成为主节点。Priority为0的成员不可能成为主节点和触发选举,但可投票。
-
Mirrored Reads
-
数据中心的丢失:尽可能的把成员分布在多个数据中心上
-
网络划分
投票的成员:
- members[n].votes = 1 的成员会在选举中投票;members[n].votes = 0 的成员不可以投票。Priority大于0的成员members[n].votes不能是0。
- 只有primary, secondary, startup2, recovering, arbiter, rollback可以投票
不可以投票的成员:这些成员有replica set里面的数据并且可以接收客户端的读操作。
不要修改即将成为primary节点的成员members[n].votes,可以修改它的members[n].priority
1.5.2 Rollbacks During Replica Set Failover
在一次故障之后的选举结束,之前的主节点重新加入replica set的时候,会撤回它的写操作(回滚)。回滚只发生在当主节点接受了写操作,但是从节点还没有成功的从挂掉的主节点上复制数据的时候。回滚是为了维持数据的一致性。
回滚应该尽可能的少,它通常是network partition的结果。
如果在主节点挂掉前有其它成员已经复制了写操作,并且这个成员是可用的可以连通replica set里的其它成员,回滚就不会发生。
1.6 Replica Set Read and Write Semantics
- Write Concern for Replica Sets
write concern 描述了在写操作成功返回前必须确认写操作的数据承载成员的数量。在收到一个写操作后,一个成员在收到一个写操作并且将这个写操作执行成功后会给这个写操作一个确认。
在replica set中,默认的write concern是w: 1,它需要只有主节点确认写在返回write concern确认前。可以重新配置这个参数,但是必须大于等于1小于等于replica set的数据承载节点的数量。
“majority” write concern需要calculated majority数量的数据承载并且可投票的节点来确认写操作。
calculated majority是以下两个数量的最小值:
-
所有的可投票成员(包含arbiter)的majority数量(比如在一直二从和一主一从一仲裁中,这个数值为2,比一半多1?)
-
所有数据承载可投票成员的数量
(The majority for write concern
"majority"
is calculated as the smaller of the following values:- the majority of all voting members (including arbiters) vs.
- the number of all data-bearing voting members.
)
越多的成员确认写操作,当主节点不可用时被写入的数据就会有更小的可能回滚。但是,write concern越大,越会增加延迟,因为客户端在没有收到指定数量的写操作时需要等待。
客户端会等待直到主节点返回write concern确认,这个确认表示着calculated majority数量的数据承载并且可投票的节点确认了写操作。一个写确认超时,不代表主节点没有写成功,它只代表了calculated majority数量的数据承载并且可投票的节点没有确认写操作。
-
Read Preference
Read preference描述了MongoDB的客户端是如何在一个replica set中给读操作路由的(就是当客户端有读请求时,选择哪一个节点来执行这个读操作)。
在默认的情形下,客户端会在主节点上进行读操作。
包含了** o u t 和 out和 out和merge的聚合操作**,只在主节点执行。
只有inline mapReduce的不写入数据的操作支持read preference,mapReduce只运行在主节点上。
-
Read Preference Mode:
-
Primary: 是默认的模式。所有的读操作都在复制集的主节点执行。如果主节点不可用,读操作报错或抛出异常。
primary模式和使用了tag sets或者maxStalenessSeconds的模式不兼容。
Multi-document transactions如果包含了读操作,必须使用primary模式。在一个事务中的所有操作必须在一个成员上执行。
-
primary preferred
大多数情况下,从主节点上执行操作。但是当主节点不可用时,会从满足了
maxStalenessSeconds
和 tag sets的节点上执行读操作。当primaryPreferred的read preference包含了一个maxStalenessSeconds值并且主节点不能提供读操作时,客户端会通过比较从节点的最后一次成功的写操作和最近一次的写操作的延迟,选择延迟时间小于等于maxStalenessSeconds的节点来执行读操作。
当read preference包含了一个tag set并且主节点不能提供读操作时,客户端会尝试找到匹配标签的从节点。如果找到了,客户端会碎金从 nearest group里匹配tag的从节点中随机选一个从节点。如果没有从节点匹配这个标签,读操作报错。
当read preference包含了maxStalenessSeconds值和一个tag set,客户端先通过maxStalenessSeconds筛选,再用tag set筛选。
primaryPreferred模式下的读操作可能会返回旧数据,可以通过使用maxStalenessSeconds来防止从滞后的从节点来读取数据。
-
secondary
只从从节点进行读操作。
secondary模式下的读操作可能会返回旧数据,可以通过使用maxStalenessSeconds来防止从滞后的从节点来读取数据。
-
secondaryPreferred
大多数情况下,在从节点上执行读操作。但是当从节点不可用时,会在主节点上执行读操作。
secondaryPreferred模式下的读操作可能会返回旧数据,可以通过使用maxStalenessSeconds来防止从滞后的从节点来读取数据。
-
nearest
当选择执行读操作的节点时,只考虑网络延迟是否在接受范围内,不考虑节点是不是主节点或者从节点。
nearest模式下的读操作可能会返回旧数据,可以通过使用maxStalenessSeconds来防止从滞后的从节点来读取数据。
-
-
Read Preference Tag Sets
配置members[n].tags:
{ "<tag1>": "<string1>", "<tag2>": "<string2>",... }
-
单标签匹配
如果一个从节点有{ “tag_a”: “a”, “tag_b”: “b” }标签,那么下面列举出的集合可以将读操作引导到这个从节点
[{ "tag_a": "a", "tag_b": "b"}, {}] //查找tag_a和tag_b,没有选一个任意的从节点 [{ "tag_a": "a"}, { "tag_b": "b"}, {}] //查找tag_a,再找tag_b,没有选一个任意的从节点 [{ "tag_b": "b"}, { "tag_a": "a"}, {}] //查找tag_b,再找tag_a,没有选一个任意的从节点 [{ "tag_a": "a"}, {}] //查找tag_a,没有选一个任意的从节点 [{ "tag_b": "b"}, {}] //查找tag_b,没有选一个任意的从节点 [{}] //选一个任意的从节点
-
标签顺序
当一个tag set列出了多个文档:
[{ “tag_a”: “a”, “tag_b”: “b” }, { “tag_c”: “c”}, {}]
先找{ "tag_a": "a", "tag_b": "b" },找到了就不考虑其他的标签 -> 再找{ "tag_c": "c"},找到了就不考虑其他的标签 -> {}没有找到,就找一个任意的节点
-
Tag Set与primary read preference模式不兼容
-
-
maxStalenessSeconds
由于一些原因(网络,磁盘空间灯)复制集的成员可能比主节点延迟。maxStalenessSeconds选项规定了一个在从节点读数据的最长的复制延迟时间。当一个从节点的延迟时间超过它,客户端就不会再这个从节点读数据。
与primary read prefernece不兼容。
maxStalenessSeconds的时间要多于90s。
-
Hedged Read Option
只可以在非primary read preference模式下使用
-
Read Preference Use Cases
-
1.7 Internal/Membership Authentication(内部认证)
可以让replica sets和sharded clusters里的成员彼此相互认证。可以使用keyfiles或者x.509。测试和开发环境可以使用keyfiles,但是生产环境推荐使用x.509。
-
keyfiles
通过使用keyfiles,replica set的每一个mongod实例都会使用keyfiles里的内容来验证其他成员。只有当mongd实例的keyfiles和replica set的keyfile匹配,它才可以加入replica set。
操作步骤:
1. 创建一个keyfile openssl rand -base64 756 > keyfile的路径 #在指定路径下生成keyfile chmod 400 keyfile的路径 #修改keyfile的权限,只可以读,其他任何人不能进行任何操作 2. 把keyfile复制到replica set中的其他成员里 3. 启动replica set中的其他成员,开启access control: 启动命令加上参数: --keyFile <path-to-keyfile> 4. 连接主节点的shell 5. 初始化replica set 6. 创建user administrator 7. 验证user administrator 8. 创建cluster administrator 9. 创建其他用户(可选)
-
x.509
1.8 Replica Set Deployment Tutorials
1.8.1 测试环境
我使用的是docker
-
创建目录
mkdir -p /home/mongodb/rs0-0 /home/mongodb/rs0-1 /home/mongodb/rs0-2
并且分别在这些目录下创建/datadir和/config目录,为了映射docker mongo中的数据和配置文件,可以不这么做,把下面的-v /home/mongodb/rs0-0/datadir:/data/db -v /home/mongodb/rs0-0/config:/data/configdb删掉就行
-
开启三个mongod
第一个:
docker run -d -p 27017:27017 -v /home/mongodb/rs0-0/datadir:/data/db -v /home/mongodb/rs0-0/config:/data/configdb --name mymongo0 mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128
第二个:
docker run -d -p 27018:27017 -v /home/mongodb/rs0-1/datadir:/data/db -v /home/mongodb/rs0-1/config:/data/configdb --name mymongo1 mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128
第三个:
docker run -d -p 27019:27017 -v /home/mongodb/rs0-2/datadir:/data/db -v /home/mongodb/rs0-2/config:/data/configdb --name mymongo2 mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128
-
进入一个mongod的docker容器
1. docker exec -it mymongo0 /bin/bash #进入容器 2. mongo #进入mongo shell 3. 输入下面的配置文件,priority值越大的越可能成为主节点,在这里我让mymongo0成为了主节点 rsconf = { _id: "rs0", members: [ { _id: 0, host: "192.168.45.3:27017", priority: 2 }, { _id: 1, host: "192.168.45.3:27018", priority: 1 }, { _id: 2, host: "192.168.45.3:27019", priority: 1 } ] } 4. 初始化配置 rs.initiate(rsconf) 5. 设置密码 use admin; //进入admin数据库 admin不存在就创建: db.createUser( { user: 'admin', pwd: '123456', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] }); admin存在就修改密码:db.changeUserPassword("admin","123456"); 6. 退出mongo shell,在/data/configdb目录下创建keyfiles,并且把keyfiles复制到其他节点的/data/configdb目录下 openssl rand -base64 756 > /data/configdbkey_file chmod 400 /data/configdb/key_file 7. 删除容器,重启所有的mongd节点,启动命令中添加 --auth --keyFile=/data/configdb/key_file: docker stop mymongo0 docker rm mymongo0 例如: docker run --rm --name mymongo0 -d -p 27017:27017 -v /home/mongodb/rs0-0/datadir:/data/db -v /home/mongodb/rs0-0/config:/data/configdb mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128 --auth --keyFile=/data/configdb/key_file docker run --rm --name mymongo1 -d -p 27018:27017 -v /home/mongodb/rs0-1/datadir:/data/db -v /home/mongodb/rs0-1/config:/data/configdb mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128 --auth --keyFile=/data/configdb/key_file docker run --rm --name mymongo2 -d -p 27019:27017 -v /home/mongodb/rs0-2/datadir:/data/db -v /home/mongodb/rs0-2/config:/data/configdb mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128 --auth --keyFile=/data/configdb/key_file
-
测试
用NoSQL Manager for MongoDB测试一下集群
可以使用mongo compass,连接这三个mongod节点,只有主节点可以看到和操作test数据库(因为没配read preference)
- golang连接集群
文档链接
mongo_mgr.go
package utils
import (
"context"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const(
rs0_0_ip = "192.168.45.3:27017"
rs0_1_ip = "192.168.45.3:27018"
rs0_2_ip = "192.168.45.3:27019"
replicaSetName = "rs0"
mongoUser = "admin"
mongoPassword = "123456"
)
func ConnectToMongo() *mongo.Client{
//"mongodb://localhost:27017,localhost:27018/?replicaSet=replset"
ipList := []string{rs0_1_ip, rs0_0_ip, rs0_2_ip}
uri, err := generateUri(ipList, replicaSetName)
if err != nil{
fmt.Println(err)
return nil
}
clientOpts := options.Client().ApplyURI(uri)
client, err := mongo.Connect(context.TODO(), clientOpts)
if err != nil {
fmt.Println(err)
return nil
}
return client
}
func generateUri(ipList []string, replicaSetName string) (string, error){
if len(ipList) == 0{
return "", errors.New("ip list's lenth is 0")
}
ipStr := ""
for _, ip := range ipList{
ipStr += ip+","
}
arr := []rune(ipStr)
if len(arr) > 0{
if arr[len(arr)-1] == ','{
arr = arr[:len(arr)-1]
}
}
ipStr = string(arr)
uri := fmt.Sprintf("mongodb://%v:%v@%v/?replicaSet=%v",
mongoUser, mongoPassword, ipStr, replicaSetName)
return uri, nil
}
main.go
package main
import (
"TestReplicaSet/utils"
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
)
type Test struct {
Info string `bson:"info"`
}
func main(){
client := utils.ConnectToMongo()
if client == nil{
return
}
cur, err := client.Database("test").Collection("test").Find(context.TODO(), bson.D{})
if err != nil{
fmt.Println(err)
return
}
defer cur.Close(context.TODO())
for cur.Next(context.TODO()){
test := &Test{}
if err := cur.Decode(test);err ==nil{
fmt.Println("info---->", test.Info)
}
}
}
1.8.2 真实部署环境(部署在不同的地理位置)
使用了两台物理机A(ip1)和B(ip2),在A开启两个mongod,B开启一个mongod
- 分别用xshell登录这两台机器,把mongo的docker镜像下载下来,分别在两台机器上创建文件夹
# 机器A
mkdir -p /home/mongodb/rs0-0/config /home/mongodb/rs0-0/datadir
mkdir -p /home/mongodb/rs0-1/config /home/mongodb/rs0-1/datadir
# 机器B
mkdir -p /home/mongodb/rs0-2/config /home/mongodb/rs0-2/datadir
- 开启mongod
第一个:
docker run -d -p 27017:27017 -v /home/mongodb/rs0-0/datadir:/data/db -v /home/mongodb/rs0-0/config:/data/configdb --name mymongo0 mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128
第二个:
docker run -d -p 27018:27017 -v /home/mongodb/rs0-1/datadir:/data/db -v /home/mongodb/rs0-1/config:/data/configdb --name mymongo1 mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128
第三个:
docker run -d -p 27017:27017 -v /home/mongodb/rs0-2/datadir:/data/db -v /home/mongodb/rs0-2/config:/data/configdb --name mymongo2 mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128
-
进入一个容器的mongo shell
1. docker exec -it mymongo0 /bin/bash #进入容器 2. mongo #进入mongo shell 3. 输入下面的配置文件,priority值越大的越可能成为主节点,在这里我让mymongo0成为了主节点 rsconf = { _id: "rs0", members: [ { _id: 0, host: "ip1:27017", priority: 2 }, { _id: 1, host: "ip1:27018", priority: 1 }, { _id: 2, host: "ip2:27019", priority: 1 } ] } 4. 初始化配置 rs.initiate(rsconf) 5. 设置密码 use admin; //进入admin数据库 admin不存在就创建: db.createUser( { user: 'admin', pwd: '******', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] }); admin存在就修改密码:db.changeUserPassword("admin","******"); 6. 退出mongo shell,在/data/configdb目录下创建keyfiles,并且把keyfiles复制到其他节点的/data/configdb目录下 openssl rand -base64 756 > /data/configdb/key_file chmod 400 /data/configdb/key_file 7. 删除容器,重启所有的mongd节点,启动命令中添加 --auth --keyFile=/data/configdb/key_file: docker stop mymongo0 docker rm mymongo0 例如: docker run --rm --name mymongo0 -d -p 27017:27017 -v /home/mongodb/rs0-0/datadir:/data/db -v /home/mongodb/rs0-0/config:/data/configdb mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128 --auth --keyFile=/data/configdb/key_file docker run --rm --name mymongo1 -d -p 27018:27017 -v /home/mongodb/rs0-1/datadir:/data/db -v /home/mongodb/rs0-1/config:/data/configdb mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128 --auth --keyFile=/data/configdb/key_file docker run --rm --name mymongo2 -d -p 27017:27017 -v /home/mongodb/rs0-2/datadir:/data/db -v /home/mongodb/rs0-2/config:/data/configdb mongo mongod --dbpath /data/db --replSet rs0 --oplogSize 128 --auth --keyFile=/data/configdb/key_file 8. 如果执行读写操作,返回了errorcode 13, 此错误是因为没有授权给admin用户对system.version表执行命令的权限,解决方法如下(http://blog.itpub.net/15498/viewspace-2145516): db.grantRolesToUser ( "admin", [ { role: "__system", db: "admin" } ] )
2. Sharding(分片)
定义
把数据分布在不同的服务器上
Sharding is a method for distributing data across multiple machines.