1. GlusterFS概述
GlusterFS是一个开源的分布式文件系统,具有强大的Scale-Out横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。GlusterFS基于可堆叠的用户空间设计,可为各种不同的数据负载提供优异的性能。
GlusterFS支持运行在任何标准IP网络上标准应用程序的标准客户端,用户可以在全局统一的命名空间中使用Glusterfs/NFS/CIFS等标准协议来访问应用数据。GlusterFS使得用户可摆脱原有的独立、高成本的封闭存储系统,能够利用普通廉价的存储设备来部署可集中管理、横向扩展、虚拟化的存储池,存储容量可扩展至TB/PB级。Glusterfs的深入剖析请参考”GlusterFS集群文件系统研究”一文。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;
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} }, };
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功能了。当然,你可能什么功能都测试不出来,因为我们什么功能都没实现。
7. 参考资料
[1] Translator 101 Lesson 1: Setting the Stage, http://hekafs.org/index.php/2011/11/translator-101-class-1-setting-the-stage/
[2] Translator 101 Lesson 2: init, fini, and privatecontext, http://hekafs.org/index.php/2011/11/translator-101-lesson-2-init-fini-and-private-context/
[3] Translator 101 Lesson 3: This Time For Real, http://hekafs.org/index.php/2011/11/translator-101-lesson-3-this-time-for-real/
[4] Translator 101 Lesson 4: Debugging a Translator, http://hekafs.org/index.php/2011/11/translator-101-lesson-4-debugging-a-translator/
[5] GlusterFS Translator API, http://hekafs.org/dist/xlator_api_2.html
[6] GlusterFS translator concepts, http://www.gluster.org/community/documentation/index.php/GlusterFS_Concepts#Translator
[7] GlusterFS rot-13 translator, https://github.com/jdarcy/glusterfs/tree/master/xlators/encryption/rot-13
[8] GlusterFS read-only translator, https://github.com/jdarcy/glusterfs/tree/master/xlators/features/read-only
[9] GlusterFS bypass translator, https://github.com/jdarcy/bypass
[10] GlusterFSnegative-lookup translator, https://github.com/jdarcy/negative-lookup
[11] GlusterFS集群文件系统研究, http://blog.csdn.net/liuben/article/details/6284551