GlusterFS是一种分布式文件系统 , 项目中使用它作为数据文件冷备存储后台。GlusterFS的技术特点是采用无元数据中心的架构 , 采用Xlator以调用栈的形式组织各功能模块 , Xlator之间通过RPC进行通信 , 客户端与服务端共用某些Xlator , 下图就是Gluster客户端与服务端的Xlator栈示意图。

1580705-20210126120412405-2035115605

由于GlusterFS采用了Xlator的架构 , 所以Distributed Replicated Volume在代码结构上是Distributed Xlator和Replicated Xlator的组合 , Distributed Striped Volume类似。

GlusterFS的使用方式

  • FUSE方式 (mount -t glusterfs)

  • NFS方式 (mount -t nfs)

  • libgfapi方式

下图是FUSE模式下的GlusterFS I/O流

579e1z1wnn

1. GlusterFS数据分布DHT

GlusterFS的分布式哈希表(Distributed Hash Tables DHT)机制是数据分布的核心 , 以目录作为文件分布的基本单位。

  • 当用户在挂载点内创建目录dir0 , 目录会在所有brick生成

  • GlusterFS将每个目录的哈希地址空间规定为0x00000000~0xffffffff的32位整数区间 , 然后按每个brick分割成一组连续的地址空间 , 每个brick对应的地址区间 (Layout)保存在对应目录的扩展文件属性(xattr)中

    • 扩展属性是文件系统提供的一个功能 , 通过Key-Value的方式将一些元数据信息同文件本体或目录存储在一起

    • Glusterfs能够通过brick的大小 , 对不同的brick分配不同大小的layout

  • 当在dir0目录下写文件时 , 根据所在目录GFID和文件名作为输入参数 , 通过D-M算法计算哈希值

  • 通过文件的哈希值确定文件位于哪一个brick

    med71r8jeu
  • 如果创建其他目录dirx , 那么会重新生成这个目录在每个brick上的划分区间 , 存储在扩展属性中(分布元数据保存在文件及目录中 , 体现去中心化) , 如果在目录dir0中再创建下级目录dir0/sub-dir1 , 类似6 , 会生成自己的哈希分布空间

  • Hash算法有一定概率 , 极端情况下会出现brick之间分布不均的情况 , 此时GlusterFS采用链接解决这种问题 , 文件读写时 , 客户端会先到源brick查找文件如果读取到的文件是链接形式 , 则通过文件的扩展属性定位到真实的brick , 然后客户端会直接在真实brick读写文件。

    zr1n27cpsm
  • 扩容 , GlusterFS的扩容有两个步骤

    • 添加brick , 现有的目录分布不会扩展到新brick;旧文件不会迁移到新brick;在现有的目录分布下创建新文件不会写到新brick

    • Reblance , 修复现有的目录hash分布(Fix-Layout)并扩展到新brick;部分文件开始迁移到新brick(Migrate-Data);新创建的文件按照新的哈希分布确定存储位置。Gluster挂载目录(root)开始 , 深度优先遍历策略修复所有目录。

  • 缩容

    • 重新计算缩容后的hash分布 , 将被缩容brick的数据迁走 , 将被缩容brick从卷中摘除

    p9wvki4zkm

分布式副本卷的数据读写

  • 写 : 采用一个文件同时写入多个副本的模式 , 当有一个副本成功写入就返回成功

    • 数据写入过程中 , 可能会发生有副本写入失败的情况

    • 更有甚者 , 各副本之间会发生脑裂(brain-split)

    rt02n0tza3
  • 读 : 从多个镜像副本中选取一个状态为正常的读取

    • 多个副本之中只要有一份是正常的 , 都可以正确读取

    12xmmq85we

操作示例

  1. 创建一个replicate为2 , 共有6个brick的distributed replicate卷 , 数据目录分别为 /brick1~/brick6

  2. 挂载目标卷 /data/mount

  3. 创建目录 /data/mount/test , 此时分别会生成/brick1/test~/brick6/test的文件目录 , 每个目录的扩展属性中记录了本test目录在本brick的hash分布区间

  4. 对于test目录 , 6个brick会分成3组 , 每组两个brick有着相同的分布区间 , 每组将存储相同的文件互为备份。

9g5e8qhts7
shyrs76sx7

2. GlusterFS 自动文件副本(AFR)

GlusterFS恢复数据都是基于副本卷来说的 , GlusterFS复制卷是采用镜像的方式做的 , 并且是同步事务性操作。简单来说就是 , 某一个客户要写文件时 , 先把这个文件锁住 , 然后同时写两个或多个副本 , 写完后解锁 , 这个操作才算结束。那么在写某一个副本时发生故障没有写成功 , 或者运行过程中某一个节点断电了 , 造成数据丢失了 , 等等 , 就能通过另一个副本来恢复。

就是GlusterFS写副本时同步写的 , 就是客户端同时写两份数据 , 这样就会产生两倍的流量 , 测2副本的分布式复制卷性能时 , 能明确看到性能只有无副本的一半 , 或者只有读的一半;另一个分布式文件系统ceph就不是这样 , 是异步来写副本的 , 就是写到一个主OSD(ceph的存储单元)就返回了 , 这个OSD再通过内部网络异步写到其他的OSD , 这样不是更快了。那么这两种方法有什么优缺点呢 , 那种比较好 , 或者各自为什么采用这样的方法?

说道恢复就有什么时候恢复 , 怎么恢复 , 凭什么说这个副本是好的 , 那个副本是坏的呢 , 这样的问题 , 一个一个来说吧。

  1. 首先 , 什么时候恢复?有这样三种场景会触发恢复 , 宕机的节点恢复正常时;副本缺失的文件被读写到时 , 比如运行如下命令:

    ls -l <file-path-on-gluster-mount>; 每十分钟gluster会自行检查;手动下命令触发恢复 , 命令为gluster volume heal VOLNAME
  2. 怎么恢复?在这三种环境的任何一种下 , gluster都会做检查 , 看需不需要来个恢复 , 检查什么呢 , 就是changelog , 通过这个changelog来决定哪个副本坏了 , 要修复了。

  3. 凭什么说它坏了呢?刚才说了changelog会记录的 , 记录的什么呢 , 就是这个文件操作了什么 , 这个可以从文件的扩展属性反正出来 , 每一个文件都有一个扩展属性 , 主要记录了这个文件操作了什么 , 以及所有其他的副本操作了什么 , 副本的扩展属性如果不一样 , 那么就是有问题 , 要恢复自己 , 还是凭自己去恢复其他副本 , 都看这个扩展属性了 , 可以用命令getfattr -m . -d -e hex <FILENAME> , (和getfattr对应 , 有一个setfattr命令是可以设置这些属性的 , 具体命令为setfattr -n trusted.glusterfs.volume-id -v 0x937d9caf46544ed0a2d22e25edb23a75 /brick2 , )这样设置了/brick2所在卷的id , 查到扩展属性的值就是如下图所示的这样的一个东西:

    907596-20180410092103119-1348149677

    这张图中值得注意的是 , trusted.afr.repvol1-client-0 , 还有trusted.afr.repvol1-client-1 , 这两条就是自己的和副本的扩展属性了。先讲名字 , repvol1是卷名 , client是固定的 , 0或1是subvolume-index , 是brick的一个编号。后面的值看起来一大串 , 一共有24bit , 分三部分 , 每部分4byte , 如下图所示:

    907596-20180410092111791-462751467

    分别表示数据,元数据,和entry(gfid),那么这三个东西每一个变化了在这个扩展属性上都会做相应的变化,怎么变化呢,这三个部分分别是三个计数,操作文件之前要先写计数,简单来说可以理解为加一,操作完就减一,这样最后还是保持0,就表示OK。扩展属性被设置的文件和目录会在/<BRICK>/.glusterfs/indices/xattrop目录中有一个索引,具体如下所示,这个文件的内容好像是会定时清空的,啥时候清空呢?

    907596-20180410092119114-1114658290

  • GlusterFS的自动文件副本(Automatic File Replication AFR)机制是实现Gluster故障恢复 , 自修复等高可用功能的基础。

  • GlusterFS利用文件/目录的扩展属性存储元数据信息(以trusted.afr开头)

  • 文件写入流程(from GlusterFS v3.7)一个完整的AFR Transaction

    • 下发Write操作

    • 文件加锁Lock

    • Pre-op设置文件扩展属性,changelog+1

    • 文件写入

    • Post-op如果写入成功,changelog-1, 如果失败就保持此属性

    • 解锁UnLock

扩展属性是由一个24位的十六进制数字构成 , 被分为三个8位十六进制段

  • 1-8位是数据内容(data)changelog

  • 9-16位是元数据(metadata)changelog

  • 17-24位是目录项(entry)changelog

71g6qhuuvi

下面是两副本文件写入属性变化示意图:

  • Pre-OP前

    mfa0fic74w
  • 数据写入中

    l0n6npbsbj
  • 写入成功

    6jcmkrueap
  • 副本b写入失败

    enui7s2rna
  • 数据写入Transaction完成后根据扩展属性changelog , 文件存在以下几种状态:

    • WISE : 自身changelog全零 , 其他副本changelog非零

    • INNOCENT : 自身和其他changelog全为零

    • FOOL : 自身changelog非零

    • IGNORANT : changelog丢失

    • 副本都为WISE : 脑裂 典型场景:数据写入时 , brick-x服务宕机(进程停止、网络断连、系统卡死…) , 一段时间之后又恢复

GlusterFS脑裂

  • 如果文件写入后 , 不同副本changelog都保持WISE状态(副本互相指责对方出现错误) , 这种情况即发生了脑裂(Brain Split)

  • 脑裂可能出现的典型场景(replica 2为例)

    • 文件X副本XA , XB分别位于svr1、svr2 , 分别由proc-a和proc-b进程管理 , 若客户端对X的写入过程中svr1 , svr2间网络中断 , 那么proc-a和proc-b将分别负责各自副本的写入但看到的对方都异常 , 当网络恢复即发生脑裂

    • Svr1掉线 , Svr2创建了一个文件 , Svr2掉线 , Svr1上线 , Svr1创建了一个相同路径和名字的文件

  • 读取脑裂的文件会出现 Input/Output Error

  • 因为Changelog有三段 , 分别对应三种类型数据的状态 , 所以脑裂也有三种类型

    • Data脑裂 – 副本之间数据内容不一致

    • Metadata脑裂 – 副本之间元数据(文件权限 , 所属组 , 文件类型等)不一致

    • Entry/GFID脑裂 – 副本之间GFID不一致(无法使用命令行方法修复 , 需要手工介入)

  • 脑裂如何修复(GlusterFS > 3.3)人工介入通过一定策略选择正确的副本

  • 在GlusterFs集群节点上 , 通过命令行方式修复

    • 选择文件比较大的文件为正确副本

    • 选择修改时间mtime靠后的作为正确副本

    • 强制指定副本作为正确副本

  • 在挂载点 , 通过命令修复

  • 手工修复 https://docs.gluster.org/en/latest/Troubleshooting/resolving-splitbrain/

  • 如何预防脑裂

    • Client-Quorum机制 , 采用3个副本 , 3个副本写成功2个才返回成功(存储成本上升)

    • Arbiter Volume , 采用3个brick , 其中有一个特殊的副本仅存储元数据和目录结构信息

涉及到数据恢复,有如下几种场景:

  1. 所有文件都是IGNORANT,这是手动触发了heal,也就是通过命令,这是怎么恢复呢,就找UID最小的文件作为源,去恢复大小为0的那些文件。

  2. 有一个节点为WISE,其他是FOOL,或其他非WISE的状态,那么就以WISE去恢复其他节点。

  3. 好几个都是WISE,就是好几个副本都说自己正常,同时还说别人不正常,这就是脑裂现象,这样就必须靠管理员手动找出脑裂的副本,自行判断哪些是对的哪些不对,自行恢复了,通常的做法留下一个对的副本,其他都删除,同样还要删除 /<BRICK>/.glusters这个目录下对应的文件,这样就只有一个WISE副本了,再出发heal,就以这个为源恢复所有副本了,触发命令为 gluster volume heal <VOLMUENAME> full,这里的full是一种自愈方式,全部恢复文件,另一种自愈方式叫diff,是差异化恢复。

通常脑裂的文件时读不出来的,读写它时会报Input/Output error,查看日志/var/log/glusterfs/glustershd.log你会有收获:

907596-20180410092128028-2042598826

要找到脑裂文件,还有一个命令可以用,gluster volume heal <VOLMUENAME> info split-brain,它的输出如下:

907596-20180410092134208-2056440793

正常情况下,所有brick的entries都是0,这里同一个副本一个是1一个是0,就是不对劲了,通过此方法找到脑裂的文件,再按上面的方法删除也可以。


参考链接

GlusterFS的数据分布(DHT)和文件副本(AFR)机制-腾讯云开发者社区-腾讯云

GlusterFS分布式存储数据的恢复机制(AFR)的说明 - 散尽浮华 - 博客园


熊熊