副本集运行机制

  副本集概述

      副本集是主/从复制集的一种特殊形式,可以在主服务器宕掉之后,可以将从服务器升级为主服务器,保证数据库的正常运行。副本集中的辅助成员数量在3.0之前有一定的限制,限制数在12,但在3.0之后,限制数更改为50。在50个辅助副本集成员中一次参与投票的个数为7。

  主副本和辅助副本

      主副本成员:客户端在进行写操作的时候,会将所有操作重定向到主成员。在出现主成员宕掉的情况时,任何优先级设置为1的辅助成员都可以精选为主成员。

      辅助副本成员:为了减轻服务器中数据库的压力,副本集可以将所有客户端读取操作定向到辅助成员,辅助成员可以进行投票或者成为主成员的候选。

  辅助副本成员类型

    优先级为0的成员

      所有将优先级设置为0的辅助成员都不会实现故障转移时成为主副本成员,但是它具有投票权,并且可以接受客户端的读取请求。  

    隐藏成员

      隐藏成员是优先级为0的辅助成员的一个子成员,除了不能升级为主成员外,客户端也不会接受客户端的请求。但是该成员会在故障转移时进行投票。

    延迟成员

     延迟成员可以有助于从人为错误中恢复数据,延迟时间应该大于维护窗口的时间,由于在延迟成员上的数据库具有滞后性,所以客户端在读取数据时,不应该从该辅助副本集获取数据,并且在故障转移时不应该成为主成员的候选。基于以上条件优先级应该设置为0,hiddle设置为true。

    仲裁成员

     仲裁成员只能够有投票权,但是没有选举权,在该成员中无数据读取的开销。

    非投票成员

     非投票成员可以成为主成员的候选,但是没有投票权。

    投票成员

     投票成员既可以投票权,也可以有选举权

  选举

    在进行选举操作时,副本集中的成员要想赢得选举,不仅需要获得多数票,还要获得过半的票数。也就是说只有获得的票数大于或者等于(X/2)+1,才能成为主节点。在原主节点重新启动后,它将成为副本集的辅助成员。切记副本集的成员数量一定要大于2,因为副本集的总票数只有两票,要成为主节点,就需要获得全部的两票,一旦有一台服务器挂掉,另一台服务器只能获得一票,无法获得多数票,也就无法成为主节点。在网络分区的情况下,主节点如果无法获得多数投票,就会降级为从节点,系统将无法进行数据操作。为了避免出现主节点降级或者辅助节点无法升级为主节点,可以使用一个仲裁节点,帮助其他节点成为主节点。

   选举过程

   现在有三台服务器,分别是A,B,C。每个成员都会每隔几秒进行一次通信,通信时会返回其他成员的基本信息,例如当前状态、是否有资格成为主节点等,并会将这些信息保存到本地的映射表。在返回的这些信息中,成员的部分信息会发生变化,发生变化时会出现一下情况:

   1、A成员是主节点,在B节点出现宕机情况后,为了保证A仍然是主节点,必须获得多数投票才能达成该目的。如果A节点无法获得多数票,就会降为从节点。

   2、A成员是从节点,在出现主节点宕掉的情况时,A节点会检查自己的基本信息,包括是否适合有选举全,是否有其他节点成为主节点。如果都没有问题,就会进行发起选举的请求,B和C在收到A的选举请求后会判断A是否有资格成为主节点,A是否有最近的数据库等,如果不符合其中的任何一条,就会终止A的选举;如果全部符合,就会进行下一阶段。进入下一阶段后,A会再次发起选举请求,B和C再次进行检查,如果全部符合,A就会实行选举锁,保证对其他节点进行投票。如果收到任何一张否决票,就会终止A的选举。

  数据复制过程

    在在mongo数据库中会保存一个oplog文件,该文件会存储所有的数据操作,但是只会存储修改操作,在执行数据同步时,辅助节点会获取主节点的oplog内容,来保持主从数据的一直。但是oplog的大小并不是无限增大的,在oplog达到一定大小,新的数据操作就会顶到最早的数据操作。oplog的大小一般是磁盘空间的5%,如果磁盘空间5%小于1GB,就会按照1GB进行分配。

    oplog的格式中包括:ts,ns,op,o

    ts是数据操作的时间戳。

    ns被执行操作的命名空间

    op保存对数据操作的类型,例如I代表插入

    o保存操作数据的基本信息

    全量同步

    全量同步的情况主要有两类:1、在加入一个新的辅助节点时,该辅助节点没有任何数据。2、在辅助节点中的数据过时,主节点的oplog的信息已经全部覆盖为新的信息。

    全量同步的步骤为:1、全部克隆主节点的数据,丢弃本节点的全部数据。2、同步oplog中的信息,对在全量同步过程中产生的数据变更进行修改。3、对数据库中的集合创建索引。

    增量同步

      在进行同步时,辅助节点N1会循环查找可能的目标节点,每个节点都会存在一个lastOpTimeWriten作为最近一次的数据操作时间,也就是op。如果在其他节点的lastOpTimeWriten的值大于N1的lastOpTimeWriten,就会纳入同步对象中。同时向副本集的目标成员发送ping请求,根据返回响应的时长获取时间最短的节点作为同步节点N2。同时本节点N1会查找本地的lastOpTimeWriten与N2的oplog中的ns进行对比,如果没有该值,就会执行全量同步,如果存在该值,就会从该处同步。如果N2的同步对象是N3的话,在N1通过N2同步时,N2也会打开从N3同步的通道,并且是两个,一个用来自身同步另一个用于N1的同步。操作流程如下图:

      

  故障转移

    如果发生故障时,分为两种请求,辅助节点发生故障,主节点发生故障。

    辅助节点发生故障,必须保证主节点此时能够获取多数投票,这样才能保证数据库的正常运行。如果故障很短,那么在重启后会进行增量同步,如果时间很长,该辅助节点会进行全量同步。

    主节点出现故障,主节点出现故障可能存在两种情况:1、主节点宕掉,此时如果大多数节点能够互相链接,会在优先级最高的辅助节点中选出主节点,如果有多个相同优先级的节点,可以通过具有最新数据的节点为主节点。2、出现网络分区,主节点的数据中心去其他数据中心的节点无法进行连接,此时其他数据中心的节点会重新选出主节点,并且原有的主节点会降级为从节点。为了保证出现网络分区时出现重新选举,可以通过仲裁节点来保证主节点不被转移。

   回滚

    回滚操作是在写操作还没有在整个副本集中完成同步,并进行还原。回滚的目的是为了保持整个副本集的数据保持一致。

    在写操作发送到主节点后,主节点发生故障,导致选举出新的节点。生成新的节点后,所有的辅助节点都会遍历新的主节点上的操作用以查找新主节点上没有的操作,如果存在,其他节点都会覆盖本节点上的新的操作,保证与新的主节点信息一致。

  一致性

    由于在数据写入主节点后,需要经过一段时间后,才会同步到辅助节点,所以在这段时间内,客户端在读取辅助节点上的该数据时,会与主节点的不一致,但是在经过一定的时间后,所有的辅助节点都会保证数据一致。

  可行的复制部署

    复制集需要通过一定的策略来保证高可用性:

    1、奇数个成员,为了在成员出现故障时保证主服务器不会降级或者在选举新主机时有多数票通过,需要有(X+1)/2的票数选举同一个成员为主节点。

    2、副本集的容错功能,也就是允许同时出现故障的成员数量。

    3、如果在现实需求中需要有备份功能,就会需要有延迟或者隐藏成员。

    4、如果应用程序以读取为主,可以增加辅助成员的节点来分散服务压力。

    5、当副本集成员跨数据中心分布,为了防止网络分区导致数据中心彼此之间不能通信,可以在同一个数据中心保存一半以上的成员。

  扩展读取

     辅助节点在扩展读取操作时,客户端在写入数据后,在短暂的时间内辅助节点会产生数据过时,对于那些无需最新数据的需求,辅助节点可以用于扩展读取,分散主节点的复合。为了提升读取吞吐量,降低延迟,可以通过以下策略:

      1、如果应用程序按照地理位置分布,可以使用夸地理位置分布的副本集,应用程序读取最近位置的辅助节点数据。

      2、如果应用程序需要获取最新的数据,可以从主数据库服务器上读取数据。

      3、如果应用程序中有两种获取数据的需求,获取最新数据和无需最新数据,可以按照读取偏好,为不同的需求设计不同的偏好。偏好模式如下:

      primary:从主节点读取数据。

      primaryPreferred:从主节点读取数据,主节点不可用,从辅助节点读取数据。

      secondary:从辅助节点读取数据。

      secondaryPreferred:从辅助节点读取数据,辅助节点不可用,从主节点读取数据。

      nearest:从最近的副本集获取数据。

  应用程序写关注

      在进行写操作时,主节点会返回响应信息给客户端,用以说明数据写入成功,但是为了保证数据能够在主节点宕掉后其他节点能够同步到该数据,可以在客户端写入数据是指定多个节点写入成功后才返回给客户端响应信息。设置多个节点写入成功的参数为w。但是为了提高写入效率,不能够将w参数设置的太大。

副本集配置

    1、首先安装三个mongo实例,分别为mongo1,mongo2,mongo3。并为mongo的配置文件配置如下信息:

    dbpath=

    logpath=

    port=

    journal=

    replSet=true

    2、启动各个实例

    3、初始化副本集:rs.initiate()

    4、添加副本集成员:rs.initiate({“_id:复制集名称”,“members”:[{"_id":0,"host":"ip:port","priorty":"优先级"},{"_id":1,"host":"ip:port","priorty":"优先级"},......,{"_id":n,"host":"ip:port","priorty":"优先级"},{"_id":1,"host":"ip:port","priorty":"优先级,"arbiterOnly" : true"}]})

    5、添加新的成员:rs.add("ip:port")

    6、添加仲裁成员:rs.addArb("ip:port")

    7、查看副本集状态:rs.status()

    8、删除副本集成员:rs.remove("ip:port")

    9、设置成员优先级:cfg = rs.conf()、cfg.members[0].priority = 0.5、cfg.members[1].priority = 2、cfg.members[2].priority = 2、rs.reconfig(cfg)

    10、强制进行一次选举:rs.stepDown()