@HUST张友东 work@taobao zyd_com@126.com
分类: LINUX
2014-04-14 20:48:31
作者:zyd_com@126.com
博客:ydzhang.blog.chinaunix.net | blog.yunnotes.net
微博:
TFS新版本(tfs-2.6)的开发主要因为要将erasure code应用到TFS中,以节省存储成本。erasure code的引入,需要TFS在数据存储结构上做改变,这对于存储系统来说是非常大的改变,借着这个机会,也对TFS做了很多的优化工作,本文主要介绍2.6.0版本TFS的一些新特性。
block id升级至64位
TFS采用每个数据块(block)由一个uint32_t类型的blockid来标识,每个block 72MB,理论上单集群能支持约300PB的存储空间,但实际上并不是每个blockid都能被利用上;另外,TFS在应用erasure code后,会引入一种新的校验块(parity block);系统设计上是直接通过blockid的值就能判断出block的类型(最高位是否为0来区分),这样就使得数据块可用的blockid范围又缩小了一半。
为了有效解决可用blockid数量不足的问题,将blockid从uint32_t升级至uint64_t;由于TFS生成的文件名里编码了blockid,升级后,生成的新文件名将比原来更长,从原来的18字节扩张到27字节。
文件元信息移至index文件
TFS针对每个block,建立一个索引(index)文件,index中存储block里每个文件的在block内的偏移和大小信息,用于快速定位文件。同时,每个文件包含大小、创建时间、修改时间等元信息,这些信息存储在每个文件数据的开始处。index文件以hash表达形式组织,直接mmap的方式映射到内存,定位文件在block的位置非常高效。
按照上图的存储布局,如果要获取block内的文件列表(list_files,集群同步、迁移、对比时经常会用到),则要在block内部读取每个文件的头的少量数据,但会引发很多次磁盘IO操作;TFS新版本里将文件元信息全部移至index文件,使得操作文件元信息的stat、delete,list_files等操作都只用访问index文件,减少了磁盘IO次数。
从线上采集的结果看,原来每次tfs读请求带来的磁盘IO约1.2次,而将元信息移至index后,下降到约0.7次;这里两次采集的值来自同一台机器,同一个时间段,但由于负载并不完全相同,同时还受page cache命中情况的影响,仅能对比参考一下。
另外,在新的存储格式里,每个File的开始部分都预留了4个字节的版本信息(图中未画出,初始均为0),主要用于以后扩展,如果有需要,可以为文件存储更加丰富的元数据信息。
加速DS启动过程
TFS每个block的头部,包含一些描述block的元信息(12bytes),这些元信息在DS启动的过程中必须加载到内存,并汇报给Nameserver,这个加载过程会导致很多次随机磁盘IO(至少每个block对应一次)。TFS最初使用的磁盘都比较小,如160G、300G、600G,DS最多在1min内能加载完成,对集群影响并不大;但随着1T、2T磁盘的应用,加载时间成倍增加,2T盘有时需要5+min才能完成,使得集群升级(rolling update)非常慢,严重的影响运维效率。
新版本里,将所有block的元信息存储到一个单独的文件,每次DS加载只需要从磁盘读出这个文件的数据即可,基本一次IO就能搞定,DS的整个加载过程在几秒内完成,现在400+台机器的集群升级半小时内就能完成。
优化扩展块使用
TFS支持文件更新,如果更新后文件比原来更大,则更新的数据会直接追加到block末尾,然后更新index文件里对应的offset、size信息;如果更新时,block的大小超过了配置值,则需要为该block分配扩展块(正常的block称为主块),每个扩展块4M(可配置),扩展块的数量以及空间是在使用前是预分配的,如下图所示,线上平均为每个主块分配2个扩展块,而实际上,每个block平均使用不到1个扩展块,超过一半的扩展块空间是浪费的。
新版本里不再单独预分配扩展块,在使用前,所有的存储空间都分配给主块(正常的block),运行过程中,如果有block需要使用扩展块,则将一个未使用的主块拆分成多个扩展块使用,如下图所示,采用动态分配扩展块的方式,提高了TFS存储空间利用率(理论计算在5%左右)。
优化写文件流程
写文件到dataserver(DS)上包含如下3个步骤,每个步骤都对应客户端到主副本DS的一次网络请求。
A1. DS为文件分配一个block内唯一的file id,并为这次写分配一个写缓冲区。
A2. 将文件的数据推送至DS上的写缓冲区,如果文件较大,则需要推送多次。
A3. 将文件的数据从写缓冲区刷到磁盘持久化存储。
新版本将步骤A1合并到步骤A2,减少一次网络交互。当第一次推送文件数据时,DS会执行步骤1里需要做的操作,为写文件分配id及写缓冲区。
步骤A2、A3里,主DS除了需要在本地执行,还需要将写请求转发至其它副本所在的DS,以步骤A3里数据刷盘为例,可以分解为如下步骤:
B1. 主DS将数据从缓冲区刷到磁盘。 B2. 如果B1成功,请求其他的副本刷盘。 B3. 等待多个副本应答,如果所有副本都成功,向客户端返回成功,否则失败。
可以看出,只有主副本本地操作成功时,才将请求转发至其它副本,这样能避免主失败而导致的无效转发,但会使得主DS和其它副本上的操作变成串行,增加了写文件的延时。
但实际上,失败的情况并不常见,绝大部分的请求都是成功的。在新版本里,主DS收到客户端的请求时,检查请求参数合法后,就将请求转发到其他副本上,然后本地执行,并等待其他副本执行完毕后的应答,将原本多个节点串行的操作并行化,降低了写文件的response time。
控制集群内部流量
当nameserver检测到集群内有block副本数不足时,会发起复制任务,block副本数不足通常由磁盘故障导致,坏掉的磁盘上所有block都需要复制,以2T盘为例,如果坏掉,磁盘上约20000个block就会出现副本数不足,全部需要复制。
缺少副本的block复制应该尽快完成以保证数据可靠性,但由于没有对block复制进行控制,大量的block复制导致部分机器网卡流量飙高,使得用户正常读写请求的response time变长。
新版本对集群内部的网络流量使用进行控制,统计每个DS进程单位时间内复制、迁移等使用的网络流量,如果超过阈值,则会减缓复制、迁移,保证用户的请求不受影响。
优化压缩block时的IO
TFS在删除文件时,只是给文件设置删除标记,并没有真正的删除文件数据。当block内文件删除量超过一定比例时,在后台对block进行压缩整理(compact),回收删除文件的存储空间。
compact时,将block未删除的文件,逐个从磁盘上读出来,写入到新的block,compact完成后删除原来的block。逐个文件的读取和写入会到来很多次磁盘IO(compact时,DS同时向客户端提供读写服务,导致这里每次读写实际上是随机IO)。
新版本里,不再逐个文件读取和写入,而是每次从磁盘读取大块(如8M)的数据,在内存里进行整理,等内存缓冲区累计到一定大小,再一次性写入到新的block,通过额外的内存开销减少了很多次磁盘IO。
应用erasure code
为降低存储成本,将引入TFS。具体做法是以block为单位,选取M个写满的数据块,运用earsure code算法编码出N个校验块,这M+N个块称为一个编组,在这个编组内,丢失N个及以下block的情况下,丢失的block都能通过组内其他block计算恢复回来,其容错成本比传统的副本机制低。
以常见的3副本配置为例,应用erasure code前,每个block3个副本,容忍2副本丢失,D1、D2、D3、D4总共12个副本;应用erasure code之后,D1、D2、D3、D4计算出两个校验块P1、P2,构成一个编组,同时删除掉D1-D4多余的副本,编组内6个副本,同样容忍2副本丢失,相比原来的12副本,节省了50%的存储成本。
实际上,目前TFS集群里的副本数通常配置为2(采用3集群备份来保证数据安全性),按照4+2的配置应用erasure code后节省的存储成本在25%左右,关于TFS应用erasure code的更多细节将会单独再写一篇文章介绍。