文件数据块分块算法解析
1. 简介
存储系统的重复数据删除过程一般是这样的:首先将数据文件分割成一组数据块,为每个数据块计算指纹,然后以指纹为关键字进行Hash查找,匹配则表示该数据块为重复数据块,仅存储数据块索引号,否则则表示该数据块是一个新的唯一块,对数据块进行存储并创建相关元信息。这样,一个物理文件在存储系统就对应一个逻辑表示,由一组FP组成的元数据。当进行读取文件时,先读取逻辑文件,然后根据FP序列,从存储系统中取出相应数据块,还原物理文件副本。从如上过程中可以看出,存储系统的重复数据删除的关键技术主要包括文件数据块切分、数据块指纹计算和数据块检索。
2.数据块分块算法
2.1 定长分块(fixed-size partition)
定长分块算法采用预先义好的块大小对文件进行切分,并进行弱校验值和md5强校验值。弱校验值主要是为了提升差异编码的性能,先计算弱校验值并进行hash查找,如果发现则计算md5强校验值并作进一步hash查找。由于弱校验值计算量要比md5小很多,因此可以有效提高编码性能。定长分块算法的优点是简单、性能高,但它对数据插入和删除非常敏感,处理十分低效,不能根据内容变化作调整和优化.
下面的chunk_file_hdr和chunk_block_entry数据结构见博客:http://blog.csdn.net/chenglinhust/article/details/8776830。
代码为:
/* fix-sized file chunking */
/*fd_src为分块的源文件*/
/*fd_chunk为分块后的文件*/
/*分块文件头chunk file header*/
static int file_chunk_fsp(int fd_src, int fd_chunk, chunk_file_header *chunk_file_hdr)
{
unsigned int rwsize;
unsigned char md5_checksum[16 + 1] = {0};
unsigned char csum[10 + 1] = {0};
char buf[BLOCK_SZ] = {0};
chunk_block_entry chunk_bentry; //一个chunk block entry
uint64_t offset = 0;
while (rwsize = read(fd_src, buf, BLOCK_SZ)) {
md5(buf, rwsize, md5_checksum);
uint_2_str(adler32_checksum(buf, rwsize), csum);
chunk_bentry.len = rwsize;
chunk_bentry.offset = offset;
memcpy(chunk_bentry.md5, md5_checksum, 16 + 1);
memcpy(chunk_bentry.csum, csum, 10 + 1);
rwsize = write(fd_chunk, &chunk_bentry, CHUNK_BLOCK_ENTRY_SZ);
if (rwsize == -1 || rwsize != CHUNK_BLOCK_ENTRY_SZ)
return -1;
offset += rwsize;
chunk_file_hdr->block_nr++;
}
if (rwsize == -1)
return -1;
return 0;
}
2.2 CDC切分(content-defined chunking)
CDC(content-defined chunking)算法是一种变长分块算法,它应用数据指纹(如Rabin指纹)将文件分割成长度大小不等的分块策略。与定长分块算法不同,它是基于文件内容进行数据块切分的,因此数据块大小是可变化的。算法执行过程中,CDC使用一个固定大小(如48字节)的滑动窗口对文件数据计算数据指纹。如果指纹满足某个条件,如当它的值模特定的整数等于预先设定的数时,则把窗口位置作为块的边界。CDC算法可能会出现病态现象,即指纹条件不能满足,块边界不能确定,导致数据块过大。实现中可以对数据块的大小进行限定,设定上下限,解决这种问题。CDC算法对文件内容变化不敏感,插入或删除数据只会影响到检少的数据块,其余数据块不受影响。CDC算法也是有缺陷的,数据块大小的确定比较困难,粒度太细则开销太大,粒度过粗则dedup效果不佳。如何两者之间权衡折衷,这是一个难点。
代码为:
/* content-defined chunking */
/*fd_src为分块的源文件*/
/*fd_chunk为分块后的文件*/
/*分块文件头chunk file header*/
static int file_chunk_cdc(int fd_src, int fd_chunk, chunk_file_header *chunk_file_hdr)
{
char buf[BUF_MAX_SZ] = {0}; //缓冲区最大值
char block_buf[BLOCK_MAX_SZ] = {0}; // 块的最大值
char win_buf[BLOCK_WIN_SZ + 1] = {0}; //块的窗口大小
unsigned char md5_checksum[16 + 1] = {0};
unsigned char csum[10 + 1] = {0};
unsigned int bpos = 0;
unsigned int rwsize = 0;
unsigned int exp_rwsize = BUF_MAX_SZ;
unsigned int head, tail;
unsigned int block_sz = 0, old_block_sz = 0;
unsigned int hkey = 0;
chunk_block_entry chunk_bentry; //分块实体
uint64_t offset = 0;
while(rwsize = read(fd_src, buf + bpos, exp_rwsize)) {
/* last chunk */
if ((rwsize + bpos + block_sz) < BLOCK_MIN_SZ)
break;
head = 0;
tail = bpos + rwsize;
/* avoid unnecessary computation and comparsion */
if (block_sz < (BLOCK_MIN_SZ - BLOCK_WIN_SZ)) {
old_block_sz = block_sz;
block_sz = ((block_sz + tail - head) > (BLOCK_MIN_SZ - BLOCK_WIN_SZ)) ?
BLOCK_MIN_SZ - BLOCK_WIN_SZ : block_sz + tail -head;
memcpy(block_buf + old_block_sz, buf + head, block_sz - old_block_sz);
head += (block_sz - old_block_sz);
}
while ((head + BLOCK_WIN_SZ) <= tail) {
memcpy(win_buf, buf + head, BLOCK_WIN_SZ);
hkey = (block_sz == (BLOCK_MIN_SZ - BLOCK_WIN_SZ)) ? adler32_checksum(win_buf, BLOCK_WIN_SZ) :
adler32_rolling_checksum(hkey, BLOCK_WIN_SZ, buf[head-1], buf[head+BLOCK_WIN_SZ-1]);
/* get a normal chunk, write block info to chunk file */
if ((hkey % BLOCK_SZ) == CHUNK_CDC_R) {
memcpy(block_buf + block_sz, buf + head, BLOCK_WIN_SZ);
head += BLOCK_WIN_SZ;
block_sz += BLOCK_WIN_SZ;
if (block_sz >= BLOCK_MIN_SZ) {
md5(block_buf, block_sz, md5_checksum);
uint_2_str(adler32_checksum(block_buf, block_sz), csum);
chunk_file_hdr->block_nr++;
chunk_bentry.len = block_sz;
chunk_bentry.offset = offset;
memcpy(chunk_bentry.md5, md5_checksum, 16 + 1);
memcpy(chunk_bentry.csum, csum, 10 + 1);
rwsize = write(fd_chunk, &chunk_bentry, CHUNK_BLOCK_ENTRY_SZ);
if (rwsize == -1 || rwsize != CHUNK_BLOCK_ENTRY_SZ)
return -1;
offset += block_sz;
block_sz = 0;
}
} else {
block_buf[block_sz++] = buf[head++];
/* get an abnormal chunk, write block info to chunk file */
if (block_sz >= BLOCK_MAX_SZ) {
md5(block_buf, block_sz, md5_checksum);
uint_2_str(adler32_checksum(block_buf, block_sz), csum);
chunk_file_hdr->block_nr++;
chunk_bentry.len = block_sz;
chunk_bentry.offset = offset;
memcpy(chunk_bentry.md5, md5_checksum, 16+1);
memcpy(chunk_bentry.csum, csum, 10 + 1);
rwsize = write(fd_chunk, &chunk_bentry, CHUNK_BLOCK_ENTRY_SZ);
if (rwsize == -1 || rwsize != CHUNK_BLOCK_ENTRY_SZ)
return -1;
offset += block_sz;
block_sz = 0;
}
}
/* avoid unnecessary computation and comparsion */
if (block_sz == 0) {
block_sz = ((tail - head) > (BLOCK_MIN_SZ - BLOCK_WIN_SZ)) ?
BLOCK_MIN_SZ - BLOCK_WIN_SZ : tail - head;
memcpy(block_buf, buf + head, block_sz);
head = ((tail - head) > (BLOCK_MIN_SZ - BLOCK_WIN_SZ)) ?
head + (BLOCK_MIN_SZ - BLOCK_WIN_SZ) : tail;
}
}
/* read expected data from file to full up buf */
bpos = tail - head;
exp_rwsize = BUF_MAX_SZ - bpos;
memmove(buf, buf + head, bpos);
}
if (rwsize == -1)
return -1;
/* process last block */
uint32_t last_block_sz = ((rwsize + bpos + block_sz) >= 0) ? rwsize + bpos + block_sz : 0;
char last_block_buf[BLOCK_MAX_SZ] = {0};
if (last_block_sz > 0) {
memcpy(last_block_buf, block_buf, block_sz);
memcpy(last_block_buf + block_sz, buf, rwsize + bpos);
md5(last_block_buf, last_block_sz, md5_checksum);
uint_2_str(adler32_checksum(last_block_buf, last_block_sz), csum);
chunk_file_hdr->block_nr++;
chunk_bentry.len = last_block_sz;
chunk_bentry.offset = offset;
memcpy(chunk_bentry.md5, md5_checksum, 16+1);
memcpy(chunk_bentry.csum, csum, 10 + 1);
rwsize = write(fd_chunk, &chunk_bentry, CHUNK_BLOCK_ENTRY_SZ);
if (rwsize == -1 || rwsize != CHUNK_BLOCK_ENTRY_SZ)
return -1;
}
return 0;
}
这个算法简单解释下:先读取一个BUF_MAX_SZ的字节流,这对这些字节流进行操作。每次移动BLOCK_WIN_SZ个字节,然后进行hash,达到指定的条件再向右移动BLOCK_WIN_SZ,当达到BLOCK_MIN_SZ和BLOCK_MAX_SZ之间的时候,就可以当做一个块。如果不满足条件,每次移动一个字节,直到达到边界条件。
2.3 滑动块切分(sliding block)
滑动块(sliding block)算法结合了定长切分和CDC切分的优点,块大小固定。它对定长数据块先计算弱校验值,如果匹配则再计算md5强校验值,两者都匹配则认为是一个数据块边界。该数据块前面的数据碎片也是一个数据块,它是不定长的。如果滑动窗口移过一个块大小的距离仍无法匹配,则也认定为一个数据块边界。滑动块算法对插入和删除问题处理非常高效,并且能够检测到比CDC更多的冗余数据,它的不足是容易产生数据碎片。