設計新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功能樹,應用了若干模塊。模塊化和堆棧式的架構設計,極大降低了系統設計複雜性,簡化了系統的實現、升級以及系統維護。


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功能了。當然,你可能什麼功能都測試不出來,因爲我們什麼功能都沒實現。

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


發佈了157 篇原創文章 · 獲贊 248 · 訪問量 179萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章