fastdfs storage server的设计与实现


fastdfs是一个针对互联网应用设计的分布式文件系统。具有架构简单。结构清晰。代码量小等特点。
详细的介绍及架构请參考分布式文件系统FastDFS架构剖析http://www.programmer.com.cn/4380/)。这篇文章是由fastdfs的作者撰写。

由于fastdfs的轻量级特点,所以也适合广大技术爱好者学习分布式文件系统的设计及实现技术。通过深入代码,了解内部细节。本文的fastdfs版本号为5.01。

服务交互场景
fastdfs採用了传统的C/S模型,服务分为client和server,之间採用私有协议通信。

系统的几种交互场景:

1)client ---->> track server
2)client ---->> storage server
3)storage server ---->> track server
4)storage server ---->> storage server

当中1)和2)是client(fastdfs的用户)使用fastdfs的情况,client先依据track server得到storage server地址。然后再storage server上做操作(如上传、下载文件等)。

由client发起操作。

3)是storage server向track server报告信息。由storage server主动发起。
4)storage server向同组内的其它storage server同步操作信息。同步的实现是系统中比較复杂的一块。


依据上面的交互图,storage server包含3部分功能:
1)接收并处理请求,来自client和storage,service threads
2)将自己的状态信息报告tracker server, report threads.
3)同步本地数据到同组内的其它storage server, sync threads

任务1)的启动及执行
主体实现代码。storage_service.c
依据配置文件里的定义。启动对应的service threads。线程入口storage_thread_entrance
storage_deal_task:解析协议,进行对应处理。

任务2)的启动与执行
主体实现代码,tracker_client_thread.c
针对每一个tracker创建一个线程(report线程),线程入口:tracker_report_thread_entrance。该任务启动过程中,storage的状态会经历一系列变化:

fastdfs storage server的设计与实现
storage_server的状态转换图

这些状态是trackerserver上记录的对应的storage的状态,tracker依据storage的状态来决定其是否可用。
ACTIVE是终于状态,表示storage server能够提供服务;其余是中间状态。

INIT
storage server启动后,发送TRACKER_PROTO_CMD_STORAGE_JOIN到tracker,tracker将storage状态设置为FDFS_STORAGE_STATUS_INIT。

在tracker_report_join中实现。


推断是否须要同步
同步:是否须要同组的其它storage server向自己同步文件。对于组中的第一个storage server,不须要从其它storage同步,由于仅仅有一个storage;对于其它的storage server。依据g_sync_old_done推断,g_sync_old_done是全局变量。默认是false。storage第一次上线后。g_sync_old_done是false,所以会有其它storage server对自己进行同步,用来同步的源storage
server有tracker进行选择,同步完毕后g_sync_old_done设置为true,并被保存到文件系统中(storage_write_to_sync_ini_file)。第一次上线后的再次上线(比如系统关机维护后的又一次启动),g_sync_old_done是true,就不会从源服务器同步数据。

再次上线和第一次上线的差别是,再次上线的storage会有状态文件保存在本地,storage启动,会拿到这些持久化数据。


转换02,WAIT_SYNC
第一次上线的storage会经过该步骤,将状态变为FDFS_STORAGE_STATUS_WAIT_SYNC。发送TRACKER_PROTO_CMD_STORAGE_SYNC_DEST_REQ到tracker(tracker_sync_dest_req),假设tracker也决定该storage server须要从源storage进行同步,会返回源storageIP和时间戳:g_sync_src_id和g_sync_until_timestamp。

tracker端会将该storage状态设置为FDFS_STORAGE_STATUS_WAIT_SYNC。


转换03,SYNCING

该转换由源storage server的同步线程进行,任务3中实现。将storage状态改为FDFS_STORAGE_STATUS_SYNCING;


转换04。OFFLINE
源storage server完毕对该storage的同步后。将其状态改为OFFLINE,任务3中实现。storage的状态变为FDFS_STORAGE_STATUS_OFFLINE;

转换05。ACTIVE
该转换由tracker server完毕。storage向tracker发送heart beat(tracker_heart_beat),tracker收到storage的heart beat后,假设storage的状态是OFFLINE,就将其改动成ACTIVE。pTargetServer->status = FDFS_STORAGE_STATUS_ACTIVE;

转换06,ONLINE
假设不须要从其它storage进行同步(g_sync_old_done == true),调用tracker_sync_notify,发送TRACKER_PROTO_CMD_STORAGE_SYNC_NOTIFY到tracker。tracker将storage状态变为online,FDFS_STORAGE_STATUS_ONLINE。

转换07。ACTIVE
该转换由tracker server完毕。storage向tracker发送heart beat(tracker_heart_beat),tracker收到storage的heart beat后,假设storage的状态是ONLINE。就将其改动成ACTIVE。pTargetServer->status = FDFS_STORAGE_STATUS_ACTIVE;

storage状态变成ACTIVE,能够对外提供服务。

状态转换涉及3个角色。一个是trackerserver,一个是源storageserver的同步线程。一个是storage的report线程。


进入active状态后。report线程进入主循环运行以下几个任务:
  tracker_heart_beat
  tracker_report_sync_timestamp
  tracker_report_df_stat

任务3)的启动与执行
主体代码,storage_sync.c

针对本组中的每一个storage server创建一个线程(sync线程,同步线程)。线程入口:storage_sync_thread_entrance。storage server列表由tracker提供。在tracker_report_join中返回。任务3的主循环就是读取binlog,然后将binlog记录的内容。同步到组内的其它storage上去。


sync线程将自己本地保存的数据(源数据而不是副本)同步给本组的其它storage:
有两种同步模式:
1)正常同步
指同步本地的源数据。代码逻辑是读binlog记录,然后依据记录运行同步操作:
storage_sync_data(&reader, &storage_server, &record))

2)追加同步
同步本地的源数据和副本数据。

该模式就是上述状态转换中须要同步的情况,运行转换03和04。

sync线程会依据被同步的storage和tracker分配给他的源storage,来决定是否由自己对storage进行同步。


转换03代码
  if (pStorage->status == FDFS_STORAGE_STATUS_WAIT_SYNC)
  {
   pStorage->status = FDFS_STORAGE_STATUS_SYNCING;
   storage_report_storage_status(pStorage->id, \
    pStorage->ip_addr, pStorage->status);
  }
推断是否须要同步副本代码:
STARAGE_CHECK_IF_NEED_SYNC_OLD(pReader, pRecord)
仅仅有针对副本(REPLICA)数据,才须要推断是否须要同步给其它storage。

转换04代码
当binlog中没有数据后(read_result == ENOENT),改动storage状态
    if (pStorage->status == \
     FDFS_STORAGE_STATUS_SYNCING)
    {
     pStorage->status = \
      FDFS_STORAGE_STATUS_OFFLINE;
     storage_report_storage_status( \
      pStorage->id, \
      pStorage->ip_addr, \
      pStorage->status);
    }
    }

binlog文件

任务1中的service线程会把对本地的文件改动(upload, delete,改动metadata等)记录在binlog文件里。


binglog格式:
文件改动时间戳
文件操作类型
  STORAGE_OP_TYPE_SOURCE_CREATE_FILE
  STORAGE_OP_TYPE_SOURCE_APPEND_FILE
  。

。。

定义在storage_sync.h

文件名称字

storage_binlog_write:写记录


binlog缓存
binglog的读写通过buffer进行。buffer SYNC_BINLOG_WRITE_BUFF_SIZE。到超过buffer容量,写入磁盘。


binlog文件大小
每一个binglog的大小是SYNC_BINLOG_FILE_MAX_SIZE,超过这个值,会创建新的binlog文件。

binlog文件名称的最后由binlog_index确定。binlog_index从0開始递增。binlog_index是全局变量。会被持久化保存到index文件里。
#define SYNC_BINLOG_INDEX_FILENAME SYNC_BINLOG_FILE_PREFIX".index"
每次又一次启动时,会依据这个index文件取到binlog_index。然后定位到须要对应的binlog文件。

binlog文件会持续添加。


static char *get_writable_binlog_filename1(char *full_filename, \
  const int binlog_index)
{
 snprintf(full_filename, MAX_PATH_SIZE, \
   "%s/data/"SYNC_DIR_NAME"/"SYNC_BINLOG_FILE_PREFIX"" \
   SYNC_BINLOG_FILE_EXT_FMT, \
   g_fdfs_base_path, binlog_index);
 return full_filename;
}

怎样使用binlog进行同步
sync线程会真对每一个storage。将本地binlog文件里的内容所有应用到对应的storage中。
针对每一个storage,synn线程会保存一份mark文件。get_mark_filename_by_reader
mark文件后缀名:#define SYNC_MARK_FILE_EXT ".mark"
mark文件里会保存上次同步到的状态(storage_write_to_mark_file):
  MARK_ITEM_BINLOG_FILE_INDEX, pReader->binlog_index, \
  MARK_ITEM_BINLOG_FILE_OFFSET, pReader->binlog_offset, \
  MARK_ITEM_NEED_SYNC_OLD, pReader->need_sync_old, \
  MARK_ITEM_SYNC_OLD_DONE, pReader->sync_old_done, \
  MARK_ITEM_UNTIL_TIMESTAMP, (int)pReader->until_timestamp, \
  MARK_ITEM_SCAN_ROW_COUNT, pReader->scan_row_count, \
  MARK_ITEM_SYNC_ROW_COUNT, pReader->sync_row_count);

后面sync thread又一次启动后,能够依据上次记录的位置。继续開始同步。

storage_reader_init
用来读取该mark文件(第一次上线的系统,没有该文件),载入上次同步状态。

其它状态文件
storage中还有另外2个保存持久化状态的文件
#define DATA_DIR_INITED_FILENAME ".data_init_flag"
#define STORAGE_STAT_FILENAME "storage_stat.dat"
每次启动时,都会从这些文件载入信息,已恢复上次的执行状态。

STORAGE_STAT_FILENAME用来记录本storage的同步信息,如是否须要从其它源storage进行同步等。通过storage_write_to_sync_ini_file写入。

DATA_DIR_INITED_FILENAME用来保存storage上的文件状态信息,如上传文件数等。通过storage_write_to_stat_file写入。

last_sync_update用来记录storage上次被同步到的时间,这个字段在tracker选择download storageserver的时候非常重要。


上一篇:JavaScript 中 onload 事件绑定多个方法


下一篇:MySQL中的共享锁