• FastDFS文件同步机制分析


    一、tracker server目录及文件结构

    数据文件storage_groups.dat和storage_servers.dat中的记录之间以换行符(\n)分隔,字段之间以英文逗号(,)分隔。

    # 我的tracker server目录是/home/fastdfs/tracker_22122,看个人配置
    /home/fastdfs/tracker_22122
    # 文件结构 
    ├── data 
    │	├── fdfs_trackerd.pid 
    │	├── storage_changelog.dat   #storage有修改过ip 
    │	├── storage_groups_new.dat  # 存储分组信息 
    │	├── storage_servers_new.dat # 存储服务器列表 
    │	└── storage_sync_timestamp.dat # 同步时间戳 
    └── logs 
    	└── trackerd.log # Server日志文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以使用tail -f logs/trackerd.log查看打印信息,这对有报错时进行日志分析非常有用。
    (1)storage_changelog.dat。例如:

    1645866390 group1 192.168.0.104 192.168.0.105
    
    • 1

    (2)storage_groups_new.dat。例如:

    # global section 
    [Global] group_count=1 
    # group: group1 
    [Group001] 
    	group_name=group1 
    	storage_port=23000 
    	storage_http_port=8888 
    	store_path_count=1 
    	subdir_count_per_path=256 
    	current_trunk_file_id=0 
    	trunk_server= 
    	last_trunk_server=
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (3)storage_servers.dat。例如:

    [Storage001] 
    group_name=group1 
    	ip_addr=192.168.0.104
    	status=7 
    	version=6.07 
    	join_time=1646292828 
    	.... 
    [Storage002] 
    	group_name=group1 
    	ip_addr=192.168.0.105
    	status=7 
    	version=6.07 
    	join_time=1646292925 
    	storage_port=23000
    	.... 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (4)主要参数如下:
    group_name:所属组名。
    ip_addr:ip地址。
    status:状态。
    sync_src_ip_addr:向该storage server同步已有数据文件的源服务器。
    sync_until_timestamp:同步已有数据文件的截至时间(UNIX时间戳)。
    stat.total_upload_count:上传文件次数。
    stat.success_upload_count:成功上传文件次数。
    stat.total_set_meta_count:更改meta data次数。
    stat.success_set_meta_count:成功更改meta data次数。
    stat.total_delete_count:删除文件次数。
    stat.success_delete_count:成功删除文件次数。
    stat.total_download_count:下载文件次数。
    stat.success_download_count:成功下载文件次数。
    stat.total_get_meta_count:获取meta data次数。
    stat.success_get_meta_count:成功获取meta data次数。
    stat.last_source_update:最近一次源头更新时间(更新操作来自客户端)。
    stat.last_sync_update:最近一次同步更新时间(更新操作来自其他storage server的同步)。

    二、storage server目录及文件结构

    |__data 
    |	|__.data_init_flag # 当前storage server初始化信息 
    | 	|__storage_stat.dat #当前storage server统计信息 
    | 	|__sync #存放数据同步相关文件 
    |	| 	|__binlog.index #当前的binlog(更新操作日志)文件索引号 
    | 	| 	|__binlog.xxx #存放更新操作记录(日志) 
    | 	| 	|__${ip_addr}_${port}.mark #存放向目标服务器同步的完成情况
    | 	|__一级目录 #256个存放数据文件的目录,目录名为十六进制字符,如:00, 1F 
    | 	|	|__二级目录 #256个存放数据文件的目录,目录名为十六进制字符,如:0A, CF |__logs
    	|__storaged.log	#storage server日志文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    重点了解sync 目录及文件结构。
    (1)binlog.index中只有一个数据项:当前binlog的文件索引号 binlog.###。
    (2)binlog.###为索引号对应的3位十进制字符,不足三位,前面补0。索引号基于0,最大为999。一个binlog文件最大为1GB。记录之间以换行符(\n)分隔,字段之间以英文空格分隔。字段依次为:

    1. timestamp:更新发生时间(Unix时间戳)。
    2. op_type:操作类型,一个字符。
    3. filename:操作(更新)的文件名,包括相对路径,如:5A/3D/FE_93_SJZ7pAAAO_BXYD.S

    (3)$ {ip_addr}_${port}.mark:ip_addr为同步的目标服务器IP地址,port为本组storage server端口。例如:10.0.0.1_23000.mark。各个参数如下:

    1. binlog_index:已处理(同步)到的binlog索引号。
    2. binlog_offset:已处理(同步)到的binlog文件偏移量(字节数)。
    3. need_sync_old:同步已有数据文件标记,0表示没有数据文件需要同步。
    4. sync_old_done:同步已有数据文件是否完成标记,0表示未完成,1表示已完成 (推送方标记)。
    5. until_timestamp:同步已有数据截至时间点(UNIX时间戳) (推送方) 上次同步时间结点。
    6. scan_row_count:总记录数。
    7. sync_row_count:已同步记录数。

    (4)如果还有其他storage,则有更多的KaTeX parse error: Expected group after '_' at position 10: {ip_addr}_̲{port}.mark,比如10.0.0.2_23000.mark 。

    三、FastDFS文件同步

    思考:文件上传成功后,其它的storage server才开始同步,其它的storage server怎么去感知?tracker server是怎么通知storage server?

    storage定时发送心跳包到tracker,并附带同步的时间节点,tracker会返回其他storage的状态。

    正常文件上传完成后,就记录进binlog缓存中,系统定时刷入binlog文件。
    系统有线程定时读取binlog文件,当有新增行时,判断该记录是源文件记录还是副本文件记录。
    系统只主动发送源文件,副本文件不做处理(非启动时流程)。

    线程:

    1. tracker_report_thread_entrance ,连接tracker有独立的线程,连接n个tracker就有n个线程。
    2. storage_sync_thread_entrance ,给同group的storage做同步,同组有n个storage,就有n-1个线程。

    storage的状态:

    #define FDFS_STORAGE_STATUS_INIT 		0 	// 初始化,尚未得到同步已有数据的源服务器
    #define FDFS_STORAGE_STATUS_WAIT_SYNC 	1 	// 等待同步,已得到同步已有数据的源服务器
    #define FDFS_STORAGE_STATUS_SYNCING 	2 	// 同步中
    #define FDFS_STORAGE_STATUS_IP_CHANGED 	3
    #define FDFS_STORAGE_STATUS_DELETED 	4 	// 已删除,该服务器从本组中摘除
    #define FDFS_STORAGE_STATUS_OFFLINE 	5 	// 离线
    #define FDFS_STORAGE_STATUS_ONLINE 		6 	// 在线,尚不能提供服务
    #define FDFS_STORAGE_STATUS_ACTIVE 		7 	// 在线,可以提供服务
    #define FDFS_STORAGE_STATUS_RECOVERY 	9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    同步命令:

    #define STORAGE_PROTO_CMD_SYNC_CREATE_FILE 	16 //新增文件
    #define STORAGE_PROTO_CMD_SYNC_DELETE_FILE 	17 // 删除文件
    #define STORAGE_PROTO_CMD_SYNC_UPDATE_FILE 	18 // 更新文件
    #define STORAGE_PROTO_CMD_SYNC_CREATE_LINK 	19 // 创建链接
    
    • 1
    • 2
    • 3
    • 4

    3.1、同步日志所在目录

    例如192.168.0.104服务器的sync log:

    192.168.0.105_23000.mark # 同步状态文件,对应发送同步的storage,记录本机到192.168.0.105的同步状态, 文件名由同步源IP_端口组成。
    binlog.000 # 本地的binglog日志,文件大小最大1G,超过1G,会重新写下个文件,可以binlog.001,binlog.002,...,同时更新 binlog.index 文件中索引值
    binlog_index.dat # 记录了当前写binlog的索引id。
    
    • 1
    • 2
    • 3

    如果有不只2个storage的时候,则该目录还有更多的 .mark文件。

    3.2、binlog格式

    FastDFS文件同步采用binlog异步复制方式。storage server使用binlog文件记录文件上传、删除等操作,根据binlog进行文件同步。binlog中只记录文件ID和操作,不记录文件内容。
    例如:

    1646123002 C M00/00/00/oYYBAF285cOIHiVCAACI-7zX1qUAAAAVgAACC8AAIkT490.txt
    1646123047 c M00/00/00/oYYBAF285luIK8jCAAAJeheau6AAAAAVgABI-cAAAmS021.xml
    1646123193 A M00/00/00/rBMYd2IaLXqASSVXAAAHuj79dAY65.txt 6 6
    1646123561 d M00/00/00/oYYBAF285luIK8jCAAAJeheau6AAAAAVgABI-cAAAmS021.xml
    
    • 1
    • 2
    • 3
    • 4

    可以看到,binlog文件有三列,依次为:时间戳,操作类型,文件ID(不带group名称)。

    文件操作类型采用单个字母编码,其中源头操作用大写字母表示,被同步的操作为对应的小写字母。文件操作字母含义如下:

    副本
    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:副本创建符号链接(文件去重功能,相同内容只保存一份)

    注意:源表示客户端直接操作的那个Storage即为源,,其他的Storage都为副本。

    同组内的storage server之间是对等的,文件上传、删除等操作可以在任意一台storage server上进行。
    文件同步只在同组内的storage server之间进行,采用push方式,即源头服务器同步给本组的其他存储服务器。对于同组的其他storage server,一台storage server分别启动一个线程进行文件同步。

    文件同步采用增量方式,记录已同步的位置到mark文件中。mark文件存放路径为:

    $base_path/data/sync/
    
    • 1

    mark文件内容示例:

    binlog_index=0 		//binlog索引id 表示上次同步给114.215.169.67机器的最后一条binlog文件索引
    binlog_offset=3944 //当前时间binlog 大小 (单位是字节)表示上次同步给其他机器的最后一条binlog偏移量,若程序重启了,也只要从这个位置开始向后同步即可。
    need_sync_old=1 	//是否需要同步老数据
    sync_old_done=1 	//是否同步完成
    until_timestamp=1621667115 //同步已有数据文件的截至时间
    scan_row_count=68 //扫描记录数
    sync_row_count=53 //同步记录数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3、同步规则

    (1)只在同组内的storage server之间进行同步。
    (2) 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了,源数据和备份数据区 分是用binlog的操作类型来区分,操作类型是大写字母,表示源数据,小写字母表示备份数据。
    (3)当新增一台storage server时,由已有的一台storage server将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器。

    增量同步:storage server之间已经在运行,它们之间的数据同步就是增量同步发。
    全量同步:新增一台storage server时,由已有的一台storage server将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器,这就是全量同步。

    3.4、Binlog同步过程

    在FastDFS之中,每个Storaged之间的同步都是由一个独立线程负责的,该线程中的所有操作都是以同步方式执行的。比如一组服务器有A、B、C三台机器,那么在每台机器上都有两个线程负责同步,如A机器,线程1负责同步数据到B,线程2负责同步数据到C。

    3.4.1、tracker_report_thread_entrance

    tracker_report_thread_entrance 线程负责向tracker上报信息。 在Storage.conf配置文件中,只配置了Tracker的IP地址,并没有配置组内其他的Storage。因此同组的其他Storage必须从Tracker获取。具体过程如下:

    1. Storage启动时为每一个配置的Tracker启动一个线程负责与该Tracker的通讯。
    2. 默认每间隔30秒,与Tracker发送一次心跳包,在心跳包的回复中,将会有该组内的其他Storage信息
    3. Storage获取到同组的其他Storage信息之后,为组内的每个其他Storage开启一个线程负责同步。

    3.4.2、storage_sync_thread_entrance

    storage_sync_thread_entrance 同步线程。每个同步线程负责到一台Storage的同步,以阻塞方式进行。

    (1)打开对应Storage的mark文件,如负责到192.168.0.104的同步则打开192.168.0.104_23000.mark文件,从中读取binlog_index、binlog_offset两个字段值,如取到值为:0、100,那么就打开binlog.000文件,seek到100这个位置。
    (2)进入一个while循环,尝试着读取一行,若读取不到则睡眠等待。若读取到一行,并且该行的操作方式为源操作,如C、A、D、T(大写的都是),则将该行指定的操作同步给对方(非源操作不需要同步),同步成功后更新binlog_offset标志,该值会定期写入到192.168.0.104_23000.mark文件之中。

    请求推送文件到其他storage,
    storage_sync_copy_file
    检测文件是否已经被删除,
    trunk_file_stat
    文件不存在,
    直接返回
    目的storage是否已经有该文件,
    storage_query_file_info_ex
    文件已经存在且大小一致
    不需要更新文件,
    则不需要推送文件
    文件已经存在但大小不一致
    更新内容,
    STORAGE_PROTO_CMD_SYNC_UPDATE_FILE
    文件不存在
    创建文件,
    STORAGE_PROTO_CMD_SYNC_CREATE_FILE
    封装协议,如果不需要更新文件则不用封装文件内容
    发送协议的上半部,
    tcpsenddata_nb
    发送协议的下半部(文件内容),
    tcpsenddata_ex
    读取回应信息,
    fdfs_recv_response

    3.4.3、同步前删除

    假如同步较为缓慢,那么有可能在开始同步一个文件之前,该文件已经被客户端删除,此时同步线程将打印一条日志,然后直接接着处理后面的Binlog。

    3.5、Storage的最后最早被同步时间

    举个例子:
    一个group内有Storage-A、Storage-B、Storage-C三台机器。对于A这台机器来说,B与C机器都会同步Binlog(包括文件)给他,A在接收同步时会记录每台机器同步给他的最后时间(Binlog中的第一个字段timpstamp,这个时间也会更新到storage_stat.dat的last_sync_update)。比如B最后同步给A的Binlog-timestamp为100,C最后同步给A的Binlog-timestamp为200,那么A机器的最后最早被同步时间就为100。也就是取最小值。

    这个值的意义在于,判断一个文件是否存在某个Storage上。
    比如这里A机器的最后最早被同步时间为100,那么如果一个文件的创建时间为99,就可以肯定这个文件在A上肯定有。

    Storage会定期将每台机器同步给他的最后时间告诉给Tracker,Tracker在客户端要下载一个文件时,需要判断一个Storage是否有该文件,只要解析文件的创建时间,然后与该值作比较,若该值大于创建创建时间,说明该Storage存在这个文件,可以从其下载。

    Tracker也会定期将该值写入到一个文件之中,Storage_sync_timestamp.dat,内容如下:

    group1,192.168.0.104,   0, 1408524351, 1408524352 
    group1,192.168.0.105,   1408524353, 0, 1408524354 
    group1,192.168.0.106,   1408524355, 1408524356, 0
    
    • 1
    • 2
    • 3

    每一行记录了对应Storage同步给其他Storage的最后时间。

    机器同步时间同步时间同步时间
    group1,192.168.0.104014085243511408524352
    group1,192.168.0.105140852435301408524354
    group1,192.168.0.10614085243551408524356,0

    如第一行含义:0表示自己同步给自己,没有记录;1408524351表示192.168.0.104同步给192.168.0.105的最后最早被同步时间;1408524352 表示192.168.0.104同步给192.168.0.106的最后最早被同步时间。其他行同理。

    3.6 新增节点的同步流程

    比如在已有A、B节点上,新增节点storage C。

    Storage C Tracker Storage A Storage B 发送STORAGE_JOIN 返回同组storage信息 发送心跳 返回同组storage list 发现新增storage C,启动同步线程 发送心跳 返回同组storage list 发现新增storage C,启动同步线程 发现新增storage A和B,启动同步线程等待同步 发送SYNC_SRC_REQ 返回同步源IP及同步截至时间戳 返回的源IP是自己,作为同步源准备同步binlog到C节点 发送SYNC_SRC_REQ 返回同步源IP及同步截至时间戳 返回的源IP不是自己,等待storage C状态为Active时再同步。 发送SYNC_DEST_REQ 收到响应,状态设置为WAIT_SYNC 请求将storage C的状态置为SYNCING 返回响应 读取binlog,解析数据 发送命令同步数据 返回同步结果 binlog读取完,请求将storage C的状态置为OFFLINE 返回响应 同步完成,请求将storage C的状态置为ONLINE 返回响应 发送心跳 状态设置ACTIVE Storage C Tracker Storage A Storage B
    1. 新节点storage C 启动的时候会创建线程tracker_report_thread_entrance,调用tracker_report_join向tracker 发送命令TRACKER_PROTO_CMD_STORAGE_JOIN(81)报告,自己的group名称,ip,端口,版本号,存储目录数,子目录数,启动时间,老数据是否同步完成,当前连接的tracker信息,当前状态信息(FDFS_STORAGE_STATUS_INIT)等信息。
    2. tracker收到TRACKER_PROTO_CMD_STORAGE_JOIN命令后,将上报的信息和已有(tracker数据文件中保存的信息)的信息进行比较,如果有则更新,没有的话,将节点及状态信息写入缓存和数据文件中,并查找同group的其他节点做为同步源,如果有返回给stroage C。
    3. 新节点stroage C 收到tracker响应继续流程。发送TRACKER_PROTO_CMD_STORAGE_SYNC_DEST_REQ (87)查询同步目的。
    4. tracker收到TRACKER_PROTO_CMD_STORAGE_SYNC_DEST_REQ 请求后, 查找同group的其他节点做为同步目标,及时间戳返回给新storage节点。
    5. 新storage节点收到响应后,保存同步源及同步时间戳。继续流程,发送TRACKER_PROTO_CMD_STORAGE_BEAT(83) 给tracker。
    6. tracker收到心跳报告后,leader trakcer(非leader不返回数据),把最新的group的 storagelist返回给新的stroaged。
    7. stroage C 收到 tracker storage list后,启动2个同步线程,准备将binlog同步到 节点 A和B(此时还不能同步,因为stroage C 还是WAIT_SYNC 状态)。
    8. 这时候,其他的已在线的storage 节点 A 、B会发送心跳给tracker ,tracker 把会收到最新的stroagelist,A、B、C返回给Storage A,B 。
    9. storage A,B 收到tracker响应后,会发现本地缓存中没有stroage C,会启动binlog同步线程,将数据同步给 stroage C。
    10. storage A 、B分别启动storage_sync_thread_entrance 同步线程,先向 tracker 发送TRACKER_PROTO_CMD_STORAGE_SYNC_SRC_REQ(86)命令,请求同步源,tracker会把同步源IP及同步时间戳返回。
    11. stroage A 、B节点的同步线程收到TRACKER_PROTO_CMD_STORAGE_SYNC_SRC_REQ 响应后,会检查返回的同步源IP是否和自己本地ip一致,如果一致置need_sync_old=1表示将做为源数据将老的数据,同步给新节点C,如果不一致置need_sync_old=0,则等待节点C状态为Active时,再同步(增量同步)。因为,如果A、 B同时作为同步源,同步数据给C的话,C数据会重复。这里假设节点A,判断tracker返回的是同步源和自己的iP一致,A做为同步源,将数据同步给storage C节点。
    12. Storage A同步线程继续同步流程,用同步目的的ip和端口,为文件名,.mark为后缀,如192.168.0.104_23000.mark,将同步信息写入此文件。将Storage C的状态置为FDFS_STORAGE_STATUS_SYNCING 上报给tracker,开始同步。

    3.7、Tracker选择客户端下载文件的storage的原则

    (1)在同group下,获取最小的一个同步时间点(各个storage在同一时间,同步完成的时间点不一样)。
    (2)在最小同步时间点之前的文件,按照用户的规则随意选择一个storage。
    (3)在最小同步时间点之后的文件,选择源storage提供给客户端。

    总结

    1. fastdfs原理,要懂得逻辑闭环。
    2. binlog格式的设计,binlog文件有三列,依次为:时间戳,操作类型,文件ID(不带group名称)。默认binlog文件最大是1GB,如果超过1GB就创建一个新的binlog,可以是binlog001,binlog002,…;同时记录到.mark文件的binlog_index。
    3. 推送到不同的storage怎么记录?每个storage都有.mark文件,记录如下相关信息:
    binlog_index=0 		//binlog索引id 表示上次同步给其他机器的最后一条binlog文件索引
    binlog_offset=3944 //当前时间binlog 大小 (单位是字节)表示上次同步给其他机器的最后一条binlog偏移量,若程序重启了,也只要从这个位置开始向后同步即可。
    need_sync_old=1 	//是否需要同步老数据
    sync_old_done=1 	//是否同步完成
    until_timestamp=1621667115 //同步已有数据文件的截至时间
    scan_row_count=68 //扫描记录数
    sync_row_count=53 //同步记录数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. FastDFS做到高可用是tracker可以做集群,storage一般部署三个及以上。
    2. 写的高并发通过增加storage是不能提高并发的,因为storage直接需要推送文件;可以通过增加group、一个storage配置多个硬盘,每个硬盘对应一个store_path两种方式来提高写并发。
    3. 读的并发在少写多读的情况下可以通过扩充storage来提高并发,可以通过增加group,一个storage配置多个硬盘,每个硬盘对应一个store_path。
    4. 需要小文件存储的原因是小文件数量太多查找麻烦,而且因为inode的存在小文件会降低磁盘的利用率。
    5. fastdfs是弱一致性,如果是需要特别安全和特别可靠的就最好不要使用fastdfs。
    6. 两个storage直接的备份通过大小写字符来区别源数据和备份数据;如果是大写(比如C)就需要推送到其他storage,如果是小写(比如c)就不需要推送。从而避免备份死循环问题。
    7. 已经存在两个storage了,然后加入第三个storage,那么由Tracker决定由谁将数据同步到新的storage中,一般是选择最后最早同步时间戳的storage ,即取最小原则。
    8. Tracker选择客户端下载文件的storage的原则是在最小同步时间点之前的文件按照用户的规则随意选择一个,在最小同步时间点之后的文件,选择源storage提高给客户端。

    在这里插入图片描述

  • 相关阅读:
    SpringBoot底层原理----配置优先级/Bean管理/springboot原理
    关于网络协议的若干问题(三)
    centos安装和配置masakari(stein版本)
    hadoop 实现数据排序
    Kubernetes 多集群管理平台 OCM v0.9.0 发布:进一步改善托管集群安全性问题
    PyTorch源码学习系列 - 2. Tensor
    【云原生之k8s】kubernetes核心组件
    《动手学深度学习 Pytorch版》 7.1 深度卷积神经网络(AlexNet)
    网络安全——使用反弹木马进行提权获取主机Shell
    POJ3259虫洞题解
  • 原文地址:https://blog.csdn.net/Long_xu/article/details/127817193