ChubaoFS DataNode
简介
chubaofs datanode 是chubaofs中的数据存储节点,用于将chubaofs中的文件数据存储在磁盘中;
chubaofs 中的datanode数据以dataPartition
为单位进行管理。dataPartition
是datanode中进行数据管理的最高单位。
大文件/小文件
文件系统中,每个文件存在元数据。由于磁盘和内存的性能成本差别,导致同一个文件系统对于大小文件的操作管理成本存在显著的差异。
对于小文件,其单个文件数据量少,平均磁盘操作成本巨大,且元数据数量膨胀快;
大文件数据文件大,顺序读写可以获得较低的磁盘操作成本,取得较高的性能,元数据相对总数据量成本低;
因此同一个文件系统对于大小文件很难使用同一策略来满足高效低费存储需求。
chubaofs对于大小文件的读写使用了不同的策略,以此满足大小文件的不同需求。
chubaofs中的小文件是客户端指定,小于一定大小(默认为:1MB)的文件。可以通过客户端配置参数tinySize
指定。
每个客户端文件的前1MB字节内的文件都使用TinyExtent
进行存储管理,
大于1MB
的文件部分使用NormalExtent
方式进行存储管理。
顺序写/随机写
ChubaoFS同时支持顺序写
和随机写
两种文件写入方式。
顺序写
: 指写入的数据每次只往文件末尾追加;随机写
: 指覆盖之前已经写过的文件内容;客户端在发起写请求时,根据写入数据的偏移是否已经存在,来决定使用那种写入方式;
顺序写
: 使用主从方式进行副本间同步数据, 对应的存储引擎;随机写
: 使用Raft协议来在数据副本间同步数据;
|
|
datanode目录结构
datanode配置文件中的
disks
指定了每个datanode dp的存储磁盘;每个
disk
中包含一系列datapartition_<id>_<dp_size>
和命名的目录,用于存储对应dp;每个
disk
中还可能包含expired_dataparition_<id>_<dp_size>
的过期dp,这些dp是在master中不存在的;
|
|
DataPartition
dp存储
datanode配置文件中的
disks
指定了每个datanode dp的存储磁盘;每个
disk
中包含一系列datapartition_<id>_<dp_size>
和命名的目录,用于存储对应dp;每个
disk
中还可能包含expired_dataparition_<id>_<dp_size>
的过期dp,这些dp是在master中不存在的;每个dp
APPLY文件:
保存当前dp的
appliedID
;dp的
StartRaftLoggingSchedule()
协程周期性(10s)将dp的appliedID 写入到APPLY文件中(先写.apply,后move);dp
META文件
META保存了当前dp的配置元信息;
当dp配置信息改变时,由
PersistMetadata()
将dp的配置元信息持久化到该文件中;dp加载时(LoadDataPartition),从META中读取dp元信息;
写入时机包括:
dp创建时;
周期性truncate raft log时,lastTruncateID发生改变;
raft 配置变更;
|
|
EXTENT_META
metadataFp
保存baseExtentID + PreAllocSpaceExtentID, 2个uint64, 总共16字节;
EXTENT_CRC
verifyExtentFp: 保存dp所有normal extent crc;
extent 加载时,根据extentID, 从EXTENT_CRC中加载对应extent 的crc到extent header 中;
normal extent写入时, 根据offset,size计算blockNo(128K为一个Block);
如果刚好是一个整block, 将crc写入
EXTENT_CRC
文件; 否则规整化后,写0
每个datapartition存储目录中有一个
EXTENT_CRC
文件,用于保存该datapartition 所有normal_extent
的crc校验头;EXTENT_CRC
文件由多个4KB大小的校验块组成,每个校验块存储一个normal_extent
的crc校验;每个4KB的校验块由1000个4B的CRC检验数据组成;
datanode节点在加载normal_extent时,
|
|
extent file
TinyExtent file(id: 1-64):
Normal Extent file:
写请求追加写到extent文件末尾;
normal extent file最大128MB, 写入前会对写入数据offset,size进行检查,超出128MB时无法写入;
NORMALEXTENT_DELETE
TINYEXTENT_DELETE
记录本dp已经删除过的tiny extent 数据块, 在第一次调用fallocate puchhole后记录;
每个被删除的tiny extent 数据块记录为24Byte,按
<extentID><offset><size>
这个依次追加到该文件中;
Status
Extent
每个dp包含多个extent
, 每个extent 对应一个extent file,用于存储数据。
extent file大小限制为128MB, 每个datapartition 包含的extent 个数不超过2000个(256GB)
extent分为NormalExtent
和 TinyExtent
两种类型。
TinyExtent
- id范围: [1, 64]
- 在每个dp加载时,会通过
NormalExtent
id: [1000, +)
ExtentStore
|
|
datanode 数据存储目录结构
EXTENT_META
:metadataFp
EXTENT_CRC
:verifyExtentFp
, 存储当前datapartition 的所有normal_extent
crc校验数据。TINYEXTENT_DELETE
:tinyExtentDeleteFp
,
TinyExtent中的删除
删除的extent数据段offset必须是4K对齐的;
先通过seek从文件中找到从offset开始的DATA起始位置newoffset;
通过newoffset 和 offset之间的关系判断要删除的数据段是否已被删除;
总共有以下4种情形:
其中第2中的数据完全落在Hole中,其中数据已经删除过;
其他情况都需要通过fallocate PunchHole来打洞删除从offset开始的size长的数据;
|
|
DataPartition加载
DataPartition修复
每个dp在新建和加载后会启动一个
statusUpdateScheduler()
协程;该协程每过1min会先更新一下dp状态(计算usage,更新status),并交替启动repair任务(TinyExtent, NormalExtent交替分开);
每5min会启动
ReloadSnapshot()
任务;
Repair任务由
LaunchRepair()
启动;先检查dp状态,
Unavailable
状态的dp不参与修复;然后
updateRelicas()
从master获取最新的副本ip;检查是否为leader,非leader退出,不启动 修复;
最后由
repair()
函数执行修复任务;
复制
每个datanode在接受每个客户端tcp连接时,会为该连接新建一个复制服务(ReplProtocol);
复制服务会启动3个后台协程:
1 2 3
go rp.OperatorAndForwardPktGoRoutine() //从toBeProcessCh管道读取待处理的packet,判断pkt是本地处理还是转发follower给 go rp.ReceiveResponseFromFollowersGoRoutine() //接收follower响应并处理 go rp.writeResponseToClientGoRroutine() //
最后通过
readPkgAndPrepare()
持续接收client conn发送的packet,先经过Prepare()对Packet做一下检查设置,然后将packet放入toBeProcessCh,交由OperatorAndForwardPktGoRoutine()
协程处理;Prepare()
在检查packet有效性后,最后会根据packet类型,增加extent info:leader上的tinyExtent的Write请求,通过
store.GetAvailableTinyExtent()
选择一个有效的tinyExtent ID;createExtent请求,通过
store.NextExtentID()
设置下一个extentID作为新extentID;最后将packet.Data复制到packet.OrgBuf上;
OperatorAndForwardPktGoRoutine()
协程主要从toBeProcessCh
接收请求,然后转发处理,具体流程如下:根据
packet.RemainingFollowers
判断是否需要转发给follower;如果
packet.RemainingFollower
==0,则调用
(datanode*)OperatorPacket()
在本地处理,处理完成后, 将reply放入
rp.responseCh
管道,通知writeResponseToClientGoRroutine()
协程处理;
如果
packet.RemainingFollower
>0,则通过
rp.sendRequestToAllFollowers()
将packet复制并发送个所有follower;若发送成功,则:
ReceiveResponseFromFollowersGoRoutine()
协程:- 接收
writeResponseToClientGoRroutine()
协程:主要调用
writeResponse()
处理响应;先检查
reply
中是否有错误;在调用
(*datanode)Post()
根据reply 添加一些额外的后续 处理:如果是
master
发送的命令,则将reply.NeedReply
标记为true
;如果是
read
命令,则将reply.NeedReply
标记为false(Read请求的reply 在Operation 的相关handle中已经处理);通过
cleanupPkt()
清理回收;设置metric;
根据
reply.NeedReply
判断是否需要响应,如果false,则返回, 否则将reply通过sourceConn发送回对端;