TFS客户端分析
TFS客户端是用户访问TFS的入口,目前TFS已支持C/C++、java、php、python等语言的客户端,本文主要分析C++客户端的实现逻辑。
TfsClient类是提供给tfs客户的原生C++接口类,其依赖TfsClientImpl代理负责具体实现,另外,在TfsClient上又封装了一套C客户端接口。
TfsSession类负责处理与nameserver相关的工作,包括获取block信息,管理block cache,如果需要进行文件排重时,还负责与tair server交互(TfsUniqueStore)。
TfsUniqueStore用于支持tfs文件排重,其将文件的指纹存储在tair上,需要进行文件排重的应用会先到tair cache上对比,如果发现指纹相同的文件,则直接返回该文件的文件名;如果没有找到指纹相同的文件,则执行一次正常的tfs写流程。
TfsClientImpl是负责客户端的具体实现,其主要包括一个(fd, TfsFile)的映射表,用于实现文件描述符fd到文件对象TfsFile的转换,类似与linux内核里fd到File对象的映射表。
FSName类封装了TFS文件名的编码规则实现,TFS文件名包含了文件所在集群信息,blockid(文件所在的block、编号),以及文件的fileid(用于在block内定为文件)。TFS客户端将这些信息进行base64编码,并返回给用户,用户通过返回的文件名来访问文件。
TfsFile代表一个打开的tfs文件对象,其提供了对文件进行open,read/write,close等接口,TfsClientImpl对文件的操作,最终会转化为调用具体文件对象的操作。
TfsSmallFile和TfsLargeFile继承自TfsFile对象,分别用于处理小文件和大文件,tfs对于大文件的处理方式是,将大文件拆分成多个小文件,然后把这些小文件的文件名再存成一个tfs文件,并返回这个文件名作为最终文件名,这个文件名以L开头,用于标识其为大文件。
LocalKey用于大文件存取时的断点续传,个人感觉这里做的太过了,远超出文件系统负责的范畴,可能是业务方使用更方便吧,毕竟小白用户还是挺多的,他们只管存取,不想关心过程。
TfsMetaClient用于支持自定义tfs文件名,通过增加metaserver存储用户自定义文件名与tfs文件名的映射,来达到支持tfs自定义文件名的目的。接口中app_id,uid分别代表应用的key和用户的淘宝id,其用于保证将某个应用某个用户的的文件映射数据分布到同一个metaserver中,以消除单用户可能出现的跨metaserver的操作(如mv等)。对于已有的TFS文件,要想得到自定义文件名的支持,须将该文件通过自定义文件名接口重新存储一份,最近一直在做的数据迁移就是为这些业务服务的,从中我发现,其实有更简单的方式达到目的,就是增加一个诸如link_file的接口,将原有的TFS文件和需要的自定义文件名对应起来即可,这样就不需要再存一次TFS文件,同时对于那些有特殊标记的文件,也完全不需要处理,对业务过渡(升级)毫无影响。
RcClient用于支持tfs集群全局资源管理,client在使用tfs前,需要先登录RC server,rc server为其分配集群信息,同时应用的读写等统计信息也会发送到RC server。
TFS客户端类之间的联系图
以TfsClient::save_buf为例分析tfs的写流程,该接口将一个缓冲区的数据存储为tfs文件。
至此,通过open调用,客户端获得了写请求对应的ds, blockid, fileid信息,新建一个tfs文件时,blockid,和fileid的确定在opne的不同阶段,分别从ns和ds获取的,可以考虑把fileid的获取融合到写过程减少一次与ds的网络交互。
附注:open调用完成后,客户端有了的映射关系,同时得到了待写文件的blockid, fileid, file_number, 其中file_number为ds为写操作分配的DataFile编号,用于临时存储写的数据,等待close调用后刷到磁盘。每个ds上file_number的分配在不同的区间内,故主ds向从ds写副本时理论上不会出现file_number相同的情形。
附注: 写文件数据到block时,首先通过索引检查文件是否已经存在,如果存在,如果新的文件数据超过原有数据大小,则要使用扩展块存储部分数据。数据写完后,更新索引信息。 客户端收到ds的回复后,close结束,整个save_buf过程也结束,save_file接口与save_buf类似,多一个读取本地文件的过程。