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

     


    Gluster卷Translator栈图

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

     


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

  • 相关阅读:
    宣布降低Windows Azure 存储和计算的价格
    物联网操作系统的概念和特点
    基于Windows8与Visual Studio11开发第一个内核驱动程序
    在 C++ 中使用 PPL 进行异步编程
    现实世界的Windows Azure:采访Figlo的全球合作伙伴支持经理Nathan Brouwer
    物联网操作系统随笔
    Windows Azure Toolkit for Windows 8 Consumer Preview的升级版发布了
    上海招聘会场所
    理解 Delphi 的类(十) 深入方法[3] 调用时参数分割
    关于类的入门例子(7): 遍历窗体的所有父类
  • 原文地址:https://www.cnblogs.com/langren1992/p/5316309.html
Copyright © 2020-2023  润新知