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