复制集简介
mongodb复制集由一组mongod实例(进程)组成,包含一个primary节点和多个secondary节点,mongodb driver(客户端)的所有数据都写入primary,secondary从primary同步写入的数据,以保持复制集内所有成员存储相同的数据集,提供数据的高可用。
下图(图片源于mongodb官方文档)是一个典型的mongdb复制集,包含一个primary节点和2个secondary节点。
primary选举
复制集通过replsetinitiate命令(或mongo shell的rs.initiate())进行初始化,初始化后各个成员间开始发送心跳消息,并发起priamry选举操作,获得『大多数』成员投票支持的节点,会成为primary,其余节点成为secondary。
初始化复制集
config = { _id : "my_replica_set", members : [ {_id : 0, host : "rs1.example.net:27017"}, {_id : 1, host : "rs2.example.net:27017"}, {_id : 2, host : "rs3.example.net:27017"}, ] } rs.initiate(config)
『大多数』的定义
假设复制集内投票成员(后续介绍)数量为n,则大多数为 n/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出primary,复制集将无法提供写服务,处于只读状态。
投票成员数 | 大多数 | 容忍失效数 |
---|---|---|
1 | 1 | 0 |
2 | 2 | 0 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 3 | 2 |
6 | 4 | 2 |
7 | 4 | 3 |
通常建议将复制集成员数量设置为奇数,从上表可以看出3个节点和4个节点的复制集都只能容忍1个节点失效,从『服务可用性』的角度看,其效果是一样的。(但无疑4个节点能提供更可靠的数据存储)
特殊的secondary
正常情况下,复制集的seconary会参与primary选举(自身也可能会被选为primary),并从primary同步最新写入的数据,以保证与primary存储相同的数据。
secondary可以提供读服务,增加secondary节点可以提供复制集的读服务能力,同时提升复制集的可用性。另外,mongodb支持对复制集的secondary节点进行灵活的配置,以适应多种场景的需求。
arbiter
arbiter节点只参与投票,不能被选为primary,并且不从primary同步数据。
比如你部署了一个2个节点的复制集,1个primary,1个secondary,任意节点宕机,复制集将不能提供服务了(无法选出primary),这时可以给复制集添加一个arbiter节点,即使有节点宕机,仍能选出primary。
arbiter本身不存储数据,是非常轻量级的服务,当复制集成员为偶数时,最好加入一个arbiter节点,以提升复制集可用性。
priority0
priority0节点的选举优先级为0,不会被选举为primary
比如你跨机房a、b部署了一个复制集,并且想指定primary必须在a机房,这时可以将b机房的复制集成员priority设置为0,这样primary就一定会是a机房的成员。(注意:如果这样部署,最好将『大多数』节点部署在a机房,否则网络分区时可能无法选出primary)
vote0
mongodb 3.0里,复制集成员最多50个,参与primary选举投票的成员最多7个,其他成员(vote0)的vote属性必须设置为0,即不参与投票。
hidden
hidden节点不能被选为主(priority为0),并且对driver不可见。
因hidden节点不会接受driver的请求,可使用hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务。
delayed
delayed节点必须是hidden节点,并且其数据落后与primary一段时间(可配置,比如1个小时)。
因delayed节点的数据比primary落后一段时间,当错误或者无效的数据写入primary时,可通过delayed节点的数据来恢复到之前的时间点。
数据同步
primary与secondary之间通过oplog来同步数据,primary上的写操作完成后,会向特殊的特殊集合写入一条oplog,secondary不断的从primary取新的oplog并应用。
因oplog的数据会不断增加,local.oplog.rs被设置成为一个,当容量达到配置上限时,会将最旧的数据删除掉。另外考虑到oplog在secondary上可能重复应用,oplog必须具有幂等性,即重复应用也会得到相同的结果。
如下oplog的格式,包含ts、h、op、ns、o等字段
{ "ts" : timestamp(1446011584, 2), "h" : numberlong("1687359108795812092"), "v" : 2, "op" : "i", "ns" : "test.nosql", "o" : { "_id" : objectid("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" } }
- ts: 操作时间,当前timestamp + 计数器,计数器每秒都被重置
- h:操作的全局唯一标识
- v:oplog版本信息
- op:操作类型
- i:插入操作
- u:更新操作
- d:删除操作
- c:执行命令(如createdatabase,dropdatabase)
- n:空操作,特殊用途
- ns:操作针对的集合
- o:操作内容,如果是更新操作
- o2:操作查询条件,仅update操作包含该字段
secondary初次同步数据时,会先进行init sync,从primary(或其他数据更新的secondary)同步全量数据,然后不断通过从primary的local.oplog.rs集合里查询最新的oplog并应用到自身。
init sync过程包含如下步骤
t1时间,从primary同步所有数据库的数据(local除外),通过listdatabases + listcollections + clonecollection敏命令组合完成,假设t2时间完成所有操作。
从primary应用[t1-t2]时间段内的所有oplog,可能部分操作已经包含在步骤1,但由于oplog的幂等性,可重复应用。
根据primary各集合的index设置,在secondary上为相应集合创建index。(每个集合_id的index已在步骤1中完成)。
oplog集合的大小应根据db规模及应用写入需求合理配置,配置得太大,会造成存储空间的浪费;配置得太小,可能造成secondary的init sync一直无法成功。比如在步骤1里由于db数据太多、并且oplog配置太小,导致oplog不足以存储[t1, t2]时间内的所有oplog,这就secondary无法从primary上同步完整的数据集。
修改复制集配置
当需要修改复制集时,比如增加成员、删除成员、或者修改成员配置(如priorty、vote、hidden、delayed等属性),可通过replsetreconfig命令(rs.reconfig())对复制集进行重新配置。
比如将复制集的第2个成员priority设置为2,可执行如下命令
cfg = rs.conf(); cfg.members[1].priority = 2; rs.reconfig(cfg);
细说primary选举
primary选举除了在复制集初始化时发生,还有如下场景
- 复制集被reconfig
- secondary节点检测到primary宕机时,会触发新primary的选举
- 当有primary节点主动stepdown(主动降级为secondary)时,也会触发新的primary选举
primary的选举受节点间心跳、优先级、最新的oplog时间等多种因素影响。
节点间心跳
复制集成员间默认每2s会发送一次心跳信息,如果10s未收到某个节点的心跳,则认为该节点已宕机;如果宕机的节点为primary,secondary(前提是可被选为primary)会发起新的primary选举。
节点优先级
- 每个节点都倾向于投票给优先级最高的节点
- 优先级为0的节点不会主动发起primary选举
- 当primary发现有优先级更高secondary,并且该secondary的数据落后在10s内,则primary会主动降级,让优先级更高的secondary有成为primary的机会。
optime
拥有最新optime(最近一条oplog的时间戳)的节点才能被选为主。
网络分区
只有更大多数投票节点间保持网络连通,才有机会被选primary;如果primary与大多数的节点断开连接,primary会主动降级为secondary。当发生网络分区时,可能在短时间内出现多个primary,故driver在写入时,最好设置『大多数成功』的策略,这样即使出现多个primary,也只有一个primary能成功写入大多数。
复制集的读写设置
read preference
默认情况下,复制集的所有读请求都发到primary,driver可通过设置read preference来将读请求路由到其他的节点。
- primary: 默认规则,所有读请求发到primary
- primarypreferred: primary优先,如果primary不可达,请求secondary
- secondary: 所有的读请求都发到secondary
- secondarypreferred:secondary优先,当所有secondary不可达时,请求primary
- nearest:读请求发送到最近的可达节点上(通过ping探测得出最近的节点)
write concern
默认情况下,primary完成写操作即返回,driver可通过设置[write concern()来设置写成功的规则。
如下的write concern规则设置写必须在大多数节点上成功,超时时间为5s。
db.products.insert( { item: "envelopes", qty : 100, type: "clasp" }, { writeconcern: { w: majority, wtimeout: 5000 } } )
上面的设置方式是针对单个请求的,也可以修改副本集默认的write concern,这样就不用每个请求单独设置。
cfg = rs.conf() cfg.settings = {} cfg.settings.getlasterrordefaults = { w: "majority", wtimeout: 5000 } rs.reconfig(cfg)
异常处理(rollback)
当primary宕机时,如果有数据未同步到secondary,当primary重新加入时,如果新的primary上已经发生了写操作,则旧primary需要回滚部分操作,以保证数据集与新的primary一致。
旧primary将回滚的数据写到单独的下,数据库管理员可根据需要使用mongorestore进行恢复。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。