Glusterfs:冗余(Replication)源码分析

1.概述.

冗余技术(redundancy)是分布式存储系统的一个重要技术手段。最简单、最传统的冗余存储技术是副本技术(replication):通过拷贝多个原始数据,并将它们分别存储在不同节点上,从而增加数据可靠性。Gluster文件系统通过多分冗余来保持其系统文件的高可用性。

 

Glusterfs冗余示意图

 

副本技术存在显著缺点是存储开销巨大——为应对部分数据丢失的风险而需要将整个原始文件拷贝存储,为此,为提高存储效率,纠删码(erasure coding)技术被引入到分布式系统中。

因此这方面,有待我们改进,通过差分编码等实现gluster的高可用性。

逻辑卷的创建的时候,可以指定冗余的份数,则以后上传一份文件就会上传到多个存储节点。

2.功能验证

1.冗余卷的配置文件如下:


    2.将文件glusterfs.txt上传到glusterfs存储系统,如下图上传了3份文件。并且确认3份数据内容一样


3.查看afr方式上传的文件的元数据相关信息:

由显示的元数据键可知,afr的三个子卷名均记录在了扩展属性内部;


任意查看一个键的值:


4.手动将文件放入某个brick,在客户端是不可见的:

[root@09:57:22@/mnt/v1]#ls   没有文件

 

而某一个brick下有文件

[root@09:56:12@/mnt/test5]#ls

glusterfs-3.2.5.tar.gz

5.文件

3.冗余设计思想分析

在冗余部分,我们主要结合文件的创建,读写方式来分析冗余是如何实现的。在概要部

分已经提及上传一个文件,会通过在冗余卷的部分,将一个文件上传到多个节点;而在进行文件的读取部分,则只会从一个节点读取一个文件。

 Gluster写文件示意图


Gluster读文件示意图

 

在afr部分,文件的读写操作,文件夹的读写操作,文件的自我修复各个部分代码分开实现。

3.1.写文件

文件的写其实会涉及到2个阶段,首先是创建文件,然后是向文件写IO流。

3.1.1创建文件

文件创建是函数afr_create。当创建操作传递到服务端的实现的POSIX接口时,其

实是通过调用open()函数实现文件创建的:

_fd = open (real_path, _flags, mode);

 

上面代码片段即来自服务端的posix_create方法。

 

如果要创建一个文件,首先当然要从要重父节点获得文件的flag,mode,fd这几个参数,fd为文件的描述符:

local->cont.create.flags = flags;

local->cont.create.mode  = mode;

local->cont.create.fd    = fd_ref (fd);

 

同时获得有效的子卷数目,这儿有效代表了子卷现在能够提供正常服务的卷:

call_count = afr_up_children_count (priv->child_count, local->child_up);

上面参数child_up代码了能正常提供服务的子卷数组,如果是child_down则是不能正常提供服务的数组。

然后就会开始对子卷的循环调用:

for (i = 0; i < priv->child_count; i++) {

                if (local->child_up[i]) {

                        STACK_WIND_COOKIE (frame, afr_create_wind_cbk,

                                           (void *) (long) i,

                                           priv->children[i],

                                           priv->children[i]->fops->create,

                                           &local->loc,

                                           local->cont.create.flags,

                                           local->cont.create.mode,

                                           local->cont.create.fd,

                                           local->cont.create.params);

                        if (!--call_count)

                                break;

                }

        }

 在上面代码片段中我们可以看到,冗余卷对所有的child_up子卷进行了create操作调用。

 3.1.2文件写

3.1.2.1.工作流程

1、对每个子卷加非阻塞inde锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;

2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;

3、在所有up子卷上写文件,中间可能有写失败的文件,op_ret返回第一个返回成功的子卷;prebuf,postbuf返回read_child对应的信息;op_errno返回最后一个子卷写返回的错误信息

4、设置xattr中的待定标识;

5、解锁;

3.1.2.2.源码分析

文件创建并且处于打开后,就开始写文件的IO流。首先我们会从父节点获得文件相关的一些参数如下:

local->cont.writev.vector     = iov_dup (vector, count);//赋值vector的前count个元素

local->cont.writev.count      = count;元素的个数

local->cont.writev.offset     = offset;文件指针偏移量

    文件真正的写操作是封装在函数afr_do_writev中,与文件的创建类似,在文件写的时候也是先获得能提供正常服务子卷的个数,在循环调用这些子卷进行写操作:

for (i = 0; i < priv->child_count; i++) {

                if (local->child_up[i]) {

                        STACK_WIND_COOKIE (frame, afr_writev_wind_cbk,

                                           (void *) (long) i,

                                           priv->children[i],

                                           priv->children[i]->fops->writev,

                                           local->fd,

                                           local->cont.writev.vector,

                                           local->cont.writev.count,

                                           local->cont.writev.offset,

                                           local->cont.writev.iobref);

 

                        if (!--call_count)

                                break;

                }

        }

   

3.2.读文件

由于在每个子卷相对应的内容都一样,所以当读取文件的时候,则是从他们任意一个节点读取相应文件均可,gluster正是这样实现。首先我们从ctx中获得read_child,即我们准备从该子卷读取文件的子卷索引,接下来通过child_up[read_child]判断该子卷的状态,如果为ttrue则call_child = read_child;即将它作为调用子卷,否则从所有子卷中查找第一个可以正常提供服务的子卷索引,代码片段如下:

call_child = afr_first_up_child (priv);

而在函数afr_first_up_child内部,通过如下实现查找:

children = priv->children;

                for (i = 0; i < priv->child_count; i++) {

                        if (priv->child_up[i]) {

                                ret = i;

                                break;

                        }

                }

 

接下来开始从相应的子卷进行读操作:

STACK_WIND_COOKIE (frame, afr_readv_cbk,

                           (void *) (long) call_child,

                           children[call_child],

                           children[call_child]->fops->readv,

                           fd, size, offset);

 

设计中是不是有缺点:

1.在afr_sh_data_open_cbk函数中

  LOCK (&frame->lock);

        {

                if (op_ret == -1) {

                        gf_log (this->name, GF_LOG_ERROR,

                                "open of %s failed on child %s (%s)",

                                local->loc.path,

                                priv->children[child_index]->name,

                                strerror (op_errno));

                        sh->op_failed = 1;

                }

 

                gf_log (this->name, GF_LOG_TRACE,

                        "open of %s succeeded on child %s",

                        local->loc.path,

                        priv->children[child_index]->name);

        }

3.3.创建文件

3.3.1.工作流程

1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;

2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;

3、在所有的up子卷上创建文件,中间可能有创建失败的,所有创建操作完成后将信息返回给父卷,返回的最后一个子卷创建操作返回的op_errno;op_ret返回最后一个成功的返回值;

4、设置xattr中的待定标识;

5、解锁

注:如果最后一个子卷创建文件失败呢??;创建文件完成后,会返回哪次返回的属性给客户端内核,让内核做相应的决策呢

3.4.创建文件夹

3.4.1.工作流程

1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;

2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;

3、在所有up子卷上创建文件夹,中间可能有创建失败的文件夹,op_errno返回最后一个子卷写返回的错误信息

4、设置xattr中的待定标识;

5、解锁

3.5.删除文件夹

3.5.1.工作流程

1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;

2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;

3、对所有up子卷发送删除文件夹的操作,中间可能有删除文件夹失败的操作,op_ret为第一个删除成功返回的op_ret;preparent,postparent为read_child节点返回的参数;op_errno为最后一次rmdir操作的返回值,可能为成功,也可能为失败

4、设置父目录xattr中的待定标识;

5、解锁

 

3.6.删除文件

3.6.1.工作流程

1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;

2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;

3、对所有up子卷发送删除文件夹的操作,中间可能有删除文件夹失败的操作,op_ret为第一个删除成功返回的op_ret;preparent,postparent为read_child节点返回的参数;op_errno为最后一次rmdir操作的返回值,可能为成功,也可能为失败;

4、设置父目录xattr中的待定标识;

5、解锁

 

4.总结

上面只是通过文件的读写分析出gluster中冗余的实现机制,理解了这些机制以后要

看该部分的其他操作都是类似的。即写文件会写到所有的节点上,而读文件只从一个节点读取。

5.可配置选型

包括了自我修复部分的参数

键名

类型

默认值

描述

read-subvolume

GF_OPTION_TYPE_XLATOR

 

 

favorite-child

GF_OPTION_TYPE_XLATOR

 

 

background-self-heal-count

GF_OPTION_TYPE_INT

.min  = 0

 

data-self-heal

GF_OPTION_TYPE_BOOL

 

 

data-self-heal-algorithm

GF_OPTION_TYPE_STR

{"diff","full" }

 

data-self-heal-window-size

GF_OPTION_TYPE_INT

min  = 1,

max  = 1024,

   default_value = "16"

Maximum number blocks per file for which self-heal "

                         "process would be applied simultaneously

metadata-self-heal

GF_OPTION_TYPE_BOOL

 

 

entry-self-heal

GF_OPTION_TYPE_BOOL

 

 

data-change-log

GF_OPTION_TYPE_BOOL

 

 

metadata-change-log

GF_OPTION_TYPE_BOOL

 

 

entry-change-log

GF_OPTION_TYPE_BOOL

 

 

optimistic-change-log

GF_OPTION_TYPE_BOOL

 

 

data-lock-server-count

GF_OPTION_TYPE_INT

0

 

metadata-lock-server-count

GF_OPTION_TYPE_INT

0

 

entry-lock-server-count

GF_OPTION_TYPE_INT

0

 

strict-readdir

GF_OPTION_TYPE_BOOL

 

 

疑问:&this->volume_options与this->options连个参数的区别

你可能感兴趣的:(GlusterFS文件系统研究)