TFS分布式文件系统

简介

    TFS(Taobao !FileSystem)是一个高可扩展、高可用、高性能、面向互联网服务的分布式文件系统,主要针对海量的非结构化数据,它构筑在普通的Linux机器集群上,可为外部提供高可靠和高并发的存储访问。TFS为淘宝提供海量小文件存储,一般文件大小不超过1M,知足了淘宝对小文件存储的需求,被普遍地应用在淘宝各项应用中。它采用了HA架构和平滑扩容,保证了整个文件系统的可用性和扩展性。同时扁平化的数据组织结构,可将文件名映射到文件的物理地址,简化了文件的访问流程,必定程度上为TFS提供了良好的读写性能。mysql

 

TFS的整体结构

    一个TFS集群由两个!NameServer节点(一主一备)和多个!DataServer节点组成。这些服务程序都是做为一个用户级的程序运行在普通Linux机器上的。算法

在TFS中,将大量的小文件(实际数据文件)合并成为一个大文件,这个大文件称为块(Block), 每一个Block拥有在集群内惟一的编号(Block Id), Block Id在!NameServer在建立Block的时候分配, !NameServer维护block与!DataServer的关系。Block中的实际数据都存储在!DataServer上。而一台!DataServer服务器通常会有多个独立!DataServer进程存在,每一个进程负责管理一个挂载点,这个挂载点通常是一个独立磁盘上的文件目录,以下降单个磁盘损坏带来的影响。sql

!NameServer主要功能是: 管理维护Block和!DataServer相关信息,包括!DataServer加入,退出, 心跳信息, block和!DataServer的对应关系创建,解除。正常状况下,一个块会在!DataServer上存在, 主!NameServer负责Block的建立,删除,复制,均衡,整理, !NameServer不负责实际数据的读写,实际数据的读写由!DataServer完成。数据库

!DataServer主要功能是: 负责实际数据的存储和读写。后端

同时为了考虑容灾,!NameServer采用了HA结构,即两台机器互为热备,同时运行,一台为主,一台为备,主机绑定到对外vip,提供服务;当主机器宕机后,迅速将vip绑定至备份!NameServer,将其切换为主机,对外提供服务。图中的HeartAgent就完成了此功能。



缓存

    TFS的块大小能够经过配置项来决定,一般使用的块大小为64M。TFS的设计目标是海量小文件的存储,因此每一个块中会存储许多不一样的小文件。!DataServer进程会给Block中的每一个文件分配一个ID(File ID,该ID在每一个Block中惟一),并将每一个文件在Block中的信息存放在和Block对应的Index文件中。这个Index文件通常都会所有load在内存,除非出现!DataServer服务器内存和集群中所存放文件平均大小不匹配的状况。服务器

另外,还能够部署一个对等的TFS集群,做为当前集群的辅集群。辅集群不提供来自应用的写入,只接受来自主集群的写入。当前主集群的每一个数据变动操做都会重放至辅集群。辅集群也能够提供对外的读,而且在主集群出现故障的时候,能够接管主集群的工做。数据结构

1. 平滑扩容

    原有TFS集群运行必定时间后,集群容量不足,此时须要对TFS集群扩容。因为DataServer与NameServer之间使用心跳机制通讯,若是系统扩容,只须要将相应数量的新!DataServer服务器部署好应用程序后启动便可。这些!DataServer服务器会向!NameServer进行心跳汇报。!NameServer会根据!DataServer容量的比率和!DataServer的负载决定新数据写往哪台!DataServer的服务器。根据写入策略,容量较小,负载较轻的服务器新数据写入的几率会比较高。同时,在集群负载比较轻的时候,!NameServer会对!DataServer上的Block进行均衡,使全部!DataServer的容量尽早达到均衡。架构

进行均衡计划时,首先计算每台机器应拥有的blocks平均数量,而后将机器划分为两堆,一堆是超过平均数量的,做为移动源;一类是低于平均数量的,做为移动目的。并发

移动目的的选择:首先一个block的移动的源和目的,应该保持在同一网段内,也就是要与另外的block不一样网段;另外,在做为目的的必定机器内,优先选择同机器的源到目的之间移动,也就是同台!DataServer服务器中的不一样!DataServer进程。
当有服务器故障或者下线退出时(单个集群内的不一样网段机器不能同时退出),不影响TFS的服务。此时!NameServer会检测到备份数减小的Block,对这些Block从新进行数据复制。

    在建立复制计划时,一次要复制多个block, 每一个block的复制源和目的都要尽量的不一样,而且保证每一个block在不一样的子网段内。所以采用轮换选择(roundrobin)算法,并结合加权平均。

    因为DataServer之间的通讯是主要发生在数据写入转发的时候和数据复制的时候,集群扩容基本没有影响。假设一个Block为64M,数量级为1PB。那么NameServer上会有 1 * 1024 * 1024 * 1024 / 64 = 16.7M个block。假设每一个Block的元数据大小为0.1K,则占用内存不到2G。

2. 存储机制

    在TFS中,将大量的小文件(实际用户文件)合并成为一个大文件,这个大文件称为块(Block)。TFS以Block的方式组织文件的存储。每个Block在整个集群内拥有惟一的编号,这个编号是由NameServer进行分配的,而DataServer上实际存储了该Block。在!NameServer节点中存储了全部的Block的信息,一个Block存储于多个!DataServer中以保证数据的冗余。对于数据读写请求,均先由!NameServer选择合适的!DataServer节点返回给客户端,再在对应的!DataServer节点上进行数据操做。!NameServer须要维护Block信息列表,以及Block与!DataServer之间的映射关系,其存储的元数据结构以下:



    在!DataServer节点上,在挂载目录上会有不少物理块,物理块以文件的形式存在磁盘上,并在!DataServer部署前预先分配,以保证后续的访问速度和减小碎片产生。为了知足这个特性,!DataServer现通常在EXT4文件系统上运行。物理块分为主块和扩展块,通常主块的大小会远大于扩展块,使用扩展块是为了知足文件更新操做时文件大小的变化。每一个Block在文件系统上以“主块+扩展块”的方式存储。每个Block可能对应于多个物理块,其中包括一个主块,多个扩展块。
在DataServer端,每一个Block可能会有多个实际的物理文件组成:一个主Physical Block文件,N个扩展Physical Block文件和一个与该Block对应的索引文件。Block中的每一个小文件会用一个block内惟一的fileid来标识。!DataServer会在启动的时候把自身所拥有的Block和对应的Index加载进来。

3. 容错机制

  1. 3.1 集群容错

TFS能够配置主辅集群,通常主辅集群会存放在两个不一样的机房。主集群提供全部功能,辅集群只提供读。主集群会把全部操做重放到辅集群。这样既提供了负载均衡,又能够在主集群机房出现异常的状况不会中断服务或者丢失数据。

  1. 3.2 !NameServer容错

Namserver主要管理了!DataServer和Block之间的关系。如每一个!DataServer拥有哪些Block,每一个Block存放在哪些!DataServer上等。同时,!NameServer采用了HA结构,一主一备,主NameServer上的操做会重放至备NameServer。若是主NameServer出现问题,能够实时切换到备NameServer。
另外!NameServer和!DataServer之间也会有定时的heartbeat,!DataServer会把本身拥有的Block发送给!NameServer。!NameServer会根据这些信息重建!DataServer和Block的关系。

  1. 3.3 !DataServer容错

    TFS采用Block存储多份的方式来实现!DataServer的容错。每个Block会在TFS中存在多份,通常为3份,而且分布在不一样网段的不一样!DataServer上。对于每个写入请求,必须在全部的Block写入成功才算成功。当出现磁盘损坏!DataServer宕机的时候,TFS启动复制流程,把备份数未达到最小备份数的Block尽快复制到其余DataServer上去。 TFS对每个文件会记录校验crc,当客户端发现crc和文件内容不匹配时,会自动切换到一个好的block上读取。此后客户端将会实现自动修复单个文件损坏的状况。

4. 并发机制

    对于同一个文件来讲,多个用户能够并发读。
    现有TFS并不支持并发写一个文件。一个文件只会有一个用户在写。这在TFS的设计里面对应着是一个block同时只能有一个写或者更新操做。

5. TFS文件名的结构

    TFS的文件名由块号和文件号经过某种对应关系组成,最大长度为18字节。文件名固定以T开始,第二字节为该集群的编号(能够在配置项中指定,取值范围 1~9)。余下的字节由Block ID和File ID经过必定的编码方式获得。文件名由客户端程序进行编码和解码,它映射方式以下图:



    TFS客户程序在读文件的时候经过将文件名转换为BlockID和FileID信息,而后能够在!NameServer取得该块所在!DataServer信息(若是客户端有该Block与!DataServere的缓存,则直接从缓存中取),而后与!DataServer进行读取操做。

6. TFS性能数据

  1. 软件环境描述

【测试机软件状况描述】
(1) Red Hat Enterprise Linux AS release 4 (Nahant Update 8) 
(2) gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-11) 
(3) 部署了TFS客户端程序 
【服务器软件状况描述】
(1) Red Hat Enterprise Linux Server release 5.4 (Tikanga) 
(2) gcc (GCC) 3.4.6 20060404 (Red Hat 3.4.6-9) 
(3) 部署了2台!DataServer程序。
【服务器软件状况描述】
(1) Red Hat Enterprise Linux Server release 5.4 (Tikanga) 
(2) gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-46) 
(3) 部署了2台!NameServer(HA)程序。

  1. 硬件环境描述

【测试机硬件状况描述】
(1) 一枚八核Intel(R) Xeon(R) CPU E5520 @ 2.27GHz 
(2) 内存总数8299424 kB 
【服务器硬件状况描述】cpu/memory等
(1) 一枚八核Intel(R) Xeon(R) CPU E5520 @ 2.27GHz 
(2) 内存总数8165616 kB

  1. 随机读取1K~50K大小的文件性能




Read的TPS随着线程数的增长而增长,增加逐渐趋缓,到90线程的时候达到第一个高峰,此时再增长读线程,则TPS再也不稳定增加。

  1. 随机写入1K~50K大小的文件




Write的TPS在线程数60左右达到高峰,此时再增长写入线程,TPS再也不稳定增加。

  1. 在不一样线程写压力下的读文件性能







能够看出随着写压力的增长,读文件的TPS会大幅下滑。当写压力达到必定程度时读文件TPS趋缓。

同时,对平均大小为20K的文件进行了测试,测试中读:写:更新:删除操做的比率为100:18:1:1时,在!DataServer服务器磁盘util访问达到80%以上时,响应时间以下: 

TYPE SUCCCOUNT FAILCOUNT AVG(us) MIN(us) MAX(us)
read 100000 0 20886 925 1170418
write 18000 0 17192 2495 1660686
update 1000 0 48489 5755 1205119
delete 1000 0 14221 382 591651

TYPE:操做类型
SUCCCOUNT:成功个数
FAILCOUNT:失败个数
AVG:平均响应时间
MIN:最短响应时间
MAX: 最大响应时间

 

 

TFS写操做数据流

 

    TFS系统中,nameserver会保证一个文件有多个副本存储于不一样的dataserver上以保证冗余。当因为dataserver服务器宕机或因为其余缘由退出系统致使某些文件副本数量降低时,nameserver将会调度新的dataserver节点存储文件备份。一样为了保证数据一致性,当写入一个文件时,只有全部参与的dataserver均写入成功时,该操做才算成功。TFS的写操做数据流图以下所示:

    客户端首先向nameserver发起写请求,nameserver须要根据dataserver上的可写块,容量和负载加权平均来选择一个可写的block。而且在该block所在的多个dataserver中选择一个做为写入的master,这个选择过程也须要根据dataserver的负载以及当前做为master的次数来计算,使得每一个dataserver做为master的机会均等。master一段选定,除非master宕机,不会更换,一旦master宕机,须要在剩余的dataserver中选择新的master。返回一个dataserver列表。
客户端向master dataserver开始数据写入操做。master server将数据传输为其余的dataserver节点,只有当全部dataserver节点写入均成功时,master server才会向nameserver和客户端返回操做成功的信息。

 

 

得到Block ID和File ID

 

    根据TFS文件名解析出Block ID和block中的File ID.

获取dataserver地址

    向nameserver发送查询请求获得Block ID所在的dataserver地址。

    因为nameserver中维护了block和dataserver的对应关系,因此nameserver可以提供相应的信息。

    Note: 因为TFS是把大量小文件放在一个block里面,

    因此TFS的文件复制是基于block的,并且复制出来的block的block id应该是一致的

请求文件

    经过发送Block_ID、File_ID和offset为参数的读请求到对应的dataserver,获得文件内容。

    dataserver会根据本地记录的信息来获得File ID所在block的偏移量,从而读取到正确的文件内容.

NS中BlockManager和ServerManager介绍

 

    1. Ns中的BlockManager用来管理全部来自Ds的Block信息。由于Block的数量比较多,所以,BlockManager将Block组织成HashMap的数据结构,Hash的桶的个数由MAX_BLOCK_CHUNK_NUMS决定。另外,为了组织方便,定义了一个双向队列std::deque<std::pair<uint32, uint64> > delete_block_queue_,用来对删除的Block(以及Block所在的Server)进行一个管理而且将最近(在规定时间内)写过的Block也组织成一个HashMap便于管理(难道这个和延迟删有关,就是最近时间内有写入的不立刻删除?)。经过这些数据结构,BlockManager能够实现insert一个Block,remove一个Block,将删除的Block以及对应的Server加入和从删除队列中移除,dump全部的Block信息以及最近写入比较频繁的Block信息。 此外,BlockManager还能够判断某个Block是否存在(经过BlockId)以及先经过BlockId得到BlockCollect结构,进而获取到该Block所对应的Ds的信息(这里提供多个重载)。在与Ds的关系方便,BlockManager提供了创建、解除以及更新具体Block与Ds关系接口,以及判断某个Block是否须要复制、压缩、迁移的接口。最后,BlockManager还会根据时间在last_write_blocks_[i]中插入和删除最近写入的Block。

    2. Ns中的ServerManager用来管理全部的Server信息。为了管理好活动的和不可服务的DS,ServerManager定义了两个Server列表servers_和dead_servers_。针对具体的DS的操做大体包括加入Server到活动列表(分为是新加入的仍是暂时不可服务又好了的),从活动列表中移除Server到不可服务列表(这种状况可能发生在Ds某种缘由退出)。当Server在不可服务列表中超过必定时间后,就会将它从不可服务列表中移除(这种状况多是磁盘坏掉了,因此等换好新盘启动须要必定的时间)。另外,经过ServerManager能够获得活动Server列表以及不可服务Server列表以及某一个范围内的Server列表。 与BlockManager相似,ServerManager也提供了创建和解除具体Ds与Block的关系接口,但这些过程是以各个Server为中心来完成的。此外,ServerManager还负责挑选可写主块,先由ServerManager挑一个Server,再由Server挑一个Block。当BlockManager中发现某些Block须要复制时,因为每一个Block对应多个Server,ServerManager负责挑选出要复制的源Server和目标Server。当ServerManager发现某个Server不知足均衡(目前是将活动列表中的前32个server根据容量百分比,特别小的做为目标Server,特别大的做为源Server)时,针对该Server(做为Source Server)里面的具体Block,ServerManager负责挑选出可作为目标的Server。当某种缘由致使Block的副本数大于最大副本数时,ServerManager会根据包含该Block的Server的容量进行排序并在知足必定条件下选择一个Server将多余的Block进行删除。(在选择复制、迁移目标Server时须要考虑Server是否不在任务队列里,是否有空间,以及是否和已经挑选的Server在不一样机架)

 

Dataserver后台线程介绍

 

1、 心跳线程

    这里的心跳是指Ds向Ns发的周期性统计信息。原先的作法是当Ds须要汇报block时会将blockInfo的信息经过心跳包的形式发给Ns。而如今的心跳只负责keepalive,汇报block的工做由专门的包进行发送。(因此以前的作法是Ns会在心跳的回复包中带上一个状态(status),Ds在收到这个状态包后,会根据状态进行一些相应的操做(好比淘汰过时的Block以及新增Block操做等))。

2、 复制线程(replicate_block.cpp)

    人工或者Ns能够添加复制Block任务至复制队列中,复制线程会从复制队列中取出并执行。结合Ns,整个复制的大体过程是ns向复制的源Ds发起复制任务,源Ds将复制任务所须要的信息结构(ReplBlockExt)加入复制队列中。复制线程取出一个复制任务后,会先经过ReadRawData接口将源Ds的Block数据读出,而后向目标Ds发WriteRawData消息,目标ds在接到writeRawData消息后复制数据,而后经过batch_write_info进行index的复制。而后源Ds将复制是否成功的状态向Ns进行回复,Ns在收到复制成功的消息后会进行Block与Ds关系的更新。当从ns中收到move的操做后,还会将源ds上的Block删除掉。在管理复制的过程当中,还用到两个重要的数据结构ReplicateBlockMap_和ClonedBlockMap_,前者用来记录源中将要进行复制的Block,后者用来记录目标中正在复制Block的状态。

3、 压缩线程(compact_block.cpp)

    真正的压缩线程也从压缩队列中取出并进行执行(按文件进行,小文件合成一块儿发送)。压缩的过程其实和复制有点像,只是说不须要将删除的文件数据以及index数据复制到新建立的压缩块中。要判断某个文件是否被删除,还须要拿index文件的offset去fileinfo里面取删除标记,若是标记不是删除的,那么就能够进行write_raw_data的操做,不然则滤过。

4、 检查线程

    a 清理过时的Datafile; b 修复check_file_queue_中的逻辑块(block_checker.cpp) c 清理过时的复制块(因为复制过程当中出错致使的错误复制块,复制目标的ds作) d 清理过时的压缩块(因为压缩过程当中出错致使的错误压缩块,压缩在同一个ds上作) e 天天rotate读写日志,清理过时的错误逻辑块 f 读日志累积后刷磁盘

 

b的详细过程: 每次对文件进行读写删操做失败的时候,会try_add_repair_task(blockid, ret)来将ret错误的block加入check_file_queue_中,正常状况下加入的为-EIO(I/O错误)的错误Block,那何时加入的是CRC的错误呢?人工进行修复的时候发该类型的CRC_ERROR_MESSAGE消息,而后也会加入check_file_queue_中.也就是说人工修复是认为CRC错误的。而后在check的时候会根据类型进行do_repair_crc仍是do_repair_eio操做,对各自类型进行错误统计,其中check_block的过程就是经过crc_error和eio_error数量来判断该Block是否过时(对于过时的逻辑块,在错误位图上进行相应物理块的设置),若是是,则请求Ns进行update_block_info, 若是不是,对于eio请求,则没法修复,设置Block不正常(abnormal)的最新时间,对于Crc的则尝试修复,修复过程当中会从其余Ds上读副原本进行修复,若出错则会请求Ns进行update_block_info,不然设置Block不正常的最新时间。

 

rcserver介绍

    TFS 在2.0版本增长了一个server, 叫作 rcserver. 这个 server 主要是为了淘宝内部管理使用 TFS 的各个应用. 咱们给每一个应用分配一个惟一的 AppKey. TFS 客户端使用这个 AppKey 登陆到 rcserver, 取得本身应该访问的 TFS 集群信息. 客户端还会按期把本身的一些统计值发送给 rcserver. 具体信息能够参看源码中 doc 目录下的关于 rcserve 的文档.

 

metaserver介绍

 

 

1. 简介

 

    metaserver是咱们在2.0版本引进的一个服务. 用来存储一些元数据信息, 这样本来不支持自定义文件名的 TFS 就能够在 metaserver 的帮助下, 支持自定义文件名了.

2. 组成

    metaserver 由一个主控节点(rootserver), 若干服务节点(metaserver) 组成. rootserver 主要管理全部的 metaserver. 而metaserver 完成跟文件相关的操做. metaserver 缓存最近的被访问到目录和文件信息. 对于任何写入, 除了更改本身的缓存外还要更改后端持久化存储中的内容. 目前咱们暂时使用 mysql 数据库提供后端持久化存储, 未来会替换成淘宝本身的分布式数据库 oceanbase.

3. 访问过程

    客户端在作自定义文件名的读操做的时候, 会先从 rootserver 获得关于 metaserver 的信息, 并缓存在本身的内存中. 而后用自定义文件名去 metaserver 中查找 TFS 文件名信息, 再去 TFS 中访问该文件. 客户端在作自定义文件名的写操做的时候, 会先写入到 TFS 中, 再把 TFS 文件名和自定义文件的对应关系写入metaserver中.

4. 自定义文件名的限制

    咱们目前要求使用自定义文件名的时候必须传入一个app_id 一个 uid. 这两个 id 成为全部自定义文件名的固定前缀. mv 操做只能在相同的app_id, uid 之下进行. 这样作是咱们为了简化实现复杂度. 咱们的应用都是要面向海量客户, 每一个客户自身的数据量有限. 有了上面的限制, 咱们能够老是把对同一个app_id uid的请求用相同的 metaserver 来响应. 这样使得咱们能够很容易的处理数据一致性问题.

5. 写文件的特殊点

    在使用自定义文件名写文件的时候, 必须先调用 creat_file 接口创建文件. 文件创建以后, 能够多进程并发的写这个文件. 这样是为了便于大文件的分片上传. 咱们仍是不支持对已有文件的修改, 因此分片上传的时候各个分片是不能互相覆盖的.

6. 后续计划

    目前自定义文件名提供的功能还比较简单初级, 咱们会根据应用的需求逐步完善功能, 提升性能. 咱们未来计划在 oceanbase 团队的帮助下, 把后端存储替换成 oceanbase 数据库. 另:编译时候咱们设置了 mysql 的最低版本, 这个版本设置的比较高, 其实只要是5.0以上版本就能够支持这个应用.

相关文章
相关标签/搜索