glusterfs通过对replica卷的所有子卷写相同的数据实现冗余功能。当glusterfs的客户端对replica卷有任何写操作(包括数据与元数据)时,glusterfs通过对所有的子卷加锁进行同步。
glusterfs的client每次对server端做操作,都需要经过多层translator,若client端完成一次完整的处理需要向server端发送多次不同的逻辑请求,而由于glusterfs的translator采用异步回调机制,导致不可能在同一个上下文顺序的向server端发送多次不同的逻辑请求,所以只能采用在当前请求的callback函数中处理下一个请求的方法。图6-1简要描述了上述过程
图6-1
下面对glusterfs replica卷的基本流程进行分析。
1. glusterfsreplica卷创建文件的基本流程:
1) 对每个up(正常工作的子卷)的子卷加非阻塞entry锁,只有所有up的子卷加锁成功,才算加锁成功。
2) 如果非阻塞锁失败,则加阻塞锁。
3) 对每个加锁成功的子卷对应的扩展属性设置changelog(扩展属性相应区域加1)。
4) 在所有up的子卷上创建文件。
5) 当所有up的子卷创建文件之后,对这些子卷对应的扩展属性清除changelog(扩展属性相应区域减1)
6) 对所有up的子卷解锁。
2. glusterfsreplica卷创建文件的详细流程:如图6-2所示
图 6-2
1. glusterfsreplica卷写文件的基本流程如下:
1) 对每个up(正常工作的子卷)的子卷对应文件加非阻塞inode锁,只有所有up的子卷加锁成功,才算加锁成功。
2) 如果非阻塞锁失败,则加阻塞锁。
3) 对每个加锁成功的子卷对应文件扩展属性设置changelog(扩展属性相应区域加1)。
4) 向所有up的子卷上对应文件写数据。
5) 当所有up的子卷写完数据后,对这些子卷对应文件扩展属性清除changelog(扩展属性相应区域减1)
6) 对所有up的子卷解锁。
2. glusterfsreplica卷写文件的详细流程:如图6-3所示
图 6-3
在glusterfsreplica的io流程中,共有两类操作:读操作和写操作。
1. 读操作包括从server端读数据、元数据,entry等信息,读操作不会对server端文件系统相关数据进行修改。其流程相对简单,在此不再赘述。
2. 写操作包括向server端写数据、元数据、entry等信息。该操作会对sever端文件系统相关数据进行修改,为保证各副本的数据一致,以及便于节点故障后的数据恢复。glusterfs replica卷在处理相关写操作时,基本都按照以下流程:
1) 对每个up(正常工作的子卷)的子卷加锁,只有所有up的子卷加锁成功,才算加锁成功。
2) 如果非阻塞锁失败,则加阻塞锁。
3) 对每个加锁成功的子卷对应扩展属性设置changelog(扩展属性相应区域加1)。
4) 对所有up的子卷上做相关操作。
5) 当所有up的子卷相关操作完成后,对这些子卷对应扩展属性清除changelog(扩展属性相应区域减1)
6) 对所有up的子卷解锁。
glusterfs在解决replica卷故障处理的机制中,利用了文件系统的扩展属性,针对replica卷的故障处理,对每个文件设置了特定的扩展属性,故障恢复时,利用这些特定扩展属性,选择出一个源节点(数据正常的节点)进行数据恢复。
例如图6-4为查看replica卷中的test文件故障处理相关扩展属性结果:
图 6-4
glusterfsreplica故障处理相关的每个扩展属性一共包括key和value两部分。其中key的格式为宏AFR_XATTR_PREFIX定义的前缀(trusted.afr)加每个子卷的名。value为12子节的内存区,这12子节的内存区被分为三个子区域,每个子区域占4个子节,为一个init类型的数据区。这三个子区域分为为data pending,metadata pending,entry pending,作用是记录对文件的相关数据操作情况。
图6-5描述了glusterfsreplica故障处理相关扩展属性的格式
图 6-5
glusterfsreplica处理写相关(包括数据,元数据,entry)操作时,都是通过构造不同的事务来处理,由于写操作可能会涉及到文件系统的数据,元数据,entry。glusterfsreplica据此对事务进行分类,共分为AFR_DATA_TRANSACTION、AFR_METADATA_TRANSACTION、AFR_ENTRY_TRANSACTION(AFR_ENTRY_RENAME_TRANSACTION)三种类型,处理某种类型的事务时,就会对扩展属性的value中对应的区间进行修改。如图6-6所示:
图 6-6
子卷中每个文件针对故障处理的扩展属性个数与replica卷的子卷(副本)个数相同。
图6-7为一个两副本卷的每个副本中同一文件的故障处理扩展属性:
图 6-7
每个文件针对故障处理的扩展属性个数与replica卷的子卷(副本)个数相同,其目的是让各个子卷相互记录,除了记住自身卷的操作状态,也要记住replica卷中其他子卷的操作状态(包括up状态和down状态的子卷),这样replica卷中的各子卷互相监督。当故障恢复时根据各自记录的扩展属性状态从所有的子节点中选出一个源节点,以源节点为标准进行数据恢复。图6-8是一个两副本卷的各子卷扩展属性互相记录的描述。
图 6-8
glusterfs replica卷的changlog操作,其实就是对每个子卷针对故障处理的扩展属性进行修改。整个changelog操作分为changelog_pre和changelog_post两个阶段。由前面的glusterfs replica io流程分析可知,client端对replica卷完成一个写操作(包括数据、元数据(inode相关)、entry三种情况),会对子卷在加锁的情况下执行三阶段操作:分别为changelog_pre、实际的写操作、changelog_post。
1. changelog_pre阶段
1) client端根据replica卷的子卷个数创建pending矩阵,只将矩阵中当前事务类型对应的区域设置为1。
2) client端根据pending矩阵,对每个up的子卷构造针对故障处理的扩展属性(这里的扩展属性也包括当前子卷记录其它所有子卷的扩展属性)。
3) client端对每个up的子卷发送构造完成的扩展属性,让子卷累加扩展属性。
4) 每个子卷收到client端发来的扩展属性之后,将对应文件或者目录原有的扩展属性与收到的扩展属性逐个区域进行累加(相同key值的扩展属性进行累加)。
2. 实际写操作阶段
1) 执行实际的写操作,这里的写操作可能是写数据,写元数据(inode相关),写entry。
3. changelog_post阶段
1) client端找到changelog_pre阶段构建的pending矩阵,若子卷执行过changlog_pre,则将子卷在pending矩阵中当前事务类型对应的区域设置为-1,否则,将子卷在pending矩阵中当前事务类型对应的区域设置为0。
1) client端根据pending矩阵,对每个up的子卷构造针对故障处理的扩展属性(这里的扩展属性也包括当前子卷记录其它所有子卷的扩展属性)。
2) client端对每个up的子卷发送构造完成的扩展属性,让子卷累加扩展属性。
3) 每个子卷收到client端发来的扩展属性之后,将对应文件或者目录原有的扩展属性与收到的扩展属性逐个区域进行累加(相同key值的扩展属性进行累加)。
总结来说,changelog_pre阶段就是让扩展属性中,当前事务类型对应的区域加1,changlog_post阶段是让扩展属性中,当前事务类型对应的区域加-1。所以当replica卷所有的子卷都正常工作时,每个子卷中所有文件针对故障处理的扩展属性(包括记录其它子卷的扩展属性)都是0。
举例说明,glusterfs replica卷的changlog变化情况。
1. 有两个副本的replica卷,两个副本卷都正常时,事务类型为AFR_ENTRY_TRANSACTION 的changlog变化。
1) changelog_pre操作
图 6-9
2) changelog_post操作
图 6-10
2. 有两个副本的replica卷,其中一个副本卷故障,事务类型为AFR_ENTRY_TRANSACTION 的changlog变化。
1) changelog_pre操作
图 6-11
2) changelog_post操作
图 6-12
glusterfs判断replica卷是否发生脑裂是在数据修复阶段,完整的数据修复共分四步操作:判断是否需要修复,标记source节点,选择一个source节点作为修复标准,以选择的source节点为标准对其他非source节点进行修复。此处主要对前两步操作说明。
1. 判断是否需要修复
1) glusterfs的故障修复也是基于不同事务的,共有三种类型的修复事务:AFR_SELF_HEAL_DATA,AFR_SELF_HEAL_METADATA,AFR_SELF_HEAL_ENTRY,本阶段判断是否需要修复,并确定修复事务类型。
2. 标记source节点
1) 从所有子卷中读取要修复文件针对故障处理的所有扩展属性。
2) 根据子卷个数,创建pending矩阵。
3) 用从子卷中读取的扩展属性初始化pending矩阵,每个子卷扩展属性中对应事务类型的字段按照key值顺序初始化子卷对应的pending矩阵。图6-13描述两个副本卷修复事务类型为entry的pending矩阵初始化流程。
图 6-13
4) 根据初始化后的pending矩阵,判断子卷的类型。pending矩阵中,每一行表示一个子卷的状态(包括指向自身以及其它子卷的状态),若该行所有的字段全为0,则该子卷类型为innocent;否则,若该行指向自身的字段非0,表示该子卷指责自己,则该子卷类型为fool;否则,若该行指向自身的字段为0,表示该子卷认为自身正常,则该子卷类型为wise。如图6-14描述有3个副本的replica卷,这三个子卷分别为innocent、fool、wise的情况。
图 6-14
当某个子卷类型为wise,并且无其它子卷指责该卷,则该子卷为wisdom,可以作为数据恢复的源节点。
glusterfs replica故障恢复过程中,在标记souce节点时,若发现wise类型的子卷互相指责,在所有的子卷中找不到wisdom的卷,则说明脑裂发生,glusterfs无法从所有的子卷中选择一个源节点进行故障恢复。图6-15描述了两副本卷replica发生脑裂时的场景。
图 6-15
在网络故障、节点因为处理器忙或者其他原因导致停止响应等场景中,容易发生脑裂。只要因为某个事故导致集群中的节点相互指责,就会出现脑裂。