简述
经过这几天对Glusterfs的分析, 对其体系结构已经有了初步的理解。 值得庆贺的一点就是 Glusterfs 的整个体系结构非常清晰, 高度模块化的设计使得我们对他的理解和扩展变得比较容易。
我打算从下面几步来分析其体系结构:
1. 给出几个从网络上收集的结构图, 用以帮助我们来从整理上认识其体系结构。
2. 以 Glusterfs 的一个客户端配置文件入手, 来理解配置文件的同时也进一步来理解其体系结构。
上面的两项都是基于宏观方面的分析, 下面我们将从系统的微观方面来理解其相关的数据流 :
3. Glusterfs 系统中, 系统的主体是一颗 translator 的树结构,我们来分析整个translator树结构的建立过程(以上面给出的客户端的配置文件为例, 来建立这棵树)
4. 以一个 "write" 操作为例, 来理解glusterfs的整个处理流程
以上就是本文的一个大致分析思路。
对体系结构的具体分析
一、给出几个从网络上收集的结构图, 来帮助我们来认识其体系结构。
图一
这是来自 glusterfs 的官方结构图, 从这个图中, 我们可以得到如下的信息:
1.
glusterfs 没有 MeteData 模块, 没有MeteData的设计模式, 使得系统的复杂度降低, 也避免了MeteData成为整个系统性能瓶颈的问题, 当然, 这种体系结构仅仅适合于基于以文件为对象的存储体系, 对于像 GoogleFS,Lustre 等基于磁盘块,inode的存储系统是不能没有MeteData的。
1.1 有MeteData系统的优势和不足:
类似于 Google, Lustre等, MeteData是其题写结构中不可缺少的部分, 因为他们是基于磁盘块,inode的存储系统。
优点: 系统性能很好,他们将文件进行了一定的分割,并且以块的方式直接存储在磁盘上,减少了类似于 VFS的处理流程, 所以他们对于高性能的数据处理是很有优势的。
缺点: 正是因为引入了 MeteData, 使得系统的复杂度增加, 并且并发能力受到很大的限制(因为所有的处理首先要通过MeteData来定位数据的分布),增对于这个问题, 业界也出现了对MeteData的集群, 这可以缓解其并发瓶颈的问题。
1.2 没有MeteData系统的优势和不足:
Glusterfs就是这一类系统
其优势是:系统复杂度降低, 扩展容易,并且是在用户层实现,容易部署和维护,并且没有MeteData的瓶颈限制, 所以其并发性能较上面的系统有优势。
但其不足也是很明显的: 这种类型的系统只能是以文件为存储对象,所以他的处理性能会比MeteData系统差。
1.3
基于我们的实际应用,结合 Glusterfs 的优势和不足, 综合起来,Glusterfs对于我们的应用还是一个不错的选择, 况且他在业界的实际使用和被关注度也越来越多, 这也是以后集群存储的一个发展方向, 也更加适合于民间的实际使用。
2.
client 和 服务器之间可以通过 RDMA 来进行数据通讯。
3.
InfiniBand 将是我们需要重点考虑和采用的方案, 他可以有效提高数据的传输效率。
图二
这个图是上面图一的细化, 从中我们可以知道:
1. client and server 的设计是高度模块化的
2. client 的复杂度比 server 要大, 客户端需要考虑的问题很多, 比如 Read Ahead, I/O Cache, Stripe, Unify, Replicate(AFR) 等。
3. 所以,我们以后的重点是在Client端。
图三
图三是整个 glusterfs 数据流的一个概要图:
1. 首先是在客户端, 用户通过glusterfs的mount point 来读写数据, 对于用户来说, 集群系统的存在对用户是完全透明的, 用户感觉不到是操作本地系统还是远端的集群系统。
2. 用户的这个操作被递交给 本地linux系统的VFS来处理。
3. VFS 将数据递交给FUSE 内核文件系统:在启动 glusterfs 客户端以前, 需要想系统注册一个实际的文件系统FUSE,如上图所示,该文件系统与ext3在同一个层次上面, ext3 是对实际的磁盘进行处理, 而 fuse 文件系统则是将数据通过 /dev/fuse 这个设备文件递交给了glusterfs client端。所以, 我们可以将 fuse 文件系统理解为一个代理。
4. 数据被 fuse 递交给 Glusterfs client 后, client 对数据进行一些指定的处理(所谓的指定,是按照client 配置文件据来进行的一系列处理, 我们在启动glusterfs client 时 需 要 指 定 这 个 文 件 , 其 默 认 位 置 :/etc/glusterfs/client.vol)。
5. 在glusterfs client的处理末端, 通过网络将数据递交给 Glusterfs Server,
并且将数据写入到服务器所控制的存储设备上。
这样, 整个数据流的处理就完成了。
二、以 Glusterfs 的一个客户端配置文件入手, 来理解配置文件的同时也进一步来理解其体系结构。
配置文件如下:
************************************************************* ### 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.2 # IP address of the remote brick option remote-port 6996 # default server port is 6996 option remote-subvolume brick # 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.3 option remote-port 6996 option remote-subvolume brick end-volume ## client 3 volume namespacenode type protocol/client option transport-type tcp option remote-host 10.0.0.4 option remote-port 6996 option remote-subvolume brick end-volume ## Add unify feature volume bricks type cluster/unify subvolumes client1 client2 option scheduler rr option namespace namespacenode end-volume## Add readahead feature volume readahead type performance/read-ahead option page-size 1MB # unit in bytes option page-count 2 # cache per file = (page-count x page-size) subvolumes bricks end-volume ##Add io-cache feature volume ioc type performance/io-cache subvolumes readahead option page-size 1MB # 128KB is default option cache-size 64MB # 32MB is default option force-revalidate-timeout 5 # 1second is default end-volume
我们可以给出上面配置文件对应的一个逻辑图, 如下图:
图四
在 Glusterfs 系统中引入了 Translator(翻译器)的处理机制(该机制有点类似于 linux 的文件系统架构, linux文件系统中, 采用了分层的设计, 也就是 VFS处理了所有文件的公共部分, 当VFS处理完成后, 会将数据流递交给下面的实际文件系统,例如ext3, reiserfs,等, 只不过 linux 的这种机制是两层的设计, 而 glusterfs 的Translator是一个多层次的设计, 也就是一颗树的构造设计,这颗书中的每一个节点称为一个 Translator, 在glusterfs的内部数据结构中称为
xlator_t)。
上面的图四就是这样的一颗 Translator 树(根据上面给出的配置文件生成),我们可以把上图中的每一个椭圆理解为一个 Translator, 也可以称为一个功能块吧, 在Glusterfs中就是这样的体系结构, 系统对每一个Translator 单独定义为一个模块, 系统会按照用户配置文件给定的信息在系统初始化时来自动生成这样的一颗树, 如果是在客户端, 树根就是FUSE模块, 也就是io-cache的父节点是FUSE模块。
那么照这样理解的话, 整个Glusterfs系统岂不是是由一棵树组成的? 没错, 就是这样的(可以对照linux的文件系统来理解这个机制, 他们具有类似的原理):
图五
这个图可以理解为Glusterfs的一个内部架构图, 所有的子功能(io-cache, readahead, unify, stripe …)被以一个xlator_t的结构表现在系统树中, 每一个 xlator_t(也就是官方文档中的translator)定义了自己的处理函数, 可以把 xlator_t 理解为C++语言中的类。我们还是以上面的配置文件以及她所对应的图为例,来讲解Glusterfs系统在初始化的时候是如何来构造这棵树的:(Glusterfs 系统的初始化部分)
客户端的启动命令:
glusterfs –l /tmp/glusterfs.log –f /etc/glusterfs/client.vol /mnt/gluster
命令说明: -l /tmp/glusterfs.log : 指出log 信息文件
-f /etc/glusterfs/client.vol 给出客户端对应的卷配置文件
/mnt/glusterfs : 客户端的mount point
1. 在系统启动的时候, 首先从命令行知道客户端的配置文件是 client.vol 文件
2. 读取该配置文件, 并进行分析, 每一个如下的信息:
volume client type protocol/client option transport-type tcp/client option remote-host 10.0.0.3 option remote-port 6996 option remote-subvolume brick end-volume
也就是 volume …. End-volume信息, 每一个这样的信息会被生成一个新的树节点(xlator_t), 挂接到以 FUSE 为根节点的树上, 每一个xlator_t节点有自己的属性定义(就是上面的 option 字段定义的(key, value)值)和大量的函数指针定义。 我们也不难发现, 实质上配置文件从开始到最后, 是先定义这棵树的叶子节点, 然后一层一层向树根方向定义的。
3. 分析完整个配置文件后, 系统中的一颗完整的 xlator_t结构树就被创建成功, 每一个 xlator_t 结构有两个重要的函数: init(), fini(), 其中 init()是在对这个xlators 初始化的时候调用的,来初始化每一个 xlator_t对象。
每个xlator_t 结构
定义了大量的函数指针, 这些函数指针大致可以分为三类:
a) 普通的数据处理函数指针(用于正常的数据处理)
b) 回调函数指针,用于处理结果的返回。
c) 管理类函数指针从源代码中可以看到, FUSE对这三类指针的定义为空,因为 FUSE是树根节点,所以没有定义这些处理函数。
可以对照源代码来理解上面的说明, 从而对 xlator_t (也就是 translator 机制会有更加深刻的理解)
4. 当 整 个 树 准 备 就 绪 后 , 根 节 点 (Fuse) 会 向 所 有 的 子 节 点 发 出 一 个 GF_EVENT_PARANT_UP的通知, 用以宣告父节点已经准备就绪, 直到到达树的叶子节点的时候,这时叶子节点会向树根方向来应答一个GF_EVENT_CHILD_UP,用以向父节点宣告子节点也准备就绪。
至此, xlator_t 结构树的初始化工作宣告完成。 下面的工作就是事件(EVENT)的dispatch。在Glusterfs中, 有一个EVENT(事件)处理部分, 用一个 event_poll 来管理整个系统的事件处理, 这些事件包括: client发送数据, client接收数据。
为什么要有EVENT这个部分? 因为很多事情的发生不是我们能够预知的,比如 client收到了来自服务器的数据, 我们不知道什么时候这个数据会来, 那么我们可以通过向 event_poll 中注册一个这个事件, 让linux系统来监督(通过poll_wait系统调用, 早期是select系统调用, 大家对select的作用应该比较了解, 所以也能够理解 EVENT在系统中的作用了)对应的socket fd句柄,如果有数据到达, 那么就按照事件在注册时指定的处理函数来处理这个数据。 所以 EVENT 机制主要处理的是一些需要监听的事件。
以一个 "write" 操作为例, 来理解整个过程的流程:
下面我们以在客户端一个写操作为例, 来打通整个流程,例如在 /mnt/glusterfs(glusterfs 客户端 的mount point)中 写一个文件为例:
1. 当向 /mnt/glusterfs 中写入数据时, linux 会向 VFS 传递这个动作, VFS会将实际的处理交给 FUSE(kernel)文件系统, 然后通过 /dev/fuse 这个设备文件, 将实际的写处理递交给了 glusterfs 系统树的 fuse_xlator_t 这个根节点,这样, 一个写数据流就正式流入了 系统的 xlator_t 结构树。
2. fuse_xlator_t 这个树的根节点会将这个写处理递交给他的子节点, 具体的递交是通过下面的这个宏完成的:
STACK_WIND (frame, writev_cbk, child, child->fops->writev, fd, vector, count, off);
该宏的定义如下:
/* make a call */ #define STACK_WIND(frame, rfn, obj, fn, params ...) do { call_frame_t *_new = 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++; fn (_new, obj, params); } while (0)
3. 这样, 每一个xlator_t 节点都会按照上面的方式将写数据递交给他的子节点来处理, 直到到达了树的叶子节点。
4. 叶子节点是跟 socket 关联在一起的, 所以这个写操作就通过 socket 递交给glusterfs 服务器处理。
5. 至于在客户端这边,处理结果的返回,与上面 父节点-> 子节点 的处理方向相反, 是由 子节点 -> 父节点, 这是通过下面的宏来实现的:
/* return from function */ #define STACK_UNWIND(frame, params ...) do { ret_fn_t fn = frame->ret; call_frame_t *_parent = frame->parent; _parent->ref_count--; fn (_parent, frame->cookie, _parent->this, params); } while (0)
这样, 处理的结果会一直被返回给 fuse_xlators, 然后通过 FUSE(kernel)返回给用户。
通过上面的分析, 我相信大家对 Glusterfs 的整体框架和内部的结构和数据流有了一个大致的了解, 有了上面这些知识的指导, 然后在结合源代码, 对Glusterfs 的理解就会更加透彻。 剩下的任务,就是针对各个 Translator 的研究分析了。
—— —— 以上内容整理自互联网