基于Dedup的数据打包技术

基于Dedup的数据打包技术

作者简介 :刘爱贵,研究方向为网络存储、数据挖掘和分布式计算;毕业于中科院,目前从事存储软件研发工作。 Email: [email protected]
注: 作者学识和经验水平有限,如有错误或不当之处,敬请批评指正。

0、引言
    Tar, winrar, winzip是最为常见的数据打包工具软件,它们把文件集体封装成一个单独的数据包,从而方便数据的分布、传输、归档以及持久保存等目的。这类工具通常都支持数据压缩技术,从而有效减少数据的存储空间,常用压缩算法有Huffman编码、Z77/z78、LZW等。压缩算法的原理是通过对数据的重新编码,高频率数据片段采用较短的编码,低频率数据片段采用较长的编码,从而获得全局的上数据量较小的文件表示。

1、Dedup原理
   Deduplication,即重复数据删除,它是一种非常新的且流行度很高的存储技术,可以大大减少数据的数量。重复数据删除技术,通过数据集中重复的数据,从而消除冗余数据。借助dedup技术,可以提高存储系统的效率,有效节约成本、减少传输过程中的网络带宽。同时它也是一种绿色存储技术,能有效降低能耗(存储空间小了,所需要存储系统磁盘也就少了,自然所需要电能就减少了)。
   dedup按照消重的粒度可以分为文件级和数据块级。文件级的dedup技术也称为单一实例存储(SIS, Single Instance Store),数据块级的重复数据删除,其消重粒度更小,可以达到4-24KB之间。显然,数据块级的可以提供更高的数据消重率,因此目前主流的dedup产品都是数据块级的。重复数据删除原理如下图所示。将文件都分割成数据块(可以是定长或变长的数据块),采用MD5或SHA1等Hash算法(可以同时使用两种或以上hash算法,或CRC校验等,以获得非常小概率的数据碰撞发生)为数据块计算FingerPrint。具有相同FP指纹的数据块即可认为是相同的数据块,存储系统中仅需要保留一份。这样,一个物理文件在存储系统中就对应一个逻辑表示,由一组FP组成的元数据。当进行读取文件时,先读取逻辑文件,然后根据FP序列,从存储系统中取出相应数据块,还原物理文件副本。
   重复数据删除目前主要应用于数据备份,因此对数据进行多次备份后,存在大量重复数据,非常适合dedup技术。事实上,dedup技术可以用于很多场合,包括在线数据、近线数据、离线数据存储系统,甚至可以在文件系统、卷管理器、NAS、SAN中实施。还可以用于网络数据传输,当然也可以应用于数据打包技术。dedup技术可以帮助众多应用降低数据存储量,节省网络带宽,提高存储效率、减小备份窗口,绿色节能。这里,基于dedup实现一种数据打包技术。

基于Dedup的数据打包技术_第1张图片



2、基于Dedup的数据打包模型

  数据包文件的数据布局:

Header

Unique block data

File metadata

 数据包由三部分组成:文件头(header)、唯一数据块集(unique block data)和逻辑文件元数据(file metadata)。其中,header为一个结构体,定义了数据块大小、唯一数据块数量、数据块ID大小、包中文件数量、元数据在包中的位置等元信息。文件头后紧接就存储着所有唯一的数据块,大小和数量由文件头中元信息指示。在数据块之后,就是数据包中文件的逻辑表示元数据,由多个实体组成,结构如下所示,一个实体表示一个文件。解包时根据文件的元数据,逐一提取数据块,还原出当初的物理文件。
 
  逻辑文件的元数据表示:

Entry header

pathname

Entry data

Last block data

  逻辑文件的实体头中记录着文件名长度、数据块数量、数据块ID大小和最后一个数据块大小等信息。紧接着是文件名数据,长度在实体头中定义。文件名数据之后,存储着一组唯一数据块的编号,编号与唯一数据块集中的数据块一一对应。最后存储着文件最后一个数据块,由于这个数据块大小通常比正常数据块小,重复概率非常小,因此单独保存。


3、原型实现
基于上面的数据布局,就可以实现支持重复数据删除的数据打包方法。本人在Linux系统上实现了一个原型,实现中使用了hashtable来记录和查询唯一数据块信息,使用MD5算法计算数据块指纹,并使用zlib中的z77压缩算法对删除了重复数据后的数据包进行压缩。hashtable, MD5, z77算法和实现,这里不作介绍,有兴趣的读者可以参考相关资源。下面给出dedup.h, dedup.c undedup.c源码文件。目前实现的原型还相对比较粗糙。

/* dedup.h */

#ifndef _DEDUP_H #define _DEDUP_H #include "md5.h" #include "hash.h" #include "hashtable.h" #include "libz.h" #ifdef __cplusplus extern "C" { #endif /* * deduplication file data layout * -------------------------------------------------- * | header | unique block data | file metadata | * -------------------------------------------------- * * file metedata entry layout * ----------------------------------------------------------------- * | entry header | pathname | entry data | last block data | * ----------------------------------------------------------------- */ typedef unsigned int block_id_t; #define BLOCK_SIZE 4096 /* 4K Bytes */ #define BACKET_SIZE 10240 #define MAX_PATH_LEN 255 #define BLOCK_ID_SIZE (sizeof(block_id_t)) /* deduplication package header */ #define DEDUP_MAGIC_NUM 0x1329149 typedef struct _dedup_package_header { unsigned int block_size; unsigned int block_num; unsigned int blockid_size; unsigned int magic_num; unsigned int file_num; unsigned long long metadata_offset; } dedup_package_header; #define DEDUP_PKGHDR_SIZE (sizeof(dedup_package_header)) /* deduplication metadata entry header */ typedef struct _dedup_entry_header { unsigned int path_len; unsigned int block_num; unsigned int entry_size; unsigned int last_block_size; int mode; } dedup_entry_header; #define DEDUP_ENTRYHDR_SIZE (sizeof(dedup_entry_header)) #ifdef __cplusplus } #endif #endif
/* dedup.c */

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <unistd.h> #include <getopt.h> #include <fcntl.h> #include <dirent.h> #include <errno.h> #include "dedup.h" /* unique block number in package */ static unsigned int g_unique_block_nr = 0; /* regular file number in package */ static unsigned int g_regular_file_nr = 0; /* block length */ static unsigned int g_block_size = BLOCK_SIZE; /* hashtable backet number */ static unsigned int g_htab_backet_nr = BACKET_SIZE; void show_md5(unsigned char md5_checksum[16]) { int i; for (i = 0; i < 16; i++) { printf("%02x", md5_checksum[i]); } } void show_pkg_header(dedup_package_header dedup_pkg_hdr) { printf("block_size = %d/n", dedup_pkg_hdr.block_size); printf("block_num = %d/n", dedup_pkg_hdr.block_num); printf("blockid_size = %d/n", dedup_pkg_hdr.blockid_size); printf("magic_num = 0x%x/n", dedup_pkg_hdr.magic_num); printf("file_num = %d/n", dedup_pkg_hdr.file_num); printf("metadata_offset = %lld/n", dedup_pkg_hdr.metadata_offset); } int dedup_regfile(char *fullpath, int prepos, int fd_bdata, int fd_mdata, hashtable *htable, int debug) { int fd; char *buf = NULL; unsigned int rwsize, pos; unsigned char md5_checksum[16 + 1] = {0}; unsigned int *metadata = NULL; unsigned int block_num = 0; struct stat statbuf; dedup_entry_header dedup_entry_hdr; if (-1 == (fd = open(fullpath, O_RDONLY))) { perror("open regulae file"); return errno; } if (-1 == fstat(fd, &statbuf)) { perror("fstat regular file"); goto _DEDUP_REGFILE_EXIT; } block_num = statbuf.st_size / g_block_size; metadata = (unsigned int *)malloc(BLOCK_ID_SIZE * block_num); if (metadata == NULL) { perror("malloc metadata for regfile"); goto _DEDUP_REGFILE_EXIT; } buf = (char *)malloc(g_block_size); if (buf == NULL) { perror("malloc buf for regfile"); goto _DEDUP_REGFILE_EXIT; } pos = 0; while (rwsize = read(fd, buf, g_block_size)) { /* if the last block */ if (rwsize != g_block_size) break; /* calculate md5 */ md5(buf, rwsize, md5_checksum); /* check hashtable with hashkey */ unsigned int *bindex = (block_id_t *)hash_value((void *)md5_checksum, htable); if (bindex == NULL) { bindex = (unsigned int *)malloc(BLOCK_ID_SIZE); if (NULL == bindex) { perror("malloc in dedup_regfile"); break; } /* insert hash entry and write unique block into bdata*/ *bindex = g_unique_block_nr; hash_insert((void *)strdup(md5_checksum), (void *)bindex, htable); write(fd_bdata, buf, rwsize); g_unique_block_nr++; } metadata[pos] = *bindex; memset(buf, 0, g_block_size); memset(md5_checksum, 0, 16 + 1); pos++; } /* write metadata into mdata */ dedup_entry_hdr.path_len = strlen(fullpath) - prepos; dedup_entry_hdr.block_num = block_num; dedup_entry_hdr.entry_size = BLOCK_ID_SIZE; dedup_entry_hdr.last_block_size = rwsize; dedup_entry_hdr.mode = statbuf.st_mode; write(fd_mdata, &dedup_entry_hdr, sizeof(dedup_entry_header)); write(fd_mdata, fullpath + prepos, dedup_entry_hdr.path_len); write(fd_mdata, metadata, BLOCK_ID_SIZE * block_num); write(fd_mdata, buf, rwsize); g_regular_file_nr++; _DEDUP_REGFILE_EXIT: close(fd); if (metadata) free(metadata); if (buf) free(buf); return 0; } int dedup_dir(char *fullpath, int prepos, int fd_bdata, int fd_mdata, hashtable *htable, int debug) { DIR *dp; struct dirent *dirp; struct stat statbuf; char subpath[MAX_PATH_LEN] = {0}; if (NULL == (dp = opendir(fullpath))) { return errno; } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) continue; sprintf(subpath, "%s/%s", fullpath, dirp->d_name); if (0 == lstat(subpath, &statbuf)) { if (debug) printf("%s/n", subpath); if (S_ISREG(statbuf.st_mode)) dedup_regfile(subpath, prepos, fd_bdata, fd_mdata, htable,debug); else if (S_ISDIR(statbuf.st_mode)) dedup_dir(subpath, prepos, fd_bdata, fd_mdata, htable, debug); } } closedir(dp); return 0; } int dedup_package(int path_nr, char **src_paths, char *dest_file, int debug) { int fd, fd_bdata, fd_mdata, ret = 0; struct stat statbuf; hashtable *htable = NULL; dedup_package_header dedup_pkg_hdr; char **paths = src_paths; int i, rwsize, prepos; char buf[1024 * 1024] = {0}; if (-1 == (fd = open(dest_file, O_WRONLY | O_CREAT, 0755))) { perror("open dest file"); ret = errno; goto _DEDUP_PKG_EXIT; } htable = create_hashtable(g_htab_backet_nr); if (NULL == htable) { perror("create_hashtable"); ret = errno; goto _DEDUP_PKG_EXIT; } fd_bdata = open("./.bdata", O_RDWR | O_CREAT, 0777); fd_mdata = open("./.mdata", O_RDWR | O_CREAT, 0777); if (-1 == fd_bdata || -1 == fd_mdata) { perror("open bdata or mdata"); ret = errno; goto _DEDUP_PKG_EXIT; } g_unique_block_nr = 0; g_regular_file_nr = 0; for (i = 0; i < path_nr; i++) { if (lstat(paths[i], &statbuf) < 0) { perror("lstat source path"); ret = errno; goto _DEDUP_PKG_EXIT; } if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode)) { if (debug) printf("%s/n", paths[i]); /* get filename position in pathname */ prepos = strlen(paths[i]) - 1; if (strcmp(paths[i], "/") != 0 && *(paths[i] + prepos) == '/') { *(paths[i] + prepos--) = '/0'; } while(*(paths[i] + prepos) != '/' && prepos >= 0) prepos--; prepos++; if (S_ISREG(statbuf.st_mode)) dedup_regfile(paths[i], prepos, fd_bdata, fd_mdata, htable, debug); else dedup_dir(paths[i], prepos, fd_bdata, fd_mdata, htable, debug); } else { if (debug) printf("%s is not regular file or directory./n", paths[i]); } } /* fill up dedup package header */ dedup_pkg_hdr.block_size = g_block_size; dedup_pkg_hdr.block_num = g_unique_block_nr; dedup_pkg_hdr.blockid_size = BLOCK_ID_SIZE; dedup_pkg_hdr.magic_num = DEDUP_MAGIC_NUM; dedup_pkg_hdr.file_num = g_regular_file_nr; dedup_pkg_hdr.metadata_offset = DEDUP_PKGHDR_SIZE + g_block_size * g_unique_block_nr; write(fd, &dedup_pkg_hdr, DEDUP_PKGHDR_SIZE); /* fill up dedup package unique blocks*/ lseek(fd_bdata, 0, SEEK_SET); while(rwsize = read(fd_bdata, buf, 1024 * 1024)) { write(fd, buf, rwsize); memset(buf, 0, 1024 * 1024); } /* fill up dedup package metadata */ lseek(fd_mdata, 0, SEEK_SET); while(rwsize = read(fd_mdata, buf, 1024 * 1024)) { write(fd, buf, rwsize); memset(buf, 0, 1024 * 1024); } if (debug) show_pkg_header(dedup_pkg_hdr); _DEDUP_PKG_EXIT: close(fd); close(fd_bdata); close(fd_mdata); unlink("./.bdata"); unlink("./.mdata"); hash_free(htable); return ret; } void usage() { printf("Usage: dedup [OPTION...] <target file> <source files ...>/n"); printf("/nPackage files with deduplicaton technique./n"); printf("Mandatory arguments to long options are mandatory for short options too./n"); printf(" -z, --compress filter the archive through compress/n"); printf(" -b, --block block size for deduplication, default is 4096/n"); printf(" -t, --hashtable hashtable backet number, default is 10240/n"); printf(" -d, --debug print debug messages/n"); printf(" -h, --help give this help list/n"); printf("/nReport bugs to <[email protected]>./n"); } int main(int argc, char *argv[]) { char tmp_file[] = "./.dedup/0"; int bz = 0, bhelp = 0, bdebug = 0; int ret = -1, c; struct option longopts[] = { {"compress", 0, &bz, 'z'}, {"block", 1, 0, 'b'}, {"hashtable", 1, 0, 't'}, {"debug", 0, &bdebug, 'd'}, {"help", 0, &bhelp, 'h'}, {0, 0, 0, 0} }; /* parse options */ while ((c = getopt_long (argc, argv, "zb:t:dh", longopts, NULL)) != EOF) { switch(c) { case 'z': bz = 1; break; case 'b': g_block_size = atoi(optarg); break; case 't': g_htab_backet_nr = atoi(optarg); break; case 'd': bdebug = 1; break; case 'h': case '?': default: bhelp = 1; break; } } if (bhelp == 1 || (argc - optind) < 2) { usage(); return 0; } if (bz) { /* dedup and compress */ ret = dedup_package(argc - optind -1 , argv + optind + 1, tmp_file, bdebug); if (ret == 0) { ret = zlib_compress_file(tmp_file, argv[optind]); unlink(tmp_file); } } else { /* dedup only */ ret = dedup_package(argc - optind - 1, argv + optind + 1, argv[optind], bdebug); } return ret; } 

/* undedup.c */

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <getopt.h> #include <fcntl.h> #include <errno.h> #include "dedup.h" /* block length */ static unsigned int g_block_size = BLOCK_SIZE; void show_pkg_header(dedup_package_header dedup_pkg_hdr) { printf("block_size = %d/n", dedup_pkg_hdr.block_size); printf("block_num = %d/n", dedup_pkg_hdr.block_num); printf("blockid_size = %d/n", dedup_pkg_hdr.blockid_size); printf("magic_num = 0x%x/n", dedup_pkg_hdr.magic_num); printf("file_num = %d/n", dedup_pkg_hdr.file_num); printf("metadata_offset = %lld/n", dedup_pkg_hdr.metadata_offset); } int prepare_target_file(char *pathname, char *basepath, int mode) { char fullpath[MAX_PATH_LEN] = {0}; char path[MAX_PATH_LEN] = {0}; char *p = NULL; int pos = 0, fd; sprintf(fullpath, "%s/%s", basepath, pathname); p = fullpath; while (*p != '/0') { path[pos++] = *p; if (*p == '/') mkdir(path, 0755); p++; } fd = open(fullpath, O_WRONLY | O_CREAT, mode); return fd; } int undedup_regfile(int fd, dedup_entry_header dedup_entry_hdr, char *dest_dir, int debug) { char pathname[MAX_PATH_LEN] = {0}; block_id_t *metadata = NULL; unsigned int block_num = 0; char *buf = NULL; char *last_block_buf = NULL; long long offset, i; int fd_dest, ret = 0; metadata = (block_id_t *) malloc(BLOCK_ID_SIZE * dedup_entry_hdr.block_num); if (NULL == metadata) return errno; buf = (char *)malloc(g_block_size); last_block_buf = (char *)malloc(g_block_size); if (NULL == buf || NULL == last_block_buf) { ret = errno; goto _UNDEDUP_REGFILE_EXIT; } read(fd, pathname, dedup_entry_hdr.path_len); read(fd, metadata, BLOCK_ID_SIZE * dedup_entry_hdr.block_num); read(fd, last_block_buf, dedup_entry_hdr.last_block_size); fd_dest = prepare_target_file(pathname, dest_dir, dedup_entry_hdr.mode); if (fd_dest == -1) { ret = errno; goto _UNDEDUP_REGFILE_EXIT; } if (debug) printf("%s/%s/n", dest_dir, pathname); /* write regular block */ block_num = dedup_entry_hdr.block_num; for(i = 0; i < block_num; ++i) { offset = DEDUP_PKGHDR_SIZE + metadata[i] * g_block_size; lseek(fd, offset, SEEK_SET); read(fd, buf, g_block_size); write(fd_dest, buf, g_block_size); } /* write last block */ write(fd_dest, last_block_buf, dedup_entry_hdr.last_block_size); close(fd_dest); _UNDEDUP_REGFILE_EXIT: if (metadata) free(metadata); if (buf) free(buf); if (last_block_buf) free(last_block_buf); return ret; } int undedup_package(char *src_file, char *dest_dir, int debug) { int fd, i, ret = 0; dedup_package_header dedup_pkg_hdr; dedup_entry_header dedup_entry_hdr; unsigned long long offset; if (-1 == (fd = open(src_file, O_RDONLY))) { perror("open source file"); return errno; } if (read(fd, &dedup_pkg_hdr, DEDUP_PKGHDR_SIZE) != DEDUP_PKGHDR_SIZE) { perror("read dedup_package_header"); ret = errno; goto _UNDEDUP_PKG_EXIT; } if (debug) show_pkg_header(dedup_pkg_hdr); offset = dedup_pkg_hdr.metadata_offset; for (i = 0; i < dedup_pkg_hdr.file_num; ++i) { if (lseek(fd, offset, SEEK_SET) == -1) { ret = errno; break; } if (read(fd, &dedup_entry_hdr, DEDUP_ENTRYHDR_SIZE) != DEDUP_ENTRYHDR_SIZE) { ret = errno; break; } ret = undedup_regfile(fd, dedup_entry_hdr, dest_dir, debug); if (ret != 0) break; offset += DEDUP_ENTRYHDR_SIZE; offset += dedup_entry_hdr.path_len; offset += dedup_entry_hdr.block_num * dedup_entry_hdr.entry_size; offset += dedup_entry_hdr.last_block_size; } _UNDEDUP_PKG_EXIT: close(fd); return ret; } void usage() { printf("Usage: undedup [OPTION...] <source file>/n"); printf("/nUnpackage files with deduplicaton technique./n"); printf("Mandatory arguments to long options are mandatory for short options too./n"); printf(" -z, --uncompress filter the archive through uncompress/n"); printf(" -c, --directory change to directory, default is PWD/n"); printf(" -d, --debug print debug messages/n"); printf(" -h, --help give this help list/n"); printf("/nReport bugs to <[email protected]>./n"); } int main(int argc, char *argv[]) { char tmp_file[] = "./.dedup/0"; char path[MAX_PATH_LEN] = "./0"; int bz = 0, bhelp = 0, bdebug = 0; int ret = -1, c; struct option longopts[] = { {"compress", 0, &bz, 'z'}, {"directory", 1, 0, 'c'}, {"debug", 0, &bdebug, 'd'}, {"help", 0, &bhelp, 'h'}, {0, 0, 0, 0} }; while ((c = getopt_long (argc, argv, "zc:dh", longopts, NULL)) != EOF) { switch(c) { case 'z': bz = 1; break; case 'c': sprintf(path, "%s", optarg); break; case 'd': bdebug = 1; break; case 'h': case '?': default: bhelp = 1; break; } } if (bhelp == 1 || (argc - optind) < 1) { usage(); return 0; } if (bz) { /* uncompress and undedup */ ret = zlib_decompress_file(argv[optind], tmp_file); if (ret == 0) { ret = undedup_package(tmp_file, path, bdebug); unlink(tmp_file); } } else { /* only undedup */ ret = undedup_package(argv[optind], path, bdebug); } return ret; }
/* dedup usage */
Usage:  dedup [OPTION...] <target file> <source files ...>

Package files with deduplicaton technique.

  -z, --compress   filter the archive through compress
  -b, --block      block size for deduplication, default is 4096
  -t, --hashtable  hashtable backet number, default is 10240
  -d, --debug      print debug messages
  -h, --help       give this help list

/* undedup usage */
Usage:  undedup [OPTION...] <source file>

Unpackage files with deduplicaton technique.


  -z, --uncompress  filter the archive through uncompress
  -c, --directory   change to directory, default is PWD
  -d, --debug       print debug messages
  -h, --help        give this help list


4、初步测试
  这里使用linux最新的kernel源码进行测试,并与tar工具进行比较。从www.kernel.org 下载linux-2.6.32.tar.gz文件,并解压出源文件,然后分别使用tar和dedup工具进行打包,分别得到以下几个文件。

Filename

File size

commands

linux-2.6.32.tar

382392320 (365MB)

tar cvf linux-2.6.32.tar linux-2.6.32/

linux-2.6.32.tar.dd 

380381944 (363M)

dedup linux-2.6.32.tar.dd linux-2.6.32.tar

linux-2.6.32.dd

357325910 (341MB)

dedup linux-2.6.32.dd linux-2.6.32/

linux-2.6.32.tar.gz

84322110 (81MB)

gzip -c linux-2.6.32.tar > linux-2.6.32.tar.gz

linux-2.6.32.tar.dd.gz

83978234 (81MB)

gzip -c linux-2.6.32.tar.dd > linux-2.6.32.tar.dd.gz

linux-2.6.32.dd.gz

83674306 (80MB)

gzip -c linux-2.6.32.dd > linux-2.6.32.dd.gz 

    linux-2.6.32.tar.gz解压出来的kernel源码文件数据很多,使用这个文件来测试应该具有普遍的意义。通过初步的测试结果,我们可以看出,即使在这样不明确数据是否具备较高重复率的情况下,dedup技术也能较明显地减少数据包的数据量。在数据重复率很高的测试用例下,比如全0或全1的大文件,dedup要远远优于tar。比如,全0的64MB文件,tar+gzip的结果为65KB,而dedup的结果才有286字节。

5、TODO
  1、变长数据块。目前是定长数据块的实现,技术上较为简单,变长数据块可能会获得更高的数据压缩率。
  2、相似文件识别。如果两个文件只有很小的差别,比如在某处插入了若干字节,找出这些数据块并单独处理,可能会提高数据压缩率。

你可能感兴趣的:(File,header,null,Path,存储系统,archive)