GlusterFS 是一个开源的网络分布式文件系统,前一阵子看了一点GlusterFS(Gluster)的代码,修改了部分代码,具体是增加了一个定制的xlator,简单记录一下。

Gluster与xlator

随着计算机技术的发展,不管哪一个领域的数据都呈现出爆炸性增长的趋势,因此产生了大数据处理与存储技术。单机的存储基本不可能满足大量离线数据(文本)的存储需求了,于是在网络分布式文件系统越来越受到重视。开源的分布式文件系统非常多,GlusterFSLustreCephHDFSFastDFS,关于这些文件系统的分类与区别,可以参考这里,我觉得从块,文件,对象的角度划分比较靠谱。我本是做高性能计算的存储方面研究的,阴差阳错地入了Gluster的坑,具体原因不表了。

Gluster是基于FUSE的用户态文件系统,意味着编译安装Gluster不需要去牵涉内核,关于FUSE,其实Gluster做的比较粗暴,用一个死循环去读取/dev/fuse这个块设备,再丢给客户端或者网络,但是FUSE的原理还是值得去研究的,我也是一知半解。兼容POSIX标准,意味着Linux标准库的read,write等I/O函数不需要经过修改就可以在Gluster上运行。

一个网络分布式文件系统的套路通常是,服务端有多台机子构成一个统一的名字空间,文件以某种分布方式存放在不同的服务器上。而客户端看到的却是一个整体,如一个目录,并且客户端可以被挂载在多个不同的节点,因此可以随时随地访问你的数据。每个文件系统为了实现这一套,都会有各种各样的概念,但本质都是一样的,例如Gluster里面有一些基础的概念, 其中brick是一个存储节点上的一个输出目录, volume是一系列的brick,代表一个功能子集,translator(xlator)是连接子volume的,xlator本身也是某一个volume的具体实现。

Gluster支持多种数据分布方式:

  1. Distributed(默认分布方式)

    一个文件分布在一个brick上,不同的文件可能分布在不同的brick上。没有容错。Distributed方式的分配粒度是文件。

  2. Replicated

    每一个文件都会在每个brick存一个copy,replica数目可以由配置文件指定。Replicated的分配粒度是文件。

  3. Distributed Replicated volume

    前两者的结合,brick的数量是replia的n倍,假如有N个brick,replica是2,则distribute数目是N/2,相邻的两个brick互为备份。先distribute,再replicate。

  4. Striped Volume

    文件被分成固定大小的块,以RR方式分布在不同的服务器上。Striped的分配粒度是文件块。

  5. Distributed Striped volume

    与striped 方式不同的是,file只在特定的brick上面stripe,相当于先distribute,再stripe。

xlator是Gluster设计的精髓所在,每一个功能都可以用一个xlator来实现,例如每种分布规则对应一个xlator,另外一些feature可以封装在一个xlator中,如文件加密。并且可以在配置文件中各种xlator混合,嵌套使用,每个xlator编译后会生成一个动态链接库,运行时按需加载。

举一个例子,上面五种分布方式的第五种 Distributed Striped Volume,他的配置文件这样写:

*************************************************************
### Add client feature and attach to remote subvolume ## client 1
volume client1
type protocol/client
option transport-type tcp/client
option remote-host 10.0.0.1 # IP address of the remote brick
option remote-port 6996 # default server port is 6996
option remote-subvolume brick1 # name of the remote volume
end-volume ## client 2
volume client2
type protocol/client
option transport-type tcp/client
option remote-host 10.0.0.2
option remote-port 6996
option remote-subvolume brick2
end-volume ## client 3
volume client3
type protocol/client
option transport-type tcp/client
option remote-host 10.0.0.3
option remote-port 6996
option remote-subvolume brick3
end-volume ## client 4
volume client4
type protocol/client
option transport-type tcp/client
option remote-host 10.0.0.4
option remote-port 6996
option remote-subvolume brick4
end-volume #stripe, subvolume is clients
volume stripe1
type cluster/stripe
subvolumes client1 client2
end-volume volume stripe2
type cluster/stripe
subvolumes client3 client4
end-volume #distribute, subvolume is stripes
volume dht
type cluster/distribute
subvolumes stripe1 stripe2
end-volume

配置文件是一种树形的xlator结构,树的根是fuse_xlator_t,在配置文件初始化的时候,由根向叶子深度优先初始化。写配置文件的顺序与Gluster读配置的顺序是相反的,例如:dht最先被读取。client端的配置文件的写法要比server端复杂,server端只需要指定哪个目录输出就足够了。

如图所示,左边是客户端的xlator嵌套关系,fuse初始化之后会初始化子卷 subvolume,即调用dht的初始化函数,依次完成初始化。同样,一个I/O请求被FUSE接受,会经过一些封装传递给dht,dht可能经过一些定位,传递给他的某一个subvolume,...一直请求由client xlator通过网络包发给对应的server。server端收到请求也同样是一样的嵌套处理,最终会把请求送到posix xlator,这个xlator里封装了最原始的系统调用,read,write等。这就是Gluster整个系统的执行流程。

上图是官方文档提供的所有类型的xlator,具体都可以在源代码xlators/目录里找到。

xlator中的调用(STACK_WIND)与回调(STACK_UNWIND)

Gluster在不同层级的xlator之间的通信有点类似于递归,主要依赖于代码中的两个宏,分别是STACK_WINDSTACK_UNWIND。每一个xlator中的相关函数都有一对,如 write 函数有着对应的 write_cbk 函数,两个函数与两个宏定义配合使用。

我们把xlator的关系简化成三层,FUSE,DHT,POSIX,关系如上图左边所示。假设系统从/dev/fuse中读到了一个write请求,系统将这个请求丢给FUSE xlator,在FUSE xlator中调用fuse_write,通过STACK_WIND将请求传递给他的subvolume,调用subvolume对应的write函数,即dht_write, dht_write同理通过STACK_WIND调用posix_write。在图中posix是最底层的xlator,因此posix_write将不会调用STACK_WIND,而是调用STACK_UNWIND将返回值或者结果返回给父volume,对应着调用父volume中的_cbk函数,即dht_write_cbk,该函数做完相应的处理后继续通过STACK_UNWIND返回到fuse_write_cbk中,这样一个write才算完成。

调用 (STACK_WIND)

接着具体分析一下STACK_WIND是如何工作的。下面是STACK\_WIND的宏定义。

/* make a call */
#define STACK_WIND(frame, rfn, obj, fn, params ...) \
do { \
call_frame_t *_new = NULL; \
xlator_t *old_THIS = NULL; \
\
_new = CALLOC (1, sizeof (call_frame_t)); \
ERR_ABORT (_new); \
typeof(fn##_cbk) tmp_cbk = rfn; \
_new->root = frame->root; \
_new->next = frame->root->frames.next; \
_new->prev = &frame->root->frames; \
if (frame->root->frames.next) \
frame->root->frames.next->prev = _new; \
frame->root->frames.next = _new; \
_new->this = obj; \
_new->ret = (ret_fn_t) tmp_cbk; \
_new->parent = frame; \
_new->cookie = _new; \
LOCK_INIT (&_new->lock); \
frame->ref_count++; \
\
old_THIS = THIS; \
THIS = obj; \
fn (_new, obj, params); \
THIS = old_THIS; \
} while (0)

在dht xlator中,dht_write 函数里这样调用STACK_WIND:

STACK_WIND (frame, dht_writev_cbk,
subvol, subvol->fops->writev,
fd, vector, count, off, iobref);

把参数代入到宏定义中,可以按照下面的代码理解:

new->parent = frame;
new->this = subvolume;
typeof(fn##_cbk) tmp_cbk = dht_writev_cbk;
new->ret = (ret_fn_t) tmp_cbk;
fn = subvol->fops->writev;
params = {fd, vector, count, off, iobref};
obj = subvol;
//用subvolume->fop->writev
//参数为new,new为新的frame, new的父节点设置为frame,
//obj 就是subvolume
fn (_new, obj, params);

可以看到STACK_WIND主要做了三件微小的事,1 传递调用之间的上下文,代码中是frame 这个数据结构,2 记录当前函数的回调函数,一般是对应的cbk函数,也有特例,像dht xlator中逻辑比较复杂的lookup操作(关于Gluster的核心dht xlator的调用分析可以参考这里),3 调用子subvolume的对应函数,将操作向下传递。

因此按照我们简化的xlator关系,即dht的subvolume是posix,那上面的STACK_WIND就调用了posix_writev

posix_writev (call_frame_t *frame, xlator_t *this,
fd_t *fd,struct iovec *vector, int32_t count,
off_t offset,struct iobref *iobref))

回调 (STACK_UNWIND)

接下来看一下STACK_UNWIND的工作原理。Gluster中有两种回调的宏,一个是STACK_UNWIND, 另一个是STACK_UNWIND_STRICT,两者的差别只是第一个参数,原理是一样的。通常xlator源码里面用的是STACK_UNWIND_STRICT,原因在宏定义的注释里写了,STACK_UNWIND_STRICT是类型安全的。下面是STACK_UNWIND_STRICT的宏定义。

/* return from function in type-safe way */
#define STACK_UNWIND_STRICT(op, frame, params ...) \
do { \
fop_##op##_cbk_t fn = NULL; \
call_frame_t *_parent = NULL; \
xlator_t *old_THIS = NULL; \
\
fn = (fop_##op##_cbk_t )frame->ret; \
_parent = frame->parent; \
_parent->ref_count--; \
old_THIS = THIS; \
THIS = _parent->this; \
frame->complete = _gf_true; \
fn (_parent, frame->cookie, _parent->this, params); \
THIS = old_THIS; \
} while (0)

前面提到如果你的xlator是最底层的(如客户端的client xlator,服务端的posix xlator),那么这个xlator里不应该存在 xxx_cbk 函数,而是在操作返回之前调用STACK_UNWIND或者STACK_UNWIND_STRICK。posix xlator里面用的是STACK_UNWIND_STRICT 向父volume返回。 STACK_UNWIND_STRICT的第一句是将第一个参数连接成cbk函数,以下是posix的调用:

STACK_UNWIND_STRICT (writev, frame, op_ret, op_errno, &preop, &postop);

把参数代入到宏定义中,可以按照下面的代码理解:

//这里的frame就是STACK_WIND里的obj
//因此 fn = dht_writev_cbk
fn = (fop_writev_cbk_t)frame->ret;
//parent 就是dht的frame
_parent = frame->parent;
//因此这里调用的是
//dht_writev_cbk(dht_frame,posix_frame,dht,params);
//实际第二个cokkie参数在dht_write_cbk会被忽略
fn (_parent, frame->cookie, _parent->this, params);

可以看到替换后,首先将上下文换成正确的上下文,即父volume的frame,然后代码的最后一句实际是调用了父volume的 cbk 方法,即dht_write_cbk,在dht_write_cbk里会继续调用STACK_UNWIND_STRICT, 这样就会将结果返回到根xlator。

工程编译,configure与make

为Gluster新增xlator实际是改变了源码的结构,因此要想xlator正确工作,需要了解一下自动编译的知识。首先看一下Gluster的代码结构:

Gluster里提供了一个默认的xlator模板叫做defaults,在libglusterfs/src/defaults.c里,里面定义了一个文件系统基本操作,包括cbk方法,但是他不做任何操作,方法里面只有基本的STACK_WINDSTACK_UNWIND调用。假设我们要在cluster中加一个新的分布规则叫dadada,那应该在对应的目录下新建自己的目录和源码,如图:

然后需要将新增的xlator加到编译选项中。为此我特意了解了一下C语言大型工程的编译套路,仅仅也是能够修改的水平。首先基础的编译方法是用make命令执行Makefile文件,GNU提供了一系列的工具帮助我们自动生成Makefile文件。下面是生成Makefile的操作过程(原图):

首先通过autoscan根据源代码生成对应的configure.ac(或者configure.in)文件,不过通常这一步不需要我们做,一般开源项目里都提供了configure.ac文件。然后aclocal命令根据configure.ac生成aclocal.m4,再运行autoconf命令生成configure文件,然后需要运行automake -a命令生成makefile.in,但这三个步骤Gluster里面有一个autogen.sh的脚本帮我们做了。生成的configure文件是可以执行的,必要的时候修改一下文件权限。执行configure文件,该文件会和makefile.in一起生成所需要的Makefile文件。

因此可以看到,在编译中通常我们只需要提供configure.ac(configure.in)Makefile.am两种文件,其他都是通过工具自动生成的。所以我们要为我们的dadada做以下修改:

1 修改configure.ac, 将cluster/dadadacluster/dadada/src 仿照其他的结构添加到configure.ac中。当然要修改的不只下图中的一处。

2 修改Makefile.am,包括cluster/Makefile.am,cluster/dadada/Makefile.am, cluster/dadada/src/Makefile.am,基本就是改一些名字。

# cluster/Makefile.am
SUBDIRS = stripe afr dht ec dadada CLEANFILES = # cluster/dadada/Makefile.am
SUBDIRS = src # cluster/dadada/src/Makefile.am 根据代码的结构修改 xlator_LTLIBRARIES = dadada.la
xlatordir = $(libdir)/glusterfs/$(PACKAGE_VERSION)/xlator/cluster dadada_la_SOURCES = dadada.c dadada_la_LDFLAGS = -module -avoidversion
dadada_la_LIBADD = $(top_builddir)/libglusterfs/src/libglusterfs.la noinst_HEADERS = dadada.h AM_CFLAGS = -fPIC -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -Wall -fno-strict-aliasing -D$(GF_HOST_OS) \
-I$(top_srcdir)/liblwfs/src $(GF_CFLAGS) -shared -nostartfiles CLEANFILES = uninstall-local:
rm -f $(DESTDIR)$(xlatordir)/dadada.so

3 运行autogen.sh,该脚本是用aclocal和autoconf生成configure文件,以及用automake生成Makefile.in

4 运行configure, configure是用Makefile.in 生成Makefile

5 make && make install

配置文件以及其他

在完成编译之后便可以自己写配置文件进行测试了,一个示例的配置文件:

##  client 1
volume client1
type protocol/client
option transport-type tcp/client
option remote-host 10.0.0.1 # IP address of the remote brick
option remote-port 6996 # default server port is 6996
option remote-subvolume brick1 # name of the remote volume
end-volume ## client 2
volume client2
type protocol/client
option transport-type tcp/client
option remote-host 10.0.0.2
option remote-port 6996
option remote-subvolume brick2
end-volume #dadada, subvolume is clients
volume dadada
type cluster/dadada
subvolumes client1 client2
end-volume

以上就是关于增加xlator所能想起来的基础知识,当然写一个xlator是非常困难的,需要理解和研究的东西的太多。有一个建议就是涉及逻辑的操作最好在cbk函数里面做处理,嵌套调用写在 STACK_UNWIND 前面。另外Gluster在高性能领域用的比较少,所有的文件系统都是有利有弊的,例如Gluster的一大特色就是没有集中的元数据服务器,文件的定位是根据文件名计算hash值来做的,因此Gluster没有分布式文件系统中常见的元数据瓶颈问题,但是在没有指定文件名时候做查询(ls),Gluster性能就不会很好,因为要全盘扫描,关于Gluster性能的探讨,推荐这个系列的博客。关于Gluster开发过程的调试方法,可以参考这里

以上。

参考

https://www.gluster.org/

http://lustre.org/

http://ceph.com/

https://hadoop.apache.org/docs/r1.0.4/cn/hdfs_design.html

https://github.com/happyfish100/fastdfs

https://turodj.gitbooks.io/those-things-about-architecture/content/cun_chu.html

http://gluster.readthedocs.org/en/latest/Administrator Guide/glossary/

http://lidawn.github.io/2015/04/30/parallel-io-basic/

http://blog.csdn.net/liuhong1123/article/details/8118258

https://www.ibm.com/developerworks/cn/linux/l-makefile/

http://blog.csdn.net/liuaigui/article/details/6284551

http://pl.atyp.us/hekafs.org/index.php/2011/11/

原文地址 https://lidawn.github.io/2016/11/28/glusterfs-xlator/

为 GlusterFS 设计新的xlator (编译及调用过程分析)的更多相关文章

  1. 设计新Xlator扩展GlusterFS[转]

    原文:http://www.linuxidc.com/Linux/2013-08/89105.htm 1. GlusterFS概述 GlusterFS是一个开源的分布式文件系统,具有强大的Scale- ...

  2. atitit.TokenService v3 qb1  token服务模块的设计 新特性.docx

    atitit.TokenService v3 qb1  token服务模块的设计 新特性.docx 1.1. V3 新特性1 1.2. V2 新特性1 2. Token的归类1 3. Token的用途 ...

  3. 《C++设计新思维》勘误,附C++14新解法

    勘误: 原书(中文版)3.13节,65-69页中GenScatterHierarchy以及FieldHelper均存在问题,当TypeList中类型有重复时,无法通过编译(原因在于“二义性基类”). ...

  4. 《C++设计新思维》Command设计模式读后感

    原文内容提领: 本书第5章标题为泛化仿函数,我认为本章真正讲述的内容可以总结出一句话! 如何利用C++老标准实现C++11新标准类似std::function提供的功能. std::function简 ...

  5. atitit.jQuery Validate验证框架详解与ati Validate 设计新特性

    atitit.jQuery Validate验证框架详解与ati Validate 设计新特性 1. AtiValidate的目标1 2. 默的认校验规则1 2.1. 使用方式 1.metadata用 ...

  6. C++编译 C # 调用方法

    C++编译    C # 调用方法 编译时使用  public ref class ABC {   ... }; 调用时  右键---引用 --- 添加dll引用  即可

  7. C++---初识《通过g++ / makefile 编译和调用动态库so文件》(ubuntu)

    C++---初识<通过g++ / makefile  编译和调用动态库so文件>(ubuntu) ------------------------目录------------------- ...

  8. 创建Unity新项目并编译成游戏程序

    注:本人所使用的Unity版本为:Unity5.3.5f1,所使用的VS版本为:Visual.Studio.2013.Ultimate 折腾了快一个月了,终于有时间做自己的啦,哈哈: ) 步骤一:启动 ...

  9. HTML5_提供的 新功能_less 编译_

    HTML5_提供的 新功能 class 操作 ele.classList(注意: 高版本的 IE 都不支持) 获取 <div id="ele" class="... ...

随机推荐

  1. Hive学习之路 (三)Hive元数据信息对应MySQL数据库表

    概述 Hive 的元数据信息通常存储在关系型数据库中,常用MySQL数据库作为元数据库管理.上一篇hive的安装也是将元数据信息存放在MySQL数据库中. Hive的元数据信息在MySQL数据中有57 ...

  2. kubenetes master使用curl 操作API

    前提条件: 已经使用kubeadm 安装集群 查看 kebelet.conf 配置内容 kubectl --kubeconfig /etc/kubernetes/kubelet.conf config ...

  3. 20165302 ch02 课下作业

    20165302 ch02 课下作业 作业内容 补充完成课上测试(不能只有截图,要有分析,问题解决过程,新学到的知识点) 完成教材 p97 2.96 2.97,要有完备的测试 发一篇相关内容的博客, ...

  4. Python自动化之复习基础

    用户在命令行输入的参数可以在sys.argv里面看到,并且是以列表的形式现实的

  5. 如何使用eclipse运行简单的java程序

    打开eclipse,选择“file——new——Java project”   为我们的java项目取一个名字,然后点击完成.   这时候左侧列表就有了我们刚才新建的java项目,点开项目,在src目 ...

  6. CAN总线布线规范

    CAN总线布线规范 摘要:今天的CAN总线已从汽车电子慢慢渗透入工业自动化,医疗,铁路等众多领域.据我们的数据统计,客户在使用CAN总线时约80%的问题均是由总线布局布线不合理导致,今天我们就来扒一扒 ...

  7. undefined reference to `sqrt'的问题

    主要问题是math.h这个头文件虽然在/lib/include 下有定义,但是该文件内并没有sqrt()的定义.解决的办法是:在编译的时候在后面加上-lm,意思是链接到math函数库. 在gcc下用到 ...

  8. jQuery----左侧导航栏面板切换实现

    页面运行结果:                                                      点击曹操 点击刘备 点击孙权 原图 需求说明:原图如上所示,点击一方诸侯的时候 ...

  9. jQuery----(类似抽奖转盘)高亮显示

    效果如图: 原图 鼠标进入后开始变化图                                                                              实现需 ...

  10. 从 OPC 到 OPC UA

    [前言]OPC是一个工业标准,所属国际组织是OPC基金会,现有会员已超过220家,包括世界上所有主要的自动化控制系统.仪器仪表及过程控制系统的公司. [经典 OPC]经典OPC规范基于微软Window ...