文件数据块分块算法解析

文件数据块分块算法解析

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更多的冗余数据,它的不足是容易产生数据碎片。

你可能感兴趣的:(文件数据块分块算法解析)