glusterfs变量

ec_readv、ec_writev

ec_writev

为什么count==1?
它是上层应用决定的,看一下writev系统是怎么定义的

offset、vector、size
offset偏移

发数据,回来的时候会报错写入失败,为什么呢?
因为在EC比较特殊,写入的数据量是要经过切割分发到下层的brick的,但是cbk返回的时候呢(返回值应该等于上层应用except写入的量才可以)
答:业务写多少,就应该返回多少(应用层想写4K,你给它返回8K或者1K,那肯定不对啊!)

[modify] vector、count已经保存在local里面了,自己在cbk的unwind中计算(不应该提前保存在local中)

wind发送后,cbk返回的次序不一定,要按照cookie(child_idx)去取出来

[note]一定要加校验
往下写的时候,返回的一定必须是写的个数
往下读的时候,返回的一定必须是读的个数
对比:读写的大小是否一样

local->cbk_data变量:保存了每个cbk返回后的信息,用于最后整合所有cbk返回的数据

对于read:在cbk_data中加struct iovec* read_vector

linux命令

dd:指定偏移skip (跳过与bs有关)


锁在gfs中是怎么用的?有的需要加锁,有的时候不需要加锁

1、local相当于一个全局变量,wind了6次请求,在cbk时,它们可能是同时返回的,必须加锁
2、cbk的时候是多线程的,一个wind是单线程的(2个wind是多线程的)
local对这次IO来说是全局的,但是wind的private是全局的,因为private是所有的wind都能访问到的
inode是所有的wind都能访问到的,所以操作inode时,就要加锁
local只是每一次fops操作能看得到
private是所有的fops操作能看得到


lookup

在每一个操作之前都要通过lookup来找到它的inode(可以理解为每一个文件都是一个inode,读写等fops操作实际上就是操作这个inode),其它的fops操作在操作前要先得到这个inode,比如我们发一个cat操作,会先调用read,之后呢,内核会先调用lookup找到这个inode,它拿到这个inode之后才会调用内核的read操作
再拿create文件来讲,在调用create后,会先通过lookup查找这个inode,当找不到这个inode时,就会创建一个inode。如果找得到呢,就会报FILE已经存在的错误

afr_loopup干了啥

afr维护了一个副本,之前画的那个图(在afr上层看到的就是一个文件,在afr下面看到的实际上是3个文件),也就是说afr会维护3个文件的一致性,可以理解afr就是把一个文件拆成3份(这3个数据都是一样的),这就和EC比较像了,实际上,EC也是管理多个文件,只不过它做的动作不一样,就是说在afr这里,在它的读、写进来的时候,它是把3个一样的参数发到不同的副本,而EC那里就不一样,EC是通过条带将数据切割后发送到不同的brick。
而lookup操作大致上是一样的,它都会发给孩子节点,afr、EC的孩子是vclnt

afr和ec的上层只能看到一个节点,而afr、ec下面实际上维护了多个节点(即vclnt),afr、ec对下层要对数据进行切割、聚合,对上层返回的时候,要返回一个上层能看到的一个节点

frame 帧:

gdb的bt打出来就是一个堆栈stack,stack里面的每一个函数,实际上就是一个个frame,跳到某一层就是f ,在afr层的frame这一层,看到的就是afr这一层的frame,上层业务发了一个读请求,到达nfs后,会创建一个stack(stack是一个全局的东西,但它到每一层的时候会创建一个自己的frame,比如到afr层会创建afr的frame),这个frame呢,在afr的dispatch、cbk时能看的到,会看到一个同样的frame

在frame里面有一个local

local是void的,为什么这样设计呢?因为每层都需要定义不同的变量,如nfs会有自己需要的参数、afr会有自己需要的参数,因此它定义的是一个void的变量,而不是一个比较通用的变量类型。(简单的说:实际上是void*万能指针的用法),这样,每一个xlator自己去生成自己的local就可以了,local里面的成员变量自己去定义就可以了。

为什么会用这个local呢? 因为每个请求会向下发,发完之后因为是异步的,在网络那里会直接返回,(异步就是在函数调用的时候,它就会returen 0了,但是直到服务端处理完成后,才会调用注册的cbk回调函数。(在cbk怎么和之前wind联系起来呢,实际上是通过frame)
假设一个请求要经过3个模块nfs、afr、vclnt,那么,这3个模块的stack是同一个,可以理解成一个全局的stack,每一个模块都会生成自己的frame,当调到vclnt后,vclnt就会注册一个cbk函数,之后wind后函数就会返回了。(函数虽然返回了,但是并没有告诉业务这个时候返回了。当底层服务端处理完成后,就会调用回调函数,回调函数就会把底下的参数保存起来,就会调stack那一层的frame。也就是说vclnt这层的cbk拿到了之前wind的frame,[在cbk看到的frame和之前wind的frame实际上是同一个]),之后,vclnt调用完成后会继续往上调。我们在wind时保存一个frame->local,就可以在cbk中拿到这个frame->local。(比方说,业务在读的时候会告诉我们它想读多大,那么我们就可以在wind之前将该信息保存到local中,当cbk时就可以在local中将其取出来,看一下已读到的大小是否等于想读到的大小)

void* local的好处:万能指针,坏处:每次用之前都要强转

loc

对文件的操作实际上操作的是inode,那为什么会有local这个东西呢?因为文件它又两种方式,case1:打开文件去做操作;case2:没打开文件去做操作。
对于打开的文件,我们推荐使用fd去做操作(因为fd是一一映射,通过它会快很多)
对于未打开的文件,推荐使用loc去操作(loc要通过路径去解析,要一层一层去解析,它会较慢)
其实loc和fd仔细看,里面都有一个inode,封装的loc和fd实际上就是封装的inode(其实inode才是最关键的东西,操作都会通过inode去操作那个文件)

填充路由

路由是什么?ec对应6个孩子,但是6个孩子怎么对应到下面的6个brick,因为brick是很多个的,brick可能有100个,但是每个文件可能只会存在某6个brick上,那是什么去保存这个信息,就是通过路由。路由是route层来维护的,它就是去找这个文件到底存在那几个brick上的(只要调用这个函数能找到brick就行了)
通过inode就可以得到路由,因为路由信息是存在结构体inode中的。路由比较特殊,很多模块都会用到,所以就提到刚刚讲到的那一点,inode就是一个全局的。frame只在这一层可见,这个frame在上一层、下一层它是看不到的!所以这个frame在某个fop时是一个局部变量,只能在自己的wind和自己的cbk中看的到。frame是没法在模块之间传递的,也就是说,更高一层想要给更第一层传下去的时候,通过frame是带不下来的。因为它们都是自己的(但是通过stack是可以传下来的,但是我们一般不会通过stack去传,frame的第一个参数就是stack)

inode 所有模块都能访问

所有模块都能看到这个inode,它是全局的,一个文件只有一个inode,它就是一个gluster客户端的全局变量
在route层将路由信息放在inode中,这样,不管是afr还是ec都可以在inode中获取到路由信息

变量作用域!

inode 所有模块都能访问
在缩小一点,就是private,就是这个某个模块的全局变量(比如该模块的读、写都能访问的到)
再小一点,就是frame,它是这一次操作能访问到的(local是frame中的成员,它作用域和frame是平级的)
在小一点,就是函数中的局部变量

作用域时十分关键的,要自己去体会!
比如你的信息可能在模块间要被用到的时候,那么就要把它放在inode中
比如你的信息它在下一次fop要用的到的时候,那么就要把它放在priate或者inode中,这样下一次就可以访问的到
比如你的信息只在wind和unwind时用的到,那么就放在frame中

还有一种作用域场景:我只希望这个模块能访问到,但是又不是private,那么应该存在哪里呢?为什么?
因为private是不和文件绑定的,也就是说,任何文件都能访问到!如果我现在有一个信息,只希望这个文件在这个模块能访问到,那其实就是inode_ctx!

变量存在哪的决定因素? 作用域最小化(不应该被不想让看到的模块看到,让想看到的都看到)

字典dict_t:很常用

dict_get(xattr_req, VS_ROUTE_FRESH_LOOKUP)
	第一个参数是哪里来的?这个参数实际上是上层传下来的。大家可以理解它和stack作用域是一样的,它会从最顶层一直向下带,
	实际上是完成2个模块之间交互的。
	5123行的含义
		在xattr_req字典里查找是否有key=VS_ROUTE_FRESH_LOOKUP,
		如果有,就将local->cont.lookup.fresh_lookup设置为_gf_true
		
		这个值是在哪里设置的呢?实际上,大家去找就可以发现,该key是在route层设置的,也就是说,这个是在它的上一层设置的,是为了模块之间的通信。即:route层想要这一次的lookup根据cont.lookup.fresh_lookup做不同的动作,就将该key的值设置进去,在afr层就去在字典里找这个key,就会根据找到或者找不到,做出不同的动作,完成2个模块之间的通信。
		记住:取出来之后,要把该key从字典中删掉(是什么意思呢?就是说这个字典的用法只在该层和更上层之间通信,不在在下层通信,用完要删掉)如果不删除,会残留到下面,下层可能就会对它有误解,之前调代码的时候,没有删除它,导致报错出core!
		用完之后,要删掉!
		
		字典的传输路线是随着wind和unwind单向的,是沿着主线一直走的(这点和xxxxx不同)

应用场景:
1、EC不过缓存读的时候,EC就可以在往下wind的时候设置一个key,在缓存这层就去取出key,如果有该key,就跳过缓存
2、比如IO不想过分层,就可以在该层通过key做一个标记,当IO到达分层时,就获取该key,如果获取到就跳过分层

gfs层次是比较分明的,在层次之间就做了这样一个机制。它是最常用的一个通信的东西!

child_up、call_cnt

在发之前,要先获取孩子在线的情况(即填充child_up这个数组变量)。不在线的是没必要发的。

STACK_WIND_COOKIE

  1. new一个新的frame给下一层:_new
  2. 保存cbk,在STACK_UNWIND中被调用
  3. _new->root = frame->root 把stack赋值给下一层的frame->root
  4. THIS是全局变量,二维指针,之前了解过,使用TLS来维护的,每一层的THIS都是该层的this
  5. 调用下层的fop
    为什么不把cookie放在local里,下去想一想。

STACK_UNWIND_STRICT

  1. 先取出它的parent: _parent = frame->parent
  2. 之前传来的cookie
  3. 调用之前注册的cbk

lookup 一个线程同步跑

lookup_cbk 多个线程在跑(因此要对local中的变量加锁)

afr是向下层发wind,多次cbk返回,因此要将多次cbk返回的参数都提前保起来,当收集完所有的cbk信息后,再根据业务场景对之前保存的参数进行构造,构造完成后,一次性向上发送&返回

同理,ec的read/write要将cbk返回的信息保存起来,当最后一个孩子也返回时(call_cnt==0),开始拼接起来,向上返回,比较恶心。

afr、ec的上层只要理解下层有一个就行了,afr、ec自己维护了多个,不让上层感知&看见

fops函数参数:都是上层传过来的,我们在这层取出来参数信息,在本层进行相关操作

cbk函数参数:都是下层返回来的,下层进行填充
op_ret:下层调用的结果,即返回值。返回是否成功、读写的数据量等信息,每个fops操作的返回含义都不一样。
op_errno: 下层返回来的错误码

变量的含义

struct iatt是干啥的啊?

ec_writev参数讲解

  1. 参数有fd的,说明它之前open过
  2. vector、count它俩是一套,决定数据块的内容和长度
    有几个count,就有几个struct iovec
    struct iovec有iov_base\iov_len
  3. struct iobref 很重要,什么用呢?维护数据的引用计数ref
    看字面意思:iobref,是对io做ref
    struct iovec内核并没有做引用计数,用iobref来做
    说明:这层用到数据块,在wind前,就ref;unwind时,要unref
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章