合并并且排序指针数组和二维数组里面存放的字符串用函数调用的格式

简述

经过这几天对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 的研究分析了。

—— —— 以上内容整理自互联网

时间: 2024-07-30 18:27:23

合并并且排序指针数组和二维数组里面存放的字符串用函数调用的格式的相关文章

一维数组,二维数组,三维数组,数组与指针,结构体数组,通过改变指针类型改变访问数组的方式

 打印数组中的每个元素,打印每个元素的地址: #include <stdio.h> #include <stdlib.h> void main(void) { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; for (int *p = a; p < a + 10;p++)  //指针类型决定4个字节 { printf("\n%p,%d", p, *p); } getchar(); } 指针数组 #inclu

直观理解C语言中指向一位数组与二维数组的指针

一维数组和指针: 对于一位数组和指针是很好理解的: 一维数组名: 对于这样的一维数组:int a[5];  a作为数组名就是我们数组的首地址, a是一个地址常量 . 首先说说常量和变量的关系, 对于变量来说, 用箱子去比喻再好不过了, 声明一个变量就声明一个箱子,比如我们开辟出一个苹果类型的箱子, 给这个变量赋值就是把盛放苹果的箱子中放入一个实实在在的苹果, 这就是变量的赋值.  而对于数组来说, 就是一组类型相同的箱子中,一组苹果箱子, 可以放入不同的苹果. 一维数组空间: 变量被声明后, 我

C/C++ 指针数组、二维数组

一. 二维数组 1> 初始化 int a[3][3] = { {1,0,0}, {0,1,0}, {0,0,1} }; // 这里是 {} , 而不是() 2> 将二维数组当做一维数组处理 void print_array( int * p, int row, int line) { for(int i=0; i<row; ++i) { for(int j=0; j<line; ++j) { printf("%d \n", *( p + i * row + j

指针数组和二维数组指针变量

指针数组 概念: 一个数组的元素值为指针则是指针数组. 指针数组是一组有序的指针的集合. 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量. 指针数组说明的一般形式为: 类型说明符 *数组名[数组长度] 其中类型说明符为指针值所指向的变量的类型. 例如: int *pa[3] 表示pa是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量. 1.用一个指针数组来指向一个二维数组. 指针数组中的每个元素被赋予二维数组每一行的首地址,因此也可理解为指向一个一维数

c语言中如何通过二级指针来操作二维数组

通过二级指针去访问二维数组需要先给二级指针分配等同于二维数组行数的一维数组指针,然后把二维数组的每行首地址赋值给对应位置的一维指针上.之后就可以通过二维指针直接访问了. 参考代码如下,可以看具体注释辅助理解. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h>//输入输出头文件. #include <stdlib.h>//本程序需要用到malloc/free函数,引

71. C++ 分别用指针数组和二维数组生成二维空间,存储数据并释放。 练习new/delete, new[]/delete[]

分别用指针数组和二维数组生成二维空间,存储数据并释放.比如,数据如下:  //使用了下fgetc() 1 #include <iostream> 2 #include <stdio.h> 3 using namespace std; 4 5 6 int main() 7 { 8 #if 0 //生成二维数组存储 9 FILE* fp = fopen("G:\\qtcode\\temp.txt","r"); 10 11 //char buf[3

用指针实现对二维数组元素的访问

在C语言中,多维数组实际上是数组的数组,二维数组就是每个元素都是一个一维数组的一维数组. 例如a[3][4]: 定义了一个3行4列的二维数组,可以将a看成是有a[0].a[1].a[2]三个元素组成的一维数组,而每个元素a[i]都是一个有4个整形元素的数组. 1 #include<stdio.h> 2 3 int main() 4 { 5 int i, j, a[4][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}}, *p; 6 p = &a[0][

java基础中的一维数组和二维数组

1.数组:具有相同数据类型的一组数据的集合. 一维数组 2.创建一维数组 a.数组作为对象允许使用new关键字进行内存分配,使用数组之前,必须首先定义数组变量所属类型,即声明数组.  声明数组: 语法:数组元素类型   数组名字[]; 数组元素类型[]  数组名字: 注释:数组元素类型:决定了数组的数据类型,包括基本数据类型和费基本数据类型. 数组名字:为一个合法的标识符,自己去定义 []:指明该变量是一个数组类型变量,单个"[]"表示要创建的是一维数组. 例如:int arr[];

【C语言探索之一】二维数组,二维数组名的意义

1.一维数组 一维数组名,代表两个(1)代表整个数组(虽然谭老爷子的书上说不能,但是只是那个情境下) (2)代表首地址 2.二维数组 想到二维数组在指针方面的理解很是麻烦,所以我自己想了一种理解方式,如下图所示 二维数组名的意义感觉很难受,所以自己来探索下其代表些什么 下面是我的代码和结果 可以看到他们四个的值是一模一样的,但是意义不一样的 1.b数组名,他是指向整个数组的指针, 2.*b是数组第一维的首地址,相当于b[0] 3.&b目的是为了看出数组名的地址,最后结果显而易见,他的地址也是这个

C++ 数组、二维数组、函数参数

一维数组: 1 #include <iostream> 2 using namespace std; 3 int main() 4 { 5 /**为了方便 数组的类型全部都是int类型的*/ 6 int numbers[5] = {1,2,3,4,5}; 7 /**通过指针的方式访问数组*/ 8 int * p = numbers; 9 cout << "普通指针的方式访问:\n"; 10 for (int i = 0; i < 5; i++) 11 co