4.3 合并存储文件
向FastDFS上传文件成功时,服务器返回该文件的存取ID fileid。当没有启动合并存储时该fileid和磁盘上实际存储的文件一 一对应。当采用合并存储时就不再一 一对应,而是多个fileid对应的文件被存储成一个大文件。
合并存储后的大文件统称为Trunk文件,没有合并存储的文件统称为源文件。
Trunk文件文件名格式:fdfs_storage1/data/00/00/000001 文件名从1开始递增,类型为int。
4.3.1 启动小文件存储时服务返回给客户端的fileid变化
1、独立文件存储的file id
文件名(不含后缀名)采用Base64编码,包含如下5个字段(每个字段均为4字节整数):
group1/M00/00/00/wKgqHV4OQQyAbo9YAAAA_fdSpmg855.txt
这个文件名中,除了.txt为文件后缀,wKgqHV4OQQyAbo9YAAAA_fdSpmg855 这部分是一个base64编码缓冲区,组成如下:
1)storage_id(ip的数值型)源storage server ID或IP地址
2)timestamp(文件创建时间戳)
3)file_size(若原始值为32位则前面加入一个随机值填充,最终为64位)
4)crc32(文件内容的检验码)
5)随机数 (引入随机数的目的是防止生成重名文件)
wKgqHV4OQQyAbo9YAAAA_fdSpmg855 | 4bytes | 4bytes | 8bytes |4bytes | 2bytes | | ip | timestamp | file_size |crc32 | 随机数 |
2、小文件存储的file id
如果采用了合并存储,生成的文件ID将变长,文件名后面多了base64文本长度16字符(12个字节)。
这部分同样采用Base64编码,包含如下几个字段:
group1/M00/00/00/eBuDxWCwrDqITi98AAAA-3Qtcs8AAAAAQAAAgAAAAIA833.txt
采用合并的文件ID更长,因为其中需要加入保存的大文件id以及偏移量,具体包括了如下信息:
1)storage_id(ip的数值型)源storage server ID或IP地址
2)timestamp(文件创建时间戳)
3)file_size:实际的文件大小
4)crc32:文件内容的crc32码
5)trunk file ID:大文件ID如000001
6)offset:文件内容在trunk文件中的偏移量
7)alloc_size:分配空间,大于或等于文件大小
8)随机数 (引入随机数的目的是防止生成重名文件
eBuDxWCwrDqITi98AAAA-3Qtcs8AAAAAQAAAgAAAAIA833 | 4bytes | 4bytes | 8bytes |4bytes | 4bytes | 4bytes | 4bytes |2bytes | | ip | timestamp | file_size |crc32 | trunk ID | offset | alloc_size | 随机数|
4.3.2 Trunk文件存储结构
trunk内部是由多个小文件组成,每个小文件都会有一个trunkHeader,以及紧跟在其后的真实数据,结构如下:
|||——————————————————————— 24bytes ——————————————————————————————————————||| |—1byte —|— 4bytes —|— 4bytes —|—4bytes— |—4bytes—|———— 7bytes ————| |—filetype—|—alloc_size—|—filesize—|—crc32 —|—mtime —|—formatted_ext_name—| |||—————————————————————— file_data filesize bytes ——————————————————————||| |———————————————————————— file_data ———————————————————————————————————————|
Trunk文件为64MB(默认),因此每次创建一次Trunk文件总是会产生空余空间.比如为存储一个10MB文件,创建一个Trunk文件,那么就会剩下接近54MB的空间(TrunkHeader 会24字节,后面为了方便叙述暂时忽略其所占空间)。下次要想再次存储10MB文件时就不需要创建新的文件,存储在已经创建的Trunk文件中即可。当 trunk 文件中的小文件被全部删除后,trucnk 文件才会被删除。
4.3.3 小文件存储平衡树
如何查询空闲块
在 storage 内部会为每个 store_path 构造一棵以空闲块大小作为关键字的空闲平衡树,相同大小的空闲块保存在链表中。每当需要存储一个文件时会首先到空闲平衡树中查找大于并且最接近的空闲块,然后试着从该空闲块中分割出多余的部分作为一个新的空闲块,加入到空闲平衡树中。若找不到满足的空闲块,则新建一个 trunk 文件 64MB,并加入到空闲平衡树,再一次执行上述查找操作。
五、FastDFS文件同步
文件上传成功后,其它的storage server才开始同步,其它的storage server怎么去感知,tracker server是怎么通知storage server?
1、storage定时发送心跳包到tracker,并附带同步的时间节点。
2、tracker收到后,向其他的 Storage Server 发送同步命令,通知它们去复制保存该文件。3、接收到 tracker 的同步命令后,其他的 storage 将请求源 storage 获取文件数据,并在本地进行保存,确保所有的 storage 都拥有相同的文件副本。
5.1 同步规则
- 只在本组内的storage server之间进行同步;
- 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了,源数据和备份数据区分是用binlog的操作类型来区分,操作类型是大写字母,表示源数据,小写字母表示备份数据;
- 当先新增加一台storage server时,由已有的一台storage server将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器。
5.2 binlog
FastDFS文件同步采用binlog异步复制方式。storage server使用binlog文件记录文件上传、删除等操作,根据binlog进行文件同步。binlog中只记录文件ID和操作,不记录文件内容。例如
1646123002 C M00/00/00/oYYBAF285cOIHiVCAACI-7zX1qUAAAAVgAACC8AAIkT490.txt 1646123561 d M00/00/00/oYYBAF285luIK8jCAAAJeheau6AAAAAVgABI-cAAAmS021.xml
binlog文件有三列,依次为:
1)时间戳
2)操作类型
3)文件ID(不带group名称)
∘ Storage_id(ip的数值型)
∘ timestamp(创建时间)
∘ file_size(若原始值为32位则前面加入一个随机值填充,最终为64位)
∘ crc32(文件内容的检验码)
文件操作类型采用单个字母编码,其中源头操作用大写字母表示,被同步的操作为对应的小写字母。文件操作字母含义如下
binlog 位置:base_path/data/sync/,例如
root@xx:/home/fastdfs/storage_group1_23000/data/sync#
5.3 mark
为解决文件同步如何推送给不同的 storage,文件同步采用增量方式,记录已同步的位置到mark文件中。mark文件存放路径为:$base_path/data/sync/。
增量同步是指在文件系统中只同步发生变化的部分,而不是对整个文件进行完全复制或传输。
//binlog索引id 表示上次同步给114.215.169.67机器的最后一条binlog文件索引 binlog_index=0 //当前时间binlog 大小 (单位是字节)表示上次同步给114.215.169.67机器的最后一条binlog偏移量, // 若程序重启了,也只要从这个位置开始向后同步即可。 binlog_offset=3944 //是否需要同步老数据 need_sync_old=1 //是否同步完成 sync_old_done=1 //同步已有数据文件的截至时间 until_timestamp=1621667115 //扫描记录数 scan_row_count=68 //每次同步操作时从源 Storage Server 读取的数据行数 sync_row_count=53
5.4 binlog同步过程
在FastDFS之中,每个Storaged之间的同步都是由一个独立线程负责的,该线程中的所有操作都是以同步方式执行的。比如一组服务器有A、B、C三台机器,那么在每台机器上都有两个线程负责同步,如A机器,线程1负责同步数据到B,线程2负责同步数据到C。
第一步:获取组内的其他Storage信息,并启动同步线程 ---- tracker_report_thread_entrance
在Storage.conf配置文件中,只配置了Tracker的IP地址,并没有配置组内其他的Storage。因此同组的其他Storage必须从Tracker获取。具体过程如下:
1)Storage启动时为每一个配置的Tracker启动一个线程负责与该Tracker的通讯。
2)默认每间隔30秒,与Tracker发送一次心跳包,在心跳包的回复中,将会有该组内的其他Storage信息。
3)Storage获取到同组的其他Storage信息之后,为组内的每个其他Storage开启一个线程负责同步。
第二步:同步线程执行过程 ---- storage_sync_thread_entrance
每个同步线程负责一台Storage的同步,以阻塞方式进行。
1)打开对应Storage的mark文件,如负责114.215.169.67的同步则打开
114.215.169.67_23000.mark文件,从中读取binlog_index、binlog_offset两个字段值,如取到值为:0、100,那么就打开binlog.000文件,seek到100这个位置。
2)进入一个while循环,尝试着读取一行,若读取不到则睡眠等待。若读取到一行,并且该行的操作方式为源操作,如C、A、D、T(大写的都是),则将该行指定的操作同步给对方(非源操作不需要同步),同步成功后更新binlog_offset标志,该值会定期写入到114.215.169.67_23000.mark文件之中。
第三步:若遇到同步前删除
假如同步较为缓慢,那么有可能在开始同步一个文件之前,该文件已经被客户端删除,此时同步线程将打印一条日志,然后直接接着处理后面的Binlog。
5.5 新增节点同步
在已有节点A、B上,新增节点 storage C。A、B竞争关系,主动请求做同步推送
5.6 高可用和高并发
通过使用 tracker 集群,storage 集群,即冗余服务,实现高可用。
高并发问题:
1)写并发
增加 group,水平扩展
注意:增加 storage 没有用,同组 storage 需要推送文件给其他 N-1 个 storage。
2)读并发
增加 storage,适用于少写多读的场景
增加 group,水平扩展
六、一些面试要点
1、storage如何分组
在 FastDFS 中,可以通过分组(Group)的方式对 Storage Server 进行分组管理。每个分组(Group)由一个或多个 Storage Server 组成,它们共享一个组名相同的存储路径。
要进行 Storage 分组,需要进行以下步骤:
1)在 Tracker Server 的配置文件 tracker.conf 中,设置group_name参数为你想要的分组名。例如:group_name=group1。
2)在每个 Storage Server 的配置文件storage.conf中,设置 group_name 参数为相同的分组名。例如:group_name=group1。
3)在 storage.conf 中,通过配置 store_path0 或 store_path1 参数指定存储路径,以供分组内的 Storage Server 共享。例如:store_path0=/data/fastdfs/group1。
4)启动并注册每个 Storage Server 到 Tracker Server。
通过这样的配置和启动步骤,多个 Storage Server 将会被分组,它们共享相同的组名和存储路径。这样的分组管理可以提高系统的可靠性和容错性,并能够实现数据的冗余备份与负载均衡。
需要注意的是,在 FastDFS 中,Tracker Server 并不直接管理分组,而是通过存储路径来识别和管理不同的分组。因此,每个分组应该使用唯一的存储路径,以确保分组的正确识别和管理。
2、不同group里的storage能否做同步
在 FastDFS 中,不同 Group 中的 Storage Server 之间是无法直接进行数据同步的。每个 Group 内的 Storage Server 是通过共享相同的存储路径来实现数据的冗余备份和负载均衡,但不同 Group 之间的数据同步需要通过其他方式来实现。
如果你需要在不同的 Group 之间进行数据同步,可以考虑使用 FastDFS 提供的文件复制功能。文件复制功能允许将一个文件从一个 Group 中的 Storage Server 复制到另一个 Group 中的 Storage Server。你可以使用 FastDFS 提供的命令行工具 fdfs_storaged 来进行文件复制操作。
3、存储的storage服务器宕机了,其他storage如何同步
在FastDFS中,数据通常会被分布式地复制到多个存储节点上。因此,在某个存储节点宕机后,其他存储节点应该已经拥有该数据的副本。如果出现数据不一致的情况(例如因为宕机导致部分副本丢失),可以通过手动触发文件重新上传或者进行修复来恢复数据的完整性。
4、FastDFS如何应对单点故障
FastDFS 的 Tracker Server 是支持高可用性的,可以使用多个 Tracker Server 构成一个集群来实现系统的容错和负载均衡。但是,在默认情况下,每个 Group 中只能有一个 Tracker Server 用于管理该 Group 中的 Storage Server,因此,如果该 Tracker Server 发生故障将会影响到整个 Group 的运行。
为了应对这种单点故障,可以采取以下措施:
1)启用多个 Tracker Server:将多个 Tracker Server 构成一个集群,同时为每个 Group 分配多个 Tracker Server 来管理该 Group 中的 Storage Server。这样可以增加系统的可靠性和容错性,即使某个 Tracker Server 发生故障,其他 Tracker Server 仍然可以继续工作。
2)使用 Keepalived 进行负载均衡:可以在多个 Tracker Server 上部署 Keepalived,如果某个 Tracker Server 发生故障,Keepalived 会自动将虚拟 IP 转移到其他可用的 Tracker Server 上,以保证服务的连续性和可靠性。
3)定期备份数据:可以定期备份 Tracker Server 的数据,以便在发生故障时能够快速恢复数据。可以使用 FastDFS 自带的 fdfs_storaged 工具进行数据备份和还原。
5、如何防止盗链
FastDFS内置防盗链采用Token的方式。
token是带时效的,包含了文件ID、时间戳ts和密钥。
在设定的时间范围内,比如5分钟内,token是有效的。
FastDFS在URL中带上当前时间戳和带时效的token,参数名分别为ts和token。
FastDFS API中提供了生成token的算法,扩展模块中会对token进行检验。
token的生成和检验都在服务器端,因此不会存在安全问题。
http.conf中防盗链相关的几个参数如下:
http.anti_steal.check_token:是否做token检查,缺省为false
http.anti_steal.token_ttl:token TTL,即生成token的有效时长
http.anti_steal.secret_key:生成token的密钥,尽量设置得长一些,千万不要泄露出去
http.anti_steal.token_check_fail:token检查失败,返回的文件内容,需指定本地文件名
配置示例如下:
# if use token to anti-steal # default value is false (0) http.anti_steal.check_token=false # token TTL (time to live), seconds # default value is 600 http.anti_steal.token_ttl=900 # secret key to generate anti-steal token # this parameter must be set when http.anti_steal.check_token set to true # the length of the secret key should not exceed 128 bytes http.anti_steal.secret_key=FastDFS1234567890 # return the content of the file when check token fail # default value is empty (no file sepecified) http.anti_steal.token_check_fail=/home/yuqing/fastdfs/conf/anti-steal.jpg