0, 跟踪学习 在dht层实现 的 rebalance 操作
a, 在目录上记录 layout范围的结果,
i, mkdir (只在mkdir时刻) 会 有一个 layout对象,除了 start_off 和 stop_off 其他值都可以填充;
而其他值的填充 必须继续往下走。
ii, dht_layout 读写 disk ? ? 客户端第一次 读时 会恢复到mem??? (where?)
iii,目录是 无 拓扑关系; /dir1/dir2 是两个目录, 分别为 /dir1 和 /dir1/dir2两个;
b, dht_layout_search (用 (path + filename)来计算,并在上级 目录的 ctx里 寻找 对应的访问,然后 确定 哪一个brick通信);
c, dht_subvol_get_cached /dht_subvol_get_hashed (cached 指最早加入的 brick 对应的client端!!)
d, 可能是因为brick里数据量太少,导致rebalance操作一直没发生 拷贝数据的现象,所以先暂停一下。
1,跟踪学习 replace-brick 操作的实现
比如 命令
gluster volume replace-brick test 172.16.30.185:/sam6 172.16.30.185:/sam7 start force
也就是用本机的 /sam7 代替 /sam6。
a, 当glusterd 进程 处理这个命令时,会修改 /sam6对应brick进程的配置文件,在 features/index 添加一个子xlator , 就是
cluster/pump -> protocol/client, 充当拷贝数据的客户端。(设为进程A )
另外, 会生成一个 以protocol/server ->features/locks->storage/posix 为配置文件,生成一个 新glusterfs进程,
作为实现replace-brick功能的临时进程。(设为进程B )
b, 实际的拷贝数据 是 这两个 进程 在socket.c 中 __socket_rwv函数中实现,
A 进程 会打开在原来 brick 上的 老文件,并读取,再写到 对B 的套接字里。
B 进程 不断接受从A来的数据,并且在新的 brick中创建同样的文件。
c, 具体实现 实在 进程B,会从 老brick上获取每个文件gfid, 并在新brick的 /sam7中查找,因为查找不到,
就会导致 cluster/afr层的self-heal
至于 cluster/afr 层 具体的 self-heal 的过程还得再跟踪下日志和代码。
d, replace-brick操作 在gdb跟进程时,只要有耽误,数据就不会再被拷贝,重启服务也不行,只能清理环境,重新再来一遍。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
1, cluster/afr 层 selfheal 过程实现
比如 命令
gluster volume replace-brick test 172.16.30.185:/sam6 172.16.30.185:/sam7 start force
老brick 对应进程设为 A , 新 的临时 rb进程设为B。
a, 过程可能是:
进程B从进程A获取在A上所有 文件和目录的gfid, 然后将 /sam7看作是一个与/sam6互为镜像的brick。而此时它刚刚online, 在上面执行lookup.
而 cluster/afr 实现机制,发现 /sam7上 任何一个文件有问题,就会从 /sam6上 拷贝一个文件 覆盖替换掉 /sam6上同名文件。
(大致过程应该是这样的,而是 还得有更仔细的分析日志的验证。 想法可以有,但是验证需要不少时间。 )
b, 从调用 afr_launch_self_heal 函数进入 selfheal过程。(应该是features/index 层调用这个函数)
流程大概是: 1,从副本中找到一个能用的 brick对应的xlator;
2, 修复 父亲目录;
3, 修复 entry
4, 修复 metadata
5, 修复 data
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1, dht 层 每个目录的 layout信息 是否 保存 到集群的对应目录扩展属性上
因为挂载的客户端毕竟是多变的,而每个目录的layout信息显然 非常重要。
分析了一下代码, 每个目录的inode的 inode_ctx 中保存的是layout数据,对它的操作也只是在内存中,并没有跟对应brick发起通信。
这个是否可靠?还是因为对应的代码没找到?
实验:清空所有日志,在 /mnt 下创建一个目录,来分析整个过程。
分析了storage/posix代码和日志,发现 服务器端实际上只是在对应目录的扩展属性里记录了gfid, (按照设计,这个gfid 包含了 根目录的路径前缀)。并 没有记录 layout信息。
结论: 每个目录layout信息确实只在客户端内存里,与服务器端无关。
layout信息中缓存的是last 在目录下动作(如本身创建,目录下建立文件等,不包括创建子目录)以来,在本卷中各个brick的可用状态。 如果出现某个brick DOWN情况,这样这个目录的layout信息就是有问题的,它会临时把 新文件创建到 临时的brick上。但是过一段时间,会进行 self-heal,即 在 brick 全UP 情况下 应该在的brick上创建链接文件。以待以后来rebalance .
2, 修改了日后实现 ()
a, 每条日志头上增加一个序列号,方便分清楚次序。
b, 对最核心的xlator_fops中 各个函数,建立了 fopslog 日志,只记录 它的名字和序列号。
c, (将做 )按照 各个 进程 建立目录,所有的日志落在这个目录下。
3,iatt gfid stat 信息
iatt 信息是 glusterfs自己定义的 文件属性信息的结构,它主要是posix的 stat结构(时间增加了微妙部分),和 gfid.
gfid 是 整个集群里的 inode-number, 唯一标志了一个文件和目录。
在posix层 最重要的是 通过系统调用 fstat(取stat) 和 lgetxattr (取gfid), 来构造 一个 iatt 或者将新的iatt落盘(实际上只有gfid).
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1, Gluster 使用 fuse 实现挂载功能
a, { ls -l } => kernel => fuse-k => fuse-u => glfs-graph
fuse-u实现 命令行的功能, 即来自fuse-k解析的请求. (这一切 在 fuse-loop大循环中完成, 其监听fd,处理请求)
线程 B: 死循环中,平时一直监听/dev/fuse 的fd, 有task时被唤醒,走graph流程来处理请求。(A, A-cbk 阻塞在哪里?)
线程 A: 解析请求,并且 添加 task, 并且 最终把数据按照 fuse spec 格式 写到 /dev/fuse 打开的fd 中去。
Gluster里fuse相关代码是 fuse-u 相关实现部分,也就是,当标准命令行处理gluster里面的数据时的实现。
由于客户端只会启动一个进程,而且在挂载点操作很方便,这样就方便了复现和调试 。
另外,标准命令行实现是由coreutils库来实现的。通常每个命令都是依次调用几个系统调用函数,将取得的结果返回给客户。
使用GDB 跟挂载的进程,能很轻松的跟踪到 每个命令实现时,实际上调用了那些的posix标准函数。
b, fuse: fuse_attr
glfs: iatt
posix: stat
这三个结构是三个系统中定义的元数据相关的核心数据结构。非常核心的功能实现时将这些数据结构互相转换。
c, 实现挂载功能时,fuse成为一个xlator, 并且在被画在客户端graph上。
在 客户端进程的核心结构上, 一般是 fuse xlator作为功能栈的 链表头节点。
当阻塞在 readv系统调用上的 fuse xlator 线程B 接受到内核数据后,按照 fuse 标准格式 会解析 OPCODE,它们是posix标准的接口
操作码。然后 会验证数据,在有必要时 会执行 STACK_WIND,将调用请求按照功能栈向下面调用,最终是Gluster来处理的请求。
还需解决问题, i, 挂载时端口映射问题; ii, syncop 做的什么。iii,fuse 层缓存数据了么? iiii, inode 是 独立的一份数据,而每个
xlator只是 借用了 唯一的 inode地址来 ctx ?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1, gd_svc_cli_actors 这个 是在哪里起作用的?
这是个struct数组。而这个struct有个成员为函数,在这个函数中会最终调用glusterd_op_begin_synctask
(里面实作是gd_sync_task_begin)
而这个整体 和 gd_svc_peer_prog, gd_svc_cli_prog, gd_svc_mgmt_prog,
gluster_pmap_prog, gluster_handshake_prog, glusterd_mgmt_hndsk_prog,
这些个整体被 glusterd_program_register 加载到 glusterd 地址空间中来执行。
2, 这个流程在 gd_sync_task_begin 函数中确定好了。
a, glusterd_lock
b, glusterd_op_set_op
c, gd_build_peers_list
d, gd_lock_op_phase
e, glusterd_op_build_payload
//往glusterd进程的一个成员dict添加了一个值,下面流程会用到。
//哪个dict ?? 栈上构造的临时空容器,会按后面的需要采集glusterd维护的数据信息。
f,gd_stage_op_phase
g, gd_brick_op_phase
h, gd_commit_op_phase
这些流程中 的每一般 对会按照 op 分别调用不同的函数。在处理结束后,会更新 glusterd保存的元数据信息。
3,一个dict指针打天下 不断处理会更新其中某些 key 对应的value.
任何操作都会需要一些glusterd进程缓存的信息,dict会事先采集这些信息,而这个本身也充当对 操作的validate。
4, 返回给命令行的结果 是在rpc-xdr来实现,按照xdr标准格式解析请求,再将结果按标准格式返回给gluster命令行进程。
最好能够 多 用GDB跟踪一下glusterd进程,熟悉很多的接口。