数据文件备份 - 完全和增量
文件完全备份和增量备份。
引言
我们过去会备份应用程序目录中的所有文件。但是,并非所有文件都会一直更改。有些文件非常大。偶尔,只有几个小文件会被更改。我们每次都会备份整个数据文件夹,并需要减少备份所需的时间和存储容量。本文将展示,完全备份然后进行增量备份可以大大缩短备份时间。额外的成本是还原时间,而还原只在需要时进行。
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*。下面的截图展示了几次备份以说明此技术。
此例程允许还原时选择一个序列号,获取完全备份,然后获取与该完全备份号关联的所有增量版本,然后按正确顺序还原每个版本。这与磁带备份系统非常相似。
这个示例程序进行了压缩和 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日 - 初始发布。