一、准备工作
1、tracker_merge_servers由report线程调用,tracker_report_thread_entrance->tracker_heart_beat或其他->tracker_check_response->tracker_merge_servers。
2、storage status
#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 //在线,尚不能提供服务(当storage server向tracker发送一个心跳时,对应storage server状态就变为active。)
#define FDFS_STORAGE_STATUS_ACTIVE 7 //在线,可以提供服务
#define FDFS_STORAGE_STATUS_RECOVERY 9
#define FDFS_STORAGE_STATUS_NONE 99
3、g_storage_serversFDFSStorageServer g_storage_servers[FDFS_MAX_SERVERS_EACH_GROUP]; //记录着本组所有storage server
typedef struct
{
<span style="white-space:pre"> </span>FDFSStorageBrief server;
<span style="white-space:pre"> </span>int last_sync_src_timestamp;
} FDFSStorageServer;
组内新增加一台storage server A时,由系统自动完成已有数据同步,处理逻辑如下:
1.storage server A连接tracker server,tracker server将storage server A的状态设置为FDFS_STORAGE_STATUS_INIT。storage serverA询问追加同步的源服务器和追加同步截至时间点,如果该组内只有storage serverA或该组内已成功上传的文件数为0,则没有数据需要同步,storage server A就可以提供在线服务,此时tracker将其状态设置为FDFS_STORAGE_STATUS_ONLINE(等待storage server发来心跳才变为ACTIVE状态),否则tracker server将其状态设置为FDFS_STORAGE_STATUS_WAIT_SYNC,进入第二步的处理;(可是我发现在实际测试时,两个storage server启动后尚未同步就都是ACTIVE了,我的版本是4.06,是新版本的更新?)
2.假设tracker server分配向storage server A同步已有数据的源storage server为B。同组的storage server和tracker server通讯得知新增了storage server A(如心跳连接发现新storage,进一步调用tracker_merge_servers),将启动同步线程(storage_sync_thread_entrance),并向tracker server询问向storage server A追加同步的源服务器和截至时间点。storage server B将把截至时间点之前的所有数据同步给storage server A;而其余的storage server从截至时间点之后进行正常同步,只把源头数据同步给storage server A。到了截至时间点之后,storage server B对storage server A的同步将由追加同步切换为正常同步,只同步源头数据;
3.storage server B向storage server A同步完所有数据,暂时没有数据要同步时,storage server B请求tracker server将storage server A的状态设置为FDFS_STORAGE_STATUS_ONLINE;
4.当storage server A向tracker server发起heart beat时,tracker server将其状态更改为FDFS_STORAGE_STATUS_ACTIVE。
三、tracker_merge_servers
tracker_merge_servers就是将获得的一个FDFSStorageBrief列表,如果发现有新的storage server,就加入到g_storage_servers中。
......
if (g_storage_count < FDFS_MAX_SERVERS_EACH_GROUP)
{
pInsertedServer = g_storage_servers + \
g_storage_count;
memcpy(&(pInsertedServer->server), \
pServer, sizeof(FDFSStorageBrief));
if (tracker_insert_into_sorted_servers( \
pInsertedServer)) //将新的storage server信息加入g_storage_servers.
{
g_storage_count++;
result = tracker_start_sync_threads( \
&(pInsertedServer->server));//注意只有在新加入的storage server IP与本storage server不同时,才会创建同步线程。
}
else
{
result = 0;
}
......
四、同步线程
tracker_start_sync_threads中,先判断新加入的storage server的IP与自己是否相同,只有IP不同才会创建同步线程。所以配置两个storage server的IP不同(由于我是本机测试,即使两个storage server使用不同IP,is_local_host_ip (pStorage ->ip_addr)仍然返回true,所以不会去同步;为了测试,将is_local_host_ip()改为总是返回false)。
static int tracker_start_sync_threads(const FDFSStorageBrief *pStorage)
{
int result;
if (strcmp(pStorage->id, g_my_server_id_str) == 0)
{
return 0;
}
result = storage_sync_thread_start(pStorage);//创建一个storage_sync_thread_entrance线程,线程id记录在sync_tids数组中。
if (result == 0)
{
if (g_if_trunker_self)
{
result = trunk_sync_thread_start(pStorage);//trunk同步线程,暂不考虑。
}
}
return result;
}
五、 storage_sync_thread_entrance
在本例中,目标storage server在尚未同步时,状态也为ACTIVE,即pStorage->status != FDFS_STORAGE_STATUS_ACTIVE。
static void* storage_sync_thread_entrance(void* arg)
{
......//一些初始工作,连接目标storage server。
if ((result=storage_reader_init(pStorage, &reader)) != 0) //初始化StorageBinLogReader reader结构;并向tracker发送TRACKER_PROTO_CMD_STORAGE_SYNC_SRC_REQ命令,获得reader->need_sync_old和 reader->until_timestamp。并打开binlog文件,记录文件描述符无reader->binlog_fd。并预读binlog至pReader->binlog_buff.buffer中。
{
g_continue_flag = false;
break;
}
if (!reader.need_sync_old)
{
while (g_continue_flag && \
(pStorage->status != FDFS_STORAGE_STATUS_ACTIVE && \
pStorage->status != FDFS_STORAGE_STATUS_DELETED && \
pStorage->status != FDFS_STORAGE_STATUS_IP_CHANGED && \
pStorage->status != FDFS_STORAGE_STATUS_NONE))
{
sleep(1);
}
if (pStorage->status != FDFS_STORAGE_STATUS_ACTIVE)
{
close(storage_server.sock);
storage_reader_destroy(&reader);
continue;
}
}
getSockIpaddr(storage_server.sock, \
local_ip_addr, IP_ADDRESS_SIZE);
insert_into_local_host_ip(local_ip_addr);
if (is_local_host_ip(pStorage->ip_addr))
{ //can't self sync to self
fdfs_quit(&storage_server);
close(storage_server.sock);
break;
}
if (storage_report_my_server_id(&storage_server) != 0) //向目标storage server发送STORAGE_PROTO_CMD_REPORT_SERVER_ID,报告自己的server id。
{
close(storage_server.sock);
storage_reader_destroy(&reader);
sleep(1);
continue;
}
if (pStorage->status == FDFS_STORAGE_STATUS_WAIT_SYNC) //一个新的storage server(目标storage)首次加入一个集群中,在同步前应当处于该状态。具体参考文章9。
{
pStorage->status = FDFS_STORAGE_STATUS_SYNCING; //进入下一状态。
storage_report_storage_status(pStorage->id, \
pStorage->ip_addr, pStorage->status);
}
if (pStorage->status == FDFS_STORAGE_STATUS_SYNCING)
{
if (reader.need_sync_old && reader.sync_old_done) //如果同步旧数据已完成,则进入offline状态。
{
pStorage->status = FDFS_STORAGE_STATUS_OFFLINE;
storage_report_storage_status(pStorage->id, \
pStorage->ip_addr, \
pStorage->status);
}
}
if (g_sync_part_time)
{
current_time = g_current_time;
storage_sync_get_start_end_times(current_time, \
&g_sync_start_time, &g_sync_end_time, \
&start_time, &end_time);
}
sync_result = 0;
while (g_continue_flag && (!g_sync_part_time || \
(current_time >= start_time && \
current_time <= end_time)) && \
(pStorage->status == FDFS_STORAGE_STATUS_ACTIVE || \
pStorage->status == FDFS_STORAGE_STATUS_SYNCING))
{
read_result = storage_binlog_read(&reader, \
&record, &record_len);
if (read_result == ENOENT)
{
......
continue;
}
if (g_sync_part_time)
{
current_time = g_current_time;
}
if (read_result != 0)
{
......
}
else if ((sync_result=storage_sync_data(&reader, \
&storage_server, &record)) != 0) //同步一行binlog,即同步文件(STORAGE_PROTO_CMD_SYNC_CREATE_FILE等命令)至目标storage server。在本地也会在mark文件中记下同步到了什么位置。
{
......
}
reader.binlog_offset += record_len;
reader.scan_row_count++;
if (g_sync_interval > 0)
{
usleep(g_sync_interval);
}
}
if (reader.last_scan_rows != reader.scan_row_count)
{
if (storage_write_to_mark_file(&reader) != 0)
{
g_continue_flag = false;
break;
}
}
close(storage_server.sock);
storage_server.sock = -1;
storage_reader_destroy(&reader);
if (!g_continue_flag)
{
break;
}
if (!(sync_result == ENOTCONN || sync_result == EIO))
{
sleep(1);
}
}
if (storage_server.sock >= 0)
{
close(storage_server.sock);
}
storage_reader_destroy(&reader);
if (pStorage->status == FDFS_STORAGE_STATUS_DELETED
|| pStorage->status == FDFS_STORAGE_STATUS_IP_CHANGED)
{
storage_changelog_req();
sleep(2 * g_heart_beat_interval + 1);
pStorage->status = FDFS_STORAGE_STATUS_NONE;
}
storage_sync_thread_exit(&storage_server); //线程退出前的清理工作。
return NULL; //本线程退出。
}
六、同步过程中,涉及到的重要参数
1、时间相关
last_heart_beat_time = 2014-05-18 00:23:09 //心跳时间
last_source_update = 2014-05-17 23:28:27 //如果客户端上传文件选择了这个server,则这个storage server的此参数更新。
last_sync_update = 2014-05-18 00:23:04 //来自其他storage的同步更新。
last_synced_timestamp = 2014-05-18 00:22:29 (1s delay) //表示本storage server已经同步到的文件时间戳。
例如上传文件至A(设文件时间戳为T),A同步该文件至B。则A首先更新last_source_update,之后开始同步,B记录同步结束时间last_sync_update,并记录同步来的文件时间戳last_synced_timestamp(即T)。应当有:B的last_synced_timestamp 约等于 A的last_source_update < B的last_sync_update。
2、${ip_addr}_${port}.mark文件
ip_addr为同步的目标服务器IP地址,port为本组storage server端口。例如:10.0.0.1_23000.mark。文件格式为ini配置文件方式,各个参数如下:
# binlog_index:已处理(同步)到的binlog索引号
# binlog_offset:已处理(同步)到的binlog文件偏移量(字节数)
# need_sync_old:同步已有数据文件标记,0表示没有数据文件需要同步
# sync_old_done:同步已有数据文件是否完成标记,0表示未完成,1表示已完成
# until_timestamp:同步已有数据截至时间点(UNIX时间戳)
# scan_row_count:已扫描的binlog记录数
# sync_row_count:已同步的binlog记录数(只同步源数据,不同步备份数据)
3、配置文件storage.conf
(1)sync_wait_msec:同步文件时,如果从binlog中没有读到要同步的文件,休眠N毫秒后重新读取。0表示不休眠,立即再次尝试读取,出于CPU消耗考虑,不建议设置为0。如何希望同步尽可能快一些,可以将本参数设置得小一些,比如设置为50ms.
(2)sync_interval:同步上一个文件后,再同步下一个文件的时间间隔,单位为毫秒,0表示不休眠,直接同步下一个文件。默认为0。
(3)write_mark_file_freq:同步完N个文件后,把storage的mark文件同步到磁盘,如果mark文件内容没有变化,则不会同步。