分布式文件系统快照的思考

      snapshot最初实在块设备上实现的,例如san阵列。lvm也支持逻辑卷级别的snapshot。例如如果用san存放oracle的dat文件,在san上设置定时快照策略,例如一天一次或者几个小时一次,这样就相当于定期对数据库做一次备份,与比定期把dat文件全拷贝相比,snapshot的速度快、占用物理存储空间比较少。

     快照的技术实现主要是两种方式,COW(copy on write)和ROW(redirect on write)。粗略的对比,COW简单易实现,但是写速度慢,空间利用率低。目前san阵列主流技术是ROW。

     文件系统实现snapshot要比块设备复杂很多。主要原因是文件系统的元数据比块设备复杂很多,文件系统元数据的粒度较低。举个简单例子,文件系统需要考虑递归,例如对一个路径做snapshot,其实就是对路径和递归到下层的路径和文件一起做snapshot。  先不考虑递归文件,首先看下如何对单个文件做snapshot。对磁盘文件系统不是很熟悉,讨论下分布式文件系统里面的snapshot。再增加一个约束条件,就是snapshot不支持读写,这样实现会更简单。

    分布式文件系统里,单个文件元数据包括三个方面:

                     fileid+file description,(file id相当于file handle,file description对应于磁盘文件系统里面inode的一些信息,即file feature,例如修改时间,size,属组权限等)

                     chunkid+chunk description(chunk description主要是指chunk的physical position和state,还有版本号等(hdfs里面有类似概念))

                     fileid-->chunkid   (fileid到chunkid的映射关系)

     MDS(metadata server)的数据结构可以简单的理解为两张内存表,fileid为key的file表和chunkid为key的chunk表。可以是key-value式的hash table,也可以像关系数据库一样的线性数组+索引。具体这两张内存表的数据结构如何组织我们先不考虑。fileid跟chunkid的映射关系选择也比较重要,可以简单认为chunkid=fileid+index,index即该chunk是file的第几个。当然file跟chunk的map关系可以更复杂一些。

     下面看下如果做了snapshot后元数据如何变化。首先,对于元数据而言,snapshot后肯定是cow(copy on write),例如在time1对file1(已经包括chunk0和chunk1)做了一个snapshot。如果不考虑对于快照文件读写,那么file表的记录不需要copy生成新的记录,只需要额外记录下快照的信息即可,例如快照时间,快照时间点的size等。如果继续对file1进行写操作,例如写chunk1,那么chunk表的里面的chunk1这条记录需要做一次cow,生成chunk1_t1。生成chunk1_t1的同时,需要在后端物理存储上执行一次chunk拷贝过程,将chunk1复制为chunk1_t1,复制完成后才能将file1新写的数据写入chunk1_t1。如果后面对file1 restore到snapshot,其实就是对调chunk表中chunk1和chunk_t1两条记录的key或者value即可,换句话说就是修改file1跟chunk1和chunk1_t1的map关系。

    接下来看下数据如何变化。如果对chunk1进行全部copy生成chunk1_t1,那么这种快照就是cow,如果chunk1_t1中只有snapshot后对chunk1修改的部分,那么就是row。类似hdfs的文件系统,chunk在data node上存储为一个磁盘文件。如果磁盘文件系统是支持natvie fallocate的ext4这种文件系统,那么row是很少实现的。chunk1_t1在生成的时候直接用fallocate分配一个空洞文件即可(这是一种偷懒的做法,把row的工作交个了ext4)。如果磁盘文件系统是xfs这种老式文件系统,实现row就比较麻烦了。如果file1是顺序写,那问题不大,新写的chunk1_t1的开始位置可以不是chunk大小对其的,直接从新的偏移量开始写就行了,当然还要在chunk1_t1这条记录和老的chunk1中分别记录下snapshot时间点的文件偏移量(即chunk1写的结束位置和chunk1_t1写开始位置)。如果是file1是随机写,如果要在chunk级别实现row就很麻烦。如果对于snapshot后多次对chunk1进行写,简单的方法是没写一次就生成一个新的chunk文件,效率可不行啊,碎片就太多了,而且还得把多个chunk随便组织成一个链表,太复杂,不可能高效。当然,也可以把chunk划分为更小粒度的block,然后对chunk1做block级别的cow,相当于chunk级别的row,似乎也不是很好的方案,分布式文件系统对文件管理就更复杂了。(貌似IBM的GPFS文件块确实有两个级别,大块小面还有小块,不过GPFS是不依赖磁盘文件系统的,GPFS本身直接管理裸盘)。

     最后,思路在发散下,如果需要对snapshot进行读写,需要做哪些额外的工作。首先如果对snapshot进行读写,那么file表中的记录在生成快照时候需要进行一次cow,即每做一个快照,file记录多一条。例如快照前为file1,快照后需要多一个file1_t1。接下来,chunk表的记录,以及file与chunk的map关系也需要cow,这次cow才是实现snapshot读写的关键。这就要看file<->chunk的map关系如何维护的。简单的做法还是snapshot时候,马上把该文件file表记录和chunk表的记录都复制一份,多个chunk记录可能指向同一个物理chunk。高效的做法就是指复制file<->chunk的map关系,chunk记录只有在下次写的时候再复制。

    简答的纸上谈兵吧,有点不是很形象,后面找个开源的分布式文件系统的case讨论下。



你可能感兴趣的:(分布式文件系统快照的思考)