Chinaunix首页 | 论坛 | 博客
  • 博客访问: 160759
  • 博文数量: 17
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 342
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-19 11:38
个人简介

A ZFS fan

文章分类
文章存档

2014年(17)

分类: 服务器与存储

2014-04-16 20:58:59

数据去重,简单地说就是重复数据删除。从某种意义上说也是一种数据压缩技术。

1. 数据去重的优势


  • 节约磁盘空间:对于村出在同一个磁盘上的同一个文件或者是不同的文件之间的重复数据删除可以大大减少磁盘的使用量。
  • 节约网络带宽:去重之后的数据在网络上传输可以大大降低网络资源的占用。很多网络同步工具都采用源端数据去重后再上传数据以节约网络带宽。
  • 提升写磁盘性能:数据在磁盘和内存之间的传输是存储系统的瓶颈,如果将数据去重之后再写入磁盘就可以大大提高磁盘的读写性能。
  • 利于虚拟服务器存储:如果为不同的服务建立多个虚拟机,其实这些虚拟机的系统是相同的,如果采用去重技术,将大大减少不必要的资源浪费,将磁盘空间用于有效数据的存储。


数据去重由于其在网络以及节省磁盘方面的绝对优势,已经被广泛应用到存储、网盘、云服务、邮件服务等方面。

2. 数据去重的分类

分类标准 去重时间 去重粒度 分块方式 数据处理位置
分类结果 在线/离线去重 文件级别/块级别 定长块/边长块 源端/目的端


3.  一般去重流程

去重流程图

ZFS作为一个整合了卷管理系统以及文件系统的新型文件系统,也提供了去重功能。由于ZFS在存储数据时按照数据块处理,自然是按照块级别的去重粒度来设计。总的来说,ZFS的去重是使用定长块级别的在线去重方式。

4. 是否需要开启去重功能

由于使用去重功能会消耗较多内存和CPU,一定意义上会影响系统的性能,所以ZFS默认情况下是关闭这个功能的,由用户根据自己系统的使用情况决定是否开启。开启的方法也很简单:

# zfs set dedup=on filesystemname(文件系统名称)

或者可以指定指纹的计算算法:

# zfs set dedup=fletcher4 filesystemname(文件系统名称)

 

既然会影响性能,我们需要考虑以下两点来判断我们是否需要开启这项功能:系统的内数据的重复率是否达到需要去重的必要?系统是否有足够的内存来满足去重的标准?如果都满足,则可以开启去重功能。

4.1.  判断是否有必要开启去重功能

# zdb -S tank

Simulated DDT histogram:

bucket              allocated                       referenced

______   ______________________________   ______________________________

refcnt   blocks   LSIZE   PSIZE   DSIZE   blocks   LSIZE   PSIZE   DSIZE

------   ------   -----   -----   -----   ------   -----   -----   -----

     1    27.1K   3.39G   3.39G   3.39G    27.1K   3.39G   3.39G   3.39G

     2       35   4.38M   4.38M   4.38M       76   9.50M   9.50M   9.50M

     4        1    128K    128K    128K        4    512K    512K    512K

   32K        1    128K    128K    128K    46.8K   5.85G   5.85G   5.85G

 Total    27.1K   3.39G   3.39G   3.39G    74.0K   9.25G   9.25G   9.25G


dedup = 2.73, compress = 1.00, copies = 1.00, dedup * compress / copies = 2.73

如果输出结果中的dedup值大于2,则可以考虑开启去重,小于2的话,说明数据重复率不高,没有必要开启。

 

4.2. 判断是否有足够的内存提供去重功能

每个DDT( Deduplication Data Table )在村村中大约占用320字节,将已经分配的block数目 * 320Byte 看内存是否满足。

当然,由于ZFS采用的是存储池管理文件系统的方式,我们可以对特定的文件系统设置去重功能,比如只开启home文件系统或是存放Software的文件系统的去重功能。

 

5. ZFS中去重功能的实现

ZFS通过DDT(Dedup Data Table)的形式来实现去重功能。

5.1 磁盘上的DDT

ZFS通过dde_type枚举类型定义了DDT在磁盘上的存储方式(目前仅有一种存储方式:ZAP方式)

  1. /* On-disk DDT formats, in the desired search order (newest version first). */
  2. enum ddt_type {
  3.     DDT_TYPE_ZAP = 0,
  4.     DDT_TYPES
  5. };


同时,定义了DDT的操作数组,便于对不同的DDT磁盘存储类型采用不同的操作方式,其中包括了DDT的创建、销毁、查找、预读取、更新、删除、遍历、统计等:由于C语言不存在面向对象的"类"的概念,所以通过下面这个结构体的定义,使得C语言也能够实现对于不同的DDT存储类型执行不同的操作。某种意义上实现了面向对象的功能。

点击(此处)折叠或打开

  1. /* Ops vector to access a specific DDT object type. */
  2. typedef struct ddt_ops {
  3.     char ddt_op_name[32];
  4.     int (*ddt_op_create)(objset_t *os, uint64_t *object, dmu_tx_t *tx,
  5.      boolean_t prehash);
  6.     int (*ddt_op_destroy)(objset_t *os, uint64_t object, dmu_tx_t *tx);
  7.     int (*ddt_op_lookup)(objset_t *os, uint64_t object, ddt_entry_t *dde);
  8.     void (*ddt_op_prefetch)(objset_t *os, uint64_t object,
  9.      ddt_entry_t *dde);
  10.     int (*ddt_op_update)(objset_t *os, uint64_t object, ddt_entry_t *dde,
  11.      dmu_tx_t *tx);
  12.     int (*ddt_op_remove)(objset_t *os, uint64_t object, ddt_entry_t *dde,
  13.      dmu_tx_t *tx);
  14.     int (*ddt_op_walk)(objset_t *os, uint64_t object, ddt_entry_t *dde,
  15.      uint64_t *walk);
  16.     uint64_t (*ddt_op_count)(objset_t *os, uint64_t object);
  17. } ddt_ops_t;


每个去重文件系统中的数据块都有对应的指纹,在磁盘上存储时对应一下ddt_key结构体:

点击(此处)折叠或打开

  1. /* On-disk ddt entry: key (name) and physical storage (value). */
  2. typedef struct ddt_key {
  3.     zio_cksum_t    ddk_cksum;    /* 256-bit block checksum */
  4.     uint64_t    ddk_prop;
  5. } ddt_key_t;


ddt_key中的ddk_prop为64位的整数,其中包含了数据块的压缩算法,占用的物理空间大小以及逻辑空间大小,每个位的具体分配如下:

   +-------+-------+-------+-------+-------+-------+-------+-------+

   |   0   |   0   |   0   | comp  |     PSIZE     |     LSIZE     |

   +-------+-------+-------+-------+-------+-------+-------+-------+

同时,ZFS也提供了一系列的宏定义来获取、设定comp、PSIZE、LSIZE的值。

 

存储时,通过ddt_object结构体来表示,其中包含了这个DDT的个数以及占用的磁盘空间和内存空间的大小:

点击(此处)折叠或打开

  1. typedef struct ddt_object {
  2.     uint64_t    ddo_count;    /* number of elments in ddt     */
  3.     uint64_t    ddo_dspace;    /* size of ddt on disk        */
  4.     uint64_t    ddo_mspace;    /* size of ddt in-core        */
  5. } ddt_object_t;


对于开启的文件系统来说,一个数据块可能同时被多个文件引用。如果这些数据块被损坏,那么影响的可能就不是一个文件那么简单了,它将会导致文件系统中多个文件不可用,这样的后果是很严重的,所以ZFS针对数据块被引用的次数的多少,提供不同份数的存储:引用次数多的将提供多个备份。备份数目通过以下变量定义:

点击(此处)折叠或打开

  1. enum ddt_class {
  2.     DDT_CLASS_DITTO = 0,
  3.     DDT_CLASS_DUPLICATE,
  4.     DDT_CLASS_UNIQUE,
  5.     DDT_CLASSES
  6. };


另外,ZFS也对DDT保留详细的统计信息,统计信息的结构体如下面两个结构体所示,单个的统计信息中包含统计的Block块的数目,实际磁盘存储空间大小(dds_lsize,dds_psize,dds_dsize)以及如果不使用去重应该占用的空间大小(dds_ref_blocks,dds_ref_psize,dds_ref_dsize)。

点击(此处)折叠或打开

  1. typedef struct ddt_stat {
  2.     uint64_t    dds_blocks;    /* blocks            */
  3.     uint64_t    dds_lsize;    /* logical size            */
  4.     uint64_t    dds_psize;    /* physical size        */
  5.     uint64_t    dds_dsize;    /* deflated allocated size    */
  6.     uint64_t    dds_ref_blocks;    /* referenced blocks        */
  7.     uint64_t    dds_ref_lsize;    /* referenced lsize * refcnt    */
  8.     uint64_t    dds_ref_psize;    /* referenced psize * refcnt    */
  9.     uint64_t    dds_ref_dsize;    /* referenced dsize * refcnt    */
  10. } ddt_stat_t;

  11. typedef struct ddt_histogram {
  12.     ddt_stat_t    ddh_stat[64];    /* power-of-two histogram buckets */
  13. } ddt_histogram_t;


在实际操作时,统计信息被按照引用计数的多少分成64类(通过ddt_histogram定义)。比如,引用计数是一个64位的整数,该整数的最高非0位是第N位,那么,这个统计信息就将被放到ddt_histogram对应的ddh_stat[N]中。比如对于前文提到的"zdb -S tank"的结果可以看出,被引用1次的block数目有27.1 * 1024个,被引用超过32×1024次的block有1个。接下来,我们将同样tank文件系统中的file1文件复制一份,将会得到下面的结果,由于file1文件是通过makefile命令创建出来的3.0G大小的文件(其中的内容可能是相同字符的填充),可以发现,dedup因子由2.73变成了3.61,重复率大大增加。

# pwd
/tank
# cp file1 file3
root@1dom_sun1# zdb -S tank
Simulated DDT histogram:

bucket              allocated                       referenced
______   ______________________________   ______________________________
refcnt   blocks   LSIZE   PSIZE   DSIZE   blocks   LSIZE   PSIZE   DSIZE
------   ------   -----   -----   -----   ------   -----   -----   -----
     1    26.2K   3.28G   3.28G   3.28G    26.2K   3.28G   3.28G   3.28G
     2      914    114M    114M    114M    1.79K    228M    228M    228M
     4       13   1.62M   1.62M   1.62M       64      8M      8M      8M
     8        1    128K    128K    128K        8      1M      1M      1M
   64K        1    128K    128K    128K    69.8K   8.73G   8.73G   8.73G
 Total    27.1K   3.39G   3.39G   3.39G    97.9K   12.2G   12.2G   12.2G

dedup = 3.61, compress = 1.00, copies = 1.00, dedup * compress / copies = 3.61


5.2 内存中的DDT

 

在内存中,DDT通过下面的结构体来表示,其中包含了两棵AVL树(树中的每个节点都是ddt_entry),DDT的指纹算法,统计信息/DDT存储的对象编号等等。

点击(此处)折叠或打开

  1. /* In-core ddt */
  2. struct ddt {
  3.     kmutex_t    ddt_lock;
  4.     avl_tree_t    ddt_tree;
  5.     avl_tree_t    ddt_repair_tree;
  6.     enum zio_checksum ddt_checksum;
  7.     spa_t        *ddt_spa;
  8.     objset_t    *ddt_os;
  9.     uint64_t    ddt_stat_object;
  10.     uint64_t    ddt_object[DDT_TYPES][DDT_CLASSES];
  11.     ddt_histogram_t    ddt_histogram[DDT_TYPES][DDT_CLASSES];
  12.     ddt_histogram_t    ddt_histogram_cache[DDT_TYPES][DDT_CLASSES];
  13.     ddt_object_t    ddt_object_stats[DDT_TYPES][DDT_CLASSES];
  14.     avl_node_t    ddt_node;
  15. };



AVL树中的节点对应的ddt_entry定义如下:


点击(此处)折叠或打开

  1. /* In-core ddt entry */
  2. struct ddt_entry {
  3.     ddt_key_t    dde_key;
  4.     ddt_phys_t    dde_phys[DDT_PHYS_TYPES];
  5.     zio_t        *dde_lead_zio[DDT_PHYS_TYPES];
  6.     void        *dde_repair_data;
  7.     enum ddt_type    dde_type;
  8.     enum ddt_class    dde_class;
  9.     uint8_t        dde_loading;
  10.     uint8_t        dde_loaded;
  11.     kcondvar_t    dde_cv;
  12.     avl_node_t    dde_node;
  13. };


OK,至此,我们说明了一下ZFS去重功能在磁盘以及内存中涉及的一些结构体/枚举类型的定义,接下来说一下去重过程中的两个函数操作:


6. 两个函数说明


6.1. 计算去重数据块所需要存储的份数


int ddt_ditto_copies_needed(ddt_t *ddt, ddt_entry_t *dde, ddt_phys_t *ddp_willref)

前文已经说到了,去重之后的数据块可能跟多个文件相关联,如果这样的数据块发生损坏将可能影响到多个文件。通过这个函数,ZFS设置了一定的规则,用以计算特定的数据块所需存储的份数。


6.2. ZIO流水线过程中static int zio_ddt_write(zio_t *zio)


zio_ddt_write函数流程

 

ZFS通过存储池管理文件系统,这样我们更加方便对不同的文件系统设定去重属性。对去重功能的合理使用不仅在可以节约磁盘空间,也可以节省内存的使用(原本需要在内存中缓存多个数据块,由于去重的功效,可能只需要存储一个数据块,从而节省内存空间)。

 

本文乃nnusun原创,请勿转载,如需转载请详细标明出处

阅读(18078) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~