设计新Xlator扩展GlusterFS

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功能树,应用了若干模块。模块化和堆栈式的架构设计,极大降低了系统设计复杂性,简化了系统的实现、升级以及系统维护。

设计新Xlator扩展GlusterFS_第1张图片
Gluster卷Translator栈图

GlusterFS概念中,由一系列translator构成的完整功能栈称之为Volume(如上图所示),分配给一个volume的本地文件系统称为brick,被至少一个translator处理过的brick称为subvolume。FUSE模块位于客户端,POSIX模块位于服务器端,它们通常是volume中首个或最后一个模块,依赖于访问数据流的方向。中间部分会再加入其他功能的模块,构成一个完整的volume,这些模块通过一张图(graph)有机结合在一起。这是一种多层设计,运行时通过有序地向上或向下调用相邻模块接口来传递消息,调用关系由每个模块根据自身功能和translator图来决定。由translator实现的卷的完整数据流,如下图所示。

设计新Xlator扩展GlusterFS_第2张图片
GlusterFS数据流

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功能了。当然,你可能什么功能都测试不出来,因为我们什么功能都没实现。

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


你可能感兴趣的:(分布式文件系统,扩展性,GlusterFS,xlator)