FastDFS 是一个C语言实现的开源轻量级分布式文件系统 。
1、FastDFS 架构
由三个部分构成
- 客户端(Client)
- 跟踪服务器(TrackerServer)
- 存储服务器(StorageServer)
FastDFS 架构
1.1、Tracker Server
跟踪服务器主要做调度工作,起到负载均衡的作用。
- 服务注册:管理 storage server 存储集群,storage server 启动时,会把自己注册到 tracker server 上,并且定期报告自身状态信息。
- 服务发现:client 访问 storage server 之前,必须先访问 tracker server,动态获取到 storage server 的连接信息,最终数据是和一个可用的 storage server 进行传输。
- 负载均衡:storage group 分配策略,storage server 分配策略,storage path 分配策略。
1.2、Storage Server
存储服务器主要提供容量和备份服务。
- 分组管理:以 group 为单位,每个 group 包含多台 storage server,数据互为备份,存储容量以group 内容量最小的 storage 为准。
- 数据同步:文件同步只能在 group 内的 storage server 间进行,采用 push 方式,即源服务器同步给目标服务器。源服务器读取 binlog 文件,将文件内容解析后,按操作命令发送给目标服务器,由目标服务按命令进行操作。
2、小文件存储机制
2.1、海量小文件存储问题 *
海量,百万级数量以上。小文件:1MB 以内的文件。
linux 文件存储结构
- inode:索引节点,存放文件信息
- block:存放文件数据
每个 inode 节点的大小,一般是 128 字节或 256 字节。
海量小文件存储主要有两个问题
- inode 节点占用空间,磁盘空间利用率不高
- 增删改查文件的时候遍历过多 inode 节点,影响效率
2.2、小文件机制配置
对于小文件,采用合并存储的方式,合并存储后的文件称为 trunk 文件。
配置文件 tracker.conf,只需要设置 use_trunk_file=true,store_server=1
即可。
# 是否启用trunk存储,默认false,支持小文件存储需要打开 use_trunk_file = true # 设置触发小文件机制的文件大小 # trunk文件最小分配单元,单位字节,默认256 slot_min_size = 256 # trunk文件最大分配单元,超过该值独立存储,默认16M # 此时,不会存储到trunk file中,而是作为一个单独的文件直接存储到文件系统 slot_max_size = 1MB # trunk文件大小,默认64MB,不要配置得过大或者过小,最好不要超过256MB。 trunk_file_size = 64MB
2.3、合并存储文件
向 FastDFS 上传文件成功时,服务器返回该文件的存取ID:fileid。当启用小文件存储时,不再是一个 fileid 对应一个磁盘文件,而是多个 fileid 对应的小文件合并存储成一个大文件 trunk。
小文件存储的 fileid,例如:
group1/M00/00/00/eBuDxWCwrDqITi98AAAA-3Qtcs8AAAAAQAAAgAAAAIA833.txt
小文件采用合并存储,与普通文件存储,增加了以下字段:
trunk ID
:大文件IDoffset
:该文件在 trunk 文件中的偏移量alloc_size
:该文件的分配大小
根据 offset
字段,通过 trunk id 对应的 trunk 文件中的偏移量,找到小文件的存储位置。其他字段的作用在后文详细介绍。
2.3.1、trunk 文件存储结构
trunk 内部由多个小文件组成,每个小文件由 trunkHeader + 数据组成
|||——————————————— trunkHeader 24bytes —————————————————————————————————||| |—1byte —|— 4bytes —|— 4bytes —|—4bytes— |—4bytes—|———— 7bytes ————| |—filetype—|—alloc_size—|—filesize—|—crc32 —|—mtime —|—formatted_ext_name—| |||—————————————————————— file_data filesize bytes ——————————————————————||| |———————————————————————— file_data ———————————————————————————————————————|
trunk 文件默认 trunk_file_size = 64MB,每次创建 trunk 文件总是会产生空余空间,可用于存储其他小文件。当 trunk 文件中的小文件被全部删除后,trucnk 文件才会被删除。
2.3.2、小文件存储平衡树
如何查询空闲块
在 storage 内部会为每个 store_path 构造一棵以空闲块大小作为关键字的空闲平衡树,相同大小的空闲块保存在链表中。每当需要存储一个文件时会首先到空闲平衡树中查找大于并且最接近的空闲块,然后试着从该空闲块中分割出多余的部分作为一个新的空闲块,加入到空闲平衡树中。若找不到满足的空闲块,则新建一个 trunk 文件 64MB,并加入到空闲平衡树,再一次执行上述查找操作。
2、文件上传原理
FastDFS 文件上传原理
2.1、负载均衡方法
2.1.1、tracker server
tracker 之间关系对等,用户上传文件时可以任意选择一个 tracker。
2.1.2、group
当 tracker 接收到上传文件请求时,为该文件分配一个 group,支持的规则有:
- Round robin,组间轮询
- Specified group,指定某一个组
- Load balance,选择最大剩余空间的组上传文件
2.1.3、storage server
选定 group 后,tracker 在 group 内选择一个 storage server 给用户,支持的规则有:
- Round robin,storage 轮询
- First server ordered by ip,storage 按 ip 排序
- First server ordered by priority,storage 按优先级排序(优先级在storage上配置)
2.1.4、storage path
当分配好 storage server 后,用户向 storage 发送写文件请求,storage 将会为文件分配一个数据存储目录,支持的规则有:
- Round robin,多个存储目录间轮询
- 剩余存储空间最多的优先
在 storage.conf 中修改:多个路径对应多个磁盘,提升磁盘并发读写能力。
store_path_count = 1 store_path0 = /home/fastdfs/storage
对于大文件来说,fastdfs 只能使用一个磁盘来整个存储。ceph 可以将大文件拆分成多个段,分配到 ceph 集群存储。
2.2、生成返回 fileid
选定存储目录后,storage 会为文件生成一个 fileid,当文件写入到 storage 后返回 fileid。
fileid 由以下字段拼接而成
- storage server ip
- 文件创建时间
- 文件大小
- 文件crc32
- 一个随机数
然后对该二进制串进行 base64 编码,转换为可打印的字符串。
2.2.1、两级目录
storage 的每个存储目录下有两级 256*256 的子目录,storage 会按 fileid 进行两次 hash(猜测),路由到其中一个子目录,然后将文件以 fileid 为文件名存储到该子目录下。
例:一个父目录存储100w文件,那么子目录存储的文件个数:100w / 65536 = 15.2 个文件
2.5.2、生成文件名
当文件存储到某个子目录后,即认为该文件存储成功,接下来为其生成文件名。
文件名由:group、存储目录、两级子目录、fileid、文件后缀名(用户指定)拼接而成
// 组名 | 磁盘 | 两级子目录 | fileid | 文件后缀名 group1/M00/00/00/wKgqHV4OQQyAbo9YAAAA_fdSpmg855.txt
独立文件 fileid
fileid 采用 base64 编码,其组成为:
storage_id | ip
:源 storage server ID 或 ip 地址timestamp
:文件创建时间戳file_size
:(若原始值为32位则前面加入一个随机值填充,最终为64位)crc32
:文件内容的检验码- 随机数 :引入随机数的目的是防止生成重名文件
wKgqHV4OQQyAbo9YAAAA_fdSpmg855 | 4bytes | 4bytes | 8bytes |4bytes | 2bytes | | ip | timestamp | file_size |crc32 | 随机数 |
小文件存储 fileid
小文件采用合并存储,fileid 除了上述字段,还增加了
trunk ID
:大文件IDoffset
:该文件在 trunk 文件中的偏移量alloc_size
:该文件的分配大小
eBuDxWCwrDqITi98AAAA-3Qtcs8AAAAAQAAAgAAAAIA833 | 4bytes | 4bytes | 8bytes |4bytes | 4bytes | 4bytes | 4bytes | 2bytes | | ip | timestamp | file_size |crc32 | trunk ID | offset | alloc_size | 随机数|
3、文件下载原理
FastDFS 文件下载原理
3.1、解析路径并路由
tracker 接收 client 发送的下载请求时,tracker 从文件名中解析出 group、大小、创建时间等信息,然后从 group 选择一个 storage server 返回。
3.2、校验读取并返回
client 和 storage server 建立连接,校验文件是否存在,并返回文件数据。
3.3、一致性 *
一致性的强弱
- 弱一致性:先返回存储结果,再同步,速度快,不可靠
- 强一致性:先同步,再返回存储结果,速度慢,安全可靠,非商业付费。
FastDFS 是弱一致性,先返回存储结果,再同步。可能发生同一组内的 storage 未同步的情况。
4、同步机制
4.1、同步规则
- 同步只在本组的 storage server 之间
- 源头数据需要同步,副本数据不需要再次同步,避免 push 循环。
- 新增 storage server 时,全量同步;其余情况,增量同步
4.2、binlog *
FastDFS 文件同步采用 binlog 异步复制方式。storage server 使用 binlog 记录文件ID和操作,根据 binlog 进行文件同步。
binlog 格式:
- 时间戳
- 操作类型
- 文件 ID(无 group)
时间戳 | 操作类型 | 文件名 1646123002 C M00/00/00/oYYBAF285cOIHiVCAACI-7zX1qUAAAAVgAACC8AAIkT490.txt 1646123047 c M00/00/00/oYYBAF285luIK8jCAAAJeheau6AAAAAVgABI-cAAAmS021.xml
文件同步只在同组内的 storage server 之间进行,采用 push 方式。FastDFS 没有主从节点,为避免 push 环路,规定只能由源 push 到副本。源指的是客户端直接操作的 storage,副本指的是本组其他的 storage,由 binlog 的操作类型来决定。
文件操作类型采用单个字母编码,大写字母表示源操作,小写字母表示被同步的副本操作
源 | 副本 |
C:上传文件(upload) | c:副本创建 |
D:删除文件(delete) | d:副本删除 |
A:追加文件(append) | a:副本追加 |
M:部分文件更新(modify) | m:副本部分文件更新(modify) |
U:整个文件更新(set metadata) | u:副本整个文件更新(set metadata) |
T:截断文件(truncate) | t:副本截断文件(truncate) |
L:创建符号链接(文件去重,只保留一份) | l:副本创建符号链接 |
binlog 位置:$base_path/data/sync/
- binlog.000:binlog 文件,最大1G。binlog.000, binlog.001, binlog.002, ...
- binlog_index.dat:记录当前写的 binlog 索引
4.3、mark *
为解决文件同步如何推送给不同的 storage,使用 .mark 文件记录已同步的位置(增量同步)。
ip.mark 位置:$base_path/data/sync/
binlog_index=0 // binlog 索引,表示上次同步的 ip-storage 的 binlog 索引 binlog_offset=3944 // 当前 binlog 偏移量,下一次读写的位置 scan_row_count=68 // 扫描 binlog 记录数 sync_row_count=53 // 同步的 binlog 记录数
4.5、新增节点同步
在已有节点A、B上,新增节点 storage C。A、B竞争关系,主动请求做同步推送
新增节点同步
5、高可用 *
通过使用 tracker 集群,storage 集群(3),即冗余服务,实现高可用
参考
6、高并发 *
高并发问题:
写并发
- 增加 group,水平扩展
- 一个 storage 配置多个硬盘,storage_path
注意:增加 storage 没有用,同组 storage 需要推送文件给其他 N-1 个 storage。
读并发
- 增加 storage,适用于少写多读的场景
- 增加 group,水平扩展
- 一个 storage 配置多个硬盘,storage_path