一个副本集ReplicaSet一般由一组mongod实例组成,这组mongod实例协调配合工作,共同向外提供高可用的数据库访问服务。
副本集中的不同节点虽然都是mongod实例,但是角色上却有不同,一般分为三种:主节点、副本节点和仲裁者节点。
主节点:负责所有的数据库写操作,默认情况下,主节点也负责处理所有的数据库读操作;
副本节点:负责同步主节点的数据操作日志更新本地数据库,从而保证副本节点的数据和主节点上的数据的一致性;副本节点的从某种意义上来讲有点像赛跑,永远在追赶主节点的数据操作;
仲裁节点:不负责保存具体的数据,只是在副本集进行主节点选举时提供自己的一个选票而已。
除了上面三个角色的节点外,还有几种其他节点,下面简单描述一下:
Secondary-Only:和副本节点一样保存了主节点的数据副本,但是在任何情况下都成为不了Primary主节点;
Hidden:这种类型的节点对于客户端程序是不可见的,同样不能成为Primary主节点,但是可以参与投票;
Delayed:这种类型的节点可以通过人为的设置,可以指定一个时间来延迟从主节点同步数据,Delayed成员主要用于从一些误操作中恢复旧数据,并且肯定不能成为主节点而且是Hidden的;
一般情况下,一个最小化的副本集由一个主节点、一个副本节点和一个仲裁节点组成,但是在实际使用过程中,大多数采用的是由一个主节点和两个副本节点组成的。
一个最小化副本集的结构图如下所示:
在上图中,副本集中的主节点负责处理来自客户端的所有读写操作,并将所有的修改数据库的操作以日志Oplog的方式记录在本地;副本节点按照一定的频率主动异步的从主节点从不操作日志并作用于自己本机的数据库上,从而保证副本节点和主节点的数据一致性。副本集中的各个节点之间每2秒进行一个心跳请求,看看副本集中的其他节点的状态。
正常情况下,读写操作是操作在主节点上,写操作我们是无法进行控制的,只能作用在主节点上,但是对于读操作我们确实可以控制的(从副本节点上读取数据,从而达到读写分离),这时候我们就需要了解ReadPreference了,下面简单列下都有哪些模式可以供我们选择:
(1)primary:默认方式,所有的读操作都从主节点获取;
(2)primaryPerferred:在大多数场景下直接从主节点进行读操作,如果主节点读操作不可用时,则从副本节点进行读取;
(3)secondary:所有的读操作都只从副本节点进行读取;
(4)secondaryPreferred:在大多数场景下,读操作都从副本节点进行读操作,如果副本节点不可用,那么考虑从主节点进行读取;
(5)nearest:所有的读操作从网络延迟最小的节点上进行读操作;
上面的结构是一个最小化的副本集结构,可以对外提供高可用的数据库访问能力,但是当主节点崩溃后或者暂时不可用时,整个副本集会发生什么呢?
(1)如果主节点发现自己和副本集中大多数节点无法连接时,为了保证数据一致性,会将自己降级为副本节点,从而阻止有操作对数据库进行修改;
(2)如果主节点崩溃了不可用了,那么副本集会内会进行一次选举操作,重新选举产生一个主节点;
关于选举的过程如下所示:
上图中,主节点Primary假设不可用了,那么另外两个副本节点在发现与主节点无法进行心跳时(正常情况下,每个节点每2秒跟其他节点进行一次心跳测试,如果在10s内未收到对方节点的回复,那么认为对方节点不可用),有资格成为主节点的副本节点就会向其他节点发起一个选举提议,基本的意思就是“我觉得我能成为主节点,你觉得呢?”,而其他节点在收到选举提议后会判断下面几个条件:
(1)副本集中是否有其他节点已经是primary了?
(2)自己的数据是否比请求成为主节点的副本节点上的数据更新?
(3)副本集中其他节点的数据是否比请求成为主节点的副本节点的数据更新?
如果上面三个条件中只要有一个条件满足,那么都会认为对方的提议不可行,于是返回一个返回包给请求节点说“我觉得你成为primary不合适,停止选举吧!”,请求节点只要收到其他任何一个节点返回不合适,都会立刻停止选举,并将自己保持在secondary角色;但是如果上面三个条件都不满足,那么就会在返回包中回复说“我觉得你可以^_^”,那么此时请求包就会进入选举的第二阶段。请求节点会向其他节点发送一个确认的请求包,基本意思就是“我宣布自己是primary了,有人反对没?”,如果在这次确认过程中其他节点都没人反对,那么请求节点就将自己升级为primary节点,所有节点在30秒内不再进行其他选举投票决定;如果有节点此时认为请求节点不适合做primary,那么请求节点在收到反对回复后会保持自己的节点角色依然是secondary。