冗余技术(redundancy)是分布式存储系统的一个重要技术手段。最简单、最传统的冗余存储技术是副本技术(replication):通过拷贝多个原始数据,并将它们分别存储在不同节点上,从而增加数据可靠性。Gluster文件系统通过多分冗余来保持其系统文件的高可用性。
Glusterfs冗余示意图
副本技术存在显著缺点是存储开销巨大——为应对部分数据丢失的风险而需要将整个原始文件拷贝存储,为此,为提高存储效率,纠删码(erasure coding)技术被引入到分布式系统中。
因此这方面,有待我们改进,通过差分编码等实现gluster的高可用性。
逻辑卷的创建的时候,可以指定冗余的份数,则以后上传一份文件就会上传到多个存储节点。
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.文件
在冗余部分,我们主要结合文件的创建,读写方式来分析冗余是如何实现的。在概要部
分已经提及上传一个文件,会通过在冗余卷的部分,将一个文件上传到多个节点;而在进行文件的读取部分,则只会从一个节点读取一个文件。
Gluster写文件示意图
Gluster读文件示意图
在afr部分,文件的读写操作,文件夹的读写操作,文件的自我修复各个部分代码分开实现。
文件的写其实会涉及到2个阶段,首先是创建文件,然后是向文件写IO流。
文件创建是函数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操作调用。
1、对每个子卷加非阻塞inde锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;
2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;
3、在所有up子卷上写文件,中间可能有写失败的文件,op_ret返回第一个返回成功的子卷;prebuf,postbuf返回read_child对应的信息;op_errno返回最后一个子卷写返回的错误信息
4、设置xattr中的待定标识;
5、解锁;
文件创建并且处于打开后,就开始写文件的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; } } |
由于在每个子卷相对应的内容都一样,所以当读取文件的时候,则是从他们任意一个节点读取相应文件均可,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); } |
1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;
2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;
3、在所有的up子卷上创建文件,中间可能有创建失败的,所有创建操作完成后将信息返回给父卷,返回的最后一个子卷创建操作返回的op_errno;op_ret返回最后一个成功的返回值;
4、设置xattr中的待定标识;
5、解锁
注:如果最后一个子卷创建文件失败呢??;创建文件完成后,会返回哪次返回的属性给客户端内核,让内核做相应的决策呢
1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;
2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;
3、在所有up子卷上创建文件夹,中间可能有创建失败的文件夹,op_errno返回最后一个子卷写返回的错误信息
4、设置xattr中的待定标识;
5、解锁
1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;
2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;
3、对所有up子卷发送删除文件夹的操作,中间可能有删除文件夹失败的操作,op_ret为第一个删除成功返回的op_ret;preparent,postparent为read_child节点返回的参数;op_errno为最后一次rmdir操作的返回值,可能为成功,也可能为失败
4、设置父目录xattr中的待定标识;
5、解锁
1、对每个子卷加非阻塞项锁,加锁成功的子卷数等于up子卷,才算加锁成功,否则会解除之前加成功的锁;
2、如果非阻塞锁失败了,会调用阻塞锁进行加锁;
3、对所有up子卷发送删除文件夹的操作,中间可能有删除文件夹失败的操作,op_ret为第一个删除成功返回的op_ret;preparent,postparent为read_child节点返回的参数;op_errno为最后一次rmdir操作的返回值,可能为成功,也可能为失败;
4、设置父目录xattr中的待定标识;
5、解锁
上面只是通过文件的读写分析出gluster中冗余的实现机制,理解了这些机制以后要
看该部分的其他操作都是类似的。即写文件会写到所有的节点上,而读文件只从一个节点读取。
包括了自我修复部分的参数
键名 |
类型 |
默认值 |
描述 |
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连个参数的区别