Hadoop笔记汇总系列第七篇-淘宝的TFS系统

495 阅读12分钟

这是我参与更文挑战的第14天,活动详情查看: 更文挑战

背景

上一篇笔记整理了一下Hadoop在存储小文件时的一些细节,这一篇把淘宝自研的TFS文件系统相关笔记汇总一下.

淘宝的TFS文件系统

TFS

  1. TFS为淘宝提供海量小文件存储
  2. 采用了HA架构和平滑扩容,保证了整个文件系统的可用性和扩展性
  3. 扁平化的数据组织结构,可将文件名映射到文件的物理地址,简化了文件的访问流程
  4. 集群由一对Name Server和多台Data Server构成,Name Server 的两台服务器互为双机
  5. 以block文件的形式存放数据文件,文件名内置元数据信息,用户自己保存TFS文件名与实际文件的对照关系–使得元数据量特别小

TFS总体结构

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

TFS元数据的管理

淘宝TFS文件系统在核心设计上最大的取巧的地方就在,传统的集群系统里面元数据只有1份,通常由管理节点来管理,因而很容易成为瓶颈。而对于淘宝网的用 户来说,图片文件究竟用什么名字来保存实际上用户并不关心,因此TFS在设计规划上考虑在图片的保存文件名上暗藏了一些元数据信息,例如图片的大小、时 间、访问频次等等信息,包括所在的逻辑块号。而在元数据上,实际上保存的信息很少,因此元数据结构非常简单。仅仅只需要一个fileID,能够准确定位文件在什么地方 由于大量的文件信息都隐藏在文件名中,整个系统完全抛弃了传统的目录树结构,因为目录树开销最大。拿掉后,整个集群的高可扩展性极大提高。实际上,这一设 计理念和目前业界的“对象存储”较为类似

NameServer的高可用

Nameserver服务部署时采用HA来避免单点故障,2台nameserver服务器共享一个vip。正常情况下,主nameserver持有vip提供服务,并将block修改信息同步至备,HA agent负责监控主备nameserver的状态,当其检测到主宕机时,HA agent将vip切换到备上,备就切换为主来接管服务,以保证服务的高可用

NameServer上元数据不持久化存储

Namserver上的所有元信息都保存在内存,并不进行持久化存储,dataserver启动后,会汇报其拥有的block信息给nameserver,nameserver根据这些信息来构建block到server的映射关系表,根据该映射关系表,为写请求分配可写的block,为读请求查询block的位置信息。Nameserver有专门的后台线程轮询各个block和server的状态,在发现block缺少副本时复制block(通常是由dataserver宕机导致的);在发现dataserver负载不均衡时,迁移数据来保证服务器间的负载均衡

RR算法分配block

TFS写文件时,nameserver在分配可写block时,简单的采用round robin的策略分配,这种策略简单有效,其他根据dataserver负载来分配的策略,实现上较复杂,同时由于负载信息不是实时的,根据过时的信息来分配block,实践证明其均衡效果并不好

DataServer生成文件名和block内的索引文件

当文件成功写到多个dataserver后,会向client返回一个由集群号、block id、file id编码而成的文件名,以后client通过该文件名即可从TFS访问到存储的文件,在dataserver上,每个block对应一个索引文件,索引中记录了block中每个文件在block内部的偏移位置以及文件的大小

缓存策略(Client或Tair)

Client负责完成读写删TFS文件的基本逻辑,并在失败时主动进行failover。

为了提高client读取文件的效率、并降低nameserver的负载,client会将block到dataserver的映射关系缓存到本地,由于block到dataserver的映射关系一般只会在发生数据迁移的时候才会发生变化,所以一旦本地cache命中,大部分情况下都能从cache里获取到的dataserver上访问到文件,如果cache已经失效(block被迁移到其他dataserver),客户端最终会从nameserver获取block最新的位置信息,从最新的位置上读取文件。

在实际应用中,通常客户端能使用的系统资源比较有限,能够用于本地缓存的内存并不大,而集群中block的数量有数千万,从而导致本地缓存的命中率并不高,为此TFS还支持远端缓存,将block到dataserver的映射关系缓存在tair中

TFS2.0中对自定义文件名和大文件的支持架构

根据业务的需求,TFS还实现了对自定义文件名和大文件存储的支持,支持这两种业务场景并没有改变TFS服务器端的存储机制,而是通过提供新的服务、封装client来实现。

对于自定义名的存储服务,TFS提供单独的元数据服务器(metaserver)来管理自定义文件名到TFS文件名的映射关系,当用户存储一个指定文件名的文件时,client首先将其存储到TFS中,得到一个由TFS分配的文件名,然后将用户指定的文件名与TFS文件名的映射关系,存储到metaserver,当读取自定文件名文件时,则会先从metaserver查询该文件名对应的TFS文件名,然后从TFS里读取文件。

对于大文件的存储,client会将大文件切分为多个小文件(通常每个2M)分片,并将每个分片都存储到TFS,得到多个文件名,然后将多个文件名作为新的文件数据存储到TFS,得到一个新的文件名(该文件名与正常的TFS文件有着不同的前缀,以区分其存储的是大文件的分片信息),当用户访问大文件时,client会先读出各个分片对应的TFS文件名信息,再从TFS里读出各个分片的数据,重新组合成大文件

TFS对客户端语言的支持&nginx模块代理

TFS提供了标准的C++客户端供开发者使用,同时由于淘宝内部业务主要使用java进行开发,因此TFS也提供了java客户端,每增加一门新语言的支持,都是在重复的实现客户端访问后端服务器的逻辑。当客户端发布给用户使用后,一旦发现bug,需要通知已经在使用的数百个应用来升级客户端,升级成本非常之高。

TFS通过开发nginx客户端模块来解决该问题,该模块代理所有的TFS读写请求,向用户提供restful的访问接口,nginx模块上线使用后,TFS的所有组件升级都能做到对用户透明,同时需要支持一门新语言访问TFS的成本也变得非常低,只需要按照协议向nginx代理发送http请求即可

TFS的块

  1. TFS会将多个小文件存储在同一个block中,并为block建立索引,以便快速在block中定位文件;每个block会存储多个副本到不同的机架上,以保证数据的高可靠性。每个block在集群中拥有全局唯一的数据块编号(block id),block id由nameserver创建时分配,block中的文件拥有一个block内唯一的文件编号(file id),file id由dataserver在存储文件时分配,block id和file id组成的二元组唯一标识一个集群里的文件
  2. TFS的块大小可以通过配置项来决定,通常使用的块大小为64M。TFS的设计目标是海量小文件的存储,所以每个块中会存储许多不同的小文件。DataServer进程会给Block中的每个文件分配一个ID(File ID,该ID在每个Block中唯一),并将每个文件在Block中的信息存放在和Block对应的Index文件中。这个Index文件一般都会全部load在内存
  3. 在TFS中,将大量的小文件(实际用户文件)合并成为一个大文件,这个大文件称为块(Block)。TFS以Block的方式组织文件的存储。每一个Block在整个集群内拥有唯一的编号,这个编号是由NameServer进行分配的,而DataServer上实际存储了该Block。在NameServer节点中存储了所有的Block的信息,一个Block存储于多个DataServer中以保证数据的冗余。对于数据读写请求,均先由NameServer选择合适的DataServer节点返回给客户端,再在对应的DataServer节点上进行数据操作。NameServer需要维护Block信息列表,以及Block与DataServer之间的映射关系

TFS集群扩容

  1. 由于DataServer与NameServer之间使用心跳机制通信,如果系统扩容,只需要将相应数量的新DataServer服务器部署好应用程序后启动即可。这些DataServer服务器会向NameServer进行心跳汇报。NameServer会根据DataServer容量的比率和DataServer的负载决定新数据写往哪台DataServer的服务器。根据写入策略,容量较小,负载较轻的服务器新数据写入的概率会比较高。同时,在集群负载比较轻的时候,NameServer会对DataServer上的Block进行均衡,使所有DataServer的容量尽早达到均衡
  2. TFS对集群的扩容支持非常友好,当集群需要扩容时,运维人员只需要准备好扩容的新机器,部署dataserver的运行环境,启动dataserver服务即可,当nameserver感知到新的dataserver加入集群时,会在新的dataserver上创建一批block用于提供写操作,此时新扩容的dataserver就可以开始提供读写服务了。

淘宝CDN&没有热点&数据迁移

由于TFS前端有淘宝CDN缓存数据,最终回源到TFS上的文件访问请求非常随机,基本不存在文件热点现象, 所以dataserver的负载与其存储的总数据量基本呈正比关系。当新dataserver加入集群时,其容量使用上与集群里其他的dataserver差距很大,因此负载上差距也很大,针对这种情况nameserver会对整个集群进行负载均衡,将部分数据从容量较高的dataserver迁移到新扩容的dataserver里

集群就近访问

对于支持多机房容灾的集群,TFS客户端提供了failover的支持,客户端读取文件时,会选择离自己最近的物理集群进行读取,如果读取不到文件(该物理集群可能是备集群,该文件还没有从主集群同步过来),客户端会尝试从其他的物理集群读取文件。

TFS负载均衡

  1. 作为目的的一定机器内,优先选择同机器的源到目的之间移动,也就是同台DataServer服务器中的不同DataServer进程
  2. 当有服务器故障或者下线退出时(单个集群内的不同网段机器不能同时退出),不影响TFS的服务。此时NameServer会检测到备份数减少的Block,对这些Block重新进行数据复制
  3. 在创建复制计划时,一次要复制多个block, 每个block的复制源和目的都要尽可能的不同,并且保证每个block在不同的子网段内。因此采用轮换选择(roundrobin)算法,并结合加权平均。

NameServer容错

  1. Namserver主要管理了DataServer和Block之间的关系。如每个DataServer拥有哪些Block,每个Block存放在哪些DataServer上等。同时,NameServer采用了HA结构,一主一备,主NameServer上的操作会重放至备NameServer。如果主NameServer出现问题,可以实时切换到备NameServer。
  2. NameServer和DataServer之间也会有定时的heartbeat,DataServer会把自己拥有的Block发送给NameServer。NameServer会根据这些信息重建DataServer和Block的关系

DataServer容错

TFS采用Block存储多份的方式来实现DataServer的容错。每一个Block会在TFS中存在多份,一般为3份,并且分布在不同网段的不同DataServer上。对于每一个写入请求,必须在所有的Block写入成功才算成功。

当出现磁盘损坏DataServer宕机的时候,TFS启动复制流程,把备份数未达到最小备份数的Block尽快复制到其他DataServer上去。 TFS对每一个文件会记录校验crc,当客户端发现crc和文件内容不匹配时,会自动切换到一个好的block上读取。此后客户端将会实现自动修复单个文件损坏的情况

文件名结构

  1. TFS的文件名由块号和文件号通过某种对应关系组成,最大长度为18字节。文件名固定以T开始,第二字节为该集群的编号(可以在配置项中指定,取值范围 1~9)。余下的字节由Block ID和File ID通过一定的编码方式得到。文件名由客户端程序进行编码和解码
  2. TFS客户程序在读文件的时候通过将文件名转换为BlockID和FileID信息,然后可以在NameServer取得该块所在DataServer信息(如果客户端有该Block与DataServere的缓存,则直接从缓存中取),然后与DataServer进行读取操作