设计新Xlator扩展GlusterFS[转]
原文:http://www.linuxidc.com/Linux/2013-08/89105.htm
1. GlusterFS概述
GlusterFS是一个开源的分布式文件系统,具有强大的Scale-Out横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。GlusterFS基于可堆叠的用户空间设计,可为各种不同的数据负载提供优异的性能。
GlusterFS支持运行在任何标准IP网络上标准应用程序的标准客户端,用户可以在全局统一的命名空间中使用Glusterfs/NFS/CIFS等标准协议来访问应用数据。GlusterFS使得用户可摆脱原有的独立、高成本的封闭存储系统,能够利用普通廉价的存储设备来部署可集中管理、横向扩展、虚拟化的存储池,存储容量可扩展至TB/PB级。Glusterfs的深入剖析请参考”GlusterFS集群文件系统研究”一文 http://www.linuxidc.com/Linux/2011-12/50579.htm 。GlusterFS主要特征如下:
1) 扩展性和高性能
2) 高可用性
3) 全局统一命名空间
4) 弹性哈希算法
5) 弹性卷管理
6) 基于标准协议
2. Xlator工作原理
GlusterFS采用模块化、堆栈式的架构,可通过灵活的配置支持高度定制化的应用环境,比如大文件存储、海量小文件存储、云存储、多传输协议应用等。每个功能以模块形式实现,然后以积木方式进行简单的组合,即可实现复杂的功能。比如,Replicate模块可实现RAID1,Stripe模块可实现RAID0,通过两者的组合可实现RAID10和RAID01,同时获得高性能和高可靠性。
GlusterFS堆栈式设计思想源自GNU/Hurd微内核操作系统,具有很强的系统扩展能力,系统设计实现复杂性降低很多,基本功能模块的堆栈式组合就可以实现强大的功能。基本模块称为Translator,它是GlusterFS提供的一种强大文件系统功能扩展机制,借助这种良好定义的接口可以高效简便地扩展文件系统的功能。
GlusterFS中所有的功能都通过Translator机制实现,服务端与客户端模块接口是兼容的,同一个translator可同时在两边加载。每个translator都是SO动态库,运行时根据配置动态加载。每个模块实现特定基本功能,比如Cluster, Storage, Performance, Protocol,Features等,基本简单的模块可以通过堆栈式的组合来实现复杂的功能,Translator可以把对外部系统的访问转换成目标系统的适当调用。大部分模块都运行在客户端,比如合成器、I/O调度器和性能优化等,服务端相对简单许多。客户端和存储服务器均有自己的存储栈,构成了一棵Translator功能树,应用了若干模块。模块化和堆栈式的架构设计,极大降低了系统设计复杂性,简化了系统的实现、升级以及系统维护。
GlusterFS概念中,由一系列translator构成的完整功能栈称之为Volume(如上图所示),分配给一个volume的本地文件系统称为brick,被至少一个translator处理过的brick称为subvolume。FUSE模块位于客户端,POSIX模块位于服务器端,它们通常是volume中首个或最后一个模块,依赖于访问数据流的方向。中间部分会再加入其他功能的模块,构成一个完整的volume,这些模块通过一张图(graph)有机结合在一起。这是一种多层设计,运行时通过有序地向上或向下调用相邻模块接口来传递消息,调用关系由每个模块根据自身功能和translator图来决定。由translator实现的卷的完整数据流,如下图所示。
3. Xlator结构和相关API
Xlator是高度模块化的组件,具有良好定义的内部结构,包括结构体和接口函数原型定义。因此,要实现一个xlator,必须严格按照定义来实现,具体讲就是要实现xlator.h中定义的xlator_fops、xlator_cbks、init、fini、volume_options等结构体中的参数和函数指针,描述如下:
struct xlator_fops {
fop_lookup_t lookup;
fop_stat_t stat;
fop_fstat_t fstat;
fop_truncate_t truncate;
fop_ftruncate_t ftruncate;
fop_access_t access;
fop_readlink_t readlink;
fop_mknod_t mknod;
fop_mkdir_t mkdir;
fop_unlink_t unlink;
fop_rmdir_t rmdir;
fop_symlink_t symlink;
fop_rename_t rename;
fop_link_t link;
fop_create_t create;
fop_open_t open;
fop_readv_t readv;
fop_writev_t writev;
fop_flush_t flush;
fop_fsync_t fsync;
fop_opendir_t opendir;
fop_readdir_t readdir;
fop_readdirp_t readdirp;
fop_fsyncdir_t fsyncdir;
fop_statfs_t statfs;
fop_setxattr_t setxattr;
fop_getxattr_t getxattr;
fop_fsetxattr_t fsetxattr;
fop_fgetxattr_t fgetxattr;
fop_removexattr_t removexattr;
fop_lk_t lk;
fop_inodelk_t inodelk;
fop_finodelk_t finodelk;
fop_entrylk_t entrylk;
fop_fentrylk_t fentrylk;
fop_rchecksum_t rchecksum;
fop_xattrop_t xattrop;
fop_fxattrop_t fxattrop;
fop_setattr_t setattr;
fop_fsetattr_t fsetattr;
fop_getspec_t getspec;
/* these entries are used for a typechecking hack in STACK_WIND _only_ */
fop_lookup_cbk_t lookup_cbk;
fop_stat_cbk_t stat_cbk;
fop_fstat_cbk_t fstat_cbk;
fop_truncate_cbk_t truncate_cbk;
fop_ftruncate_cbk_t ftruncate_cbk;
fop_access_cbk_t access_cbk;
fop_readlink_cbk_t readlink_cbk;
fop_mknod_cbk_t mknod_cbk;
fop_mkdir_cbk_t mkdir_cbk;
fop_unlink_cbk_t unlink_cbk;
fop_rmdir_cbk_t rmdir_cbk;
fop_symlink_cbk_t symlink_cbk;
fop_rename_cbk_t rename_cbk;
fop_link_cbk_t link_cbk;
fop_create_cbk_t create_cbk;
fop_open_cbk_t open_cbk;
fop_readv_cbk_t readv_cbk;
fop_writev_cbk_t writev_cbk;
fop_flush_cbk_t flush_cbk;
fop_fsync_cbk_t fsync_cbk;
fop_opendir_cbk_t opendir_cbk;
fop_readdir_cbk_t readdir_cbk;
fop_readdirp_cbk_t readdirp_cbk;
fop_fsyncdir_cbk_t fsyncdir_cbk;
fop_statfs_cbk_t statfs_cbk;
fop_setxattr_cbk_t setxattr_cbk;
fop_getxattr_cbk_t getxattr_cbk;
fop_fsetxattr_cbk_t fsetxattr_cbk;
fop_fgetxattr_cbk_t fgetxattr_cbk;
fop_removexattr_cbk_t removexattr_cbk;
fop_lk_cbk_t lk_cbk;
fop_inodelk_cbk_t inodelk_cbk;
fop_finodelk_cbk_t finodelk_cbk;
fop_entrylk_cbk_t entrylk_cbk;
fop_fentrylk_cbk_t fentrylk_cbk;
fop_rchecksum_cbk_t rchecksum_cbk;
fop_xattrop_cbk_t xattrop_cbk;
fop_fxattrop_cbk_t fxattrop_cbk;
fop_setattr_cbk_t setattr_cbk;
fop_fsetattr_cbk_t fsetattr_cbk;
fop_getspec_cbk_t getspec_cbk;
};
struct xlator_cbks {
cbk_forget_t forget;
cbk_release_t release;
cbk_release_t releasedir;
};
void (*fini) (xlator_t *this);
int32_t (*init) (xlator_t *this);
typedef struct volume_options {
char *key[ZR_VOLUME_MAX_NUM_KEY];
/* different key, same meaning */
volume_option_type_t type;
int64_t min; /* 0 means no range */
int64_t max; /* 0 means no range */
char *value[ZR_OPTION_MAX_ARRAY_SIZE];
/* If specified, will check for one of
the value from this array */
char *default_value;
char *description; /* about the key */
} volume_option_t;
xlator_fops和xlator_cbks结构体中的函数指针在xlator.h中都有严格的明确定义,实现时必须完全遵从。其中,xlator_fops是Linux中file_operations, inode_operations和super_operatioins的组合。另外,以上结构体和函数指针名分别确定为fops, cbks, init, fini, options,不可更改。因为xlator最终以SO动态库形式提供给glusterfs主体程序使用,需要使用统一确定的名称来加载和定位xlator中函数指针和变量。Init, fini分别用于xlator加载和卸载时的处理工作,这个对于每个xlator的个性化私有数据处理非常有用。如果xlator模板提供的接口和参数无法满足需求,可以有效利用这两个接口进行处理。值得一提的是,xlator并不一定要实现以上全部的函数指针和变量,可以仅实现特定相关的部分,其它的部分会在运行时自动填入默认的值,并直接传递给下一个translator, 同时指定回调函数,回调函数传回之前translator的结果。
Translator采用异步和回调函数的实现机制,这意味着处理特定请求的代码必须被分为两个部分:调用函数和回调函数。一个xlator的函数调用下一个translator的函数,然后无阻塞的返回。当调用下一个translator的函数时,回调函数可能会立即被调用,也可能稍后在一个不同的线程上被调用。在两种情况下,回调函数都不会像同步函数那样获取其上下文。GlusterFS提供了几种方式用于在调用函数及其回调函数间保存和传递上下文,但是必须xlator自行处理而不能完全依赖协议栈。
Translator的回调机制主要采用了STACK_WIND和STACK_UNWIND。当xlator fops某个函数被调用,表示接受到一个请求,使用frame stack来表示。Fops函数中执行相应操作,然后可把该请求使用STACK_WIND传递给下一个或多个translator。当完成一个请求而不再需要调用下一个translator,或者当任务完成从回调函数中回到上一个translator时需要调用STACK_UNWIND。实际上,最好使用STACK_UNWIND_STRICT,它可以用来指定那类请求你已经完成了。相关宏在stack.h中定义,原型如下:
#define STACK_WIND(frame, rfn, obj, fn, params …)
#define STACK_WIND_COOKIE(frame, rfn, cky, obj, fn,params …)
#define STACK_UNWIND(frame, params …)
#define STACK_UNWIND_STRICT(op, frame, params …)
其中用到的参数如下:
Frame:stack frame表示请求
Rfn::回调函数,当下一个translator完成时会调用该函数
Obj::正在控制的translator对象
Fn:从下一个translator的fops table中指定要调用的translator函数
Params:任何其他被调用函数的参数(比如,inodes, fd, offset, data buffer)
Cky:cookie,这是一个opaque指针
Op:操作类型,用来检查附加的参数符合函数的期望
每个translator-stack frame都有一个local指针,用来储存该translator特定的上下文,这是在调用和回调函数间存储上下文的主要机制。当stack销毁时,每个frame的local如果不为NULL都会被传递给GF_FREE,但是不会执行其他的清理工作。如果local结构体包含指针或引用其他对象,就需要仔细处理这些。因此比较理想的情况是,内存和其它资源能在stack被销毁前被释放,不要完全依赖自动的GFS_GFREE。最为妥当的做法是定义特定translator的销毁函数,并在STACK_UNWIND返回前手工调用。
Xlator大部分的调用函数和回调函数以文件描述符(fd_t)或inode(inode_t)作为参数。通常translator需要存储一些自有的上下文,这些上下文独立于单个请求的生命周期。比如,DHT存储目录对应的布局映射(layout map)和某inode最后可知的位置。Glusterfs提供了一系列的函数用于存储此类上下文。在每种情况下,第二个参数是一个指向translator对象的指针,需要存储的数据与其相关,存储的值是一个unsigned的64位整数。这些函数返回0代表成功,在_get和_del函数中使用引用参数而不是返回值。
inode_ctx_put (inode, xlator, value)
inode_ctx_get (inode, xlator, &value)
inode_ctx_del (inode, xlator, &value)
fd_ctx_set (fd, xlator, value)
fd_ctx_get (fd, xlator, &value)
fd_ctx_del (fd, xlator, &value)
传递给调用函数和回调函数的inode_t或fd_t指针只是借来(borrowed)引用。如果希望该对象稍后还存在,最好调用inode_ref或fd_ref增加一个持久引用,并且当引用不再需要时调用inode_unref或fd_unref。
另外一个常用的类型是dict_t,它是一个通用的排序字典或hash-map的数据结构,可用来存放任意类型值并以字符串为键值。例如,存储的值可以是任意大小的有符号或无符号整数,字符串,或二进制。字符串和二进制需要被标记在不需要时由glusterfs函数释放,或由glibc释放或根本不释放。Dict_t*和*data_t对象都是引用计数的,只有当引用数为0时被释放。同inodes和文件描述符一样,如果希望通过参数接受的dict_t持久存在,必须调用_ref和_unref处理器生命周期。字典并不是仅用于调用和回调函数,也可用于传递不同的模块选项,包括translator初始化的选项。事实上,目前translator的init函数主要用于解析字典中的选项。向translatro中添加一个选项,需要在translator的options数组中添加一个实体。每一个选项可以是boolean,整数,字符串,路径,translator名称,和其它一些自定义类型。如果是字符串,可以指定有效值。解析后的选项和其它的信息可以存放在xlator_t结构体中的private内。
Translators中大部分的logging是通过gf_log函数实现,其参数包括字符串(通常是this->name)、log等级、格式化串以及格式化的其他参数。日志等级分为GF_LOG_ERROR, GF_LOG_WARNING , GF_LOG_LOG和GF_LOG_DEBUG。Xlator可以封装gfs_log自定义相关宏,或采用现有等级,这样translator的日志在运行时就能被输出。设计xlator时,可以添加一个translator 日志等级选项,也可以实现一个特定的xattr调用用于传递新值。
4. 构造新Xlator
这里我们通过构造一个称为NULL的Xlator来梳理构造新xlator的基本方法。NULL Xlator本身不实现具体的功能,仅作为类似Proxy的中转,用于演示构造xlator的结构和方法。NULL Xlator实现包括四个文件,null.h, null.c, null_fops.h, null_fops.c,其中null_fops.h, null_fops.c与defaults.h, defaults.c完全相同。Null.h内容如下:
#ifndef __NULL_H__
#define __NULL_H__
#ifndef _CONFIG_H
#define _CONFIG_H
#include “config.h”
#endif
#include “mem-types.h”
typedef struct {
xlator_t *target;
} null_private_t;
enum gf_null_mem_types_ {
gf_null_mt_priv_t = gf_common_mt_end + 1,
gf_null_mt_end
};
#endif /* __NULL_H__ */
其中,自定义了私有数据结构体null_private_t和内部数据类型。Null.c内容如下:
#include <ctype.h>
#include <sys/uio.h>
#ifndef _CONFIG_H
#define _CONFIG_H
#include “config.h”
#endif
#include “glusterfs.h”
#include “call-stub.h”
#include “defaults.h”
#include “logging.h”
#include “xlator.h”
#include “null.h”
#include “null_fops.h”
int32_t
init (xlator_t *this)
{
xlator_t *tgt_xl = NULL;
null_private_t *priv = NULL;
if (!this->children || this->children->next) {
gf_log (this->name, GF_LOG_ERROR,
“FATAL: null should have exactly one child”);
return -1;
}
priv = GF_CALLOC (1, sizeof (null_private_t), gf_null_mt_priv_t);
if (!priv)
return -1;
/* Init priv here */
priv->target = tgt_xl;
gf_log (this->name, GF_LOG_DEBUG, “null xlator loaded”);
return 0;
}
void
fini (xlator_t *this)
{
null_private_t *priv = this->private;
if (!priv)
return;
this->private = NULL;
GF_FREE (priv);
return;
}
struct xlator_fops fops = {
.lookup = null_lookup,
.stat = null_stat,
.fstat = null_fstat,
.truncate = null_truncate,
.ftruncate = null_ftruncate,
.access = null_access,
.readlink = null_readlink,
.mknod = null_mknod,
.mkdir = null_mkdir,
.unlink = null_unlink,
.rmdir = null_rmdir,
.symlink = null_symlink,
.rename = null_rename,
.link = null_link,
.create = null_create,
.open = null_open,
.readv = null_readv,
.writev = null_writev,
.flush = null_flush,
.fsync = null_fsync,
.opendir = null_opendir,
.readdir = null_readdir,
.readdirp = null_readdirp,
.fsyncdir = null_fsyncdir,
.statfs = null_statfs,
.setxattr = null_setxattr,
.getxattr = null_getxattr,
.fsetxattr = null_fsetxattr,
.fgetxattr = null_fgetxattr,
.removexattr = null_removexattr,
.lk = null_lk,
.inodelk = null_inodelk,
.finodelk = null_finodelk,
.entrylk = null_entrylk,
.fentrylk = null_fentrylk,
.rchecksum = null_rchecksum,
.xattrop = null_xattrop,
.fxattrop = null_fxattrop,
.setattr = null_setattr,
.fsetattr = null_fsetattr,
.getspec = null_getspec,
};
struct xlator_cbks cbks = {
.forget = null_forget,
.release = null_release,
.releasedir = null_releasedir,
};
struct volume_options options[] = {
{ .key = {NULL} },
};
这其中主要实现了上面提到的init和fini函数,fops调用函数指针和cbks回调函数指针,以及卷参数选项options。一个新xlator的代码基本框架就是如此,这里因为没有实现具体功能,各种结构体、变量和函数实现都相对非常简单。如果要实现特定功能的xlator,可以以此为模块进行扩展。Glusterfs源码中xlator都是很好的例子,但有些很复杂,不适合初学者,可以先从简单的rot-13, read-only, bypass, negative-lookup等xlator开始研究,然后构造出自己所需功能的xlator。
5. 编译新Xlator
设计并编码实现新Xlator后,我们需要将其编译成SO形式的动态库,提供给Glusterfs使用。编译过程中,需要使用到glusterfs其他部分的相关代码,要求设置较为复杂的编译环境。这里我们编写了Makefile文件设置环境并进行编译,内容如下:
# Change these to match your source code.
TARGET = null.so
OBJECTS = null.o null_fops.o
# Change these to match your environment.
GLFS_SRC = /home/liuag/glusterfs-3.2.5
GLFS_VERS = 3.2.5
GLFS_LIB = /opt/glusterfs/3.2.5/lib64/
HOST_OS = GF_LINUX_HOST_OS
# You shouldn\’t need to change anything below here.
CFLAGS = -fPIC -Wall -O2 \
-DHAVE_CONFIG_H -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D$(HOST_OS) \
-I$(GLFS_SRC) -I$(GLFS_SRC)/libglusterfs/src \
-I$(GLFS_SRC)/contrib/uuid -I.
LDFLAGS = -shared -nostartfiles -L$(GLFS_LIB) -lglusterfs -lpthread
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) $(LDFLAGS) -o $(TARGET)
install: $(TARGET)
cp $(TARGET) $(GLFS_LIB)/glusterfs/$(GLFS_VERS)/xlator/null
clean:
rm -f $(TARGET) $(OBJECTS)
将Makefile设置成与自己相匹配的编译环境,然后直接make即可生成null.so动态库,make install安装null新xlator。至此,一个新xlator就构造成功了,之后我们就可以使用它了。
6. 测试新Xlator
最激动人心的时刻终于到来了,现在我们可以通过修改volume配置文件来添加并测试新构造的null xlator。这个xlator可以工作在客户端或服务器端,可以修改相应卷配置文件或fuse卷配置文件来实现。下面以服务器端加载为例,局部卷配置修改如下:
volume test-posix
type storage/posix
option directory /data/test-1
end-volume
volume test-null
type null/null
subvolumes test-posix
end-volume
volume test-access-control
type features/access-control
subvolumes test-null
end-volume
… …
OK,现在重启glusterd服务,然后mount这个卷,就可以测试null xlator功能了。当然,你可能什么功能都测试不出来,因为我们什么功能都没实现。