65.9K
CodeProject 正在变化。 阅读更多。
Home

数据文件备份 - 完全和增量

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (8投票s)

2009年5月16日

CPOL

5分钟阅读

viewsIcon

82869

downloadIcon

4160

文件完全备份和增量备份。

main programcscreen

引言

我们过去会备份应用程序目录中的所有文件。但是,并非所有文件都会一直更改。有些文件非常大。偶尔,只有几个小文件会被更改。我们每次都会备份整个数据文件夹,并需要减少备份所需的时间和存储容量。本文将展示,完全备份然后进行增量备份可以大大缩短备份时间。额外的成本是还原时间,而还原只在需要时进行。

Using the Code

首先,我需要一个用于压缩和还原文件的 zip 例程。这是我遇到的第一个问题。我找到了这些例程,以及一篇关于如何使用它们的文章。感谢 VB Rocks 和他的文章 Zip Files Easy!。这至少让我理解了如何压缩文件。我发现这种方法存在问题。我发现 zip 例程还包含一个用于显示压缩文件内容的工具。我还发现这些例程只能解压用这些例程创建的文件,而不能解压 Windows 创建的所有 zip 文件。就我的目的而言,这没关系,因为我只将此程序用作概念验证。在最终版本中,我们可能会使用 GZip 而不是 Zip。

常规备份方法是先进行完全备份,然后进行增量备份(与完全备份的差异),之后每次都进行增量备份。在我的例子中,我将计划在周日进行完全备份,然后在周一至周五进行增量备份。然后重新开始。要进行还原,您需要还原完全备份,然后按顺序还原每个增量备份,直到完成整个增量备份集。为了测试这一点,程序将采用源目录和用于放置 zip 文件的目录。生成的文件名是备份类型(完全、增量)和其顺序的组合。为了存储完全备份和增量备份的序列,我使用一个 XML 文件来保存设置。一篇很好的文章 Quick and Dirty Settings Persistence with XML,作者是 circumpunct,它描述了一种在 XML 文件中快速简便地保存设置的方法,我使用了它。每次执行备份时,程序都会更新设置,以便下次使用不同的文件名。我区分序列的方式是,完全备份的文件名类似于 *BackupFull-(完全备份序列号).zip*。每次执行完全备份时,序列号都会递增。增量文件名是 *BackupInc-(完全备份序列号)-(增量序列号).zip*。下面的截图展示了几次备份以说明此技术。

examples of filenames

此例程允许还原时选择一个序列号,获取完全备份,然后获取与该完全备份号关联的所有增量版本,然后按正确顺序还原每个版本。这与磁带备份系统非常相似。

这个示例程序进行了压缩和 FTP 备份。我两者都做了,因为它们都相对容易实现并且展示了概念。在还原时,我只想进行解压缩,因为 FTP 还原会花费更长的时间来编写,并且不会多展示多少概念。下面的代码演示了如何根据是否为完全备份来执行压缩。我处理增量备份的方法是检查每个文件的存档位。如果设置了该位,那么我们就不会备份自上次备份以来已更改的文件。一旦文件被备份,存档位就会被清除,这样下次它就不会被再次备份,除非它确实被更改了。

/// <summary>
/// Zip the files up depending on if this is a full or incremental. 
/// </summary>
/// <param name="zipPath">The directory that will contain the zip file when done</param>
/// <param name="sourceDirectory">The directory that contains the source files to zip</param>
/// <param name="bcktyp">The type of backup</param>
public void ZipFiles(string zipPath, string sourceDirectory, BackupType bcktyp)
{
    DirectoryInfo di = new DirectoryInfo(sourceDirectory);
    FileInfo[] filess = di.GetFiles();
    FileAttributes fileAttributes;

    // Open the zip file if it exists, else create a new one 
    Package zip = ZipPackage.Open(zipPath, FileMode.OpenOrCreate, FileAccess.ReadWrite);

    //Add as many files as you like
    for (int ii = 0; ii < filess.Length; ii++)
    {
        if (bcktyp == BackupType.Incremental)
        {
            // get the attibutes
            fileAttributes = File.GetAttributes(filess[ii].FullName);

            // check whether a file has archive attribute
            bool isArchive = ((File.GetAttributes(filess[ii].FullName) & 
                 FileAttributes.Archive) == FileAttributes.Archive);

            // if the archive bit is set then clear it
            if (isArchive)
            {
                // add to the archive file
                AddToArchive(zip, filess[ii].FullName);
            }
        }
        else
        {
            // add to the archive file
            AddToArchive(zip, filess[ii].FullName);
        }

        // clear the bit we archived it 
        File.SetAttributes(filess[ii].FullName, FileAttributes.Normal);
    }
    // Close the zip file
    zip.Close();
}
/// <summary>
/// Add the file to the zpi package
/// </summary>
/// <param name="zip">The package to add the fiel to </param>
/// <param name="fileToAdd">The fielname to add</param>
private void AddToArchive(Package zip, string fileToAdd)
{
    // Replace spaces with an underscore (_) 
    string uriFileName = fileToAdd.Replace(" ", "_");

    // A Uri always starts with a forward slash "/" 
    string zipUri = string.Concat("/", Path.GetFileName(uriFileName));

    Uri partUri = new Uri(zipUri, UriKind.Relative);
    string contentType = MediaTypeNames.Application.Zip;

    // The PackagePart contains the information: 
    // Where to extract the file when it's extracted (partUri) 
    // The type of content stream (MIME type):  (contentType) 
    // The type of compression:  (CompressionOption.Normal)   
    PackagePart pkgPart = zip.CreatePart(partUri, contentType, CompressionOption.Normal);

    // Read all of the bytes from the file to add to the zip file 
    Byte[] bites = File.ReadAllBytes(fileToAdd);

    // Compress and write the bytes to the zip file 
    pkgPart.GetStream().Write(bites, 0, bites.Length);
}

/// <summary>
/// Unzip the files
/// </summary>
/// <param name="zipPath">The file to unzip</param>
/// <param name="destinationDirecory">The destination directory</param>
public void UnZipFiles(string zipPath, string destinationDirecory)
{
    PackagePartCollection ppc;
    // Open the zip file if it exists, else create a new one 
    Package zip = ZipPackage.Open(zipPath, FileMode.Open, FileAccess.Read);

    ppc = zip.GetParts();

    foreach (PackagePart pp in ppc)
    {
        // Gets the complete path without the leading "/"
        string fileName = pp.Uri.OriginalString.Substring(1);

        Stream stream = pp.GetStream();

        // Read all of the bytes from the file to add to the zip file
        int il = (int)stream.Length - 1;
        Byte[] bites = new Byte[il] ;

        stream.Read(bites, 0, bites.Length);

        fileName = fileName.Replace("_", " ");  // replace underscore with space
        File.WriteAllBytes(String.Concat(destinationDirecory, "\\", fileName), bites);
    }
    // Close the zip file
    zip.Close();
}

以下是我用来保存和获取备份序列号和增量编号的代码。

private void GetSettings()
{
    Settings settings;
    fullbacknum = 1;
    Incnumber = 1;

    settings = new Settings();

    fullbacknum = settings.GetSetting("BackupNumber", fullbacknum);
    Incnumber = settings.GetSetting("Incnumber", Incnumber);
}

private void SaveSettings()
{
    Settings settings;

    settings = new Settings();

    settings.PutSetting("BackupNumber", fullbacknum);
    settings.PutSetting("Incnumber", Incnumber);
}

private void btnCompress_Click(object sender, EventArgs e)
{
    string strsourcedir = "";    // the directory we want to compress
    string strzipdir = "";      // where the zip file is to be created
    string strfilename ="";     // the final filename we want to create
    HCompress   hc;
    BackupType bt = BackupType.Full;

    GetSettings();

    errorProvider1.Clear();
    if (txtSourceDir.Text.Length == 0)
    {
        errorProvider1.SetError(txtDestinationDir, "Enter Source dir");
        return;
    }
    if (txtZipDir.Text.Length == 0)
    {
        errorProvider1.SetError(txtZipFile, "Enter zip dir");
        return;
    }

    // Get the directory locations
    strsourcedir = txtSourceDir.Text;
    strzipdir = txtZipDir.Text;

    // check what radio button is enabled
    if (rdobtnFull.Checked)
    {
        bt = BackupType.Full;
        fullbacknum++;
        strfilename = string.Format("BackupFull-{0}.zip", fullbacknum);
        Incnumber = 0;  // On a full backup reset the increment number
    }
    if (rdobtnInc.Checked)
    {
        bt = BackupType.Incremental;
        Incnumber++;
        strfilename = string.Format("BackupInc-{0}-{1}.zip", fullbacknum, Incnumber);
    }
    strfilename = strzipdir + "\\" + strfilename;
    txtFilename.Text = strfilename;

    hc = new HCompress();
    hc.ZipFiles(strfilename, strsourcedir, bt);

    SaveSettings();
}

下面的图表试图显示我们将节省的空间量。我使用 1000 个文件作为可能总文件数的示例,并计算每个大小类别的总文件百分比,以计算将使用的总磁盘存储量。这表明使用完全备份方法,每次夜间备份通常需要 7.3 GB。第二组数字显示了每个大小类别的文件可能在夜间变化的百分比。您可以更改百分比来探索各种大小,但我以此为例。增量备份仅为 1.6 GB,这是一个可观的磁盘存储节省量,更不用说处理如此多数据所需的时间了。如果我们查看每天进行一次完全备份的成本,总共是 36.5 GB,而进行一次完全备份和一次增量备份则为 14 GB。这些数字非常好。现在,您可以看到为什么磁带备份公司一直使用这些方法。

% 文件 数据文件夹中的文件数 大小(兆字节) 总大小(兆字节) 兆字节
1000 10 100 1 100 完全备份 7300
30 300 4 1200
30 300 5 1500 增量备份 1680
20 200 10 2000
10 100 25 2500
100 7300
全部完全 36500
完全/增量 14020
% 更改 总大小(兆字节) 更改大小(兆字节)
10 100 10
10 1200 120
30 1500 450 还原完全备份 7300
30 2000 600
20 2500 500 还原增量备份 14020
100 1680

关注点

我还没有找到一个对 zip 文件压缩例程有效的例子。在我阅读过的所有文章中,有两款压缩库我将重点关注。第一款是 SharpZipLib,第二款是 DotNetZip。它们都有很多很好的评论,而且可以免费使用。

历史

  • 2009年5月16日 - 初始发布。
© . All rights reserved.