码迷,mamicode.com
首页 > 其他好文 > 详细

增量备份工具deepcopy

时间:2015-05-13 14:45:02      阅读:209      评论:0      收藏:0      [点我收藏+]

标签:增量备份   docker   go   

       对于拥有多台电脑,或者经常换电脑的人来说,文件系统备份是一个常见性问题。比如,将个人电脑和办公室电脑的home目录进行备份,每个人的不同电脑上,home目录上应该有一些相同的文件,如Document、sources(个人存放代码和工具的目录)、read(个人存放电子书的目录)、install(个人安装程序的目录)等等。在长期使用的过程中,不同电脑上都会对原本相同的文件目录做不同的修改(大部分是添加新文件),这样怎么备份不同电脑上的home目录呢?

      方法一,对每个电脑上的home目录都做独立备份。如果home目录比较小到还好,如果非常大(这应该是常见情况),这样会导致空间极大浪费,没有利用不同home目录上存在相同文件的特征。

      方法二,使用cp -u,只拷贝比较新的或者目标目录中不存在的文件。在实践过程中,发现即使在文件的内容没有改变的情况下,文件的modify time也是很容易被修改的,cp一个文件时,本身也会修改文件的modify time。所以使用cp -u时会拷贝原本并没有改变的文件,浪费大量时间。

      方法三,增量备份,首先记录当前home目录与备份home目录中改变的部分,再将改变的部分从当前home目录拷贝到备份home目录中。这样原本没有变化的部分不用拷贝,可以节约时间。而且对于多台电脑,可以只备份不同的部分,共用原本相同的部分,类似合并操作。

       所以综合比较,增量备份对于文件系统的备份有非常大的优势。最近在学习docker时,受到naiveDiffDriver实现的启发,根据它实现了一个增量备份工具deepcopy。该工具在备份时,分为两个步骤:1 记录改变的部分,2 将改变的部分增量拷贝到备份的文件系统中。下面简单介绍下它的实现。

       记录改变部分,该工具首先利用path/filepath.walk来同时遍历当前home目录和备份home目录,并利用system.Lstat获取每个文件的状态信息,通过树形结构的FileInfo保存下来,然后根据文件的size大小来判断不同:

    for name, newChild := range info.children {                                 
        oldChild, _ := oldChildren[name]                                        
        if oldChild != nil {                                                    
            // change?                                                          
            oldStat := oldChild.stat                                            
            newStat := newChild.stat                                            
                                                     
            if (oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) ||
                bytes.Compare(oldChild.capability, newChild.capability) != 0 {   
                change := Change{                                               
                    Path: newChild.path(),                                      
                    Kind: ChangeModify,                                         
                }                                                               
                *changes = append(*changes, change)                             
                newChild.added = true                                           
            }                                                                   
                                                                                
            // Remove from copy so we can detect deletions                      
            delete(oldChildren, name)                                           
        }                                                                       
                                                                                
        newChild.addChanges(oldChild, changes)                                  
    }  

      拷贝增量部分,之前想,既然已经找到了改变的文件列表,那么就可以自己写一个脚本,来完成将改变的文件拷贝到备份的文件系统中。具体实现时发现又要区分目录和文件情况,又要考虑层级的情况,比我想的复杂。再来看看docker的实现,它首先建立一个管道,使用docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar包,新建一个TarWriter将改变的文件使用tar格式写入管道,然后从管道的另一端读取数据加压到备份的文件系统中。注意这里使用了go协程,所以读和写可以同时进行。

     写代码:

    func() {                                                                 
        ta := &tarAppender{                                                     
            TarWriter: tar.NewWriter(writer),                                   
            Buffer:    pools.BufioWriter32KPool.Get(nil),                       
            SeenFiles: make(map[uint64]string),                                 
        }                                                                       
        // this buffer is needed for the duration of this piped stream          
        defer pools.BufioWriter32KPool.Put(ta.Buffer)   
                                                          
        for _, change := range changes {                                        
            if change.Kind != ChangeDelete {                                    
                path := filepath.Join(dir, change.Path)                         
                if err := ta.addTarFile(path, change.Path[1:]); err != nil {    
                    log.Debugf("Can't add file %s to tar: %s", path, err)       
                }                                                               
            }                                                                   
        }                                                                       
                                                                                
        // Make sure to check the error on Close.                               
        if err := ta.TarWriter.Close(); err != nil {                            
            log.Debugf("Can't close layer: %s", err)                            
        }                                                                       
        if err := writer.Close(); err != nil {                                  
            log.Debugf("failed close Changes writer: %s", err)                  
        }                                                                       
    }

     读代码,经过简化,具体请看docker/pkg/archive/diff.go:

func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {    
    tr := tar.NewReader(layer)                                                  
    trBuf := pools.BufioReader32KPool.Get(tr)                                   
    defer pools.BufioReader32KPool.Put(trBuf)                                   
                                                                                
    var dirs []*tar.Header                                                      
                                                                                
    aufsTempdir := ""                                                           
    aufsHardlinks := make(map[string]*tar.Header)                               
                                                                                
    // Iterate through the files in the archive.                                
    for {                                                                       
        hdr, err := tr.Next()                                                   
        if err == io.EOF {                                                      
            // end of tar archive                                               
            break                                                               
        }                                                                       
        if err != nil {                                                         
            return 0, err                                                       
        }                                                                       
                                                                                
        size += hdr.Size                                                        
                                                                                
        // Normalize name, for safety and for a simple is-root check            
        hdr.Name = filepath.Clean(hdr.Name)                                     
                                                                                
        if !strings.HasSuffix(hdr.Name, "/") {                                           
            parent := filepath.Dir(hdr.Name)                                    
            parentPath := filepath.Join(dest, parent)                           
            if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
                err = os.MkdirAll(parentPath, 0600)                             
                if err != nil {                                                 
                    return 0, err                                               
                }                                                               
            }                                                                   
        }                                                                        
                                                                                
        path := filepath.Join(dest, hdr.Name)                                   
        rel, err := filepath.Rel(dest, path)                                    
        if err != nil {                                                         
            return 0, err                                                       
        }                                                                       
        if strings.HasPrefix(rel, "..") {                                       
            return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
        }                                                                       
        base := filepath.Base(path)

        if true {                
            if fi, err := os.Lstat(path); err == nil {                          
                if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {               
                    if err := os.RemoveAll(path); err != nil {                  
                        return 0, err                                           
                    }                                                           
                }                                                               
            }                                                                   
                                                                                
            trBuf.Reset(tr)                                                     
            srcData := io.Reader(trBuf)                                         
            srcHdr := hdr                                                       
                                                                                
            // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
            // we manually retarget these into the temporary files we extracted them into
            if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") {
                linkBasename := filepath.Base(hdr.Linkname)                     
                srcHdr = aufsHardlinks[linkBasename]                            
                if srcHdr == nil {                                              
                    return 0, fmt.Errorf("Invalid aufs hardlink")               
                }                                                               
                tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
                if err != nil {                                                 
                    return 0, err                                               
                }                                                               
                defer tmpFile.Close()                                           
                srcData = tmpFile                                               
            }                                                                   
                                                                                
            if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil {
                return 0, err                                                   
            }                                                                   
                                                                                
            // Directory mtimes must be handled at the end to avoid further     
            // file creation in them to modify the directory mtime              
            if hdr.Typeflag == tar.TypeDir {                                    
                dirs = append(dirs, hdr)                                        
            }                                                                   
        }                                                                       
    }
      该工具的完整代码请看https://github.com/xuriwuyun/deepcopy。该代码非常原始,仅提供参考。

增量备份工具deepcopy

标签:增量备份   docker   go   

原文地址:http://blog.csdn.net/xuriwuyun/article/details/45692079

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!