MongoDB开发深入之三:复制
复制是基于操作日志oplog,相当于MySQL中的二进制日志,只记录发生改变的记录,复制是将主节点的oplog日志同步并应用到其他从节点的过程。
首先要理解两个概念:
1、复制:提供冗余和高可用性;
2、拆分分片:提供水平扩容;
复制提供冗余并增加数据可用性。通过在不同数据库服务器上提供多个数据副本,复制可以提供对单个数据库服务器丢失的级别容错。
主要概念:
- 副本集
- OPLOG(operations log)
- 多副本策略(超过3个)
- 副本集选举
- 写关注
- 读偏好
副本集
副本集的成员是:
- 主节点:主要接收所有写操作。
- 辅助节点:辅助节点从主节点复制以维护相同的数据集。
副本集包含:
- 多个数据承载节点(主节点、辅助节点);
- 可选的一个仲裁节点;
在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要节点。
如下图:
可以将一个额外的mongod实例添加到副本集中作为仲裁者。确保副本集具有奇数个投票成员。如果您拥有偶数个投票成员,请部署仲裁者以使该集合具有奇数个投票成员。
仲裁者不维护数据集。仲裁者的目的是通过响应其他副本集成员的心跳和选举请求来维护副本集中的仲裁。因为它们不存储数据集,所以仲裁器可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。如果您的副本集具有偶数个成员,请添加仲裁者以获得主要选举中的大多数投票。
如下图:
自动故障转移
当主节点与集合中的其他成员通信的时间超过配置的electionTimeoutMillis期间(默认为 10 秒)时,符合条件的辅助节点用于选举以指定自己作为新主节点。
如下图:
在选举成功完成之前,副本集不能处理写操作。
默认情况下,clients 从主节点读取;但是,clients 也可以指定阅读偏好来向辅助节点读取。
异步复制到辅助节点意味着从辅助节点读取可能会返回不反映主节点上数据的(最后一次更新状态的)真实值的数据。
OPLOG
OPLOG(operations log)是一个特殊的上限集合,它保存了修改存储在数据库中的数据的所有操作的历史记录。 复制是将主节点的oplog日志同步并应用到其他从节点的过程。
MongoDB 在主节点上应用数据库操作(添加、删除等),然后在 oplog 上记录操作,然后辅助节点异步操作中复制并应用这些操作。为了便于复制,所有副本集成员都会向所有其他成员发送心跳(ping)。任何辅助节点都可以导入来自任何其他成员的 oplog 条目。oplog 中的每个操作都是幂等。也就是说,无论是对目标数据集应用一次还是多次,oplog 操作都会产生相同的结果。
oplog 大小取决于存储引擎:
多副本策略(超过3个)
以具有 5 个成员的副本集为例,一些可能的成员分布包括:
- 两个数据中心:数据中心 1 的三个成员和数据中心 2 的两个成员。
- 如果数据中心 1 关闭,则副本集将变为 read-only。
- 如果数据中心 2 关闭,则副本集仍然可写,因为数据中心 1 中的成员可以创建多数。
- 三个数据中心:数据中心 1 的两个成员,数据中心 2 的两个成员和数据中心 3 的一个成员。
- 如果任何数据中心发生故障,副本集仍然可写,因为其余成员可以举行选举。
如图
例如,以下 5 个成员副本集将其成员分布在三个数据中心
副本集选举
触发副本集选举的事件如下:
- 将新节点添加到副本集;
- 启动副本集;
- 使用rs.stepDown()或rs.reconfig()等方法执行副本集维护;
- 次节点失去与主节点的连接超过配置的超时(默认为 10 秒)。
在选举成功完成之前,副本集不能处理写操作。如果此类查询配置为run on secondaryaries,则副本集可以继续提供读取查询。
心跳
副本集成员每两秒发送一次心跳(ping)。如果心跳在 10 秒内没有回复,则其他成员会将其标记为无法访问。
选举策略
选举受节点间心跳、优先级、最新的oplog时间等多种因素影响。
- 心跳: 复制集中的所有members之间都互相建立心跳连接,且每隔两秒发送一次心跳,如果未在10秒内收到回复,则此member将会被标记为“不可用”,Secondary(前提是可被选为Primary)会发起新的Primary选举,而其他能正常收到心跳反馈的Secondary能否决新的Primary选举。
- 优先级: 优先级即Priority值,每个member都有权重值,默认为都为1,members倾向于选举权重最高者。上述提到,priority为0的member不能被选举为primary且不能发起选举;只要当前primary的权重最高或者持有最新oplog数据的secondaries没有比它更高的权重时,集群不会触发选举。当Primary发现有优先级更高Secondary,并且该Secondary的数据落后在10s内,则Primary会主动降级,让优先级更高的Secondary有成为Primary的机会。
- Optime: 当前member已经从primary的oplog中应用的最后一个operation的时间戳(此时间戳由primary生成,在oplog中每个操作记录都有);一个member能成为primary的首要条件就是在所有有效的members中它持有最新的optime。
- 多数派连接: 一个member要成为primary,它必须与“多数派”的其他members建立连接,如果未能与足够多的member建立连接,事实上它本身也无法被选举为primary;多数派参考的是“总票数”,而不是member的个数,因为我们可以给每个member设定不同的“票数”。假设复制集内投票成员数量为N,则大多数为 N/2 + 1。
写关注
用来描述数据库写操作返回信息的保证级别。写关注的强度决定了保证的级别。当插入,更新,删除是弱写关注的时候,操作返回的速度则快。如果写关注是弱的,在一些写失败的时候,写操作可能不会持久。写关注级别越强,客户端则需要越长的时间来等待MongoDB确认写操作。
例如对于副本集,w:1的默认写入关注要求在返回写入关注确认之前,只有主副本集成员确认写入,才予以返回。
如图:
为任何给定的写操作选择理想的写入关注取决于您的应用的性能目标和数据持久性要求。关键数据,可以牺牲性能的,就设置大一些,非关键数据,w:1的默认写入关注就可以了。
以下操作包括insert()方法的writeConcern选项。该操作使用wtimeout写入关注参数指定“多数”写入关注和 5 秒超时,以便操作不会无限期地阻塞。
db.products.insert( { item: "envelopes", qty : 100, type: "Clasp" }, { writeConcern: { w: "majority" , wtimeout: 5000 } } )
例如,在 3-member 副本集中,操作将需要来自 3 个成员中的 2 个的确认。如果稍后缩放副本集以包括两个额外的投票节点,则相同的操作将需要来自 5 个副本集成员中的 3 个的确认。如果主要没有在wtimeout限制内 return 写入关注确认,则写入操作将失败并出现写入问题错误。
读偏好
在某些情况下,将读请求发送给副本集的备份节点是合理的,例如,单个服务器无法处理应用的读压力,就可以把查询请求路由到可复制集中的多台服务器上。
5种读偏好模式:
- primary — 这是默认的设置,表明只从可复制集的主节点读取数据,因此具有强一致性。如果可复制集有问题,并且没有可选举的从节点,就表示出现错误。
- premaryPreferred — 设置了此参数的驱动会从主节点读取数据,除非某些原因使主节点不可用或者没有主节点,此时它会从从节点读取数据。此种设置下,读请求无法保证一致性。
- secondary — 这个设置告诉驱动应该一直从从节点读取数据。这种设置对于我们想确保读请求不会影响主节点的写入请求时非常有用。如果没有可用的从节点,读请求会抛出异常。
- secondarypreferred — 读请求会发出到从节点,除非没有从节点可用,此时才会从主节点读取。
- nearest – 驱动会尝试从最近的可复制集成员节点读取读取数据,通过网络延迟判断。可以是主节点也可以是从节点。因此读请求只会发送给驱动认为最快通信的节点。
primary是唯一一个可以确保读一致的模式。因为写请求首先在主节点完成,从服务器的更新会有些延迟,所以可能在从节点无法找到刚刚在主节点写入的文档数据。
考虑一下不需要读一致的场景,可以考虑使用其他模式:
- 1、为地理位置分散的应用程序提供数据读取(不要求一致性)。
- 如果您在多个数据中心中有服务器,则可以考虑使用地理分布的副本集并使用secondary或nearest首选项。
- 2、在故障转移期间维护可用性
- 如果希望应用在正常情况下从主数据库读取,则使用primaryPreferred,但是当主数据库不可用时允许从辅助数据库读取过时数据。
实操
通过发出类似于以下内容的命令为每个成员创建必要的数据目录:
mkdir -p /srv/mongodb/rs0-0 /srv/mongodb/rs0-1 /srv/mongodb/rs0-2
这将创建名为“rs0-0”,“rs0-1”和“rs0-2”的目录,它们将包含实例的数据库 files。
第一位成员: mongod --replSet rs0 --port 27017 --bind_ip localhost,<ip address of mongod host> --dbpath /srv/mongodb/rs0-0 --smallfiles --oplogSize 128 第二位成员: mongod --replSet rs0 --port 27018 --bind_ip localhost,<ip address of mongod host> --dbpath /srv/mongodb/rs0-1 --smallfiles --oplogSize 128 第三名成员: mongod --replSet rs0 --port 27019 --bind_ip localhost,<ip address of mongod host> --dbpath /srv/mongodb/rs0-2 --smallfiles --oplogSize 128
这会将每个实例作为名为rs0的副本集的成员启动,每个副本在不同的 port 上运行,并使用--dbpath设置指定数据目录的路径。如果您已在使用建议的端口,请选择不同的端口。
实例绑定到 host 的 localhost 和 ip 地址。
--smallfiles和--oplogSize设置减少了每个mongod实例使用的磁盘空间。这是测试和开发部署的理想选择,因为它可以防止机器过载。
mongo --port 27017
在mongo shell 中,使用rs.initiate()启动副本集。您可以在mongo shell 环境中创建副本集 configuration object,如下面的示例所示:
rsconf = { _id: "rs0", members: [ { _id: 0, host: "<hostname>:27017" }, { _id: 1, host: "<hostname>:27018" }, { _id: 2, host: "<hostname>:27019" } ] }
用系统的主机名替换<hostname>,然后将rsconf文件传递给rs.initiate(),如下所示:
rs.initiate( rsconf )
通过发出以下命令显示当前副本 configuration:
rs.conf()
{ "_id" : "rs0", "version" : 1, "protocolVersion" : NumberLong(1), "members" : [ { "_id" : 0, "host" : "<hostname>:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 }, { "_id" : 1, "host" : "<hostname>:27018", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 }, { "_id" : 2, "host" : "<hostname>:27019", "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, "getLastErrorModes" : { }, "getLastErrorDefaults" : { "w" : 1, "wtimeout" : 0 }, "replicaSetId" : ObjectId("598f630adc9053c6ee6d5f38") } }
使用rs.status()操作检查副本集的状态。
目前维护的开源产品:https://gitee.com/475660