From e39e96eaad698085341bc2ab32019cf9289b56ef Mon Sep 17 00:00:00 2001 From: retamia Date: Tue, 16 Apr 2019 10:01:52 +0800 Subject: [PATCH 01/95] =?UTF-8?q?fix:=20mac=20=E4=B8=8Bphp=5Fclient?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=87=BA=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 82 ++++++++++++++++++++++++++++++++++++++++++++++ client/Makefile.in | 2 +- make.sh | 12 +++++-- 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..383a7e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# Makefile.in +storage/Makefile +tracker/Makefile +client/test/Makefile +client/Makefile + +# client/fdfs_link_library.sh.in +client/fdfs_link_library.sh + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +client/fdfs_append_file* +client/fdfs_appender_test* +client/fdfs_crc32* +client/fdfs_delete_file* +client/fdfs_download_file* +client/fdfs_file_info* +client/fdfs_monitor* +client/fdfs_test* +client/fdfs_test1* +client/fdfs_upload_appender* +client/fdfs_upload_file* +storage/fdfs_storaged* +tracker/fdfs_trackerd* + +# other +php_client/.deps +php_client/.libs/ + +php_client/Makefile +php_client/Makefile.fragments +php_client/Makefile.global +php_client/Makefile.objects +php_client/acinclude.m4 +php_client/aclocal.m4 +php_client/autom4te.cache/ +php_client/build/ +php_client/config.guess +php_client/config.h +php_client/config.h.in +php_client/config.log +php_client/config.nice +php_client/config.status +php_client/config.sub +php_client/configure +php_client/configure.ac +php_client/install-sh +php_client/libtool +php_client/ltmain.sh +php_client/missing +php_client/mkinstalldirs +php_client/run-tests.php \ No newline at end of file diff --git a/client/Makefile.in b/client/Makefile.in index 7d702ee..29d2152 100644 --- a/client/Makefile.in +++ b/client/Makefile.in @@ -6,7 +6,7 @@ ENABLE_SHARED_LIB = $(ENABLE_SHARED_LIB) INC_PATH = -I../common -I../tracker -I/usr/include/fastcommon LIB_PATH = $(LIBS) -lfastcommon TARGET_PATH = $(TARGET_PREFIX)/bin -TARGET_LIB = $(TARGET_PREFIX)/lib64 +TARGET_LIB = $(TARGET_PREFIX)/$(LIB_VERSION) TARGET_INC = $(TARGET_PREFIX)/include CONFIG_PATH = $(TARGET_CONF_PATH) diff --git a/make.sh b/make.sh index 1294ef6..69bb072 100755 --- a/make.sh +++ b/make.sh @@ -23,15 +23,21 @@ else OS_BITS=64 fi +uname=$(uname) + if [ "$OS_BITS" -eq 64 ]; then - LIB_VERSION=lib64 + if [ "$uname" = "Darwin" ]; then + LIB_VERSION=lib + else + LIB_VERSION=lib64 + fi else LIB_VERSION=lib fi LIBS='' -uname=$(uname) + if [ "$uname" = "Linux" ]; then if [ "$OS_BITS" -eq 64 ]; then LIBS="$LIBS -L/usr/lib64" @@ -44,6 +50,7 @@ elif [ "$uname" = "FreeBSD" ] || [ "$uname" = "Darwin" ]; then CFLAGS="$CFLAGS" if [ "$uname" = "Darwin" ]; then CFLAGS="$CFLAGS -DDARWIN" + TARGET_PREFIX=$TARGET_PREFIX/local fi elif [ "$uname" = "SunOS" ]; then LIBS="$LIBS -L/usr/lib" @@ -131,6 +138,7 @@ cp Makefile.in Makefile perl -pi -e "s#\\\$\(CFLAGS\)#$CFLAGS#g" Makefile perl -pi -e "s#\\\$\(LIBS\)#$LIBS#g" Makefile perl -pi -e "s#\\\$\(TARGET_PREFIX\)#$TARGET_PREFIX#g" Makefile +perl -pi -e "s#\\\$\(LIB_VERSION\)#$LIB_VERSION#g" Makefile perl -pi -e "s#\\\$\(TARGET_CONF_PATH\)#$TARGET_CONF_PATH#g" Makefile perl -pi -e "s#\\\$\(ENABLE_STATIC_LIB\)#$ENABLE_STATIC_LIB#g" Makefile perl -pi -e "s#\\\$\(ENABLE_SHARED_LIB\)#$ENABLE_SHARED_LIB#g" Makefile From 5eb02fd01f8a1ce0bb9a41cb6c28fab32bdb65b0 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 21 Oct 2019 15:06:18 +0800 Subject: [PATCH 02/95] correct spell for tracker --- HISTORY | 2 +- storage/storage_sync.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/HISTORY b/HISTORY index b03e0e2..4c0910d 100644 --- a/HISTORY +++ b/HISTORY @@ -1,7 +1,7 @@ Version 6.00 2019-10-16 * tracker and storage server support dual IPs - 1. you can config dual trackr IPs in storage.conf and client.conf, + 1. you can config dual tracker IPs in storage.conf and client.conf, the configuration item name is "tracker_server" 2. you can config dual storage IPs in storage_ids.conf more detail please see the config files. diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 20e1633..c8e7128 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -1774,6 +1774,7 @@ static int storage_reader_sync_init_req(StorageBinLogReader *pReader) } do { + conn = NULL; while (g_continue_flag) { conn = tracker_connect_server_no_pool_ex(pTServer, From 9d2db48f318acc202ac677143c3cb8b45eaa8fc8 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 21 Oct 2019 19:53:23 +0800 Subject: [PATCH 03/95] make.sh fix TARGET_PREFIX --- make.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/make.sh b/make.sh index da084ce..1294ef6 100755 --- a/make.sh +++ b/make.sh @@ -1,7 +1,6 @@ ENABLE_STATIC_LIB=0 ENABLE_SHARED_LIB=1 -DESTDIR=/usr -TARGET_PREFIX=$DESTDIR +TARGET_PREFIX=$DESTDIR/usr TARGET_CONF_PATH=$DESTDIR/etc/fdfs TARGET_INIT_PATH=$DESTDIR/etc/init.d From 77da832e05e8958b8fb5c27055a07ff1a13676e9 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 23 Oct 2019 14:56:28 +0800 Subject: [PATCH 04/95] compress and uncompress binlog file by gzip when need --- HISTORY | 4 + README.md | 2 +- common/fdfs_global.c | 2 +- conf/storage.conf | 11 + storage/fdfs_storaged.c | 182 ++++++------ storage/storage_func.c | 16 +- storage/storage_global.c | 3 + storage/storage_global.h | 3 + storage/storage_service.c | 2 + storage/storage_sync.c | 512 +++++++++++++++++++++++++++------ storage/storage_sync.h | 10 +- tracker/tracker_relationship.c | 15 +- 12 files changed, 577 insertions(+), 185 deletions(-) diff --git a/HISTORY b/HISTORY index 4c0910d..0ccafe7 100644 --- a/HISTORY +++ b/HISTORY @@ -1,4 +1,8 @@ +Version 6.01 2019-10-23 + * compress and uncompress binlog file by gzip when need, + config items in storage.conf: compress_binlog and compress_binlog_time + Version 6.00 2019-10-16 * tracker and storage server support dual IPs 1. you can config dual tracker IPs in storage.conf and client.conf, diff --git a/README.md b/README.md index 5aa4cee..c954f2e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ FastDFS may be copied only under the terms of the GNU General Public License V3, which may be found in the FastDFS source kit. Please visit the FastDFS Home Page for more detail. English language: http://english.csource.org/ -Chinese language: http://www.csource.org/ +Chinese language: http://www.fastken.com/ FastDFS is an open source high performance distributed file system. It's major diff --git a/common/fdfs_global.c b/common/fdfs_global.c index 61bf5c6..e33dab4 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -23,7 +23,7 @@ int g_fdfs_connect_timeout = DEFAULT_CONNECT_TIMEOUT; int g_fdfs_network_timeout = DEFAULT_NETWORK_TIMEOUT; char g_fdfs_base_path[MAX_PATH_SIZE] = {'/', 't', 'm', 'p', '\0'}; -Version g_fdfs_version = {5, 12}; +Version g_fdfs_version = {6, 1}; bool g_use_connection_pool = false; ConnectionPool g_connection_pool; int g_connection_pool_max_idle_time = 3600; diff --git a/conf/storage.conf b/conf/storage.conf index a317733..7c8aba9 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -283,6 +283,17 @@ use_connection_pool = false # since V4.05 connection_pool_max_idle_time = 3600 +# if compress the binlog files by gzip +# default value is false +# since V6.01 +compress_binlog = false + +# try to compress binlog time, time format: Hour:Minute +# Hour from 0 to 23, Minute from 0 to 59 +# default value is 01:30 +# since V6.01 +compress_binlog_time=01:30 + # use the ip address of this storage server if domain_name is empty, # else this domain name will ocur in the url redirected by the tracker server http.domain_name= diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 8ff7134..1787646 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -57,6 +57,8 @@ static void sigHupHandler(int sig); static void sigUsrHandler(int sig); static void sigAlarmHandler(int sig); +static int setupSchedules(pthread_t *schedule_tid); + #if defined(DEBUG_FLAG) /* @@ -68,8 +70,6 @@ static void sigSegvHandler(int signum, siginfo_t *info, void *ptr); static void sigDumpHandler(int sig); #endif -#define SCHEDULE_ENTRIES_MAX_COUNT 9 - static void usage(const char *program) { fprintf(stderr, "Usage: %s [start | stop | restart]\n", @@ -84,8 +84,6 @@ int main(int argc, char *argv[]) int wait_count; pthread_t schedule_tid; struct sigaction act; - ScheduleEntry scheduleEntries[SCHEDULE_ENTRIES_MAX_COUNT]; - ScheduleArray scheduleArray; char pidFilename[MAX_PATH_SIZE]; bool stop; @@ -307,85 +305,12 @@ int main(int argc, char *argv[]) return result; } - scheduleArray.entries = scheduleEntries; - scheduleArray.count = 0; - memset(scheduleEntries, 0, sizeof(scheduleEntries)); - - INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, - g_sync_log_buff_interval, log_sync_func, &g_log_context); - scheduleArray.count++; - - INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, - g_sync_binlog_buff_interval, fdfs_binlog_sync_func, NULL); - scheduleArray.count++; - - INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, - g_sync_stat_file_interval, fdfs_stat_file_sync_func, NULL); - scheduleArray.count++; - - if (g_if_use_trunk_file) - { - INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, - 1, trunk_binlog_sync_func, NULL); - scheduleArray.count++; - } - - if (g_use_access_log) - { - INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, - g_sync_log_buff_interval, log_sync_func, &g_access_log_context); - scheduleArray.count++; - - if (g_rotate_access_log) - { - INIT_SCHEDULE_ENTRY_EX(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, g_access_log_rotate_time, - 24 * 3600, log_notify_rotate, &g_access_log_context); - scheduleArray.count++; - - if (g_log_file_keep_days > 0) - { - log_set_keep_days(&g_access_log_context, - g_log_file_keep_days); - - INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, 1, 0, 0, 24 * 3600, - log_delete_old_files, &g_access_log_context); - scheduleArray.count++; - } - } - } - - if (g_rotate_error_log) - { - INIT_SCHEDULE_ENTRY_EX(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, g_error_log_rotate_time, - 24 * 3600, log_notify_rotate, &g_log_context); - scheduleArray.count++; - - if (g_log_file_keep_days > 0) - { - log_set_keep_days(&g_log_context, g_log_file_keep_days); - - INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], - scheduleArray.count + 1, 1, 0, 0, 24 * 3600, - log_delete_old_files, &g_log_context); - scheduleArray.count++; - } - } - - if ((result=sched_start(&scheduleArray, &schedule_tid, \ - g_thread_stack_size, (bool * volatile)&g_continue_flag)) != 0) - { + if ((result=setupSchedules(&schedule_tid)) != 0) + { logCrit("exit abnormally!\n"); log_destroy(); return result; - } + } if ((result=set_run_by(g_run_by_group, g_run_by_user)) != 0) { @@ -556,3 +481,100 @@ static void sigDumpHandler(int sig) } #endif +static int setupSchedules(pthread_t *schedule_tid) +{ +#define SCHEDULE_ENTRIES_MAX_COUNT 10 + + ScheduleEntry scheduleEntries[SCHEDULE_ENTRIES_MAX_COUNT]; + ScheduleArray scheduleArray; + int result; + + scheduleArray.entries = scheduleEntries; + scheduleArray.count = 0; + memset(scheduleEntries, 0, sizeof(scheduleEntries)); + + INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, + g_sync_log_buff_interval, log_sync_func, &g_log_context); + scheduleArray.count++; + + INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, + g_sync_binlog_buff_interval, fdfs_binlog_sync_func, NULL); + scheduleArray.count++; + + INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, + g_sync_stat_file_interval, fdfs_stat_file_sync_func, NULL); + scheduleArray.count++; + + if (g_if_use_trunk_file) + { + INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, + 1, trunk_binlog_sync_func, NULL); + scheduleArray.count++; + } + + if (g_use_access_log) + { + INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, TIME_NONE, TIME_NONE, TIME_NONE, + g_sync_log_buff_interval, log_sync_func, &g_access_log_context); + scheduleArray.count++; + + if (g_rotate_access_log) + { + INIT_SCHEDULE_ENTRY_EX(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, g_access_log_rotate_time, + 24 * 3600, log_notify_rotate, &g_access_log_context); + scheduleArray.count++; + + if (g_log_file_keep_days > 0) + { + log_set_keep_days(&g_access_log_context, + g_log_file_keep_days); + + INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, 1, 0, 0, 24 * 3600, + log_delete_old_files, &g_access_log_context); + scheduleArray.count++; + } + } + } + + if (g_rotate_error_log) + { + INIT_SCHEDULE_ENTRY_EX(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, g_error_log_rotate_time, + 24 * 3600, log_notify_rotate, &g_log_context); + scheduleArray.count++; + + if (g_log_file_keep_days > 0) + { + log_set_keep_days(&g_log_context, g_log_file_keep_days); + + INIT_SCHEDULE_ENTRY(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, 1, 0, 0, 24 * 3600, + log_delete_old_files, &g_log_context); + scheduleArray.count++; + } + } + + if (g_compress_binlog) + { + INIT_SCHEDULE_ENTRY_EX(scheduleEntries[scheduleArray.count], + scheduleArray.count + 1, g_compress_binlog_time, + 60, fdfs_binlog_compress_func, NULL); + scheduleArray.count++; + } + + if ((result=sched_start(&scheduleArray, schedule_tid, + g_thread_stack_size, (bool * volatile)&g_continue_flag)) != 0) + { + return result; + } + + return 0; +} + diff --git a/storage/storage_func.c b/storage/storage_func.c index e0eb796..d11f4be 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -1785,6 +1785,14 @@ int storage_func_init(const char *filename, \ g_file_sync_skip_invalid_record = iniGetBoolValue(NULL, \ "file_sync_skip_invalid_record", &iniContext, false); + g_compress_binlog = iniGetBoolValue(NULL, + "compress_binlog", &iniContext, false); + if ((result=get_time_item_from_conf(&iniContext, + "compress_binlog_time", &g_compress_binlog_time, 1, 30)) != 0) + { + break; + } + if ((result=fdfs_connection_pool_init(filename, &iniContext)) != 0) { break; @@ -1853,7 +1861,9 @@ int storage_func_init(const char *filename, \ "log_file_keep_days=%d, " \ "file_sync_skip_invalid_record=%d, " \ "use_connection_pool=%d, " \ - "g_connection_pool_max_idle_time=%ds", \ + "g_connection_pool_max_idle_time=%ds, " \ + "compress_binlog=%d, " \ + "compress_binlog_time=%02d:%02d", \ g_fdfs_version.major, g_fdfs_version.minor, \ g_fdfs_base_path, g_fdfs_store_paths.count, \ g_subdir_count_per_path, \ @@ -1888,7 +1898,9 @@ int storage_func_init(const char *filename, \ g_access_log_context.rotate_size, \ g_log_context.rotate_size, g_log_file_keep_days, \ g_file_sync_skip_invalid_record, \ - g_use_connection_pool, g_connection_pool_max_idle_time); + g_use_connection_pool, g_connection_pool_max_idle_time, \ + g_compress_binlog, g_compress_binlog_time.hour, \ + g_compress_binlog_time.minute); #ifdef WITH_HTTPD if (!g_http_params.disabled) diff --git a/storage/storage_global.c b/storage/storage_global.c index 9be4a42..0d7f0a1 100644 --- a/storage/storage_global.c +++ b/storage/storage_global.c @@ -107,6 +107,9 @@ bool g_storage_ip_changed_auto_adjust = false; bool g_thread_kill_done = false; bool g_file_sync_skip_invalid_record = false; +bool g_compress_binlog = false; +TimeInfo g_compress_binlog_time = {0, 0}; + int g_thread_stack_size = 512 * 1024; int g_upload_priority = DEFAULT_UPLOAD_PRIORITY; time_t g_up_time = 0; diff --git a/storage/storage_global.h b/storage/storage_global.h index 0fc19ce..f03114e 100644 --- a/storage/storage_global.h +++ b/storage/storage_global.h @@ -162,6 +162,9 @@ extern bool g_thread_kill_done; extern bool g_file_sync_skip_invalid_record; +extern bool g_compress_binlog; +extern TimeInfo g_compress_binlog_time; //compress binlog time base + extern int g_thread_stack_size; extern int g_upload_priority; extern time_t g_up_time; diff --git a/storage/storage_service.c b/storage/storage_service.c index c6e7f47..4a24202 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -4254,6 +4254,7 @@ static void fetch_one_path_binlog_finish_clean_up(struct fast_task_info *pTask) pClientInfo->extra_arg = NULL; + storage_reader_remove_from_list(pReader); storage_reader_destroy(pReader); get_mark_filename_by_reader(pReader, full_filename); if (fileExists(full_filename)) @@ -4292,6 +4293,7 @@ static int storage_server_do_fetch_one_path_binlog( \ free(pReader); return result; } + storage_reader_add_to_list(pReader); pClientInfo->deal_func = storage_server_fetch_one_path_binlog_dealer; pClientInfo->clean_func = fetch_one_path_binlog_finish_clean_up; diff --git a/storage/storage_sync.c b/storage/storage_sync.c index c8e7128..3a0819c 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -40,7 +40,8 @@ #define SYNC_BINLOG_FILE_MAX_SIZE 1024 * 1024 * 1024 #define SYNC_BINLOG_FILE_PREFIX "binlog" -#define SYNC_BINLOG_INDEX_FILENAME SYNC_BINLOG_FILE_PREFIX".index" +#define SYNC_BINLOG_INDEX_FILENAME_OLD SYNC_BINLOG_FILE_PREFIX".index" +#define SYNC_BINLOG_INDEX_FILENAME SYNC_BINLOG_FILE_PREFIX"_index.dat" #define SYNC_MARK_FILE_EXT ".mark" #define SYNC_BINLOG_FILE_EXT_FMT ".%03d" #define SYNC_DIR_NAME "sync" @@ -53,9 +54,13 @@ #define MARK_ITEM_SYNC_ROW_COUNT "sync_row_count" #define SYNC_BINLOG_WRITE_BUFF_SIZE (16 * 1024) +#define BINLOG_INDEX_ITEM_CURRENT_WRITE "current_write" +#define BINLOG_INDEX_ITEM_CURRENT_COMPRESS "current_compress" + int g_binlog_fd = -1; int g_binlog_index = 0; static int64_t binlog_file_size = 0; +static int binlog_compress_index = 0; int g_storage_sync_thread_count = 0; static pthread_mutex_t sync_thread_lock; @@ -66,6 +71,8 @@ static int binlog_write_version = 1; /* save sync thread ids */ static pthread_t *sync_tids = NULL; +static struct fc_list_head reader_head; + static int storage_write_to_mark_file(StorageBinLogReader *pReader); static int storage_binlog_reader_skip(StorageBinLogReader *pReader); static int storage_binlog_fsync(const bool bNeedLock); @@ -1078,30 +1085,33 @@ static int storage_sync_data(StorageBinLogReader *pReader, \ static int write_to_binlog_index(const int binlog_index) { char full_filename[MAX_PATH_SIZE]; - char buff[16]; + char buff[256]; int fd; int len; - snprintf(full_filename, sizeof(full_filename), \ - "%s/data/"SYNC_DIR_NAME"/%s", g_fdfs_base_path, \ + snprintf(full_filename, sizeof(full_filename), + "%s/data/"SYNC_DIR_NAME"/%s", g_fdfs_base_path, SYNC_BINLOG_INDEX_FILENAME); if ((fd=open(full_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) { - logError("file: "__FILE__", line: %d, " \ - "open file \"%s\" fail, " \ - "errno: %d, error info: %s", \ - __LINE__, full_filename, \ + logError("file: "__FILE__", line: %d, " + "open file \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, full_filename, errno, STRERROR(errno)); return errno != 0 ? errno : ENOENT; } - len = sprintf(buff, "%d", binlog_index); + len = sprintf(buff, "%s=%d\n" + "%s=%d\n", + BINLOG_INDEX_ITEM_CURRENT_WRITE, binlog_index, + BINLOG_INDEX_ITEM_CURRENT_COMPRESS, binlog_compress_index); if (fc_safe_write(fd, buff, len) != len) { - logError("file: "__FILE__", line: %d, " \ - "write to file \"%s\" fail, " \ - "errno: %d, error info: %s", \ - __LINE__, full_filename, \ + logError("file: "__FILE__", line: %d, " + "write to file \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, full_filename, errno, STRERROR(errno)); close(fd); return errno != 0 ? errno : EIO; @@ -1114,6 +1124,92 @@ static int write_to_binlog_index(const int binlog_index) return 0; } +static int get_binlog_index_from_file_old() +{ + char full_filename[MAX_PATH_SIZE]; + char file_buff[64]; + int fd; + int bytes; + + snprintf(full_filename, sizeof(full_filename), + "%s/data/"SYNC_DIR_NAME"/%s", g_fdfs_base_path, + SYNC_BINLOG_INDEX_FILENAME_OLD); + if ((fd=open(full_filename, O_RDONLY)) >= 0) + { + bytes = fc_safe_read(fd, file_buff, sizeof(file_buff) - 1); + close(fd); + if (bytes <= 0) + { + logError("file: "__FILE__", line: %d, " \ + "read file \"%s\" fail, bytes read: %d", \ + __LINE__, full_filename, bytes); + return errno != 0 ? errno : EIO; + } + + file_buff[bytes] = '\0'; + g_binlog_index = atoi(file_buff); + if (g_binlog_index < 0) + { + logError("file: "__FILE__", line: %d, " \ + "in file \"%s\", binlog_index: %d < 0", \ + __LINE__, full_filename, g_binlog_index); + return EINVAL; + } + } + else + { + g_binlog_index = 0; + } + + return 0; +} + +static int get_binlog_index_from_file() +{ + char full_filename[MAX_PATH_SIZE]; + IniContext iniContext; + int result; + + snprintf(full_filename, sizeof(full_filename), + "%s/data/"SYNC_DIR_NAME"/%s", g_fdfs_base_path, + SYNC_BINLOG_INDEX_FILENAME); + if (access(full_filename, F_OK) != 0) + { + if (errno == ENOENT) + { + if ((result=get_binlog_index_from_file_old()) == 0) + { + if ((result=write_to_binlog_index(g_binlog_index)) != 0) + { + return result; + } + } + + return result; + } + } + + memset(&iniContext, 0, sizeof(IniContext)); + if ((result=iniLoadFromFile(full_filename, &iniContext)) != 0) + { + logError("file: "__FILE__", line: %d, " + "load from file \"%s\" fail, " + "error code: %d", + __LINE__, full_filename, result); + return result; + } + + g_binlog_index = iniGetIntValue(NULL, + BINLOG_INDEX_ITEM_CURRENT_WRITE, + &iniContext, 0); + binlog_compress_index = iniGetIntValue(NULL, + BINLOG_INDEX_ITEM_CURRENT_COMPRESS, + &iniContext, 0); + + iniFreeContext(&iniContext); + return 0; +} + static char *get_writable_binlog_filename(char *full_filename) { static char buff[MAX_PATH_SIZE]; @@ -1189,10 +1285,7 @@ int storage_sync_init() char data_path[MAX_PATH_SIZE]; char sync_path[MAX_PATH_SIZE]; char full_filename[MAX_PATH_SIZE]; - char file_buff[64]; - int bytes; int result; - int fd; snprintf(data_path, sizeof(data_path), "%s/data", g_fdfs_base_path); if (!fileExists(data_path)) @@ -1238,38 +1331,10 @@ int storage_sync_init() return errno != 0 ? errno : ENOMEM; } - snprintf(full_filename, sizeof(full_filename), \ - "%s/%s", sync_path, SYNC_BINLOG_INDEX_FILENAME); - if ((fd=open(full_filename, O_RDONLY)) >= 0) - { - bytes = fc_safe_read(fd, file_buff, sizeof(file_buff) - 1); - close(fd); - if (bytes <= 0) - { - logError("file: "__FILE__", line: %d, " \ - "read file \"%s\" fail, bytes read: %d", \ - __LINE__, full_filename, bytes); - return errno != 0 ? errno : EIO; - } - - file_buff[bytes] = '\0'; - g_binlog_index = atoi(file_buff); - if (g_binlog_index < 0) - { - logError("file: "__FILE__", line: %d, " \ - "in file \"%s\", binlog_index: %d < 0", \ - __LINE__, full_filename, g_binlog_index); - return EINVAL; - } - } - else - { - g_binlog_index = 0; - if ((result=write_to_binlog_index(g_binlog_index)) != 0) - { - return result; - } - } + if ((result=get_binlog_index_from_file()) != 0) + { + return result; + } get_writable_binlog_filename(full_filename); g_binlog_fd = open(full_filename, O_WRONLY | O_CREAT | O_APPEND, 0644); @@ -1309,6 +1374,8 @@ int storage_sync_init() load_local_host_ip_addrs(); + FC_INIT_LIST_HEAD(&reader_head); + return 0; } @@ -1514,25 +1581,181 @@ int storage_binlog_write_ex(const int timestamp, const char op_type, \ return write_ret; } -static char *get_binlog_readable_filename(const void *pArg, \ - char *full_filename) +static char *get_binlog_readable_filename_ex( + const int binlog_index, char *full_filename) { - const StorageBinLogReader *pReader; static char buff[MAX_PATH_SIZE]; - pReader = (const StorageBinLogReader *)pArg; if (full_filename == NULL) { full_filename = buff; } - snprintf(full_filename, MAX_PATH_SIZE, \ - "%s/data/"SYNC_DIR_NAME"/"SYNC_BINLOG_FILE_PREFIX"" \ - SYNC_BINLOG_FILE_EXT_FMT, \ - g_fdfs_base_path, pReader->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; } +static inline char *get_binlog_readable_filename(const void *pArg, + char *full_filename) +{ + return get_binlog_readable_filename_ex( + ((const StorageBinLogReader *)pArg)->binlog_index, + full_filename); +} + +static int uncompress_binlog_file(StorageBinLogReader *pReader, + const char *filename) +{ + char gzip_filename[MAX_PATH_SIZE]; + char flag_filename[MAX_PATH_SIZE]; + char command[MAX_PATH_SIZE]; + char output[256]; + struct stat flag_stat; + int result; + + snprintf(gzip_filename, sizeof(gzip_filename), + "%s.gz", filename); + if (access(gzip_filename, F_OK) != 0) + { + return errno != 0 ? errno : ENOENT; + } + + snprintf(flag_filename, sizeof(flag_filename), + "%s.flag", filename); + if (stat(flag_filename, &flag_stat) == 0) + { + if (g_current_time - flag_stat.st_mtime > 3600) + { + logInfo("file: "__FILE__", line: %d, " + "flag file %s expired, continue to uncompress", + __LINE__, flag_filename); + } + else + { + logWarning("file: "__FILE__", line: %d, " + "uncompress %s already in progress", + __LINE__, gzip_filename); + return EINPROGRESS; + } + } + + if ((result=writeToFile(flag_filename, "unzip", 5)) != 0) + { + return result; + } + + logInfo("file: "__FILE__", line: %d, " + "try to uncompress binlog %s", + __LINE__, gzip_filename); + snprintf(command, sizeof(command), "gzip -d %s 2>&1", gzip_filename); + result = getExecResult(command, output, sizeof(output)); + unlink(flag_filename); + if (result != 0) + { + logError("file: "__FILE__", line: %d, " + "exec command \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, command, result, STRERROR(result)); + return result; + } + if (*output != '\0') + { + logWarning("file: "__FILE__", line: %d, " + "exec command \"%s\", output: %s", + __LINE__, command, output); + } + + if (access(filename, F_OK) == 0) + { + if (pReader->binlog_index < binlog_compress_index) + { + binlog_compress_index = pReader->binlog_index; + write_to_binlog_index(g_binlog_index); + } + } + + logInfo("file: "__FILE__", line: %d, " + "uncompress binlog %s done", + __LINE__, gzip_filename); + return 0; +} + +static int compress_binlog_file(const char *filename) +{ + char gzip_filename[MAX_PATH_SIZE]; + char flag_filename[MAX_PATH_SIZE]; + char command[MAX_PATH_SIZE]; + char output[256]; + struct stat flag_stat; + int result; + + snprintf(gzip_filename, sizeof(gzip_filename), + "%s.gz", filename); + if (access(gzip_filename, F_OK) == 0) + { + return 0; + } + + if (access(filename, F_OK) != 0) + { + return errno != 0 ? errno : ENOENT; + } + + snprintf(flag_filename, sizeof(flag_filename), + "%s.flag", filename); + if (stat(flag_filename, &flag_stat) == 0) + { + if (g_current_time - flag_stat.st_mtime > 3600) + { + logInfo("file: "__FILE__", line: %d, " + "flag file %s expired, continue to compress", + __LINE__, flag_filename); + } + else + { + logWarning("file: "__FILE__", line: %d, " + "compress %s already in progress", + __LINE__, filename); + return EINPROGRESS; + } + } + + if ((result=writeToFile(flag_filename, "zip", 3)) != 0) + { + return result; + } + + logInfo("file: "__FILE__", line: %d, " + "try to compress binlog %s", + __LINE__, filename); + + snprintf(command, sizeof(command), "gzip %s 2>&1", filename); + result = getExecResult(command, output, sizeof(output)); + unlink(flag_filename); + if (result != 0) + { + logError("file: "__FILE__", line: %d, " + "exec command \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, command, result, STRERROR(result)); + return result; + } + if (*output != '\0') + { + logWarning("file: "__FILE__", line: %d, " + "exec command \"%s\", output: %s", + __LINE__, command, output); + } + + logInfo("file: "__FILE__", line: %d, " + "compress binlog %s done", + __LINE__, filename); + return 0; +} + int storage_open_readable_binlog(StorageBinLogReader *pReader, \ get_filename_func filename_func, const void *pArg) { @@ -1544,6 +1767,14 @@ int storage_open_readable_binlog(StorageBinLogReader *pReader, \ } filename_func(pArg, full_filename); + if (access(full_filename, F_OK) != 0) + { + if (errno == ENOENT) + { + uncompress_binlog_file(pReader, full_filename); + } + } + pReader->binlog_fd = open(full_filename, O_RDONLY); if (pReader->binlog_fd < 0) { @@ -1833,7 +2064,14 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader bool bFileExist; bool bNeedSyncOld; - memset(pReader, 0, sizeof(StorageBinLogReader)); + pReader->binlog_index = 0; + pReader->binlog_offset = 0; + pReader->need_sync_old = 0; + pReader->sync_old_done = 0; + pReader->until_timestamp = 0; + pReader->scan_row_count = 0; + pReader->sync_row_count = 0; + pReader->last_file_exist = 0; pReader->mark_fd = -1; pReader->binlog_fd = -1; @@ -2546,7 +2784,7 @@ static void storage_sync_thread_exit(ConnectionInfo *pStorage) static void* storage_sync_thread_entrance(void* arg) { FDFSStorageBrief *pStorage; - StorageBinLogReader reader; + StorageBinLogReader *pReader; StorageBinLogRecord record; ConnectionInfo storage_server; char local_ip_addr[IP_ADDRESS_SIZE]; @@ -2559,26 +2797,39 @@ static void* storage_sync_thread_entrance(void* arg) time_t end_time; time_t last_keep_alive_time; + pStorage = (FDFSStorageBrief *)arg; + strcpy(storage_server.ip_addr, pStorage->ip_addr); + storage_server.port = g_server_port; + storage_server.sock = -1; + memset(local_ip_addr, 0, sizeof(local_ip_addr)); - memset(&reader, 0, sizeof(reader)); - reader.mark_fd = -1; - reader.binlog_fd = -1; + pReader = (StorageBinLogReader *)malloc(sizeof(StorageBinLogReader)); + if (pReader == NULL) + { + logCrit("file: "__FILE__", line: %d, " + "malloc %d bytes fail, " + "fail, program exit!", + __LINE__, (int)sizeof(StorageBinLogReader)); + g_continue_flag = false; + storage_sync_thread_exit(&storage_server); + return NULL; + } + + memset(pReader, 0, sizeof(StorageBinLogReader)); + pReader->mark_fd = -1; + pReader->binlog_fd = -1; + + storage_reader_add_to_list(pReader); current_time = g_current_time; last_keep_alive_time = 0; start_time = 0; end_time = 0; - pStorage = (FDFSStorageBrief *)arg; - - strcpy(storage_server.ip_addr, pStorage->ip_addr); - storage_server.port = g_server_port; - storage_server.sock = -1; - logDebug("file: "__FILE__", line: %d, " \ "sync thread to storage server %s:%d started", \ __LINE__, storage_server.ip_addr, storage_server.port); - + while (g_continue_flag && \ pStorage->status != FDFS_STORAGE_STATUS_DELETED && \ pStorage->status != FDFS_STORAGE_STATUS_IP_CHANGED && \ @@ -2635,7 +2886,7 @@ static void* storage_sync_thread_entrance(void* arg) continue; } - if ((result=storage_reader_init(pStorage, &reader)) != 0) + if ((result=storage_reader_init(pStorage, pReader)) != 0) { logCrit("file: "__FILE__", line: %d, " \ "storage_reader_init fail, errno=%d, " \ @@ -2645,7 +2896,7 @@ static void* storage_sync_thread_entrance(void* arg) break; } - if (!reader.need_sync_old) + if (!pReader->need_sync_old) { while (g_continue_flag && \ (pStorage->status != FDFS_STORAGE_STATUS_ACTIVE && \ @@ -2659,7 +2910,7 @@ static void* storage_sync_thread_entrance(void* arg) if (pStorage->status != FDFS_STORAGE_STATUS_ACTIVE) { close(storage_server.sock); - storage_reader_destroy(&reader); + storage_reader_destroy(pReader); continue; } } @@ -2689,7 +2940,7 @@ static void* storage_sync_thread_entrance(void* arg) if (storage_report_my_server_id(&storage_server) != 0) { close(storage_server.sock); - storage_reader_destroy(&reader); + storage_reader_destroy(pReader); sleep(1); continue; } @@ -2703,7 +2954,7 @@ static void* storage_sync_thread_entrance(void* arg) if (pStorage->status == FDFS_STORAGE_STATUS_SYNCING) { - if (reader.need_sync_old && reader.sync_old_done) + if (pReader->need_sync_old && pReader->sync_old_done) { pStorage->status = FDFS_STORAGE_STATUS_OFFLINE; storage_report_storage_status(pStorage->id, \ @@ -2727,15 +2978,15 @@ static void* storage_sync_thread_entrance(void* arg) (pStorage->status == FDFS_STORAGE_STATUS_ACTIVE || \ pStorage->status == FDFS_STORAGE_STATUS_SYNCING)) { - read_result = storage_binlog_read(&reader, \ + read_result = storage_binlog_read(pReader, \ &record, &record_len); if (read_result == ENOENT) { - if (reader.need_sync_old && \ - !reader.sync_old_done) + if (pReader->need_sync_old && \ + !pReader->sync_old_done) { - reader.sync_old_done = true; - if (storage_write_to_mark_file(&reader) != 0) + pReader->sync_old_done = true; + if (storage_write_to_mark_file(pReader) != 0) { logCrit("file: "__FILE__", line: %d, " \ "storage_write_to_mark_file " \ @@ -2758,9 +3009,9 @@ static void* storage_sync_thread_entrance(void* arg) } - if (reader.last_scan_rows!=reader.scan_row_count) + if (pReader->last_scan_rows!=pReader->scan_row_count) { - if (storage_write_to_mark_file(&reader)!=0) + if (storage_write_to_mark_file(pReader)!=0) { logCrit("file: "__FILE__", line: %d, " \ "storage_write_to_mark_file fail, " \ @@ -2799,8 +3050,8 @@ static void* storage_sync_thread_entrance(void* arg) logWarning("file: "__FILE__", line: %d, " \ "skip invalid record, binlog index: " \ "%d, offset: %"PRId64, \ - __LINE__, reader.binlog_index, \ - reader.binlog_offset); + __LINE__, pReader->binlog_index, \ + pReader->binlog_offset); } else { @@ -2808,17 +3059,17 @@ static void* storage_sync_thread_entrance(void* arg) break; } } - else if ((sync_result=storage_sync_data(&reader, \ + else if ((sync_result=storage_sync_data(pReader, \ &storage_server, &record)) != 0) { logDebug("file: "__FILE__", line: %d, " \ "binlog index: %d, current record " \ "offset: %"PRId64", next " \ "record offset: %"PRId64, \ - __LINE__, reader.binlog_index, \ - reader.binlog_offset, \ - reader.binlog_offset + record_len); - if (rewind_to_prev_rec_end(&reader) != 0) + __LINE__, pReader->binlog_index, \ + pReader->binlog_offset, \ + pReader->binlog_offset + record_len); + if (rewind_to_prev_rec_end(pReader) != 0) { logCrit("file: "__FILE__", line: %d, " \ "rewind_to_prev_rec_end fail, "\ @@ -2829,8 +3080,8 @@ static void* storage_sync_thread_entrance(void* arg) break; } - reader.binlog_offset += record_len; - reader.scan_row_count++; + pReader->binlog_offset += record_len; + pReader->scan_row_count++; if (g_sync_interval > 0) { @@ -2838,9 +3089,9 @@ static void* storage_sync_thread_entrance(void* arg) } } - if (reader.last_scan_rows != reader.scan_row_count) + if (pReader->last_scan_rows != pReader->scan_row_count) { - if (storage_write_to_mark_file(&reader) != 0) + if (storage_write_to_mark_file(pReader) != 0) { logCrit("file: "__FILE__", line: %d, " \ "storage_write_to_mark_file fail, " \ @@ -2852,7 +3103,7 @@ static void* storage_sync_thread_entrance(void* arg) close(storage_server.sock); storage_server.sock = -1; - storage_reader_destroy(&reader); + storage_reader_destroy(pReader); if (!g_continue_flag) { @@ -2869,7 +3120,8 @@ static void* storage_sync_thread_entrance(void* arg) { close(storage_server.sock); } - storage_reader_destroy(&reader); + storage_reader_remove_from_list(pReader); + storage_reader_destroy(pReader); if (pStorage->status == FDFS_STORAGE_STATUS_DELETED || pStorage->status == FDFS_STORAGE_STATUS_IP_CHANGED) @@ -2880,7 +3132,6 @@ static void* storage_sync_thread_entrance(void* arg) } storage_sync_thread_exit(&storage_server); - return NULL; } @@ -2970,3 +3221,76 @@ int storage_sync_thread_start(const FDFSStorageBrief *pStorage) return 0; } + +void storage_reader_add_to_list(StorageBinLogReader *pReader) +{ + pthread_mutex_lock(&sync_thread_lock); + fc_list_add_tail(&pReader->link, &reader_head); + pthread_mutex_unlock(&sync_thread_lock); +} + +void storage_reader_remove_from_list(StorageBinLogReader *pReader) +{ + pthread_mutex_lock(&sync_thread_lock); + fc_list_del_init(&pReader->link); + pthread_mutex_unlock(&sync_thread_lock); +} + +static int calc_compress_until_binlog_index() +{ + StorageBinLogReader *pReader; + int min_index; + + pthread_mutex_lock(&sync_thread_lock); + logInfo("g_storage_sync_thread_count: %d, reader count: %d", g_storage_sync_thread_count, fc_list_count(&reader_head)); + + min_index = g_binlog_index; + fc_list_for_each_entry(pReader, &reader_head, link) + { + if (pReader->binlog_fd >= 0) + { + logInfo("storage_id: %s, binlog_fd: %d, binlog_index: %d, binlog_offset: %"PRId64, + pReader->storage_id, pReader->binlog_fd, + pReader->binlog_index, pReader->binlog_offset); + } + + if (pReader->binlog_fd >= 0 && pReader->binlog_index >= 0 && + pReader->binlog_index < min_index) + { + min_index = pReader->binlog_index; + } + } + pthread_mutex_unlock(&sync_thread_lock); + + return min_index; +} + +int fdfs_binlog_compress_func(void *args) +{ + char full_filename[MAX_PATH_SIZE]; + int until_index; + int bindex; + int result; + + if (binlog_compress_index >= g_binlog_index) + { + return 0; + } + + until_index = calc_compress_until_binlog_index(); + for (bindex = binlog_compress_index; bindex < until_index; + bindex++) + { + get_binlog_readable_filename_ex(bindex, full_filename); + result = compress_binlog_file(full_filename); + if (!(result == 0 || result == ENOENT)) + { + break; + } + + binlog_compress_index = bindex + 1; + write_to_binlog_index(g_binlog_index); + } + + return 0; +} diff --git a/storage/storage_sync.h b/storage/storage_sync.h index 6ca73f8..5cc10b6 100644 --- a/storage/storage_sync.h +++ b/storage/storage_sync.h @@ -11,6 +11,7 @@ #ifndef _STORAGE_SYNC_H_ #define _STORAGE_SYNC_H_ +#include "fastcommon/fc_list.h" #include "storage_func.h" #define STORAGE_OP_TYPE_SOURCE_CREATE_FILE 'C' //upload file @@ -37,6 +38,7 @@ extern "C" { typedef struct { + struct fc_list_head link; char storage_id[FDFS_STORAGE_ID_MAX_SIZE]; bool need_sync_old; bool sync_old_done; @@ -99,9 +101,15 @@ int storage_open_readable_binlog(StorageBinLogReader *pReader, \ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader); void storage_reader_destroy(StorageBinLogReader *pReader); -int storage_report_storage_status(const char *storage_id, \ +int storage_report_storage_status(const char *storage_id, const char *ip_addr, const char status); +int fdfs_binlog_compress_func(void *args); + +void storage_reader_add_to_list(StorageBinLogReader *pReader); + +void storage_reader_remove_from_list(StorageBinLogReader *pReader); + #ifdef __cplusplus } #endif diff --git a/tracker/tracker_relationship.c b/tracker/tracker_relationship.c index 60fc3f9..7f5721a 100644 --- a/tracker/tracker_relationship.c +++ b/tracker/tracker_relationship.c @@ -416,11 +416,11 @@ static int relationship_select_leader() { if (trackerStatus.if_leader) { - g_tracker_servers.leader_index = \ - trackerStatus.pTrackerServer - \ + g_tracker_servers.leader_index = + trackerStatus.pTrackerServer - g_tracker_servers.servers; - if (g_tracker_servers.leader_index < 0 || \ - g_tracker_servers.leader_index >= \ + if (g_tracker_servers.leader_index < 0 || + g_tracker_servers.leader_index >= g_tracker_servers.server_count) { logError("file: "__FILE__", line: %d, " @@ -429,12 +429,15 @@ static int relationship_select_leader() g_tracker_servers.leader_index = -1; return EINVAL; } + } + if (g_tracker_servers.leader_index >= 0) + { logInfo("file: "__FILE__", line: %d, " "the tracker leader %s:%d", __LINE__, conn->ip_addr, conn->port); - } - else + } + else { logInfo("file: "__FILE__", line: %d, " "waiting for the candidate tracker leader %s:%d notify ...", From 461e78ca306dbf6739d16afdccd997151a1d2ddd Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 23 Oct 2019 16:50:40 +0800 Subject: [PATCH 05/95] remove debug info --- storage/fdfs_storaged.c | 2 +- storage/storage_sync.c | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 1787646..db6e846 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -565,7 +565,7 @@ static int setupSchedules(pthread_t *schedule_tid) { INIT_SCHEDULE_ENTRY_EX(scheduleEntries[scheduleArray.count], scheduleArray.count + 1, g_compress_binlog_time, - 60, fdfs_binlog_compress_func, NULL); + 24 * 3600, fdfs_binlog_compress_func, NULL); scheduleArray.count++; } diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 3a0819c..706e641 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -1606,6 +1606,23 @@ static inline char *get_binlog_readable_filename(const void *pArg, full_filename); } +static void get_binlog_flag_file(const char *filepath, + char *flag_filename, const int size) +{ + const char *filename; + + filename = strrchr(filepath, '/'); + if (filename == NULL) + { + snprintf(flag_filename, size, ".%s.flag", filepath); + } + else + { + snprintf(flag_filename, size, "%.*s.%s.flag", + (int)(filename - filepath + 1), filepath, filename + 1); + } +} + static int uncompress_binlog_file(StorageBinLogReader *pReader, const char *filename) { @@ -1623,8 +1640,7 @@ static int uncompress_binlog_file(StorageBinLogReader *pReader, return errno != 0 ? errno : ENOENT; } - snprintf(flag_filename, sizeof(flag_filename), - "%s.flag", filename); + get_binlog_flag_file(filename, flag_filename, sizeof(flag_filename)); if (stat(flag_filename, &flag_stat) == 0) { if (g_current_time - flag_stat.st_mtime > 3600) @@ -1704,8 +1720,7 @@ static int compress_binlog_file(const char *filename) return errno != 0 ? errno : ENOENT; } - snprintf(flag_filename, sizeof(flag_filename), - "%s.flag", filename); + get_binlog_flag_file(filename, flag_filename, sizeof(flag_filename)); if (stat(flag_filename, &flag_stat) == 0) { if (g_current_time - flag_stat.st_mtime > 3600) @@ -3242,18 +3257,10 @@ static int calc_compress_until_binlog_index() int min_index; pthread_mutex_lock(&sync_thread_lock); - logInfo("g_storage_sync_thread_count: %d, reader count: %d", g_storage_sync_thread_count, fc_list_count(&reader_head)); min_index = g_binlog_index; fc_list_for_each_entry(pReader, &reader_head, link) { - if (pReader->binlog_fd >= 0) - { - logInfo("storage_id: %s, binlog_fd: %d, binlog_index: %d, binlog_offset: %"PRId64, - pReader->storage_id, pReader->binlog_fd, - pReader->binlog_index, pReader->binlog_offset); - } - if (pReader->binlog_fd >= 0 && pReader->binlog_index >= 0 && pReader->binlog_index < min_index) { From ecca01766a925b29af17848bfb68e3334d51506b Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 24 Oct 2019 10:59:59 +0800 Subject: [PATCH 06/95] small change for config files --- conf/storage.conf | 2 +- conf/tracker.conf | 4 ++-- storage/storage_disk_recovery.c | 7 +++---- storage/storage_func.c | 13 +++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/conf/storage.conf b/conf/storage.conf index 7c8aba9..c9713b9 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -175,7 +175,7 @@ sync_log_buff_interval=10 # sync binlog buff / cache to disk every interval seconds # default value is 60 seconds -sync_binlog_buff_interval=10 +sync_binlog_buff_interval=1 # sync storage stat info to disk every interval seconds # default value is 300 seconds diff --git a/conf/tracker.conf b/conf/tracker.conf index 5e28136..c2d645b 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -80,7 +80,7 @@ download_server=0 ### K or k for kilobyte(KB) ### no unit for byte(B) ### XX.XX% as ratio such as reserved_storage_space = 10% -reserved_storage_space = 10% +reserved_storage_space = 20% #standard log level as syslog, case insensitive, value list: ### emerg for emergency @@ -217,7 +217,7 @@ storage_ids_filename = storage_ids.conf # this paramter is valid only when use_storage_id set to true # default value is ip # since V4.03 -id_type_in_filename = ip +id_type_in_filename = id # if store slave file use symbol link # default value is false diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 6cd9a11..09ddd58 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -263,8 +263,7 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) if (pStorageStat->status == FDFS_STORAGE_STATUS_ACTIVE) { - strcpy(pSrcStorage->ip_addr, \ - pStorageStat->ip_addr); + strcpy(pSrcStorage->ip_addr, pStorageStat->ip_addr); pSrcStorage->port = pStorageStat->storage_port; break; } @@ -283,8 +282,8 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) return EINTR; } - logDebug("file: "__FILE__", line: %d, " \ - "disk recovery: get source storage server %s:%d", \ + logDebug("file: "__FILE__", line: %d, " + "disk recovery: get source storage server %s:%d", __LINE__, pSrcStorage->ip_addr, pSrcStorage->port); return 0; } diff --git a/storage/storage_func.c b/storage/storage_func.c index d11f4be..7da246e 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -883,8 +883,8 @@ static int storage_check_and_make_data_dirs() static int storage_make_data_dirs(const char *pBasePath, bool *pathCreated) { char data_path[MAX_PATH_SIZE]; - char dir_name[9]; - char sub_name[9]; + char dir_name[16]; + char sub_name[16]; char min_sub_path[16]; char max_sub_path[16]; int i, k; @@ -919,10 +919,11 @@ static int storage_make_data_dirs(const char *pBasePath, bool *pathCreated) return errno != 0 ? errno : ENOENT; } - sprintf(min_sub_path, FDFS_STORAGE_DATA_DIR_FORMAT"/"FDFS_STORAGE_DATA_DIR_FORMAT, - 0, 0); - sprintf(max_sub_path, FDFS_STORAGE_DATA_DIR_FORMAT"/"FDFS_STORAGE_DATA_DIR_FORMAT, - g_subdir_count_per_path-1, g_subdir_count_per_path-1); + sprintf(min_sub_path, FDFS_STORAGE_DATA_DIR_FORMAT"/" + FDFS_STORAGE_DATA_DIR_FORMAT, 0, 0); + sprintf(max_sub_path, FDFS_STORAGE_DATA_DIR_FORMAT"/" + FDFS_STORAGE_DATA_DIR_FORMAT, g_subdir_count_per_path - 1, + g_subdir_count_per_path - 1); if (fileExists(min_sub_path) && fileExists(max_sub_path)) { return 0; From 6b1f5e0cca3da7fe1e9be83edad75d8098d1434e Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 25 Oct 2019 11:58:20 +0800 Subject: [PATCH 07/95] bugfix: must check and create data path --- HISTORY | 4 +++- storage/fdfs_storaged.c | 5 +++++ storage/storage_func.c | 42 ++++++++++++++++++++++++++--------------- storage/storage_func.h | 2 ++ 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/HISTORY b/HISTORY index 0ccafe7..f77e853 100644 --- a/HISTORY +++ b/HISTORY @@ -1,7 +1,9 @@ -Version 6.01 2019-10-23 +Version 6.01 2019-10-25 * compress and uncompress binlog file by gzip when need, config items in storage.conf: compress_binlog and compress_binlog_time + * bugfix: must check and create data path before write_to_pid_file + in fdfs_storaged.c Version 6.00 2019-10-16 * tracker and storage server support dual IPs diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index db6e846..c2fc25b 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -107,6 +107,11 @@ int main(int argc, char *argv[]) return result; } + if ((result=storage_check_and_make_data_path()) != 0) + { + log_destroy(); + return result; + } snprintf(pidFilename, sizeof(pidFilename), "%s/data/fdfs_storaged.pid", g_fdfs_base_path); if ((result=process_action(pidFilename, argv[2], &stop)) != 0) diff --git a/storage/storage_func.c b/storage/storage_func.c index 7da246e..4229b15 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -684,6 +684,29 @@ int storage_write_to_sync_ini_file() return 0; } +int storage_check_and_make_data_path() +{ + char data_path[MAX_PATH_SIZE]; + snprintf(data_path, sizeof(data_path), "%s/data", + g_fdfs_base_path); + if (!fileExists(data_path)) + { + if (mkdir(data_path, 0755) != 0) + { + logError("file: "__FILE__", line: %d, " + "mkdir \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, data_path, + errno, STRERROR(errno)); + return errno != 0 ? errno : EPERM; + } + + STORAGE_CHOWN(data_path, geteuid(), getegid()) + } + + return 0; +} + static int storage_check_and_make_data_dirs() { int result; @@ -820,21 +843,10 @@ static int storage_check_and_make_data_dirs() } else { - if (!fileExists(data_path)) - { - if (mkdir(data_path, 0755) != 0) - { - logError("file: "__FILE__", line: %d, " \ - "mkdir \"%s\" fail, " \ - "errno: %d, error info: %s", \ - __LINE__, data_path, \ - errno, STRERROR(errno)); - return errno != 0 ? errno : EPERM; - } - - STORAGE_CHOWN(data_path, geteuid(), getegid()) - } - + if ((result=storage_check_and_make_data_path()) != 0) + { + return result; + } g_last_server_port = g_server_port; g_last_http_port = g_http_port; g_storage_join_time = g_current_time; diff --git a/storage/storage_func.h b/storage/storage_func.h index 1208498..5960ac4 100644 --- a/storage/storage_func.h +++ b/storage/storage_func.h @@ -37,6 +37,8 @@ bool storage_id_is_myself(const char *storage_id); int storage_set_tracker_client_ips(ConnectionInfo *conn, const int tracker_index); +int storage_check_and_make_data_path(); + #define STORAGE_CHOWN(path, current_uid, current_gid) \ if (!(g_run_by_gid == current_gid && g_run_by_uid == current_uid)) \ { \ From 1943f3d49aaa84fb51e94a54f4ff531a9ea3d574 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 25 Oct 2019 14:49:51 +0800 Subject: [PATCH 08/95] upgrade version to V6.0.1 --- fastdfs.spec | 2 +- storage/storage_nio.c | 39 +++++++++++++++++++++++++-------------- tracker/tracker_nio.c | 33 ++++++++++++++++++++++----------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/fastdfs.spec b/fastdfs.spec index 04268f2..7431efa 100644 --- a/fastdfs.spec +++ b/fastdfs.spec @@ -3,7 +3,7 @@ %define FDFSClient libfdfsclient %define FDFSClientDevel libfdfsclient-devel %define FDFSTool fastdfs-tool -%define FDFSVersion 6.0.0 +%define FDFSVersion 6.0.1 %define CommitVersion %(echo $COMMIT_VERSION) Name: %{FastDFS} diff --git a/storage/storage_nio.c b/storage/storage_nio.c index 466b4ad..aa8c58f 100644 --- a/storage/storage_nio.c +++ b/storage/storage_nio.c @@ -267,23 +267,34 @@ static void client_sock_read(int sock, short event, void *arg) if (event & IOEVENT_TIMEOUT) { - if (pClientInfo->total_offset == 0 && pTask->req_count > 0) + if (pClientInfo->total_offset == 0) { - pTask->event.timer.expires = g_current_time + - g_fdfs_network_timeout; - fast_timer_add(&pTask->thread_data->timer, - &pTask->event.timer); + if (pTask->req_count > 0) + { + pTask->event.timer.expires = g_current_time + + g_fdfs_network_timeout; + fast_timer_add(&pTask->thread_data->timer, + &pTask->event.timer); + } + else + { + logWarning("file: "__FILE__", line: %d, " + "client ip: %s, recv timeout. " + "after the connection is established, " + "you must send a request before %ds timeout", + __LINE__, pTask->client_ip, g_fdfs_network_timeout); + task_finish_clean_up(pTask); + } } else - { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, recv timeout, " \ - "recv offset: %d, expect length: %d", \ - __LINE__, pTask->client_ip, \ - pTask->offset, pTask->length); - - task_finish_clean_up(pTask); - } + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, recv timeout, " + "recv offset: %d, expect length: %d, " + "req_count: %"PRId64, __LINE__, pTask->client_ip, + pTask->offset, pTask->length, pTask->req_count); + task_finish_clean_up(pTask); + } return; } diff --git a/tracker/tracker_nio.c b/tracker/tracker_nio.c index 51b1f24..36a2a46 100644 --- a/tracker/tracker_nio.c +++ b/tracker/tracker_nio.c @@ -198,21 +198,32 @@ static void client_sock_read(int sock, short event, void *arg) if (event & IOEVENT_TIMEOUT) { - if (pTask->offset == 0 && pTask->req_count > 0) + if (pTask->offset == 0) { - pTask->event.timer.expires = g_current_time + - g_fdfs_network_timeout; - fast_timer_add(&pTask->thread_data->timer, - &pTask->event.timer); + if (pTask->req_count > 0) + { + pTask->event.timer.expires = g_current_time + + g_fdfs_network_timeout; + fast_timer_add(&pTask->thread_data->timer, + &pTask->event.timer); + } + else + { + logWarning("file: "__FILE__", line: %d, " + "client ip: %s, recv timeout. " + "after the connection is established, " + "you must send a request before %ds timeout", + __LINE__, pTask->client_ip, g_fdfs_network_timeout); + task_finish_clean_up(pTask); + } } else { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, recv timeout, " \ - "recv offset: %d, expect length: %d", \ - __LINE__, pTask->client_ip, \ - pTask->offset, pTask->length); - + logError("file: "__FILE__", line: %d, " + "client ip: %s, recv timeout, " + "recv offset: %d, expect length: %d, " + "req_count: %"PRId64, __LINE__, pTask->client_ip, + pTask->offset, pTask->length, pTask->req_count); task_finish_clean_up(pTask); } From fc8c6f8ebc42022ee34c9c09bcc4d420c389e386 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 25 Oct 2019 15:58:55 +0800 Subject: [PATCH 09/95] log more info when recv timeout --- storage/storage_nio.c | 3 ++- tracker/tracker_nio.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/storage/storage_nio.c b/storage/storage_nio.c index aa8c58f..911ff0a 100644 --- a/storage/storage_nio.c +++ b/storage/storage_nio.c @@ -281,7 +281,8 @@ static void client_sock_read(int sock, short event, void *arg) logWarning("file: "__FILE__", line: %d, " "client ip: %s, recv timeout. " "after the connection is established, " - "you must send a request before %ds timeout", + "you must send a request before %ds timeout, " + "maybe connections leak in you application.", __LINE__, pTask->client_ip, g_fdfs_network_timeout); task_finish_clean_up(pTask); } diff --git a/tracker/tracker_nio.c b/tracker/tracker_nio.c index 36a2a46..796843e 100644 --- a/tracker/tracker_nio.c +++ b/tracker/tracker_nio.c @@ -212,7 +212,8 @@ static void client_sock_read(int sock, short event, void *arg) logWarning("file: "__FILE__", line: %d, " "client ip: %s, recv timeout. " "after the connection is established, " - "you must send a request before %ds timeout", + "you must send a request before %ds timeout, " + "maybe connections leak in you application.", __LINE__, pTask->client_ip, g_fdfs_network_timeout); task_finish_clean_up(pTask); } From 0ed18812f21429008a5211db432bd33fe27ca09d Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 26 Oct 2019 09:57:10 +0800 Subject: [PATCH 10/95] add Wechat public account description --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c954f2e..36db3a0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ Copyright (C) 2008 Happy Fish / YuQing FastDFS may be copied only under the terms of the GNU General Public License V3, which may be found in the FastDFS source kit. Please visit the FastDFS Home Page for more detail. -English language: http://english.csource.org/ Chinese language: http://www.fastken.com/ @@ -42,3 +41,6 @@ The identification of a file is composed of two parts: the volume name and the file name. Client test code use client library please refer to the directory: client/test. + +For more FastDFS related articles, please subscribe the Wechat/Weixin public account +(Chinese Language): happyfish100 From 9cb1182776dd6ac03e55521e7e0a0591c2fcea26 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 26 Oct 2019 10:03:44 +0800 Subject: [PATCH 11/95] correct Wechat public account --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36db3a0..63db25f 100644 --- a/README.md +++ b/README.md @@ -43,4 +43,4 @@ the file name. Client test code use client library please refer to the directory: client/test. For more FastDFS related articles, please subscribe the Wechat/Weixin public account -(Chinese Language): happyfish100 +(Chinese Language): fastdfs From 1a546865aca3e74eaf5ee2b1e6b3195c618cebee Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 29 Oct 2019 21:25:22 +0800 Subject: [PATCH 12/95] client/test OK --- client/test/Makefile.in | 3 ++- client/test/fdfs_monitor.c | 28 ++++++++++++++++------------ client/test/fdfs_test.c | 4 ++-- client/test/fdfs_test1.c | 4 ++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/client/test/Makefile.in b/client/test/Makefile.in index 3910089..4a5a55e 100644 --- a/client/test/Makefile.in +++ b/client/test/Makefile.in @@ -1,7 +1,8 @@ .SUFFIXES: .c .o COMPILE = $(CC) $(CFLAGS) -INC_PATH = -I/usr/include/fastcommon -I/usr/include/fastdfs +INC_PATH = -I/usr/include/fastcommon -I/usr/include/fastdfs \ + -I/usr/local/include/fastcommon -I/usr/local/include/fastdfs LIB_PATH = -L/usr/local/lib -lfastcommon -lfdfsclient $(LIBS) TARGET_PATH = $(TARGET_PATH) diff --git a/client/test/fdfs_monitor.c b/client/test/fdfs_monitor.c index 7978b29..67afd9f 100644 --- a/client/test/fdfs_monitor.c +++ b/client/test/fdfs_monitor.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "fastcommon/sockopt.h" #include "fastcommon/logger.h" @@ -25,8 +26,11 @@ static int list_all_groups(const char *group_name); static void usage(char *argv[]) { - printf("Usage: %s [-h ] [list|delete|set_trunk_server " \ - "[storage_id]]\n", argv[0]); + printf("Usage: %s [-h ] " + "[list|delete|set_trunk_server [storage_id]]\n" + "\tthe tracker server format: host[:port], " + "the tracker default port is %d\n\n", + argv[0], FDFS_TRACKER_SERVER_DEF_PORT); } int main(int argc, char *argv[]) @@ -115,21 +119,20 @@ int main(int argc, char *argv[]) else { int i; - char ip_addr[IP_ADDRESS_SIZE]; + ConnectionInfo conn; - *ip_addr = '\0'; - if (getIpaddrByName(tracker_server, ip_addr, sizeof(ip_addr)) \ - == INADDR_NONE) + if ((result=conn_pool_parse_server_info(tracker_server, &conn, + FDFS_TRACKER_SERVER_DEF_PORT)) != 0) { - printf("resolve ip address of tracker server: %s " \ - "fail!\n", tracker_server); - return 2; + printf("resolve ip address of tracker server: %s " + "fail!, error info: %s\n", tracker_server, hstrerror(h_errno)); + return result; } for (i=0; i Date: Sun, 3 Nov 2019 21:14:09 +0800 Subject: [PATCH 13/95] set delay_seconds to 0 when delay_seconds < 0 --- client/fdfs_monitor.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/fdfs_monitor.c b/client/fdfs_monitor.c index 67afd9f..b0a45ca 100644 --- a/client/fdfs_monitor.c +++ b/client/fdfs_monitor.c @@ -352,8 +352,12 @@ static int list_storages(FDFSGroupStat *pGroupStat) int second; char szDelayTime[64]; - delay_seconds = (int)(max_last_source_update - \ + delay_seconds = (int)(max_last_source_update - pStorageStat->last_synced_timestamp); + if (delay_seconds < 0) + { + delay_seconds = 0; + } day = delay_seconds / (24 * 3600); remain_seconds = delay_seconds % (24 * 3600); hour = remain_seconds / 3600; From 57d2d815c6544a9cf704d665795fdcf676157a29 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 5 Nov 2019 20:30:54 +0800 Subject: [PATCH 14/95] add README_zh.md --- README_zh.md | 20 ++++++++++++++++++++ images/architect.png | Bin 0 -> 393687 bytes 2 files changed, 20 insertions(+) create mode 100644 README_zh.md create mode 100644 images/architect.png diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..95a434f --- /dev/null +++ b/README_zh.md @@ -0,0 +1,20 @@ + FastDFS是一款开源的分布式文件系统,功能主要包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了文件大容量存储和高性能访问的问题。FastDFS特别适合以文件为载体的在线服务,如图片、视频、文档等等。 + + FastDFS作为一款轻量级分布式文件系统,版本V6.01代码量6.3万行。FastDFS用C语言实现,支持Linux、FreeBSD、MacOS等类UNIX系统。FastDFS类似google FS,属于应用级文件系统,不是通用的文件系统,只能通过专有API访问,目前提供了C和Java SDK,以及PHP扩展SDK。 + + FastDFS为互联网应用量身定做,解决大容量文件存储问题,追求高性能和高扩展性。FastDFS可以看做是基于文件的key value存储系统,key为文件ID,value为文件内容,因此称作分布式文件存储服务更为合适。 + + FastDFS的架构比较简单,如下图所示: + ![architect](images/architect.png) + + FastDFS特点如下: + 1)分组存储,简单灵活; + 2)对等结构,不存在单点; + 3)文件ID由FastDFS生成,作为文件访问凭证。FastDFS不需要传统的name server或meta server; + 4)大、中、小文件均可以很好支持,可以存储海量小文件; + 5)一台storage支持多块磁盘,支持单盘数据恢复; + 6)提供了nginx扩展模块,可以和nginx无缝衔接; + 7)支持多线程方式上传和下载文件,支持断点续传; + 8)存储服务器上可以保存文件附加属性。 + + FastDFS更多更详细的功能和特性介绍,请参阅FastDFS微信公众号的其他文章,搜索公众号:fastdfs。 diff --git a/images/architect.png b/images/architect.png new file mode 100644 index 0000000000000000000000000000000000000000..b7c40a38cd1f758798490f482713793adf8d5039 GIT binary patch literal 393687 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

}RIKGQp<)ZTe7L3wjdNyeh8(hA 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�>3B6f{*OP#>n0##JevY~oyHGF>c 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

?@^(A$k^;-aP{{|3?##T|duwf$Lwb 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 HG5PjCgr~`V literal 0 HcmV?d00001 From cdb180ae324693df7aef875b0abe90dcda80d013 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 6 Nov 2019 19:33:19 +0800 Subject: [PATCH 15/95] get_file_info calculate CRC32 for appender file type --- HISTORY | 3 + storage/storage_dio.c | 21 +++- storage/storage_nio.h | 3 + storage/storage_service.c | 245 +++++++++++++++++++++++++++++--------- storage/storage_service.h | 2 +- 5 files changed, 212 insertions(+), 62 deletions(-) diff --git a/HISTORY b/HISTORY index f77e853..1b88095 100644 --- a/HISTORY +++ b/HISTORY @@ -1,4 +1,7 @@ +Version 6.02 2019-11-06 + * get_file_info calculate CRC32 for appender file type + Version 6.01 2019-10-25 * compress and uncompress binlog file by gzip when need, config items in storage.conf: compress_binlog and compress_binlog_time diff --git a/storage/storage_dio.c b/storage/storage_dio.c index 5936c17..90caa76 100644 --- a/storage/storage_dio.c +++ b/storage/storage_dio.c @@ -144,12 +144,12 @@ void storage_dio_terminate() int storage_dio_queue_push(struct fast_task_info *pTask) { - StorageClientInfo *pClientInfo; + StorageClientInfo *pClientInfo; StorageFileContext *pFileContext; struct storage_dio_context *pContext; int result; - pClientInfo = (StorageClientInfo *)pTask->arg; + pClientInfo = (StorageClientInfo *)pTask->arg; pFileContext = &(pClientInfo->file_context); pContext = g_dio_contexts + pFileContext->dio_thread_index; @@ -245,7 +245,7 @@ int dio_discard_file(struct fast_task_info *pTask) else { pFileContext->buff_offset = 0; - storage_nio_notify(pTask); //notify nio to deal + pFileContext->continue_callback(pTask); } return 0; @@ -353,6 +353,12 @@ int dio_read_file(struct fast_task_info *pTask) break; } + if (pFileContext->calc_crc32) + { + pFileContext->crc32 = CRC32_ex(pTask->data + pTask->length, + read_bytes, pFileContext->crc32); + } + pTask->length += read_bytes; pFileContext->offset += read_bytes; @@ -363,7 +369,7 @@ int dio_read_file(struct fast_task_info *pTask) if (pFileContext->offset < pFileContext->end) { - storage_nio_notify(pTask); //notify nio to deal + pFileContext->continue_callback(pTask); } else { @@ -371,6 +377,11 @@ int dio_read_file(struct fast_task_info *pTask) close(pFileContext->fd); pFileContext->fd = -1; + if (pFileContext->calc_crc32) + { + pFileContext->crc32 = CRC32_FINAL( \ + pFileContext->crc32); + } pFileContext->done_callback(pTask, result); } @@ -475,7 +486,7 @@ int dio_write_file(struct fast_task_info *pTask) if (pFileContext->offset < pFileContext->end) { pFileContext->buff_offset = 0; - storage_nio_notify(pTask); //notify nio to deal + pFileContext->continue_callback(pTask); } else { diff --git a/storage/storage_nio.h b/storage/storage_nio.h index 79094a5..d33870f 100644 --- a/storage/storage_nio.h +++ b/storage/storage_nio.h @@ -46,6 +46,8 @@ typedef void (*DeleteFileLogCallback)(struct fast_task_info *pTask, \ typedef void (*FileDealDoneCallback)(struct fast_task_info *pTask, \ const int err_no); +typedef int (*FileDealContinueCallback)(struct fast_task_info *pTask); + typedef int (*FileBeforeOpenCallback)(struct fast_task_info *pTask); typedef int (*FileBeforeCloseCallback)(struct fast_task_info *pTask); @@ -109,6 +111,7 @@ typedef struct int64_t start; //the start offset of file int64_t end; //the end offset of file int64_t offset; //the current offset of file + FileDealContinueCallback continue_callback; FileDealDoneCallback done_callback; DeleteFileLogCallback log_callback; diff --git a/storage/storage_service.c b/storage/storage_service.c index 4a24202..77e6173 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -28,6 +28,7 @@ #include "fastcommon/shared_func.h" #include "fastcommon/pthread_func.h" #include "fastcommon/sched_thread.h" +#include "fastcommon/fast_mblock.h" #include "tracker_types.h" #include "tracker_proto.h" #include "storage_service.h" @@ -59,6 +60,12 @@ #define ACCESS_LOG_ACTION_TRUNCATE_FILE "truncate" #define ACCESS_LOG_ACTION_QUERY_FILE "status" +typedef struct +{ + int storage_id; + time_t mtime; + int64_t fsize; +} StorageFileInfoForCRC32; pthread_mutex_t g_storage_thread_lock; int g_storage_thread_count = 0; @@ -69,6 +76,8 @@ static int64_t temp_file_sequence = 0; static pthread_mutex_t path_index_thread_lock; static pthread_mutex_t stat_count_thread_lock; +static struct fast_mblock_man finfo_for_crc32_allocator; + static void *work_thread_entrance(void* arg); extern int storage_client_create_link(ConnectionInfo *pTrackerServer, \ @@ -1708,6 +1717,9 @@ int storage_service_init() return result; } + result = fast_mblock_init(&finfo_for_crc32_allocator, + sizeof(StorageFileInfoForCRC32), 1024); + return result; } @@ -1901,7 +1913,7 @@ void storage_accept_loop(int server_sock) accept_thread_entrance((void *)(long)server_sock); } -void storage_nio_notify(struct fast_task_info *pTask) +int storage_nio_notify(struct fast_task_info *pTask) { StorageClientInfo *pClientInfo; struct storage_nio_thread_data *pThreadData; @@ -1922,6 +1934,8 @@ void storage_nio_notify(struct fast_task_info *pTask) __LINE__, result, STRERROR(result)); abort(); } + + return 0; } static void *work_thread_entrance(void* arg) @@ -2775,6 +2789,7 @@ static int storage_trunk_do_create_link(struct fast_task_info *pTask, \ pTask, pFileContext->extra_info.upload.trunk_info.path. \ store_path_index, pFileContext->op); + pFileContext->continue_callback = storage_nio_notify; pFileContext->done_callback = done_callback; pClientInfo->clean_func = dio_trunk_write_finish_clean_up; @@ -3432,6 +3447,153 @@ static int storage_server_trunk_sync_binlog(struct fast_task_info *pTask) return trunk_binlog_write_buffer(binlog_buff, nInPackLen); } +static int query_file_info_response(struct fast_task_info *pTask, + const StorageFileInfoForCRC32 *finfo, const int crc32) +{ + char *p; + + p = pTask->data + sizeof(TrackerHeader); + long2buff(finfo->fsize, p); + p += FDFS_PROTO_PKG_LEN_SIZE; + long2buff(finfo->mtime, p); + p += FDFS_PROTO_PKG_LEN_SIZE; + long2buff(crc32, p); + p += FDFS_PROTO_PKG_LEN_SIZE; + + memset(p, 0, IP_ADDRESS_SIZE); + if (fdfs_get_server_id_type(finfo->storage_id) == FDFS_ID_TYPE_SERVER_ID) + { + if (g_use_storage_id) + { + FDFSStorageIdInfo *pStorageIdInfo; + char id[16]; + + sprintf(id, "%d", finfo->storage_id); + pStorageIdInfo = fdfs_get_storage_by_id(id); + if (pStorageIdInfo != NULL) + { + strcpy(p, fdfs_get_ipaddr_by_peer_ip( + &pStorageIdInfo->ip_addrs, + pTask->client_ip)); + } + } + } + else + { + struct in_addr ip_addr; + memset(&ip_addr, 0, sizeof(ip_addr)); + ip_addr.s_addr = finfo->storage_id; + inet_ntop(AF_INET, &ip_addr, p, IP_ADDRESS_SIZE); + } + p += IP_ADDRESS_SIZE; + + ((StorageClientInfo *)pTask->arg)->total_length = p - pTask->data; + return 0; +} + +static void calc_crc32_done_callback(struct fast_task_info *pTask, + const int err_no) +{ + StorageClientInfo *pClientInfo; + StorageFileInfoForCRC32 *crc32_file_info; + StorageFileContext *pFileContext; + TrackerHeader *pHeader; + int result; + + pClientInfo = (StorageClientInfo *)pTask->arg; + pFileContext = &(pClientInfo->file_context); + crc32_file_info = (StorageFileInfoForCRC32 *)pClientInfo->extra_arg; + + if (err_no == 0) + { + result = query_file_info_response(pTask, crc32_file_info, + pFileContext->crc32); + } + else + { + result = err_no; + pClientInfo->total_length = sizeof(TrackerHeader); + } + + fast_mblock_free_object(&finfo_for_crc32_allocator, crc32_file_info); + + pClientInfo->total_offset = 0; + pTask->length = pClientInfo->total_length; + + pHeader = (TrackerHeader *)pTask->data; + pHeader->status = result; + pHeader->cmd = STORAGE_PROTO_CMD_RESP; + long2buff(pTask->length - sizeof(TrackerHeader), pHeader->pkg_len); + + storage_nio_notify(pTask); +} + +static int calc_crc32_continue_callback(struct fast_task_info *pTask) +{ + pTask->length = 0; + return storage_dio_queue_push(pTask); +} + +static int query_file_info_deal_response(struct fast_task_info *pTask, + const char *filename, const char *true_filename, + struct stat *file_stat, const int store_path_index) +{ + char decode_buff[64]; + int buff_len; + int storage_id; + int crc32; + int64_t file_size; + StorageFileInfoForCRC32 finfo; + StorageFileInfoForCRC32 *crc32_file_info; + + memset(decode_buff, 0, sizeof(decode_buff)); + base64_decode_auto(&g_fdfs_base64_context, filename + + FDFS_LOGIC_FILE_PATH_LEN, FDFS_FILENAME_BASE64_LENGTH, + decode_buff, &buff_len); + storage_id = ntohl(buff2int(decode_buff)); + file_size = buff2long(decode_buff + sizeof(int) * 2); + if (IS_APPENDER_FILE(file_size)) + { + StorageClientInfo *pClientInfo; + StorageFileContext *pFileContext; + + pClientInfo = (StorageClientInfo *)pTask->arg; + pFileContext = &(pClientInfo->file_context); + + crc32_file_info = (StorageFileInfoForCRC32 *)fast_mblock_alloc_object( + &finfo_for_crc32_allocator); + if (crc32_file_info == NULL) + { + logError("file: "__FILE__", line: %d, " + "finfo_for_crc32_allocator %d bytes object fail", + __LINE__, (int)sizeof(StorageFileInfoForCRC32)); + return errno != 0 ? errno : ENOMEM; + } + crc32_file_info->storage_id = storage_id; + crc32_file_info->fsize = file_stat->st_size; + crc32_file_info->mtime = file_stat->st_mtime; + + snprintf(pFileContext->fname2log, sizeof(pFileContext->fname2log), + "%s", filename); + snprintf(pFileContext->filename, sizeof(pFileContext->filename), + "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], + true_filename); + pClientInfo->extra_arg = crc32_file_info; + + pFileContext->fd = -1; + pFileContext->calc_crc32 = true; + pFileContext->continue_callback = calc_crc32_continue_callback; + return storage_read_from_file(pTask, 0, file_stat->st_size, + calc_crc32_done_callback, store_path_index); + } + + finfo.storage_id = storage_id; + finfo.fsize = file_stat->st_size; + finfo.mtime = file_stat->st_mtime; + crc32 = buff2int(decode_buff + sizeof(int) * 4); + return query_file_info_response(pTask, &finfo, crc32); +} + /** FDFS_GROUP_NAME_MAX_LEN bytes: group_name filename @@ -3441,11 +3603,9 @@ static int storage_server_query_file_info(struct fast_task_info *pTask) StorageClientInfo *pClientInfo; char *in_buff; char *filename; - char *p; char group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; char true_filename[128]; char src_filename[MAX_PATH_SIZE + 128]; - char decode_buff[64]; struct stat file_lstat; struct stat file_stat; FDFSTrunkFullInfo trunkInfo; @@ -3454,11 +3614,8 @@ static int storage_server_query_file_info(struct fast_task_info *pTask) int store_path_index; int filename_len; int true_filename_len; - int crc32; - int storage_id; int result; int len; - int buff_len; bool bSilence; pClientInfo = (StorageClientInfo *)pTask->arg; @@ -3597,10 +3754,9 @@ static int storage_server_query_file_info(struct fast_task_info *pTask) } else { - char full_filename[MAX_PATH_SIZE + 128]; - - sprintf(full_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], \ + char full_filename[MAX_PATH_SIZE + 128]; + sprintf(full_filename, "%s/data/%s", + g_fdfs_store_paths.paths[store_path_index], true_filename); if ((len=readlink(full_filename, src_filename, \ sizeof(src_filename))) < 0) @@ -3624,6 +3780,8 @@ static int storage_server_query_file_info(struct fast_task_info *pTask) return result; } } + + file_stat.st_mtime = file_lstat.st_mtime; } else { @@ -3641,50 +3799,8 @@ static int storage_server_query_file_info(struct fast_task_info *pTask) return EINVAL; } - memset(decode_buff, 0, sizeof(decode_buff)); - base64_decode_auto(&g_fdfs_base64_context, filename + \ - FDFS_LOGIC_FILE_PATH_LEN, FDFS_FILENAME_BASE64_LENGTH, \ - decode_buff, &buff_len); - storage_id = ntohl(buff2int(decode_buff)); - crc32 = buff2int(decode_buff + sizeof(int) * 4); - - p = pTask->data + sizeof(TrackerHeader); - long2buff(file_stat.st_size, p); - p += FDFS_PROTO_PKG_LEN_SIZE; - long2buff(file_lstat.st_mtime, p); - p += FDFS_PROTO_PKG_LEN_SIZE; - long2buff(crc32, p); - p += FDFS_PROTO_PKG_LEN_SIZE; - - memset(p, 0, IP_ADDRESS_SIZE); - if (fdfs_get_server_id_type(storage_id) == FDFS_ID_TYPE_SERVER_ID) - { - if (g_use_storage_id) - { - FDFSStorageIdInfo *pStorageIdInfo; - char id[16]; - - sprintf(id, "%d", storage_id); - pStorageIdInfo = fdfs_get_storage_by_id(id); - if (pStorageIdInfo != NULL) - { - strcpy(p, fdfs_get_ipaddr_by_peer_ip( - &pStorageIdInfo->ip_addrs, - pTask->client_ip)); - } - } - } - else - { - struct in_addr ip_addr; - memset(&ip_addr, 0, sizeof(ip_addr)); - ip_addr.s_addr = storage_id; - inet_ntop(AF_INET, &ip_addr, p, IP_ADDRESS_SIZE); - } - p += IP_ADDRESS_SIZE; - - pClientInfo->total_length = p - pTask->data; - return 0; + return query_file_info_deal_response(pTask, filename, + true_filename, &file_stat, store_path_index); } #define CHECK_TRUNK_SERVER(pTask) \ @@ -4563,8 +4679,9 @@ static int storage_upload_file(struct fast_task_info *pTask, bool bAppenderFile) | g_extra_open_file_flags; } - return storage_write_to_file(pTask, file_offset, file_bytes, \ - p - pTask->data, dio_write_file, \ + pFileContext->continue_callback = storage_nio_notify; + return storage_write_to_file(pTask, file_offset, file_bytes, \ + p - pTask->data, dio_write_file, \ storage_upload_file_done_callback, \ clean_func, store_path_index); } @@ -4756,6 +4873,7 @@ static int storage_append_file(struct fast_task_info *pTask) store_path_index; pFileContext->op = FDFS_STORAGE_FILE_OP_APPEND; pFileContext->open_flags = O_WRONLY | O_APPEND | g_extra_open_file_flags; + pFileContext->continue_callback = storage_nio_notify; return storage_write_to_file(pTask, stat_buf.st_size, file_bytes, \ p - pTask->data, dio_write_file, \ @@ -4951,6 +5069,7 @@ static int storage_modify_file(struct fast_task_info *pTask) store_path_index; pFileContext->op = FDFS_STORAGE_FILE_OP_WRITE; pFileContext->open_flags = O_WRONLY | g_extra_open_file_flags; + pFileContext->continue_callback = storage_nio_notify; return storage_write_to_file(pTask, file_offset, file_bytes, \ p - pTask->data, dio_write_file, \ @@ -5134,6 +5253,7 @@ static int storage_do_truncate_file(struct fast_task_info *pTask) store_path_index; pFileContext->op = FDFS_STORAGE_FILE_OP_WRITE; pFileContext->open_flags = O_WRONLY | g_extra_open_file_flags; + pFileContext->continue_callback = storage_nio_notify; return storage_write_to_file(pTask, remain_bytes, \ stat_buf.st_size, 0, dio_truncate_file, \ @@ -5368,6 +5488,7 @@ static int storage_upload_slave_file(struct fast_task_info *pTask) pFileContext->op = FDFS_STORAGE_FILE_OP_WRITE; pFileContext->open_flags = O_WRONLY | O_CREAT | O_TRUNC \ | g_extra_open_file_flags; + pFileContext->continue_callback = storage_nio_notify; return storage_write_to_file(pTask, 0, file_bytes, p - pTask->data, \ dio_write_file, storage_upload_file_done_callback, \ @@ -5680,6 +5801,7 @@ static int storage_sync_copy_file(struct fast_task_info *pTask, \ pFileContext->calc_crc32 = false; pFileContext->calc_file_hash = false; strcpy(pFileContext->fname2log, filename); + pFileContext->continue_callback = storage_nio_notify; if (have_file_content) { @@ -5915,6 +6037,7 @@ static int storage_sync_append_file(struct fast_task_info *pTask) pFileContext->calc_file_hash = false; pFileContext->extra_info.upload.before_open_callback = NULL; pFileContext->extra_info.upload.before_close_callback = NULL; + pFileContext->continue_callback = storage_nio_notify; return storage_write_to_file(pTask, start_offset, append_bytes, \ p - pTask->data, deal_func, \ @@ -6116,6 +6239,7 @@ static int storage_sync_modify_file(struct fast_task_info *pTask) pFileContext->calc_file_hash = false; pFileContext->extra_info.upload.before_open_callback = NULL; pFileContext->extra_info.upload.before_close_callback = NULL; + pFileContext->continue_callback = storage_nio_notify; return storage_write_to_file(pTask, start_offset, modify_bytes, \ p - pTask->data, deal_func, \ @@ -6301,6 +6425,7 @@ static int storage_sync_truncate_file(struct fast_task_info *pTask) pFileContext->calc_file_hash = false; pFileContext->extra_info.upload.before_open_callback = NULL; pFileContext->extra_info.upload.before_close_callback = NULL; + pFileContext->continue_callback = storage_nio_notify; return storage_write_to_file(pTask, new_file_size, \ old_file_size, 0, dio_truncate_file, \ @@ -6761,6 +6886,8 @@ static int storage_server_get_metadata(struct fast_task_info *pTask) } pFileContext->fd = -1; + pFileContext->calc_crc32 = false; + pFileContext->continue_callback = storage_nio_notify; return storage_read_from_file(pTask, 0, file_bytes, \ storage_get_metadata_done_callback, store_path_index); } @@ -6941,6 +7068,8 @@ static int storage_server_download_file(struct fast_task_info *pTask) g_fdfs_store_paths.paths[store_path_index], true_filename); } + pFileContext->calc_crc32 = false; + pFileContext->continue_callback = storage_nio_notify; return storage_read_from_file(pTask, file_offset, download_bytes, \ storage_download_file_done_callback, store_path_index); } @@ -7006,6 +7135,10 @@ static int storage_read_from_file(struct fast_task_info *pTask, \ pHeader->cmd = STORAGE_PROTO_CMD_RESP; long2buff(download_bytes, pHeader->pkg_len); + if (pFileContext->calc_crc32) + { + pFileContext->crc32 = CRC32_XINIT; + } if ((result=storage_dio_queue_push(pTask)) != 0) { if (pFileContext->fd >= 0) diff --git a/storage/storage_service.h b/storage/storage_service.h index fb896b0..ea96ce5 100644 --- a/storage/storage_service.h +++ b/storage/storage_service.h @@ -36,7 +36,7 @@ void storage_service_destroy(); int fdfs_stat_file_sync_func(void *args); int storage_deal_task(struct fast_task_info *pTask); -void storage_nio_notify(struct fast_task_info *pTask); +int storage_nio_notify(struct fast_task_info *pTask); void storage_accept_loop(int server_sock); int storage_terminate_threads(); From 9f0a914c933d1134be6d472d22b7388f04ceaa24 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 8 Nov 2019 20:39:46 +0800 Subject: [PATCH 16/95] recovery download file to local temp file then rename --- HISTORY | 4 +- storage/storage_disk_recovery.c | 153 +++++++++++++++++++++----------- 2 files changed, 102 insertions(+), 55 deletions(-) diff --git a/HISTORY b/HISTORY index 1b88095..3c9faf5 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,8 @@ -Version 6.02 2019-11-06 +Version 6.02 2019-11-07 * get_file_info calculate CRC32 for appender file type + * disk recovery download file to local temp file then rename it + when the local file exists Version 6.01 2019-10-25 * compress and uncompress binlog file by gzip when need, diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 09ddd58..b36724a 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -510,19 +510,97 @@ static int recovery_reader_check_init(const char *pBasePath, \ return recovery_reader_init(pBasePath, pReader); } +static int recovery_download_file_to_local(StorageBinLogRecord *pRecord, + ConnectionInfo *pTrackerServer, ConnectionInfo *pStorageConn) +{ + int result; + bool bTrunkFile; + char local_filename[MAX_PATH_SIZE]; + char tmp_filename[MAX_PATH_SIZE + 32]; + char *download_filename; + int64_t file_size; + + if (fdfs_is_trunk_file(pRecord->filename, pRecord->filename_len)) + { + FDFSTrunkFullInfo trunk_info; + char *pTrunkPathEnd; + char *pLocalFilename; + + bTrunkFile = true; + if (fdfs_decode_trunk_info(pRecord->store_path_index, + pRecord->true_filename, pRecord->true_filename_len, + &trunk_info) != 0) + { + return -EINVAL; + } + + trunk_get_full_filename(&trunk_info, + local_filename, sizeof(local_filename)); + + pTrunkPathEnd = strrchr(pRecord->filename, '/'); + pLocalFilename = strrchr(local_filename, '/'); + if (pTrunkPathEnd == NULL || pLocalFilename == NULL) + { + return -EINVAL; + } + sprintf(pTrunkPathEnd + 1, "%s", pLocalFilename + 1); + } + else + { + bTrunkFile = false; + sprintf(local_filename, "%s/data/%s", + g_fdfs_store_paths.paths[pRecord->store_path_index], + pRecord->true_filename); + } + + if (access(local_filename, F_OK) == 0) + { + sprintf(tmp_filename, "%s.recovery.tmp", local_filename); + download_filename = tmp_filename; + } + else + { + download_filename = local_filename; + } + + result = storage_download_file_to_file(pTrackerServer, + pStorageConn, g_group_name, pRecord->filename, + download_filename, &file_size); + if (result == 0) + { + if (download_filename != local_filename) + { + if (rename(download_filename, local_filename) != 0) + { + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", __LINE__, + download_filename, local_filename, + errno, STRERROR(errno)); + return errno != 0 ? errno : EPERM; + } + } + if (!bTrunkFile) + { + set_file_utimes(local_filename, pRecord->timestamp); + } + } + + return result; +} + static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pReader, \ ConnectionInfo *pSrcStorage) { + TrackerServerInfo trackerServer; ConnectionInfo *pTrackerServer; ConnectionInfo *pStorageConn; - FDFSTrunkFullInfo trunk_info; StorageBinLogRecord record; int record_length; int result; int log_level; int count; int store_path_index; - int64_t file_size; int64_t total_count; int64_t success_count; int64_t noent_count; @@ -530,15 +608,23 @@ static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pRead char local_filename[MAX_PATH_SIZE]; char src_filename[MAX_PATH_SIZE]; - pTrackerServer = g_tracker_group.servers->connections; //TODO: fix me !!! + pTrackerServer = tracker_get_connection_r(&trackerServer, &result); + if (pTrackerServer == NULL) + { + logError("file: "__FILE__", line: %d, " + "get tracker connection fail, result: %d", + __LINE__, result); + return result; + } + count = 0; total_count = 0; success_count = 0; noent_count = 0; result = 0; - logInfo("file: "__FILE__", line: %d, " \ - "disk recovery: recovering files of data path: %s ...", \ + logInfo("file: "__FILE__", line: %d, " + "disk recovery: recovering files of data path: %s ...", __LINE__, pBasePath); bContinueFlag = true; @@ -571,59 +657,16 @@ static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pRead if (record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_FILE || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_FILE) { - bool bTrunkFile; - - if (fdfs_is_trunk_file(record.filename, \ - record.filename_len)) - { - char *pTrunkPathEnd; - char *pLocalFilename; - - bTrunkFile = true; - if (fdfs_decode_trunk_info(record.store_path_index, \ - record.true_filename, record.true_filename_len,\ - &trunk_info) != 0) - { - pReader->binlog_offset += record_length; - count++; - continue; - } - - trunk_get_full_filename(&trunk_info, \ - local_filename, sizeof(local_filename)); - - pTrunkPathEnd = strrchr(record.filename, '/'); - pLocalFilename = strrchr(local_filename, '/'); - if (pTrunkPathEnd == NULL || pLocalFilename == NULL) - { - pReader->binlog_offset += record_length; - count++; - continue; - } - sprintf(pTrunkPathEnd + 1, "%s", pLocalFilename + 1); - } - else - { - bTrunkFile = false; - sprintf(local_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[record.store_path_index], \ - record.true_filename); - } - - result = storage_download_file_to_file(pTrackerServer, \ - pStorageConn, g_group_name, \ - record.filename, local_filename, \ - &file_size); + result = recovery_download_file_to_local(&record, + pTrackerServer, pStorageConn); if (result == 0) { - if (!bTrunkFile) - { - set_file_utimes(local_filename, \ - record.timestamp); - } - success_count++; } + else if (result == -EINVAL) + { + result = 0; + } else if (result == ENOENT) { result = 0; @@ -751,6 +794,8 @@ static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pRead } } + tracker_close_connection_ex(pTrackerServer, true); + if (result == 0) { logInfo("file: "__FILE__", line: %d, " \ From 9c0bbce9df40ec5f9b4f075db55009f92361877f Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 10 Nov 2019 20:38:36 +0800 Subject: [PATCH 17/95] support regenerate filename for appender file --- HISTORY | 4 +- client/Makefile.in | 2 +- client/client_func.h | 2 + client/fdfs_file_info.c | 22 +- client/fdfs_regenerate_filename.c | 68 ++++ client/storage_client.c | 147 +++++++- client/storage_client.h | 17 + client/storage_client1.h | 16 + common/fdfs_global.c | 2 +- storage/storage_service.c | 556 +++++++++++++++++++----------- storage/storage_sync.h | 30 +- tracker/tracker_proto.h | 12 +- tracker/tracker_types.h | 4 + 13 files changed, 652 insertions(+), 230 deletions(-) create mode 100644 client/fdfs_regenerate_filename.c diff --git a/HISTORY b/HISTORY index 3c9faf5..0bde129 100644 --- a/HISTORY +++ b/HISTORY @@ -1,8 +1,10 @@ -Version 6.02 2019-11-07 +Version 6.02 2019-11-10 * get_file_info calculate CRC32 for appender file type * disk recovery download file to local temp file then rename it when the local file exists + * support regenerate filename for appender file + NOTE: the regenerated file will be a normal file! Version 6.01 2019-10-25 * compress and uncompress binlog file by gzip when need, diff --git a/client/Makefile.in b/client/Makefile.in index 859b8b8..346ca44 100644 --- a/client/Makefile.in +++ b/client/Makefile.in @@ -39,7 +39,7 @@ ALL_OBJS = $(STATIC_OBJS) $(FDFS_SHARED_OBJS) ALL_PRGS = fdfs_monitor fdfs_test fdfs_test1 fdfs_crc32 fdfs_upload_file \ fdfs_download_file fdfs_delete_file fdfs_file_info \ fdfs_appender_test fdfs_appender_test1 fdfs_append_file \ - fdfs_upload_appender + fdfs_upload_appender fdfs_regenerate_filename STATIC_LIBS = libfdfsclient.a diff --git a/client/client_func.h b/client/client_func.h index 01c338c..f3f2dfd 100644 --- a/client/client_func.h +++ b/client/client_func.h @@ -16,6 +16,8 @@ #define _CLIENT_FUNC_H_ typedef struct { + short file_type; + bool get_from_server; time_t create_timestamp; int crc32; int source_id; //source storage id diff --git a/client/fdfs_file_info.c b/client/fdfs_file_info.c index 2e153ad..050a6c2 100644 --- a/client/fdfs_file_info.c +++ b/client/fdfs_file_info.c @@ -19,6 +19,7 @@ int main(int argc, char *argv[]) { char *conf_filename; + const char *file_type_str; char file_id[128]; int result; FDFSFileInfo file_info; @@ -44,7 +45,7 @@ int main(int argc, char *argv[]) result = fdfs_get_file_info_ex1(file_id, true, &file_info); if (result != 0) { - printf("query file info fail, " \ + fprintf(stderr, "query file info fail, " \ "error no: %d, error info: %s\n", \ result, STRERROR(result)); } @@ -52,6 +53,25 @@ int main(int argc, char *argv[]) { char szDatetime[32]; + switch (file_info.file_type) + { + case FDFS_FILE_TYPE_NORMAL: + file_type_str = "normal"; + break; + case FDFS_FILE_TYPE_SLAVE: + file_type_str = "slave"; + break; + case FDFS_FILE_TYPE_APPENDER: + file_type_str = "appender"; + break; + default: + file_type_str = "unkown"; + break; + } + + printf("GET FROM SERVER: %s\n\n", + file_info.get_from_server ? "true" : "false"); + printf("file type: %s\n", file_type_str); printf("source storage id: %d\n", file_info.source_id); printf("source ip address: %s\n", file_info.source_ip_addr); printf("file create timestamp: %s\n", formatDatetime( diff --git a/client/fdfs_regenerate_filename.c b/client/fdfs_regenerate_filename.c new file mode 100644 index 0000000..ef07462 --- /dev/null +++ b/client/fdfs_regenerate_filename.c @@ -0,0 +1,68 @@ +/** +* Copyright (C) 2008 Happy Fish / YuQing +* +* FastDFS may be copied only under the terms of the GNU General +* Public License V3, which may be found in the FastDFS source kit. +* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +**/ + +#include +#include +#include +#include +#include +#include +#include +#include "fdfs_client.h" +#include "fastcommon/logger.h" + +int main(int argc, char *argv[]) +{ + char *conf_filename; + ConnectionInfo *pTrackerServer; + int result; + char appender_file_id[128]; + char new_file_id[128]; + + if (argc < 3) + { + fprintf(stderr, "regenerate filename for the appender file.\n" + "NOTE: the regenerated file will be a normal file!\n" + "Usage: %s \n", + argv[0]); + return 1; + } + + log_init(); + g_log_context.log_level = LOG_ERR; + + conf_filename = argv[1]; + if ((result=fdfs_client_init(conf_filename)) != 0) + { + return result; + } + + pTrackerServer = tracker_get_connection(); + if (pTrackerServer == NULL) + { + fdfs_client_destroy(); + return errno != 0 ? errno : ECONNREFUSED; + } + + snprintf(appender_file_id, sizeof(appender_file_id), "%s", argv[2]); + if ((result=storage_regenerate_appender_filename1(pTrackerServer, + NULL, appender_file_id, new_file_id)) != 0) + { + fprintf(stderr, "regenerate file %s fail, " + "error no: %d, error info: %s\n", + appender_file_id, result, STRERROR(result)); + return result; + } + + printf("%s\n", new_file_id); + + tracker_close_connection_ex(pTrackerServer, true); + fdfs_client_destroy(); + + return result; +} diff --git a/client/storage_client.c b/client/storage_client.c index ce7912a..37d5710 100644 --- a/client/storage_client.c +++ b/client/storage_client.c @@ -35,20 +35,20 @@ static struct base64_context the_base64_context; static int the_base64_context_inited = 0; #define FDFS_SPLIT_GROUP_NAME_AND_FILENAME(file_id) \ - char new_file_id[FDFS_GROUP_NAME_MAX_LEN + 128]; \ + char in_file_id[FDFS_GROUP_NAME_MAX_LEN + 128]; \ char *group_name; \ char *filename; \ char *pSeperator; \ \ - snprintf(new_file_id, sizeof(new_file_id), "%s", file_id); \ - pSeperator = strchr(new_file_id, FDFS_FILE_ID_SEPERATOR); \ + snprintf(in_file_id, sizeof(in_file_id), "%s", file_id); \ + pSeperator = strchr(in_file_id, FDFS_FILE_ID_SEPERATOR); \ if (pSeperator == NULL) \ { \ return EINVAL; \ } \ \ *pSeperator = '\0'; \ - group_name = new_file_id; \ + group_name = in_file_id; \ filename = pSeperator + 1; \ #define storage_get_read_connection(pTrackerServer, \ @@ -2203,8 +2203,21 @@ int fdfs_get_file_info_ex(const char *group_name, const char *remote_filename, \ pFileInfo->create_timestamp = buff2int(buff + sizeof(int)); pFileInfo->file_size = buff2long(buff + sizeof(int) * 2); - if (IS_SLAVE_FILE(filename_len, pFileInfo->file_size) || \ - IS_APPENDER_FILE(pFileInfo->file_size) || \ + if (IS_APPENDER_FILE(pFileInfo->file_size)) + { + pFileInfo->file_type = FDFS_FILE_TYPE_APPENDER; + } + else if (IS_SLAVE_FILE(filename_len, pFileInfo->file_size)) + { + pFileInfo->file_type = FDFS_FILE_TYPE_SLAVE; + } + else + { + pFileInfo->file_type = FDFS_FILE_TYPE_NORMAL; + } + + if (pFileInfo->file_type == FDFS_FILE_TYPE_SLAVE || + pFileInfo->file_type == FDFS_FILE_TYPE_APPENDER || (*(pFileInfo->source_ip_addr) == '\0' && get_from_server)) { //slave file or appender file if (get_from_server) @@ -2218,21 +2231,24 @@ int fdfs_get_file_info_ex(const char *group_name, const char *remote_filename, \ return result; } - result = storage_query_file_info(conn, \ + result = storage_query_file_info(conn, NULL, group_name, remote_filename, pFileInfo); - tracker_close_connection_ex(conn, result != 0 && \ + tracker_close_connection_ex(conn, result != 0 && result != ENOENT); + pFileInfo->get_from_server = true; return result; } else { + pFileInfo->get_from_server = false; pFileInfo->file_size = -1; return 0; } } else //master file (normal file) { + pFileInfo->get_from_server = false; if ((pFileInfo->file_size >> 63) != 0) { pFileInfo->file_size &= 0xFFFFFFFF; //low 32 bits is file size @@ -2352,3 +2368,118 @@ int storage_truncate_file(ConnectionInfo *pTrackerServer, \ return result; } +int storage_regenerate_appender_filename(ConnectionInfo *pTrackerServer, + ConnectionInfo *pStorageServer, const char *group_name, + const char *appender_filename, char *new_group_name, + char *new_remote_filename) +{ + TrackerHeader *pHeader; + int result; + char out_buff[512]; + char in_buff[256]; + char *p; + char *pInBuff; + int64_t in_bytes; + ConnectionInfo storageServer; + bool new_connection; + int appender_filename_len; + + appender_filename_len = strlen(appender_filename); + if ((result=storage_get_update_connection(pTrackerServer, + &pStorageServer, group_name, appender_filename, + &storageServer, &new_connection)) != 0) + { + return result; + } + + do + { + pHeader = (TrackerHeader *)out_buff; + p = out_buff + sizeof(TrackerHeader); + + snprintf(p, sizeof(out_buff) - sizeof(TrackerHeader), + "%s", group_name); + p += FDFS_GROUP_NAME_MAX_LEN; + memcpy(p, appender_filename, appender_filename_len); + p += appender_filename_len; + + long2buff((p - out_buff) - sizeof(TrackerHeader), + pHeader->pkg_len); + pHeader->cmd = STORAGE_PROTO_CMD_REGENERATE_APPENDER_FILENAME; + pHeader->status = 0; + + if ((result=tcpsenddata_nb(pStorageServer->sock, out_buff, + p - out_buff, g_fdfs_network_timeout)) != 0) + { + logError("file: "__FILE__", line: %d, " + "send data to storage server %s:%d fail, " + "errno: %d, error info: %s", __LINE__, + pStorageServer->ip_addr, pStorageServer->port, + result, STRERROR(result)); + break; + } + + pInBuff = in_buff; + if ((result=fdfs_recv_response(pStorageServer, + &pInBuff, sizeof(in_buff), &in_bytes)) != 0) + { + logError("file: "__FILE__", line: %d, " + "fdfs_recv_response fail, result: %d", + __LINE__, result); + break; + } + + if (in_bytes <= FDFS_GROUP_NAME_MAX_LEN) + { + logError("file: "__FILE__", line: %d, " + "storage server %s:%d response data " + "length: %"PRId64" is invalid, " + "should > %d", __LINE__, + pStorageServer->ip_addr, pStorageServer->port, + in_bytes, FDFS_GROUP_NAME_MAX_LEN); + result = EINVAL; + break; + } + + in_buff[in_bytes] = '\0'; + memcpy(new_group_name, in_buff, FDFS_GROUP_NAME_MAX_LEN); + new_group_name[FDFS_GROUP_NAME_MAX_LEN] = '\0'; + + memcpy(new_remote_filename, in_buff + FDFS_GROUP_NAME_MAX_LEN, + in_bytes - FDFS_GROUP_NAME_MAX_LEN + 1); + + } while (0); + + if (new_connection) + { + tracker_close_connection_ex(pStorageServer, result != 0); + } + + return result; +} + +int storage_regenerate_appender_filename1(ConnectionInfo *pTrackerServer, + ConnectionInfo *pStorageServer, const char *appender_file_id, + char *new_file_id) +{ + int result; + char new_group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; + char new_remote_filename[128]; + + FDFS_SPLIT_GROUP_NAME_AND_FILENAME(appender_file_id) + + result = storage_regenerate_appender_filename(pTrackerServer, + pStorageServer, group_name, filename, + new_group_name, new_remote_filename); + if (result == 0) + { + sprintf(new_file_id, "%s%c%s", new_group_name, + FDFS_FILE_ID_SEPERATOR, new_remote_filename); + } + else + { + new_file_id[0] = '\0'; + } + + return result; +} diff --git a/client/storage_client.h b/client/storage_client.h index 12489a9..fd4849e 100644 --- a/client/storage_client.h +++ b/client/storage_client.h @@ -563,6 +563,23 @@ int fdfs_get_file_info_ex(const char *group_name, const char *remote_filename, \ const bool get_from_server, FDFSFileInfo *pFileInfo); +/** +* regenerate normal filename for appender file +* Note: the appender file will change to normal file +* params: +* pTrackerServer: the tracker server +* pStorageServer: the storage server +* group_name: the group name +* appender_filename: the appender filename +* new_group_name: return the new group name +* new_remote_filename: return the new filename +* return: 0 success, !=0 fail, return the error code +**/ +int storage_regenerate_appender_filename(ConnectionInfo *pTrackerServer, + ConnectionInfo *pStorageServer, const char *group_name, + const char *appender_filename, char *new_group_name, + char *new_remote_filename); + #ifdef __cplusplus } #endif diff --git a/client/storage_client1.h b/client/storage_client1.h index 63b5e4c..7a4bf77 100644 --- a/client/storage_client1.h +++ b/client/storage_client1.h @@ -524,6 +524,22 @@ int fdfs_get_file_info_ex1(const char *file_id, const bool get_from_server, \ int storage_file_exist1(ConnectionInfo *pTrackerServer, \ ConnectionInfo *pStorageServer, \ const char *file_id); + +/** +* regenerate normal filename for appender file +* Note: the appender file will change to normal file +* params: +* pTrackerServer: the tracker server +* pStorageServer: the storage server +* group_name: the group name +* appender_file_id: the appender file id +* file_id: regenerated file id return by storage server +* return: 0 success, !=0 fail, return the error code +**/ +int storage_regenerate_appender_filename1(ConnectionInfo *pTrackerServer, + ConnectionInfo *pStorageServer, const char *appender_file_id, + char *new_file_id); + #ifdef __cplusplus } #endif diff --git a/common/fdfs_global.c b/common/fdfs_global.c index e33dab4..9963451 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -23,7 +23,7 @@ int g_fdfs_connect_timeout = DEFAULT_CONNECT_TIMEOUT; int g_fdfs_network_timeout = DEFAULT_NETWORK_TIMEOUT; char g_fdfs_base_path[MAX_PATH_SIZE] = {'/', 't', 'm', 'p', '\0'}; -Version g_fdfs_version = {6, 1}; +Version g_fdfs_version = {6, 2}; bool g_use_connection_pool = false; ConnectionPool g_connection_pool; int g_connection_pool_max_idle_time = 3600; diff --git a/storage/storage_service.c b/storage/storage_service.c index 77e6173..bd49875 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -59,6 +59,7 @@ #define ACCESS_LOG_ACTION_APPEND_FILE "append" #define ACCESS_LOG_ACTION_TRUNCATE_FILE "truncate" #define ACCESS_LOG_ACTION_QUERY_FILE "status" +#define ACCESS_LOG_ACTION_RENAME_FILE "rename" typedef struct { @@ -2111,9 +2112,9 @@ void storage_get_store_path(const char *filename, const int filename_len, \ } while (0) -static int storage_gen_filename(StorageClientInfo *pClientInfo, \ - const int64_t file_size, const int crc32, \ - const char *szFormattedExt, const int ext_name_len, \ +static int storage_gen_filename(StorageClientInfo *pClientInfo, + const int64_t file_size, const int crc32, + const char *szFormattedExt, const int ext_name_len, const time_t timestamp, char *filename, int *filename_len) { char buff[sizeof(int) * 5]; @@ -2143,18 +2144,18 @@ static int storage_gen_filename(StorageClientInfo *pClientInfo, \ { int sub_path_high; int sub_path_low; - storage_get_store_path(encoded, *filename_len, \ + storage_get_store_path(encoded, *filename_len, &sub_path_high, &sub_path_low); pTrunkInfo->path.sub_path_high = sub_path_high; pTrunkInfo->path.sub_path_low = sub_path_low; - pClientInfo->file_context.extra_info.upload. \ + pClientInfo->file_context.extra_info.upload. if_sub_path_alloced = true; } - len = sprintf(filename, FDFS_STORAGE_DATA_DIR_FORMAT"/" \ - FDFS_STORAGE_DATA_DIR_FORMAT"/", \ + len = sprintf(filename, FDFS_STORAGE_DATA_DIR_FORMAT"/" + FDFS_STORAGE_DATA_DIR_FORMAT"/", pTrunkInfo->path.sub_path_high, pTrunkInfo->path.sub_path_low); memcpy(filename+len, encoded, *filename_len); @@ -2187,7 +2188,7 @@ static int storage_sort_metadata_buff(char *meta_buff, const int meta_size) return 0; } -static void storage_format_ext_name(const char *file_ext_name, \ +static void storage_format_ext_name(const char *file_ext_name, char *szFormattedExt) { int i; @@ -2220,9 +2221,9 @@ static void storage_format_ext_name(const char *file_ext_name, \ *p = '\0'; } -static int storage_get_filename(StorageClientInfo *pClientInfo, \ - const int start_time, const int64_t file_size, const int crc32, \ - const char *szFormattedExt, char *filename, \ +static int storage_get_filename(StorageClientInfo *pClientInfo, + const int start_time, const int64_t file_size, const int crc32, + const char *szFormattedExt, char *filename, int *filename_len, char *full_filename) { int i; @@ -2233,14 +2234,14 @@ static int storage_get_filename(StorageClientInfo *pClientInfo, \ trunk_info.path.store_path_index; for (i=0; i<10; i++) { - if ((result=storage_gen_filename(pClientInfo, file_size, \ - crc32, szFormattedExt, FDFS_FILE_EXT_NAME_MAX_LEN+1, \ + if ((result=storage_gen_filename(pClientInfo, file_size, + crc32, szFormattedExt, FDFS_FILE_EXT_NAME_MAX_LEN + 1, start_time, filename, filename_len)) != 0) { return result; } - sprintf(full_filename, "%s/data/%s", \ + sprintf(full_filename, "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], filename); if (!fileExists(full_filename)) { @@ -2252,7 +2253,7 @@ static int storage_get_filename(StorageClientInfo *pClientInfo, \ if (*full_filename == '\0') { - logError("file: "__FILE__", line: %d, " \ + logError("file: "__FILE__", line: %d, " "Can't generate uniq filename", __LINE__); *filename = '\0'; *filename_len = 0; @@ -3491,8 +3492,8 @@ static int query_file_info_response(struct fast_task_info *pTask, return 0; } -static void calc_crc32_done_callback(struct fast_task_info *pTask, - const int err_no) +static void calc_crc32_done_callback_for_query_finfo( + struct fast_task_info *pTask, const int err_no) { StorageClientInfo *pClientInfo; StorageFileInfoForCRC32 *crc32_file_info; @@ -3525,6 +3526,7 @@ static void calc_crc32_done_callback(struct fast_task_info *pTask, pHeader->cmd = STORAGE_PROTO_CMD_RESP; long2buff(pTask->length - sizeof(TrackerHeader), pHeader->pkg_len); + STORAGE_ACCESS_LOG(pTask, ACCESS_LOG_ACTION_QUERY_FILE, result); storage_nio_notify(pTask); } @@ -3534,6 +3536,39 @@ static int calc_crc32_continue_callback(struct fast_task_info *pTask) return storage_dio_queue_push(pTask); } +static int push_calc_crc32_to_dio_queue(struct fast_task_info *pTask, + FileDealDoneCallback done_callback, const int store_path_index, + const struct stat *file_stat, const int storage_id) +{ + StorageClientInfo *pClientInfo; + StorageFileContext *pFileContext; + StorageFileInfoForCRC32 *crc32_file_info; + + pClientInfo = (StorageClientInfo *)pTask->arg; + pFileContext = &(pClientInfo->file_context); + + crc32_file_info = (StorageFileInfoForCRC32 *)fast_mblock_alloc_object( + &finfo_for_crc32_allocator); + if (crc32_file_info == NULL) + { + logError("file: "__FILE__", line: %d, " + "finfo_for_crc32_allocator %d bytes object fail", + __LINE__, (int)sizeof(StorageFileInfoForCRC32)); + return errno != 0 ? errno : ENOMEM; + } + + crc32_file_info->storage_id = storage_id; + crc32_file_info->fsize = file_stat->st_size; + crc32_file_info->mtime = file_stat->st_mtime; + pClientInfo->extra_arg = crc32_file_info; + + pFileContext->fd = -1; + pFileContext->calc_crc32 = true; + pFileContext->continue_callback = calc_crc32_continue_callback; + return storage_read_from_file(pTask, 0, file_stat->st_size, + done_callback, store_path_index); +} + static int query_file_info_deal_response(struct fast_task_info *pTask, const char *filename, const char *true_filename, struct stat *file_stat, const int store_path_index) @@ -3544,7 +3579,6 @@ static int query_file_info_deal_response(struct fast_task_info *pTask, int crc32; int64_t file_size; StorageFileInfoForCRC32 finfo; - StorageFileInfoForCRC32 *crc32_file_info; memset(decode_buff, 0, sizeof(decode_buff)); base64_decode_auto(&g_fdfs_base64_context, filename + @@ -3560,31 +3594,15 @@ static int query_file_info_deal_response(struct fast_task_info *pTask, pClientInfo = (StorageClientInfo *)pTask->arg; pFileContext = &(pClientInfo->file_context); - crc32_file_info = (StorageFileInfoForCRC32 *)fast_mblock_alloc_object( - &finfo_for_crc32_allocator); - if (crc32_file_info == NULL) - { - logError("file: "__FILE__", line: %d, " - "finfo_for_crc32_allocator %d bytes object fail", - __LINE__, (int)sizeof(StorageFileInfoForCRC32)); - return errno != 0 ? errno : ENOMEM; - } - crc32_file_info->storage_id = storage_id; - crc32_file_info->fsize = file_stat->st_size; - crc32_file_info->mtime = file_stat->st_mtime; - snprintf(pFileContext->fname2log, sizeof(pFileContext->fname2log), "%s", filename); snprintf(pFileContext->filename, sizeof(pFileContext->filename), "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], true_filename); - pClientInfo->extra_arg = crc32_file_info; - pFileContext->fd = -1; - pFileContext->calc_crc32 = true; - pFileContext->continue_callback = calc_crc32_continue_callback; - return storage_read_from_file(pTask, 0, file_stat->st_size, - calc_crc32_done_callback, store_path_index); + return push_calc_crc32_to_dio_queue(pTask, + calc_crc32_done_callback_for_query_finfo, + store_path_index, file_stat, storage_id); } finfo.storage_id = storage_id; @@ -4708,6 +4726,274 @@ static int storage_deal_active_test(struct fast_task_info *pTask) return 0; } +static int storage_server_check_appender_file(struct fast_task_info *pTask, + char *appender_filename, const int appender_filename_len, + char *true_filename, struct stat *stat_buf, int *store_path_index) +{ + StorageFileContext *pFileContext; + char decode_buff[64]; + int result; + int filename_len; + int buff_len; + int64_t appender_file_size; + + pFileContext = &(((StorageClientInfo *)pTask->arg)->file_context); + filename_len = appender_filename_len; + if ((result=storage_split_filename_ex(appender_filename, + &filename_len, true_filename, store_path_index)) != 0) + { + return result; + } + if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) + { + return result; + } + + snprintf(pFileContext->filename, sizeof(pFileContext->filename), + "%s/data/%s", g_fdfs_store_paths.paths[*store_path_index], + true_filename); + if (lstat(pFileContext->filename, stat_buf) == 0) + { + if (!S_ISREG(stat_buf->st_mode)) + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, appender file: %s " + "is not a regular file", __LINE__, + pTask->client_ip, pFileContext->filename); + + return EINVAL; + } + } + else + { + result = errno != 0 ? errno : ENOENT; + if (result == ENOENT) + { + logWarning("file: "__FILE__", line: %d, " + "client ip: %s, appender file: %s " + "not exist", __LINE__, pTask->client_ip, + pFileContext->filename); + } + else + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, stat appednder file %s fail, " + "errno: %d, error info: %s", + __LINE__, pTask->client_ip, + pFileContext->filename, result, STRERROR(result)); + } + + return result; + } + + strcpy(pFileContext->fname2log, appender_filename); + + memset(decode_buff, 0, sizeof(decode_buff)); + base64_decode_auto(&g_fdfs_base64_context, pFileContext->fname2log + + FDFS_LOGIC_FILE_PATH_LEN, FDFS_FILENAME_BASE64_LENGTH, + decode_buff, &buff_len); + + appender_file_size = buff2long(decode_buff + sizeof(int) * 2); + if (!IS_APPENDER_FILE(appender_file_size)) + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, file: %s is not a valid " + "appender file, file size: %"PRId64, + __LINE__, pTask->client_ip, appender_filename, + appender_file_size); + + return EINVAL; + } + + return 0; +} + +static void calc_crc32_done_callback_for_regenerate( + struct fast_task_info *pTask, const int err_no) +{ + StorageClientInfo *pClientInfo; + StorageFileInfoForCRC32 *crc32_file_info; + StorageFileContext *pFileContext; + TrackerHeader *pHeader; + char full_filename[MAX_PATH_SIZE + 128]; + int result; + + pClientInfo = (StorageClientInfo *)pTask->arg; + pFileContext = &(pClientInfo->file_context); + crc32_file_info = (StorageFileInfoForCRC32 *)pClientInfo->extra_arg; + pHeader = (TrackerHeader *)pTask->data; + + if (err_no == 0) + { + char *formatted_ext_name; + char return_filename[128]; + char filename[128]; + int filename_len; + + *filename = '\0'; + filename_len = 0; + formatted_ext_name = pFileContext->filename + + (strlen(pFileContext->filename) - + (FDFS_FILE_EXT_NAME_MAX_LEN + 1)); + + pFileContext->timestamp2log = g_current_time; + if ((result=storage_get_filename(pClientInfo, + pFileContext->timestamp2log, + crc32_file_info->fsize, pFileContext->crc32, + formatted_ext_name, filename, &filename_len, + full_filename)) == 0) + { + if (*full_filename == '\0') + { + result = EBUSY; + logWarning("file: "__FILE__", line: %d, " + "Can't generate uniq filename", __LINE__); + } + else + { + if (rename(pFileContext->filename, full_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + logWarning("file: "__FILE__", line: %d, " + "rename %s to %s fail, " + "errno: %d, error info: %s", __LINE__, + pFileContext->filename, full_filename, + result, STRERROR(result)); + } + else + { + char *p; + char binlog_msg[256]; + + filename_len = snprintf(return_filename, + sizeof(return_filename), + "%c"FDFS_STORAGE_DATA_DIR_FORMAT"/%s", + FDFS_STORAGE_STORE_PATH_PREFIX_CHAR, + pFileContext->extra_info.upload.trunk_info. + path.store_path_index, filename); + + sprintf(binlog_msg, "%s %s", + pFileContext->fname2log, return_filename); + result = storage_binlog_write( + pFileContext->timestamp2log, + STORAGE_OP_TYPE_SOURCE_RENAME_FILE, + binlog_msg); + + pClientInfo->total_length = sizeof(TrackerHeader) + + FDFS_GROUP_NAME_MAX_LEN + filename_len; + p = pTask->data + sizeof(TrackerHeader); + memcpy(p, g_group_name, FDFS_GROUP_NAME_MAX_LEN); + p += FDFS_GROUP_NAME_MAX_LEN; + memcpy(p, return_filename, filename_len); + + if (g_use_access_log) + { + snprintf(pFileContext->fname2log + + strlen(pFileContext->fname2log), + sizeof(pFileContext->fname2log), + "=>%s", return_filename); + } + } + } + } + } + else + { + result = err_no; + } + + fast_mblock_free_object(&finfo_for_crc32_allocator, crc32_file_info); + if (result != 0) + { + pClientInfo->total_length = sizeof(TrackerHeader); + } + + pClientInfo->total_offset = 0; + pTask->length = pClientInfo->total_length; + + pHeader->status = result; + pHeader->cmd = STORAGE_PROTO_CMD_RESP; + long2buff(pTask->length - sizeof(TrackerHeader), pHeader->pkg_len); + + STORAGE_ACCESS_LOG(pTask, ACCESS_LOG_ACTION_RENAME_FILE, result); + storage_nio_notify(pTask); +} + +/** +FDFS_GROUP_NAME_MAX_LEN: group name +body length - FDFS_GROUP_NAME_MAX_LEN: appender filename +**/ +static int storage_server_regenerate_appender_filename(struct fast_task_info *pTask) +{ + StorageClientInfo *pClientInfo; + StorageFileContext *pFileContext; + FDFSTrunkFullInfo *pTrunkInfo; + char *p; + char group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; + char appender_filename[128]; + char true_filename[128]; + struct stat stat_buf; + int appender_filename_len; + int64_t nInPackLen; + int result; + int store_path_index; + + pClientInfo = (StorageClientInfo *)pTask->arg; + pFileContext = &(pClientInfo->file_context); + + nInPackLen = pClientInfo->total_length - sizeof(TrackerHeader); + appender_filename_len = nInPackLen - FDFS_GROUP_NAME_MAX_LEN; + if ((nInPackLen < FDFS_GROUP_NAME_MAX_LEN + + FDFS_LOGIC_FILE_PATH_LEN + FDFS_FILENAME_BASE64_LENGTH + + FDFS_FILE_EXT_NAME_MAX_LEN + 1) + || (appender_filename_len >= sizeof(appender_filename))) + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, invalid package length: %"PRId64, + __LINE__, pTask->client_ip, nInPackLen); + return EINVAL; + } + + p = pTask->data + sizeof(TrackerHeader); + memcpy(group_name, p, FDFS_GROUP_NAME_MAX_LEN); + *(group_name + FDFS_GROUP_NAME_MAX_LEN) = '\0'; + if (strcmp(group_name, g_group_name) != 0) + { + logError("file: "__FILE__", line: %d, " + "client ip:%s, group_name: %s " + "not correct, should be: %s", + __LINE__, pTask->client_ip, + group_name, g_group_name); + return EINVAL; + } + + p += FDFS_GROUP_NAME_MAX_LEN; + memcpy(appender_filename, p, appender_filename_len); + *(appender_filename + appender_filename_len) = '\0'; + + STORAGE_ACCESS_STRCPY_FNAME2LOG(appender_filename, + appender_filename_len, pClientInfo); + + if ((result=storage_server_check_appender_file(pTask, + appender_filename, appender_filename_len, + true_filename, &stat_buf, &store_path_index)) != 0) + { + return result; + } + + pTrunkInfo = &(pClientInfo->file_context.extra_info.upload.trunk_info); + pClientInfo->file_context.extra_info.upload.if_sub_path_alloced = true; + pFileContext->extra_info.upload.trunk_info.path.store_path_index = + store_path_index; + pTrunkInfo->path.sub_path_high = strtol(true_filename, NULL, 16); + pTrunkInfo->path.sub_path_low = strtol(true_filename + 3, NULL, 16); + + return push_calc_crc32_to_dio_queue(pTask, + calc_crc32_done_callback_for_regenerate, + store_path_index, &stat_buf, + g_server_id_in_filename); +} + /** 8 bytes: appender filename length 8 bytes: file size @@ -4721,16 +5007,12 @@ static int storage_append_file(struct fast_task_info *pTask) char *p; char appender_filename[128]; char true_filename[128]; - char decode_buff[64]; struct stat stat_buf; int appender_filename_len; int64_t nInPackLen; int64_t file_bytes; - int64_t appender_file_size; int result; int store_path_index; - int filename_len; - int buff_len; pClientInfo = (StorageClientInfo *)pTask->arg; pFileContext = &(pClientInfo->file_context); @@ -4778,76 +5060,16 @@ static int storage_append_file(struct fast_task_info *pTask) memcpy(appender_filename, p, appender_filename_len); *(appender_filename + appender_filename_len) = '\0'; p += appender_filename_len; - filename_len = appender_filename_len; - STORAGE_ACCESS_STRCPY_FNAME2LOG(appender_filename, \ + STORAGE_ACCESS_STRCPY_FNAME2LOG(appender_filename, appender_filename_len, pClientInfo); - if ((result=storage_split_filename_ex(appender_filename, \ - &filename_len, true_filename, &store_path_index)) != 0) - { - return result; - } - if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) - { - return result; - } - - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], true_filename); - if (lstat(pFileContext->filename, &stat_buf) == 0) - { - if (!S_ISREG(stat_buf.st_mode)) - { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, appender file: %s " \ - "is not a regular file", __LINE__, \ - pTask->client_ip, pFileContext->filename); - - return EINVAL; - } - } - else - { - result = errno != 0 ? errno : ENOENT; - if (result == ENOENT) - { - logWarning("file: "__FILE__", line: %d, " \ - "client ip: %s, appender file: %s " \ - "not exist", __LINE__, \ - pTask->client_ip, pFileContext->filename); - } - else - { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, stat appednder file %s fail" \ - ", errno: %d, error info: %s.", \ - __LINE__, pTask->client_ip, \ - pFileContext->filename, \ - result, STRERROR(result)); - } - - return result; - } - - strcpy(pFileContext->fname2log, appender_filename); - - memset(decode_buff, 0, sizeof(decode_buff)); - base64_decode_auto(&g_fdfs_base64_context, pFileContext->fname2log + \ - FDFS_LOGIC_FILE_PATH_LEN, FDFS_FILENAME_BASE64_LENGTH, \ - decode_buff, &buff_len); - - appender_file_size = buff2long(decode_buff + sizeof(int) * 2); - if (!IS_APPENDER_FILE(appender_file_size)) - { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, file: %s is not a valid " \ - "appender file, file size: %"PRId64, \ - __LINE__, pTask->client_ip, appender_filename, \ - appender_file_size); - - return EINVAL; - } + if ((result=storage_server_check_appender_file(pTask, + appender_filename, appender_filename_len, + true_filename, &stat_buf, &store_path_index)) != 0) + { + return result; + } if (file_bytes == 0) { @@ -4860,24 +5082,20 @@ static int storage_append_file(struct fast_task_info *pTask) pFileContext->calc_crc32 = false; pFileContext->calc_file_hash = false; - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], \ - true_filename); - pFileContext->sync_flag = STORAGE_OP_TYPE_SOURCE_APPEND_FILE; pFileContext->timestamp2log = pFileContext->extra_info.upload.start_time; pFileContext->extra_info.upload.file_type = _FILE_TYPE_APPENDER; pFileContext->extra_info.upload.before_open_callback = NULL; pFileContext->extra_info.upload.before_close_callback = NULL; - pFileContext->extra_info.upload.trunk_info.path.store_path_index = \ + pFileContext->extra_info.upload.trunk_info.path.store_path_index = store_path_index; pFileContext->op = FDFS_STORAGE_FILE_OP_APPEND; pFileContext->open_flags = O_WRONLY | O_APPEND | g_extra_open_file_flags; pFileContext->continue_callback = storage_nio_notify; - return storage_write_to_file(pTask, stat_buf.st_size, file_bytes, \ - p - pTask->data, dio_write_file, \ - storage_append_file_done_callback, \ + return storage_write_to_file(pTask, stat_buf.st_size, file_bytes, + p - pTask->data, dio_write_file, + storage_append_file_done_callback, dio_append_finish_clean_up, store_path_index); } @@ -4895,17 +5113,13 @@ static int storage_modify_file(struct fast_task_info *pTask) char *p; char appender_filename[128]; char true_filename[128]; - char decode_buff[64]; struct stat stat_buf; int appender_filename_len; int64_t nInPackLen; int64_t file_offset; int64_t file_bytes; - int64_t appender_file_size; int result; int store_path_index; - int filename_len; - int buff_len; pClientInfo = (StorageClientInfo *)pTask->arg; pFileContext = &(pClientInfo->file_context); @@ -4964,83 +5178,23 @@ static int storage_modify_file(struct fast_task_info *pTask) memcpy(appender_filename, p, appender_filename_len); *(appender_filename + appender_filename_len) = '\0'; p += appender_filename_len; - filename_len = appender_filename_len; STORAGE_ACCESS_STRCPY_FNAME2LOG(appender_filename, \ appender_filename_len, pClientInfo); - if ((result=storage_split_filename_ex(appender_filename, \ - &filename_len, true_filename, &store_path_index)) != 0) - { - return result; - } - if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) - { - return result; - } - - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], true_filename); - if (lstat(pFileContext->filename, &stat_buf) == 0) - { - if (!S_ISREG(stat_buf.st_mode)) - { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, appender file: %s " \ - "is not a regular file", __LINE__, \ - pTask->client_ip, pFileContext->filename); - - return EINVAL; - } - } - else - { - result = errno != 0 ? errno : ENOENT; - if (result == ENOENT) - { - logWarning("file: "__FILE__", line: %d, " \ - "client ip: %s, appender file: %s " \ - "not exist", __LINE__, \ - pTask->client_ip, pFileContext->filename); - } - else - { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, stat appednder file %s fail" \ - ", errno: %d, error info: %s.", \ - __LINE__, pTask->client_ip, \ - pFileContext->filename, \ - result, STRERROR(result)); - } - - return result; - } - - strcpy(pFileContext->fname2log, appender_filename); - - memset(decode_buff, 0, sizeof(decode_buff)); - base64_decode_auto(&g_fdfs_base64_context, pFileContext->fname2log + \ - FDFS_LOGIC_FILE_PATH_LEN, FDFS_FILENAME_BASE64_LENGTH, \ - decode_buff, &buff_len); - - appender_file_size = buff2long(decode_buff + sizeof(int) * 2); - if (!IS_APPENDER_FILE(appender_file_size)) - { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, file: %s is not a valid " \ - "appender file, file size: %"PRId64, \ - __LINE__, pTask->client_ip, appender_filename, \ - appender_file_size); - - return EINVAL; - } + if ((result=storage_server_check_appender_file(pTask, + appender_filename, appender_filename_len, + true_filename, &stat_buf, &store_path_index)) != 0) + { + return result; + } if (file_offset > stat_buf.st_size) { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, file offset: %"PRId64 \ - " is invalid, which > appender file size: %" \ - PRId64, __LINE__, pTask->client_ip, \ + logError("file: "__FILE__", line: %d, " + "client ip: %s, file offset: %"PRId64 + " is invalid, which > appender file size: %" + PRId64, __LINE__, pTask->client_ip, file_offset, (int64_t)stat_buf.st_size); return EINVAL; @@ -5057,23 +5211,20 @@ static int storage_modify_file(struct fast_task_info *pTask) pFileContext->calc_crc32 = false; pFileContext->calc_file_hash = false; - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], true_filename); - pFileContext->sync_flag = STORAGE_OP_TYPE_SOURCE_MODIFY_FILE; pFileContext->timestamp2log = pFileContext->extra_info.upload.start_time; pFileContext->extra_info.upload.file_type = _FILE_TYPE_APPENDER; pFileContext->extra_info.upload.before_open_callback = NULL; pFileContext->extra_info.upload.before_close_callback = NULL; - pFileContext->extra_info.upload.trunk_info.path.store_path_index = \ + pFileContext->extra_info.upload.trunk_info.path.store_path_index = store_path_index; pFileContext->op = FDFS_STORAGE_FILE_OP_WRITE; pFileContext->open_flags = O_WRONLY | g_extra_open_file_flags; pFileContext->continue_callback = storage_nio_notify; - return storage_write_to_file(pTask, file_offset, file_bytes, \ - p - pTask->data, dio_write_file, \ - storage_modify_file_done_callback, \ + return storage_write_to_file(pTask, file_offset, file_bytes, + p - pTask->data, dio_write_file, + storage_modify_file_done_callback, dio_modify_finish_clean_up, store_path_index); } @@ -8137,8 +8288,8 @@ int storage_deal_task(struct fast_task_info *pTask) case STORAGE_PROTO_CMD_QUERY_FILE_INFO: ACCESS_LOG_INIT_FIELDS(); result = storage_server_query_file_info(pTask); - STORAGE_ACCESS_LOG(pTask, \ - ACCESS_LOG_ACTION_QUERY_FILE, \ + STORAGE_ACCESS_LOG(pTask, + ACCESS_LOG_ACTION_QUERY_FILE, result); break; case STORAGE_PROTO_CMD_CREATE_LINK: @@ -8198,6 +8349,13 @@ int storage_deal_task(struct fast_task_info *pTask) case STORAGE_PROTO_CMD_TRUNK_TRUNCATE_BINLOG_FILE: result = storage_server_trunk_truncate_binlog_file(pTask); break; + case STORAGE_PROTO_CMD_REGENERATE_APPENDER_FILENAME: + ACCESS_LOG_INIT_FIELDS(); + result = storage_server_regenerate_appender_filename(pTask); + STORAGE_ACCESS_LOG(pTask, + ACCESS_LOG_ACTION_RENAME_FILE, + result); + break; default: logError("file: "__FILE__", line: %d, " \ "client ip: %s, unkown cmd: %d", \ diff --git a/storage/storage_sync.h b/storage/storage_sync.h index 5cc10b6..96b2230 100644 --- a/storage/storage_sync.h +++ b/storage/storage_sync.h @@ -14,20 +14,22 @@ #include "fastcommon/fc_list.h" #include "storage_func.h" -#define STORAGE_OP_TYPE_SOURCE_CREATE_FILE 'C' //upload file -#define STORAGE_OP_TYPE_SOURCE_APPEND_FILE 'A' //append file -#define STORAGE_OP_TYPE_SOURCE_DELETE_FILE 'D' //delete file -#define STORAGE_OP_TYPE_SOURCE_UPDATE_FILE 'U' //for whole file update such as metadata file -#define STORAGE_OP_TYPE_SOURCE_MODIFY_FILE 'M' //for part modify -#define STORAGE_OP_TYPE_SOURCE_TRUNCATE_FILE 'T' //truncate file -#define STORAGE_OP_TYPE_SOURCE_CREATE_LINK 'L' //create symbol link -#define STORAGE_OP_TYPE_REPLICA_CREATE_FILE 'c' -#define STORAGE_OP_TYPE_REPLICA_APPEND_FILE 'a' -#define STORAGE_OP_TYPE_REPLICA_DELETE_FILE 'd' -#define STORAGE_OP_TYPE_REPLICA_UPDATE_FILE 'u' -#define STORAGE_OP_TYPE_REPLICA_MODIFY_FILE 'm' -#define STORAGE_OP_TYPE_REPLICA_TRUNCATE_FILE 't' -#define STORAGE_OP_TYPE_REPLICA_CREATE_LINK 'l' +#define STORAGE_OP_TYPE_SOURCE_CREATE_FILE 'C' //upload file +#define STORAGE_OP_TYPE_SOURCE_APPEND_FILE 'A' //append file +#define STORAGE_OP_TYPE_SOURCE_DELETE_FILE 'D' //delete file +#define STORAGE_OP_TYPE_SOURCE_UPDATE_FILE 'U' //for whole file update such as metadata file +#define STORAGE_OP_TYPE_SOURCE_MODIFY_FILE 'M' //for part modify +#define STORAGE_OP_TYPE_SOURCE_TRUNCATE_FILE 'T' //truncate file +#define STORAGE_OP_TYPE_SOURCE_CREATE_LINK 'L' //create symbol link +#define STORAGE_OP_TYPE_SOURCE_RENAME_FILE 'R' //rename appender file to normal file +#define STORAGE_OP_TYPE_REPLICA_CREATE_FILE 'c' +#define STORAGE_OP_TYPE_REPLICA_APPEND_FILE 'a' +#define STORAGE_OP_TYPE_REPLICA_DELETE_FILE 'd' +#define STORAGE_OP_TYPE_REPLICA_UPDATE_FILE 'u' +#define STORAGE_OP_TYPE_REPLICA_MODIFY_FILE 'm' +#define STORAGE_OP_TYPE_REPLICA_TRUNCATE_FILE 't' +#define STORAGE_OP_TYPE_REPLICA_CREATE_LINK 'l' +#define STORAGE_OP_TYPE_REPLICA_RENAME_FILE 'r' #define STORAGE_BINLOG_BUFFER_SIZE 64 * 1024 #define STORAGE_BINLOG_LINE_SIZE 256 diff --git a/tracker/tracker_proto.h b/tracker/tracker_proto.h index e37fd6c..2d754dc 100644 --- a/tracker/tracker_proto.h +++ b/tracker/tracker_proto.h @@ -78,7 +78,7 @@ #define STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE 21 #define STORAGE_PROTO_CMD_QUERY_FILE_INFO 22 #define STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE 23 //create appender file -#define STORAGE_PROTO_CMD_APPEND_FILE 24 //append file +#define STORAGE_PROTO_CMD_APPEND_FILE 24 //append file #define STORAGE_PROTO_CMD_SYNC_APPEND_FILE 25 #define STORAGE_PROTO_CMD_FETCH_ONE_PATH_BINLOG 26 //fetch binlog of one store path #define STORAGE_PROTO_CMD_RESP TRACKER_PROTO_CMD_RESP @@ -92,10 +92,12 @@ #define STORAGE_PROTO_CMD_TRUNK_DELETE_BINLOG_MARKS 32 //since V3.07, tracker to storage #define STORAGE_PROTO_CMD_TRUNK_TRUNCATE_BINLOG_FILE 33 //since V3.07, trunk storage to storage -#define STORAGE_PROTO_CMD_MODIFY_FILE 34 //since V3.08 -#define STORAGE_PROTO_CMD_SYNC_MODIFY_FILE 35 //since V3.08 -#define STORAGE_PROTO_CMD_TRUNCATE_FILE 36 //since V3.08 -#define STORAGE_PROTO_CMD_SYNC_TRUNCATE_FILE 37 //since V3.08 +#define STORAGE_PROTO_CMD_MODIFY_FILE 34 //since V3.08 +#define STORAGE_PROTO_CMD_SYNC_MODIFY_FILE 35 //since V3.08 +#define STORAGE_PROTO_CMD_TRUNCATE_FILE 36 //since V3.08 +#define STORAGE_PROTO_CMD_SYNC_TRUNCATE_FILE 37 //since V3.08 +#define STORAGE_PROTO_CMD_REGENERATE_APPENDER_FILENAME 38 //since V6.02, rename appender file to normal file +#define STORAGE_PROTO_CMD_SYNC_RENAME_FILE 40 //since V6.02 //for overwrite all old metadata #define STORAGE_SET_METADATA_FLAG_OVERWRITE 'O' diff --git a/tracker/tracker_types.h b/tracker/tracker_types.h index 04fc51e..40cb765 100644 --- a/tracker/tracker_types.h +++ b/tracker/tracker_types.h @@ -117,6 +117,10 @@ #define FDFS_TRUNK_FILE_TRUE_SIZE(file_size) \ (file_size & 0xFFFFFFFF) +#define FDFS_FILE_TYPE_NORMAL 1 //normal file +#define FDFS_FILE_TYPE_APPENDER 2 //appender file +#define FDFS_FILE_TYPE_SLAVE 4 //slave file + #define FDFS_STORAGE_ID_MAX_SIZE 16 #define TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB 0 From c36419d5bbcaa630acb1a53458890caeb0dc8c59 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 11 Nov 2019 22:55:51 +0800 Subject: [PATCH 18/95] php ext support regenerate filename for appender file --- HISTORY | 2 +- client/storage_client.c | 3 - conf/storage.conf | 1 + php_client/fastdfs_client.c | 289 ++++++++++++++++++++++++++++++++++-- php_client/fastdfs_client.h | 2 + storage/storage_service.c | 22 +-- 6 files changed, 286 insertions(+), 33 deletions(-) diff --git a/HISTORY b/HISTORY index 0bde129..6eb47f6 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.02 2019-11-10 +Version 6.02 2019-11-11 * get_file_info calculate CRC32 for appender file type * disk recovery download file to local temp file then rename it when the local file exists diff --git a/client/storage_client.c b/client/storage_client.c index 37d5710..ec38e95 100644 --- a/client/storage_client.c +++ b/client/storage_client.c @@ -2397,9 +2397,6 @@ int storage_regenerate_appender_filename(ConnectionInfo *pTrackerServer, pHeader = (TrackerHeader *)out_buff; p = out_buff + sizeof(TrackerHeader); - snprintf(p, sizeof(out_buff) - sizeof(TrackerHeader), - "%s", group_name); - p += FDFS_GROUP_NAME_MAX_LEN; memcpy(p, appender_filename, appender_filename_len); p += appender_filename_len; diff --git a/conf/storage.conf b/conf/storage.conf index c9713b9..82dc876 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -107,6 +107,7 @@ store_path_count=1 # store_path#, based 0, if store_path0 not exists, it's value is base_path # the paths must be exist +# NOTE: the store paths' order is very important, don't mess up. store_path0=/home/yuqing/fastdfs #store_path1=/home/yuqing/fastdfs2 diff --git a/php_client/fastdfs_client.c b/php_client/fastdfs_client.c index 2e435d0..3c9d8ba 100644 --- a/php_client/fastdfs_client.c +++ b/php_client/fastdfs_client.c @@ -169,6 +169,8 @@ const zend_fcall_info empty_fcall_info = { 0, NULL, NULL, NULL, NULL, 0, NULL, N ZEND_FE(fastdfs_storage_file_exist1, NULL) ZEND_FE(fastdfs_gen_slave_filename, NULL) ZEND_FE(fastdfs_send_data, NULL) + ZEND_FE(fastdfs_storage_regenerate_appender_filename, NULL) + ZEND_FE(fastdfs_storage_regenerate_appender_filename1, NULL) {NULL, NULL, NULL} /* Must be the last line */ }; @@ -4047,6 +4049,195 @@ static void php_fdfs_storage_modify_file_impl( \ } } +/* +boolean fastdfs_storage_regenerate_appender_filename(string group_name, + string appender_filename, [array tracker_server, array storage_server]) +return assoc array for success, false for error +*/ +static void php_fdfs_storage_regenerate_appender_filename_impl( + INTERNAL_FUNCTION_PARAMETERS, FDFSPhpContext *pContext, + const bool bFileId) +{ + int result; + int argc; + char *appender_filename; + zval *tracker_obj; + zval *storage_obj; + char *group_name; + HashTable *tracker_hash; + HashTable *storage_hash; + ConnectionInfo tracker_server; + ConnectionInfo storage_server; + ConnectionInfo *pTrackerServer; + ConnectionInfo *pStorageServer; + char new_group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; + char new_remote_filename[128]; + char new_file_id[FDFS_GROUP_NAME_MAX_LEN + 128]; + zend_size_t group_name_len; + zend_size_t appender_filename_len; + int saved_tracker_sock; + int saved_storage_sock; + int min_param_count; + int max_param_count; + + if (bFileId) + { + min_param_count = 1; + max_param_count = 3; + } + else + { + min_param_count = 2; + max_param_count = 4; + } + + argc = ZEND_NUM_ARGS(); + if (argc < min_param_count || argc > max_param_count) + { + logError("file: "__FILE__", line: %d, " \ + "storage_modify_file parameters " \ + "count: %d < %d or > %d", __LINE__, argc, \ + min_param_count, max_param_count); + pContext->err_no = EINVAL; + RETURN_BOOL(false); + } + + tracker_obj = NULL; + storage_obj = NULL; + if (bFileId) + { + char *pSeperator; + char *appender_file_id; + zend_size_t appender_file_id_len; + + result = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s|aa", &appender_file_id, &appender_file_id_len, + &tracker_obj, &storage_obj); + if (result == FAILURE) + { + logError("file: "__FILE__", line: %d, " \ + "zend_parse_parameters fail!", __LINE__); + pContext->err_no = EINVAL; + RETURN_BOOL(false); + } + + snprintf(new_file_id, sizeof(new_file_id), "%s", appender_file_id); + pSeperator = strchr(new_file_id, FDFS_FILE_ID_SEPERATOR); + if (pSeperator == NULL) + { + logError("file: "__FILE__", line: %d, " + "appender_file_id is invalid, " + "appender_file_id=%s", + __LINE__, appender_file_id); + pContext->err_no = EINVAL; + RETURN_BOOL(false); + } + + *pSeperator = '\0'; + group_name = new_file_id; + appender_filename = pSeperator + 1; + } + else + { + result = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "ss|aa", &group_name, &group_name_len, + &appender_filename, &appender_filename_len, + &tracker_obj, &storage_obj); + if (result == FAILURE) + { + logError("file: "__FILE__", line: %d, " + "zend_parse_parameters fail!", __LINE__); + pContext->err_no = EINVAL; + RETURN_BOOL(false); + } + } + + if (tracker_obj == NULL) + { + pTrackerServer = tracker_get_connection_no_pool( \ + pContext->pTrackerGroup); + if (pTrackerServer == NULL) + { + pContext->err_no = ENOENT; + RETURN_BOOL(false); + } + saved_tracker_sock = -1; + tracker_hash = NULL; + } + else + { + pTrackerServer = &tracker_server; + tracker_hash = Z_ARRVAL_P(tracker_obj); + if ((result=php_fdfs_get_server_from_hash(tracker_hash, \ + pTrackerServer)) != 0) + { + pContext->err_no = result; + RETURN_BOOL(false); + } + saved_tracker_sock = pTrackerServer->sock; + } + + if (storage_obj == NULL) + { + pStorageServer = NULL; + storage_hash = NULL; + saved_storage_sock = -1; + } + else + { + pStorageServer = &storage_server; + storage_hash = Z_ARRVAL_P(storage_obj); + if ((result=php_fdfs_get_server_from_hash(storage_hash, \ + pStorageServer)) != 0) + { + pContext->err_no = result; + RETURN_BOOL(false); + } + + saved_storage_sock = pStorageServer->sock; + } + + pContext->err_no = storage_regenerate_appender_filename(pTrackerServer, + pStorageServer, group_name, appender_filename, + new_group_name, new_remote_filename); + + if (tracker_hash != NULL && pTrackerServer->sock != \ + saved_tracker_sock) + { + CLEAR_HASH_SOCK_FIELD(tracker_hash) + } + if (pStorageServer != NULL && pStorageServer->sock != \ + saved_storage_sock) + { + CLEAR_HASH_SOCK_FIELD(storage_hash) + } + + if (pContext->err_no != 0) + { + RETURN_BOOL(false); + } + if (bFileId) + { + char file_id[FDFS_GROUP_NAME_MAX_LEN + 128]; + int file_id_len; + + file_id_len = sprintf(file_id, "%s%c%s", new_group_name, + FDFS_FILE_ID_SEPERATOR, new_remote_filename); + ZEND_RETURN_STRINGL(file_id, file_id_len, 1); + } + else + { + array_init(return_value); + + zend_add_assoc_stringl_ex(return_value, "group_name", + sizeof("group_name"), new_group_name, + strlen(new_group_name), 1); + zend_add_assoc_stringl_ex(return_value, "filename", + sizeof("filename"), new_remote_filename, + strlen(new_remote_filename), 1); + } +} + static void php_fdfs_storage_set_metadata_impl(INTERNAL_FUNCTION_PARAMETERS, \ FDFSPhpContext *pContext, const bool bFileId) { @@ -4271,7 +4462,7 @@ static void php_fdfs_http_gen_token_impl(INTERNAL_FUNCTION_PARAMETERS, \ if (argc != 2) { logError("file: "__FILE__", line: %d, " \ - "storage_upload_file parameters " \ + "fdfs_http_gen_token parameters " \ "count: %d != 2", __LINE__, argc); pContext->err_no = EINVAL; RETURN_BOOL(false); @@ -4359,7 +4550,7 @@ static void php_fdfs_get_file_info_impl(INTERNAL_FUNCTION_PARAMETERS, \ if (argc != param_count) { logError("file: "__FILE__", line: %d, " \ - "storage_upload_file parameters " \ + "fdfs_get_file_info parameters " \ "count: %d != %d", __LINE__, argc, param_count); pContext->err_no = EINVAL; RETURN_BOOL(false); @@ -4416,16 +4607,20 @@ static void php_fdfs_get_file_info_impl(INTERNAL_FUNCTION_PARAMETERS, \ } array_init(return_value); - zend_add_assoc_long_ex(return_value, "source_id", \ + zend_add_assoc_bool_ex(return_value, "get_from_server", + sizeof("get_from_server"), file_info.get_from_server); + zend_add_assoc_long_ex(return_value, "file_type", + sizeof("file_type"), file_info.file_type); + zend_add_assoc_long_ex(return_value, "source_id", sizeof("source_id"), file_info.source_id); - zend_add_assoc_long_ex(return_value, "create_timestamp", \ + zend_add_assoc_long_ex(return_value, "create_timestamp", sizeof("create_timestamp"), file_info.create_timestamp); - zend_add_assoc_long_ex(return_value, "file_size", \ + zend_add_assoc_long_ex(return_value, "file_size", sizeof("file_size"), (long)file_info.file_size); - zend_add_assoc_stringl_ex(return_value, "source_ip_addr", \ - sizeof("source_ip_addr"), file_info.source_ip_addr, \ + zend_add_assoc_stringl_ex(return_value, "source_ip_addr", + sizeof("source_ip_addr"), file_info.source_ip_addr, strlen(file_info.source_ip_addr), 1); - zend_add_assoc_long_ex(return_value, "crc32", \ + zend_add_assoc_long_ex(return_value, "crc32", sizeof("crc32"), file_info.crc32); } @@ -4453,7 +4648,7 @@ static void php_fdfs_gen_slave_filename_impl(INTERNAL_FUNCTION_PARAMETERS, \ if (argc != 2 && argc != 3) { logError("file: "__FILE__", line: %d, " \ - "storage_upload_file parameters " \ + "fdfs_gen_slave_filename parameters " \ "count: %d != 2 or 3", __LINE__, argc); pContext->err_no = EINVAL; RETURN_BOOL(false); @@ -4940,7 +5135,7 @@ ZEND_FUNCTION(fastdfs_storage_modify_by_callback) /* boolean fastdfs_storage_modify_by_callback1(array callback_array, - long file_offset, string group_name, string appender_filename + long file_offset, string appender_file_id, [, array tracker_server, array storage_server]) return true for success, false for error */ @@ -4950,6 +5145,28 @@ ZEND_FUNCTION(fastdfs_storage_modify_by_callback1) &php_context, FDFS_UPLOAD_BY_CALLBACK, true); } +/* +boolean fastdfs_storage_regenerate_appender_filename(string group_name, + string appender_filename, [array tracker_server, array storage_server]) +return assoc array for success, false for error +*/ +ZEND_FUNCTION(fastdfs_storage_regenerate_appender_filename) +{ + php_fdfs_storage_regenerate_appender_filename_impl( + INTERNAL_FUNCTION_PARAM_PASSTHRU, &php_context, false); +} + +/* +boolean fastdfs_storage_regenerate_appender_filename1( + string appender_file_id, [array tracker_server, array storage_server]) +return regenerated file id for success, false for error +*/ +ZEND_FUNCTION(fastdfs_storage_regenerate_appender_filename1) +{ + php_fdfs_storage_regenerate_appender_filename_impl( + INTERNAL_FUNCTION_PARAM_PASSTHRU, &php_context, true); +} + /* array fastdfs_storage_upload_appender_by_filename(string local_filename, [string file_ext_name, string meta_list, string group_name, @@ -5962,6 +6179,36 @@ PHP_METHOD(FastDFS, storage_modify_by_callback1) &(i_obj->context), FDFS_UPLOAD_BY_CALLBACK, true); } +/* +boolean fastdfs_storage_regenerate_appender_filename(string group_name, + string appender_filename, [array tracker_server, array storage_server]) +return assoc array for success, false for error +*/ +PHP_METHOD(FastDFS, storage_regenerate_appender_filename) +{ + zval *object = getThis(); + php_fdfs_t *i_obj; + + i_obj = (php_fdfs_t *) fdfs_get_object(object); + php_fdfs_storage_regenerate_appender_filename_impl( + INTERNAL_FUNCTION_PARAM_PASSTHRU, &(i_obj->context), false); +} + +/* +boolean fastdfs_storage_regenerate_appender_filename1( + string appender_file_id, [array tracker_server, array storage_server]) +return regenerated file id for success, false for error +*/ +PHP_METHOD(FastDFS, storage_regenerate_appender_filename1) +{ + zval *object = getThis(); + php_fdfs_t *i_obj; + + i_obj = (php_fdfs_t *) fdfs_get_object(object); + php_fdfs_storage_regenerate_appender_filename_impl( + INTERNAL_FUNCTION_PARAM_PASSTHRU, &(i_obj->context), true); +} + /* array FastDFS::storage_upload_appender_by_filename(string local_filename, [string file_ext_name, string meta_list, string group_name, @@ -6758,6 +7005,19 @@ ZEND_ARG_INFO(0, tracker_server) ZEND_ARG_INFO(0, storage_server) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_storage_regenerate_appender_filename, 0, 0, 2) +ZEND_ARG_INFO(0, group_name) +ZEND_ARG_INFO(0, appender_filename) +ZEND_ARG_INFO(0, tracker_server) +ZEND_ARG_INFO(0, storage_server) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_storage_regenerate_appender_filename1, 0, 0, 1) +ZEND_ARG_INFO(0, appender_file_id) +ZEND_ARG_INFO(0, tracker_server) +ZEND_ARG_INFO(0, storage_server) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_storage_upload_appender_by_filename, 0, 0, 1) ZEND_ARG_INFO(0, local_filename) ZEND_ARG_INFO(0, file_ext_name) @@ -7073,6 +7333,8 @@ static zend_function_entry fdfs_class_methods[] = { FDFS_ME(storage_modify_by_filebuff1, arginfo_storage_modify_by_filebuff1) FDFS_ME(storage_modify_by_callback, arginfo_storage_modify_by_callback) FDFS_ME(storage_modify_by_callback1, arginfo_storage_modify_by_callback1) + FDFS_ME(storage_regenerate_appender_filename, arginfo_storage_regenerate_appender_filename) + FDFS_ME(storage_regenerate_appender_filename1, arginfo_storage_regenerate_appender_filename1) FDFS_ME(storage_upload_appender_by_filename, arginfo_storage_upload_appender_by_filename) FDFS_ME(storage_upload_appender_by_filename1, arginfo_storage_upload_appender_by_filename1) FDFS_ME(storage_upload_appender_by_filebuff, arginfo_storage_upload_appender_by_filebuff) @@ -7539,6 +7801,13 @@ PHP_MINIT_FUNCTION(fastdfs_client) REGISTER_LONG_CONSTANT("FDFS_STORAGE_STATUS_NONE", \ FDFS_STORAGE_STATUS_NONE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FDFS_FILE_TYPE_NORMAL", + FDFS_FILE_TYPE_NORMAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FDFS_FILE_TYPE_SLAVE", + FDFS_FILE_TYPE_SLAVE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FDFS_FILE_TYPE_APPENDER", + FDFS_FILE_TYPE_APPENDER, CONST_CS | CONST_PERSISTENT); + return SUCCESS; } diff --git a/php_client/fastdfs_client.h b/php_client/fastdfs_client.h index 14b5c10..34fa9cb 100644 --- a/php_client/fastdfs_client.h +++ b/php_client/fastdfs_client.h @@ -88,6 +88,8 @@ ZEND_FUNCTION(fastdfs_storage_modify_by_callback); ZEND_FUNCTION(fastdfs_storage_modify_by_callback1); ZEND_FUNCTION(fastdfs_storage_truncate_file); ZEND_FUNCTION(fastdfs_storage_truncate_file1); +ZEND_FUNCTION(fastdfs_storage_regenerate_appender_filename); +ZEND_FUNCTION(fastdfs_storage_regenerate_appender_filename1); PHP_FASTDFS_API zend_class_entry *php_fdfs_get_ce(void); PHP_FASTDFS_API zend_class_entry *php_fdfs_get_exception(void); diff --git a/storage/storage_service.c b/storage/storage_service.c index bd49875..4700c92 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -4920,8 +4920,7 @@ static void calc_crc32_done_callback_for_regenerate( } /** -FDFS_GROUP_NAME_MAX_LEN: group name -body length - FDFS_GROUP_NAME_MAX_LEN: appender filename +body length: appender filename **/ static int storage_server_regenerate_appender_filename(struct fast_task_info *pTask) { @@ -4929,7 +4928,6 @@ static int storage_server_regenerate_appender_filename(struct fast_task_info *pT StorageFileContext *pFileContext; FDFSTrunkFullInfo *pTrunkInfo; char *p; - char group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; char appender_filename[128]; char true_filename[128]; struct stat stat_buf; @@ -4942,9 +4940,8 @@ static int storage_server_regenerate_appender_filename(struct fast_task_info *pT pFileContext = &(pClientInfo->file_context); nInPackLen = pClientInfo->total_length - sizeof(TrackerHeader); - appender_filename_len = nInPackLen - FDFS_GROUP_NAME_MAX_LEN; - if ((nInPackLen < FDFS_GROUP_NAME_MAX_LEN + - FDFS_LOGIC_FILE_PATH_LEN + FDFS_FILENAME_BASE64_LENGTH + + appender_filename_len = nInPackLen; + if ((nInPackLen < FDFS_LOGIC_FILE_PATH_LEN + FDFS_FILENAME_BASE64_LENGTH + FDFS_FILE_EXT_NAME_MAX_LEN + 1) || (appender_filename_len >= sizeof(appender_filename))) { @@ -4955,19 +4952,6 @@ static int storage_server_regenerate_appender_filename(struct fast_task_info *pT } p = pTask->data + sizeof(TrackerHeader); - memcpy(group_name, p, FDFS_GROUP_NAME_MAX_LEN); - *(group_name + FDFS_GROUP_NAME_MAX_LEN) = '\0'; - if (strcmp(group_name, g_group_name) != 0) - { - logError("file: "__FILE__", line: %d, " - "client ip:%s, group_name: %s " - "not correct, should be: %s", - __LINE__, pTask->client_ip, - group_name, g_group_name); - return EINVAL; - } - - p += FDFS_GROUP_NAME_MAX_LEN; memcpy(appender_filename, p, appender_filename_len); *(appender_filename + appender_filename_len) = '\0'; From 6fb8fe206bed90cc8a4dd2c82205acd8da3b755a Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 12 Nov 2019 10:25:53 +0800 Subject: [PATCH 19/95] modify php test for regenerate_appender_filename --- client/fdfs_file_info.c | 2 +- php_client/fastdfs_appender_test.php | 33 ++++++++++++++++++++++++--- php_client/fastdfs_appender_test1.php | 28 ++++++++++++++++++++--- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/client/fdfs_file_info.c b/client/fdfs_file_info.c index 050a6c2..cb06e7f 100644 --- a/client/fdfs_file_info.c +++ b/client/fdfs_file_info.c @@ -79,7 +79,7 @@ int main(int argc, char *argv[]) szDatetime, sizeof(szDatetime))); printf("file size: %"PRId64"\n", \ file_info.file_size); - printf("file crc32: %u (0x%08X)\n", \ + printf("file crc32: %d (0x%08X)\n", \ file_info.crc32, file_info.crc32); } diff --git a/php_client/fastdfs_appender_test.php b/php_client/fastdfs_appender_test.php index 00653b4..d6af08b 100644 --- a/php_client/fastdfs_appender_test.php +++ b/php_client/fastdfs_appender_test.php @@ -30,7 +30,6 @@ echo "fastdfs_storage_modify_by_filename fail, errno: " . fastdfs_get_last_error_no() . ", error info: " . fastdfs_get_last_error_info() . "\n"; exit; } - var_dump(fastdfs_get_file_info($group_name, $appender_filename)); if (!fastdfs_storage_truncate_file($group_name, $appender_filename, 0)) @@ -38,9 +37,23 @@ echo "fastdfs_storage_truncate_file fail, errno: " . fastdfs_get_last_error_no() . ", error info: " . fastdfs_get_last_error_info() . "\n"; exit; } - var_dump(fastdfs_get_file_info($group_name, $appender_filename)); + $new_file_info = fastdfs_storage_regenerate_appender_filename($group_name, $appender_filename); + if (!$new_file_info) + { + echo "fastdfs_storage_regenerate_appender_filename fail, errno: " . fastdfs_get_last_error_no() . ", error info: " . fastdfs_get_last_error_info() . "\n"; + exit; + } + + $group_name = $new_file_info['group_name']; + $appender_filename = $new_file_info['filename']; + echo "regenerated file id: $group_name/$appender_filename\n"; + var_dump(fastdfs_get_file_info($group_name, $appender_filename)); + + $result = fastdfs_storage_delete_file($group_name, $appender_filename); + echo "delete file $group_name/$appender_filename return: $result\n"; + echo "function test done\n\n"; $fdfs = new FastDFS(); @@ -81,8 +94,22 @@ echo "$fdfs->storage_truncate_file fail, errno: " . $fdfs->get_last_error_no() . ", error info: " . $fdfs->get_last_error_info() . "\n"; exit; } - var_dump($fdfs->get_file_info($group_name, $appender_filename)); + $new_file_info = $fdfs->storage_regenerate_appender_filename($group_name, $appender_filename); + if (!$new_file_info) + { + echo "$fdfs->storage_regenerate_appender_filename fail, errno: " . $fdfs->get_last_error_no() . ", error info: " . $fdfs->get_last_error_info() . "\n"; + exit; + } + + $group_name = $new_file_info['group_name']; + $appender_filename = $new_file_info['filename']; + echo "regenerated file id: $group_name/$appender_filename\n"; + var_dump($fdfs->get_file_info($group_name, $appender_filename)); + + $result = $fdfs->storage_delete_file($group_name, $appender_filename); + echo "delete file $group_name/$appender_filename return: $result\n"; + echo 'tracker_close_all_connections result: ' . $fdfs->tracker_close_all_connections() . "\n"; ?> diff --git a/php_client/fastdfs_appender_test1.php b/php_client/fastdfs_appender_test1.php index 107180b..9ec4368 100644 --- a/php_client/fastdfs_appender_test1.php +++ b/php_client/fastdfs_appender_test1.php @@ -25,7 +25,6 @@ echo "fastdfs_storage_modify_by_filename1 fail, errno: " . fastdfs_get_last_error_no() . ", error info: " . fastdfs_get_last_error_info() . "\n"; exit; } - var_dump(fastdfs_get_file_info1($appender_file_id)); if (!fastdfs_storage_truncate_file1($appender_file_id, 0)) @@ -33,8 +32,20 @@ echo "fastdfs_storage_truncate_file1 fail, errno: " . fastdfs_get_last_error_no() . ", error info: " . fastdfs_get_last_error_info() . "\n"; exit; } - var_dump(fastdfs_get_file_info1($appender_file_id)); + + $new_file_id = fastdfs_storage_regenerate_appender_filename1($appender_file_id); + if (!$new_file_id) + { + echo "fastdfs_storage_regenerate_appender_filename1 fail, errno: " . fastdfs_get_last_error_no() . ", error info: " . fastdfs_get_last_error_info() . "\n"; + exit; + } + $appender_file_id = $new_file_id; + var_dump(fastdfs_get_file_info1($appender_file_id)); + + $result = fastdfs_storage_delete_file1($appender_file_id); + echo "delete file $appender_file_id return: $result\n"; + echo "function test done\n\n"; $fdfs = new FastDFS(); @@ -69,8 +80,19 @@ echo "\$fdfs->torage_truncate_file1 torage_modify_by_filename1 fail, errno: " . $fdfs->get_last_error_no() . ", error info: " . $fdfs->get_last_error_info() . "\n"; exit; } - var_dump($fdfs->get_file_info1($appender_file_id)); + $new_file_id = $fdfs->storage_regenerate_appender_filename1($appender_file_id); + if (!$new_file_id) + { + echo "$fdfs->storage_regenerate_appender_filename1 fail, errno: " . $fdfs->get_last_error_no() . ", error info: " . $fdfs->get_last_error_info() . "\n"; + exit; + } + $appender_file_id = $new_file_id; + var_dump($fdfs->get_file_info1($appender_file_id)); + + $result = $fdfs->storage_delete_file1($appender_file_id); + echo "delete file $appender_file_id return: $result\n"; + echo 'tracker_close_all_connections result: ' . $fdfs->tracker_close_all_connections() . "\n"; ?> From 9bc762bffba23eb9eb53d36c8810f7321435a3a6 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 12 Nov 2019 19:07:49 +0800 Subject: [PATCH 20/95] sync regenerated appender file --- HISTORY | 2 +- storage/storage_func.c | 23 ++++ storage/storage_func.h | 4 + storage/storage_service.c | 246 +++++++++++++++++++++++++++----------- storage/storage_sync.c | 164 ++++++++++++++++++++++--- 5 files changed, 353 insertions(+), 86 deletions(-) diff --git a/HISTORY b/HISTORY index 6eb47f6..5ae2c36 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.02 2019-11-11 +Version 6.02 2019-11-12 * get_file_info calculate CRC32 for appender file type * disk recovery download file to local temp file then rename it when the local file exists diff --git a/storage/storage_func.c b/storage/storage_func.c index 4229b15..3102a15 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -2180,6 +2180,29 @@ int storage_set_tracker_client_ips(ConnectionInfo *conn, return 0; } +int storage_logic_to_local_full_filename(const char *logic_filename, + const int logic_filename_len, int *store_path_index, + char *full_filename, const int filename_size) +{ + int result; + int filename_len; + char true_filename[128]; + + filename_len = logic_filename_len; + if ((result=storage_split_filename_ex(logic_filename, + &filename_len, true_filename, store_path_index)) != 0) + { + return result; + } + if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) + { + return result; + } + + snprintf(full_filename, filename_size, "%s/data/%s", + g_fdfs_store_paths.paths[*store_path_index], true_filename); + return 0; +} /* int write_serialized(int fd, const char *buff, size_t count, const bool bSync) diff --git a/storage/storage_func.h b/storage/storage_func.h index 5960ac4..712d394 100644 --- a/storage/storage_func.h +++ b/storage/storage_func.h @@ -39,6 +39,10 @@ int storage_set_tracker_client_ips(ConnectionInfo *conn, int storage_check_and_make_data_path(); +int storage_logic_to_local_full_filename(const char *logic_filename, + const int logic_filename_len, int *store_path_index, + char *full_filename, const int filename_size); + #define STORAGE_CHOWN(path, current_uid, current_gid) \ if (!(g_run_by_gid == current_gid && g_run_by_uid == current_uid)) \ { \ diff --git a/storage/storage_service.c b/storage/storage_service.c index 4700c92..6245d83 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -489,32 +489,30 @@ static void storage_sync_truncate_file_done_callback( \ storage_nio_notify(pTask); } -static int storage_sync_copy_file_rename_filename( \ +static int storage_sync_copy_file_rename_filename( StorageFileContext *pFileContext) { char full_filename[MAX_PATH_SIZE + 256]; - char true_filename[128]; int filename_len; int result; int store_path_index; filename_len = strlen(pFileContext->fname2log); - if ((result=storage_split_filename_ex(pFileContext->fname2log, \ - &filename_len, true_filename, &store_path_index)) != 0) + if ((result=storage_logic_to_local_full_filename( + pFileContext->fname2log, filename_len, + &store_path_index, full_filename, + sizeof(full_filename))) != 0) { return result; } - snprintf(full_filename, sizeof(full_filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], \ - true_filename); if (rename(pFileContext->filename, full_filename) != 0) { result = errno != 0 ? errno : EPERM; - logWarning("file: "__FILE__", line: %d, " \ - "rename %s to %s fail, " \ - "errno: %d, error info: %s", __LINE__, \ - pFileContext->filename, full_filename, \ + logWarning("file: "__FILE__", line: %d, " + "rename %s to %s fail, " + "errno: %d, error info: %s", __LINE__, + pFileContext->filename, full_filename, result, STRERROR(result)); return result; } @@ -4212,7 +4210,9 @@ static int storage_server_fetch_one_path_binlog_dealer( \ if (!(record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_FILE || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_FILE || record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_LINK - || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_LINK)) + || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_LINK + || record.op_type == STORAGE_OP_TYPE_SOURCE_RENAME_FILE + || record.op_type == STORAGE_OP_TYPE_REPLICA_RENAME_FILE)) { continue; } @@ -4230,8 +4230,8 @@ static int storage_server_fetch_one_path_binlog_dealer( \ } else { - snprintf(full_filename, sizeof(full_filename), "%s/data/%s", \ - g_fdfs_store_paths.paths[record.store_path_index], \ + snprintf(full_filename, sizeof(full_filename), "%s/data/%s", + g_fdfs_store_paths.paths[record.store_path_index], record.true_filename); if (lstat(full_filename, &stat_buf) != 0) { @@ -4241,10 +4241,10 @@ static int storage_server_fetch_one_path_binlog_dealer( \ } else { - logError("file: "__FILE__", line: %d, " \ - "call stat fail, file: %s, "\ - "error no: %d, error info: %s", \ - __LINE__, full_filename, \ + logError("file: "__FILE__", line: %d, " + "call stat fail, file: %s, " + "error no: %d, error info: %s", + __LINE__, full_filename, errno, STRERROR(errno)); result = errno != 0 ? errno : EPERM; break; @@ -4291,11 +4291,20 @@ static int storage_server_fetch_one_path_binlog_dealer( \ } } + if (record.op_type == STORAGE_OP_TYPE_SOURCE_RENAME_FILE) + { + record.op_type = STORAGE_OP_TYPE_SOURCE_CREATE_FILE; + } + else if (record.op_type == STORAGE_OP_TYPE_REPLICA_RENAME_FILE) + { + record.op_type = STORAGE_OP_TYPE_REPLICA_CREATE_FILE; + } + if (record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_FILE || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_FILE) { - pOutBuff += sprintf(pOutBuff, "%d %c %s\n", \ - (int)record.timestamp, \ + pOutBuff += sprintf(pOutBuff, "%d %c %s\n", + (int)record.timestamp, record.op_type, record.filename); } else @@ -4344,7 +4353,7 @@ static int storage_server_fetch_one_path_binlog_dealer( \ src_filename + base_path_len + 6); } - if (pTask->size - (pOutBuff - pTask->data) < \ + if (pTask->size - (pOutBuff - pTask->data) < STORAGE_BINLOG_LINE_SIZE + FDFS_PROTO_PKG_LEN_SIZE) { break; @@ -4873,7 +4882,7 @@ static void calc_crc32_done_callback_for_regenerate( path.store_path_index, filename); sprintf(binlog_msg, "%s %s", - pFileContext->fname2log, return_filename); + return_filename, pFileContext->fname2log); result = storage_binlog_write( pFileContext->timestamp2log, STORAGE_OP_TYPE_SOURCE_RENAME_FILE, @@ -5223,7 +5232,6 @@ static int storage_do_truncate_file(struct fast_task_info *pTask) StorageFileContext *pFileContext; char *p; char appender_filename[128]; - char true_filename[128]; char decode_buff[64]; struct stat stat_buf; int appender_filename_len; @@ -5285,18 +5293,14 @@ static int storage_do_truncate_file(struct fast_task_info *pTask) STORAGE_ACCESS_STRCPY_FNAME2LOG(appender_filename, \ appender_filename_len, pClientInfo); - if ((result=storage_split_filename_ex(appender_filename, \ - &filename_len, true_filename, &store_path_index)) != 0) - { - return result; - } - if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) + if ((result=storage_logic_to_local_full_filename( + appender_filename, filename_len, + &store_path_index, pFileContext->filename, + sizeof(pFileContext->filename))) != 0) { return result; } - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], true_filename); if (lstat(pFileContext->filename, &stat_buf) == 0) { if (!S_ISREG(stat_buf.st_mode)) @@ -5376,9 +5380,6 @@ static int storage_do_truncate_file(struct fast_task_info *pTask) pFileContext->calc_crc32 = false; pFileContext->calc_file_hash = false; - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], true_filename); - pFileContext->sync_flag = STORAGE_OP_TYPE_SOURCE_TRUNCATE_FILE; pFileContext->timestamp2log = pFileContext->extra_info.upload.start_time; pFileContext->extra_info.upload.file_type = _FILE_TYPE_APPENDER; @@ -5968,7 +5969,6 @@ static int storage_sync_append_file(struct fast_task_info *pTask) TaskDealFunc deal_func; char *p; char group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; - char true_filename[128]; char filename[128]; bool need_write_file; int filename_len; @@ -6064,19 +6064,13 @@ static int storage_sync_append_file(struct fast_task_info *pTask) *(filename + filename_len) = '\0'; p += filename_len; - if ((result=storage_split_filename_ex(filename, \ - &filename_len, true_filename, &store_path_index)) != 0) + if ((result=storage_logic_to_local_full_filename( + filename, filename_len, + &store_path_index, pFileContext->filename, + sizeof(pFileContext->filename))) != 0) { return result; } - if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) - { - return result; - } - - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], \ - true_filename); if (lstat(pFileContext->filename, &stat_buf) != 0) { @@ -6196,7 +6190,6 @@ static int storage_sync_modify_file(struct fast_task_info *pTask) TaskDealFunc deal_func; char *p; char group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; - char true_filename[128]; char filename[128]; bool need_write_file; int filename_len; @@ -6293,19 +6286,13 @@ static int storage_sync_modify_file(struct fast_task_info *pTask) *(filename + filename_len) = '\0'; p += filename_len; - if ((result=storage_split_filename_ex(filename, \ - &filename_len, true_filename, &store_path_index)) != 0) + if ((result=storage_logic_to_local_full_filename( + filename, filename_len, + &store_path_index, pFileContext->filename, + sizeof(pFileContext->filename))) != 0) { return result; } - if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) - { - return result; - } - - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], \ - true_filename); if (lstat(pFileContext->filename, &stat_buf) != 0) { @@ -6396,7 +6383,6 @@ static int storage_sync_truncate_file(struct fast_task_info *pTask) StorageFileContext *pFileContext; char *p; char group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; - char true_filename[128]; char filename[128]; int filename_len; int64_t nInPackLen; @@ -6494,19 +6480,13 @@ static int storage_sync_truncate_file(struct fast_task_info *pTask) *(filename + filename_len) = '\0'; p += filename_len; - if ((result=storage_split_filename_ex(filename, \ - &filename_len, true_filename, &store_path_index)) != 0) + if ((result=storage_logic_to_local_full_filename( + filename, filename_len, + &store_path_index, pFileContext->filename, + sizeof(pFileContext->filename))) != 0) { return result; } - if ((result=fdfs_check_data_filename(true_filename, filename_len)) != 0) - { - return result; - } - - snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], \ - true_filename); if (lstat(pFileContext->filename, &stat_buf) != 0) { @@ -6905,6 +6885,135 @@ static int storage_sync_link_file(struct fast_task_info *pTask) return STORAGE_STATUE_DEAL_FILE; } +static int storage_sync_rename_file(struct fast_task_info *pTask) +{ + StorageClientInfo *pClientInfo; + StorageFileContext *pFileContext; + char *p; + char group_name[FDFS_GROUP_NAME_MAX_LEN + 1]; + char dest_filename[128]; + char dest_full_filename[MAX_PATH_SIZE]; + char src_full_filename[MAX_PATH_SIZE]; + char src_filename[128]; + int64_t nInPackLen; + int dest_filename_len; + int src_filename_len; + int result; + int dest_store_path_index; + int src_store_path_index; + + pClientInfo = (StorageClientInfo *)pTask->arg; + pFileContext = &(pClientInfo->file_context); + + nInPackLen = pClientInfo->total_length - sizeof(TrackerHeader); + if (nInPackLen <= 2 * FDFS_PROTO_PKG_LEN_SIZE + + 4 + FDFS_GROUP_NAME_MAX_LEN) + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, package size " + "%"PRId64" is not correct, " + "expect length > %d", __LINE__, + pTask->client_ip, nInPackLen, + 2 * FDFS_PROTO_PKG_LEN_SIZE + + 4 + FDFS_GROUP_NAME_MAX_LEN); + return EINVAL; + } + + p = pTask->data + sizeof(TrackerHeader); + + dest_filename_len = buff2long(p); + p += FDFS_PROTO_PKG_LEN_SIZE; + + src_filename_len = buff2long(p); + p += FDFS_PROTO_PKG_LEN_SIZE; + + if (dest_filename_len < 0 || dest_filename_len >= sizeof(dest_filename)) + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, in request pkg, " + "filename length: %d is invalid, " + "which < 0 or >= %d", __LINE__, pTask->client_ip, + dest_filename_len, (int)sizeof(dest_filename)); + return EINVAL; + } + + pFileContext->timestamp2log = buff2int(p); + p += 4; + + memcpy(group_name, p, FDFS_GROUP_NAME_MAX_LEN); + *(group_name + FDFS_GROUP_NAME_MAX_LEN) = '\0'; + p += FDFS_GROUP_NAME_MAX_LEN; + if (strcmp(group_name, g_group_name) != 0) + { + logError("file: "__FILE__", line: %d, " + "client ip:%s, group_name: %s " + "not correct, should be: %s", + __LINE__, pTask->client_ip, + group_name, g_group_name); + return EINVAL; + } + + if (nInPackLen != 2 * FDFS_PROTO_PKG_LEN_SIZE + 4 + + FDFS_GROUP_NAME_MAX_LEN + dest_filename_len + src_filename_len) + { + logError("file: "__FILE__", line: %d, " + "client ip: %s, in request pkg, " + "pgk length: %"PRId64" != bytes: %d", + __LINE__, pTask->client_ip, + nInPackLen, 2 * FDFS_PROTO_PKG_LEN_SIZE + + FDFS_GROUP_NAME_MAX_LEN + dest_filename_len + + src_filename_len); + return EINVAL; + } + + memcpy(dest_filename, p, dest_filename_len); + *(dest_filename + dest_filename_len) = '\0'; + p += dest_filename_len; + + memcpy(src_filename, p, src_filename_len); + *(src_filename + src_filename_len) = '\0'; + + if ((result=storage_logic_to_local_full_filename( + dest_filename, dest_filename_len, + &dest_store_path_index, dest_full_filename, + sizeof(dest_full_filename))) != 0) + { + return result; + } + + if (access(dest_full_filename, F_OK) == 0) + { + logDebug("file: "__FILE__", line: %d, " + "client ip: %s, dest file: %s " + "already exist", __LINE__, + pTask->client_ip, dest_full_filename); + return EEXIST; + } + + if ((result=storage_logic_to_local_full_filename( + src_filename, src_filename_len, + &src_store_path_index, src_full_filename, + sizeof(src_full_filename))) != 0) + { + return result; + } + + if (rename(src_full_filename, dest_full_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + logWarning("file: "__FILE__", line: %d, " + "client ip: %s, rename %s to %s fail, " + "errno: %d, error info: %s", __LINE__, + pTask->client_ip, src_full_filename, + dest_full_filename, result, STRERROR(result)); + return result; + } + + return storage_binlog_write_ex(pFileContext->timestamp2log, + STORAGE_OP_TYPE_REPLICA_RENAME_FILE, + dest_filename, src_filename); +} + /** pkg format: Header @@ -8297,6 +8406,9 @@ int storage_deal_task(struct fast_task_info *pTask) case STORAGE_PROTO_CMD_SYNC_TRUNCATE_FILE: result = storage_sync_truncate_file(pTask); break; + case STORAGE_PROTO_CMD_SYNC_RENAME_FILE: + result = storage_sync_rename_file(pTask); + break; case STORAGE_PROTO_CMD_SYNC_CREATE_LINK: result = storage_sync_link_file(pTask); break; diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 706e641..f771efe 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -105,8 +105,8 @@ static int storage_sync_copy_file(ConnectionInfo *pStorageServer, \ int result; bool need_sync_file; - if ((result=trunk_file_stat(pRecord->store_path_index, \ - pRecord->true_filename, pRecord->true_filename_len, \ + if ((result=trunk_file_stat(pRecord->store_path_index, + pRecord->true_filename, pRecord->true_filename_len, &stat_buf, &trunkInfo, &trunkHeader)) != 0) { if (result == ENOENT) @@ -138,7 +138,7 @@ static int storage_sync_copy_file(ConnectionInfo *pStorageServer, \ { FDFSFileInfo file_info; result = storage_query_file_info_ex(NULL, \ - pStorageServer, g_group_name, \ + pStorageServer, g_group_name, \ pRecord->filename, &file_info, true); if (result == 0) { @@ -312,11 +312,11 @@ static int storage_sync_modify_file(ConnectionInfo *pStorageServer, \ StorageBinLogReader *pReader, StorageBinLogRecord *pRecord, \ const char cmd) { -#define FIELD_COUNT 3 +#define SYNC_MODIFY_FIELD_COUNT 3 TrackerHeader *pHeader; char *p; char *pBuff; - char *fields[FIELD_COUNT]; + char *fields[SYNC_MODIFY_FIELD_COUNT]; char full_filename[MAX_PATH_SIZE]; char out_buff[sizeof(TrackerHeader)+FDFS_GROUP_NAME_MAX_LEN+256]; char in_buff[1]; @@ -328,8 +328,8 @@ static int storage_sync_modify_file(ConnectionInfo *pStorageServer, \ int result; int count; - if ((count=splitEx(pRecord->filename, ' ', fields, FIELD_COUNT)) \ - != FIELD_COUNT) + if ((count=splitEx(pRecord->filename, ' ', fields, SYNC_MODIFY_FIELD_COUNT)) + != SYNC_MODIFY_FIELD_COUNT) { logError("file: "__FILE__", line: %d, " \ "the format of binlog not correct, filename: %s", \ @@ -483,11 +483,11 @@ filename bytes : filename static int storage_sync_truncate_file(ConnectionInfo *pStorageServer, \ StorageBinLogReader *pReader, StorageBinLogRecord *pRecord) { -#define FIELD_COUNT 3 +#define SYNC_TRUNCATE_FIELD_COUNT 3 TrackerHeader *pHeader; char *p; char *pBuff; - char *fields[FIELD_COUNT]; + char *fields[SYNC_TRUNCATE_FIELD_COUNT]; char full_filename[MAX_PATH_SIZE]; char out_buff[sizeof(TrackerHeader)+FDFS_GROUP_NAME_MAX_LEN+256]; char in_buff[1]; @@ -498,8 +498,8 @@ static int storage_sync_truncate_file(ConnectionInfo *pStorageServer, \ int result; int count; - if ((count=splitEx(pRecord->filename, ' ', fields, FIELD_COUNT)) \ - != FIELD_COUNT) + if ((count=splitEx(pRecord->filename, ' ', fields, + SYNC_TRUNCATE_FIELD_COUNT)) != SYNC_TRUNCATE_FIELD_COUNT) { logError("file: "__FILE__", line: %d, " \ "the format of binlog not correct, filename: %s", \ @@ -968,6 +968,123 @@ static int storage_sync_link_file(ConnectionInfo *pStorageServer, \ return result; } +/** +8 bytes: dest filename length +8 bytes: source filename length +4 bytes: source op timestamp +FDFS_GROUP_NAME_MAX_LEN bytes: group_name +dest filename length: dest filename +source filename length: source filename +**/ +static int storage_sync_rename_file(ConnectionInfo *pStorageServer, + StorageBinLogReader *pReader, StorageBinLogRecord *pRecord) +{ + TrackerHeader *pHeader; + int result; + char out_buff[sizeof(TrackerHeader) + 2 * FDFS_PROTO_PKG_LEN_SIZE + + 4 + FDFS_GROUP_NAME_MAX_LEN + 256]; + char in_buff[1]; + int out_body_len; + int64_t in_bytes; + char *pBuff; + char full_filename[MAX_PATH_SIZE]; + struct stat stat_buf; + + if (pRecord->op_type == STORAGE_OP_TYPE_REPLICA_RENAME_FILE) + { + return storage_sync_copy_file(pStorageServer, + pReader, pRecord, + STORAGE_PROTO_CMD_SYNC_CREATE_FILE); + } + + snprintf(full_filename, sizeof(full_filename), "%s/data/%s", + g_fdfs_store_paths.paths[pRecord->store_path_index], + pRecord->true_filename); + if (lstat(full_filename, &stat_buf) != 0) + { + if (errno == ENOENT) + { + logWarning("file: "__FILE__", line: %d, " + "sync file rename, file: %s not exists, " + "maybe deleted later?", __LINE__, full_filename); + + return 0; + } + else + { + result = errno != 0 ? errno : EPERM; + logError("file: "__FILE__", line: %d, " + "call stat fail, file: %s, " + "error no: %d, error info: %s", + __LINE__, full_filename, + result, STRERROR(result)); + return result; + } + } + + pHeader = (TrackerHeader *)out_buff; + memset(out_buff, 0, sizeof(out_buff)); + long2buff(pRecord->filename_len, out_buff + sizeof(TrackerHeader)); + long2buff(pRecord->src_filename_len, out_buff + sizeof(TrackerHeader) + + FDFS_PROTO_PKG_LEN_SIZE); + int2buff(pRecord->timestamp, out_buff + sizeof(TrackerHeader) + + 2 * FDFS_PROTO_PKG_LEN_SIZE); + sprintf(out_buff + sizeof(TrackerHeader) + 2 * FDFS_PROTO_PKG_LEN_SIZE + + 4, "%s", g_group_name); + memcpy(out_buff + sizeof(TrackerHeader) + 2 * FDFS_PROTO_PKG_LEN_SIZE + + 4 + FDFS_GROUP_NAME_MAX_LEN, + pRecord->filename, pRecord->filename_len); + memcpy(out_buff + sizeof(TrackerHeader) + 2 * FDFS_PROTO_PKG_LEN_SIZE + + 4 + FDFS_GROUP_NAME_MAX_LEN + pRecord->filename_len, + pRecord->src_filename, pRecord->src_filename_len); + + out_body_len = 2 * FDFS_PROTO_PKG_LEN_SIZE + 4 + + FDFS_GROUP_NAME_MAX_LEN + pRecord->filename_len + + pRecord->src_filename_len; + long2buff(out_body_len, pHeader->pkg_len); + pHeader->cmd = STORAGE_PROTO_CMD_SYNC_RENAME_FILE; + + if ((result=tcpsenddata_nb(pStorageServer->sock, out_buff, + sizeof(TrackerHeader) + out_body_len, + g_fdfs_network_timeout)) != 0) + { + logError("FILE: "__FILE__", line: %d, " + "send data to storage server %s:%d fail, " + "errno: %d, error info: %s", + __LINE__, pStorageServer->ip_addr, + pStorageServer->port, result, STRERROR(result)); + return result; + } + + pBuff = in_buff; + result = fdfs_recv_response(pStorageServer, &pBuff, 0, &in_bytes); + if (result != 0) + { + if (result == ENOENT) + { + return storage_sync_copy_file(pStorageServer, + pReader, pRecord, + STORAGE_PROTO_CMD_SYNC_CREATE_FILE); + } + else if (result == EEXIST) + { + logDebug("file: "__FILE__", line: %d, " + "storage server ip: %s:%d, data file: %s " + "already exists", __LINE__, pStorageServer->ip_addr, + pStorageServer->port, pRecord->filename); + return 0; + } + else + { + logError("file: "__FILE__", line: %d, " + "fdfs_recv_response fail, result: %d", + __LINE__, result); + } + } + + return result; +} + #define STARAGE_CHECK_IF_NEED_SYNC_OLD(pReader, pRecord) \ if ((!pReader->need_sync_old) || pReader->sync_old_done || \ (pRecord->timestamp > pReader->until_timestamp)) \ @@ -1022,6 +1139,10 @@ static int storage_sync_data(StorageBinLogReader *pReader, \ result = storage_sync_truncate_file(pStorageServer, \ pReader, pRecord); break; + case STORAGE_OP_TYPE_SOURCE_RENAME_FILE: + result = storage_sync_rename_file(pStorageServer, + pReader, pRecord); + break; case STORAGE_OP_TYPE_SOURCE_CREATE_LINK: result = storage_sync_link_file(pStorageServer, \ pRecord); @@ -1048,6 +1169,11 @@ static int storage_sync_data(StorageBinLogReader *pReader, \ result = storage_sync_link_file(pStorageServer, \ pRecord); break; + case STORAGE_OP_TYPE_REPLICA_RENAME_FILE: + STARAGE_CHECK_IF_NEED_SYNC_OLD(pReader, pRecord) + result = storage_sync_rename_file(pStorageServer, + pReader, pRecord); + break; case STORAGE_OP_TYPE_REPLICA_APPEND_FILE: return 0; case STORAGE_OP_TYPE_REPLICA_MODIFY_FILE: @@ -2560,8 +2686,10 @@ int storage_binlog_read(StorageBinLogReader *pReader, \ memcpy(pRecord->filename, cols[2], pRecord->filename_len); *(pRecord->filename + pRecord->filename_len) = '\0'; - if (pRecord->op_type == STORAGE_OP_TYPE_SOURCE_CREATE_LINK || \ - pRecord->op_type == STORAGE_OP_TYPE_REPLICA_CREATE_LINK) + if (pRecord->op_type == STORAGE_OP_TYPE_SOURCE_CREATE_LINK || + pRecord->op_type == STORAGE_OP_TYPE_REPLICA_CREATE_LINK || + pRecord->op_type == STORAGE_OP_TYPE_SOURCE_RENAME_FILE || + pRecord->op_type == STORAGE_OP_TYPE_REPLICA_RENAME_FILE) { char *p; @@ -2573,14 +2701,14 @@ int storage_binlog_read(StorageBinLogReader *pReader, \ } else { - pRecord->src_filename_len = pRecord->filename_len - \ + pRecord->src_filename_len = pRecord->filename_len - (p - pRecord->filename) - 1; pRecord->filename_len = p - pRecord->filename; *p = '\0'; - memcpy(pRecord->src_filename, p + 1, \ + memcpy(pRecord->src_filename, p + 1, pRecord->src_filename_len); - *(pRecord->src_filename + \ + *(pRecord->src_filename + pRecord->src_filename_len) = '\0'; } } @@ -2591,8 +2719,8 @@ int storage_binlog_read(StorageBinLogReader *pReader, \ } pRecord->true_filename_len = pRecord->filename_len; - if ((result=storage_split_filename_ex(pRecord->filename, \ - &pRecord->true_filename_len, pRecord->true_filename, \ + if ((result=storage_split_filename_ex(pRecord->filename, + &pRecord->true_filename_len, pRecord->true_filename, &pRecord->store_path_index)) != 0) { return result; From ec34a1f8449c01e83877dbf1ac9a682bb5415144 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 13 Nov 2019 16:48:49 +0800 Subject: [PATCH 21/95] fdfs_file_info.c change crc32 output format --- client/fdfs_file_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/fdfs_file_info.c b/client/fdfs_file_info.c index cb06e7f..f8b41ae 100644 --- a/client/fdfs_file_info.c +++ b/client/fdfs_file_info.c @@ -79,7 +79,7 @@ int main(int argc, char *argv[]) szDatetime, sizeof(szDatetime))); printf("file size: %"PRId64"\n", \ file_info.file_size); - printf("file crc32: %d (0x%08X)\n", \ + printf("file crc32: %d (0x%08x)\n", \ file_info.crc32, file_info.crc32); } From 6712843a80bf2a6a1d30431adfd67588abb6c2af Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 13 Nov 2019 16:55:11 +0800 Subject: [PATCH 22/95] upgrade version to V6.0.2 --- fastdfs.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastdfs.spec b/fastdfs.spec index 7431efa..ff51a9d 100644 --- a/fastdfs.spec +++ b/fastdfs.spec @@ -3,7 +3,7 @@ %define FDFSClient libfdfsclient %define FDFSClientDevel libfdfsclient-devel %define FDFSTool fastdfs-tool -%define FDFSVersion 6.0.1 +%define FDFSVersion 6.0.2 %define CommitVersion %(echo $COMMIT_VERSION) Name: %{FastDFS} From 86cd69ed2dfb5cc662e36498d11d63ed60950974 Mon Sep 17 00:00:00 2001 From: saintkay <30994129+SaintKayLuk@users.noreply.github.com> Date: Wed, 13 Nov 2019 17:15:22 +0800 Subject: [PATCH 23/95] Update Dockerfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改小bug --- docker/dockerfile_network/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/dockerfile_network/Dockerfile b/docker/dockerfile_network/Dockerfile index ff82a1b..8a7bdb3 100644 --- a/docker/dockerfile_network/Dockerfile +++ b/docker/dockerfile_network/Dockerfile @@ -1,5 +1,5 @@ # centos 7 -FROM centos +FROM centos:7 # 添加配置文件 ADD conf/client.conf /etc/fdfs/ ADD conf/http.conf /etc/fdfs/ @@ -11,7 +11,7 @@ ADD conf/nginx.conf /etc/fdfs/ ADD conf/mod_fastdfs.conf /etc/fdfs # run -RUN yum install git gcc gcc-c ++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y \ +RUN yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y \ && cd /usr/local/src \ && git clone https://github.com/happyfish100/libfastcommon.git --depth 1 \ && git clone https://github.com/happyfish100/fastdfs.git --depth 1 \ From 52ac538a71fc9753c9dbcd4c75f581e9402f39a5 Mon Sep 17 00:00:00 2001 From: saintkay <30994129+SaintKayLuk@users.noreply.github.com> Date: Wed, 13 Nov 2019 17:22:45 +0800 Subject: [PATCH 24/95] Update Dockerfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加centos版本, gcc-c ++ 改为gcc-c++ --- docker/dockerfile_local/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/dockerfile_local/Dockerfile b/docker/dockerfile_local/Dockerfile index 79df7bc..d7b7179 100644 --- a/docker/dockerfile_local/Dockerfile +++ b/docker/dockerfile_local/Dockerfile @@ -1,5 +1,5 @@ # centos 7 -FROM centos +FROM centos:7 # 添加配置文件 # add profiles ADD conf/client.conf /etc/fdfs/ @@ -19,7 +19,7 @@ ADD source/fastdfs-nginx-module.tar.gz /usr/local/src/ ADD source/nginx-1.15.4.tar.gz /usr/local/src/ # Run -RUN yum install git gcc gcc-c ++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y \ +RUN yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vim -y \ && mkdir /home/dfs \ && cd /usr/local/src/ \ && cd libfastcommon/ \ From 21c52cf4068fee00c349c776e6608fc81ced6dc7 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 14 Nov 2019 19:19:11 +0800 Subject: [PATCH 25/95] dual IPs support two different types of inner (intranet) IPs --- HISTORY | 3 + client/storage_client.c | 2 +- storage/storage_disk_recovery.c | 15 +++-- storage/storage_func.c | 16 ++--- storage/storage_global.c | 8 ++- storage/storage_ip_changed_dealer.c | 12 ++-- storage/storage_sync_func.c | 7 +- tracker/fdfs_server_id_func.c | 2 +- tracker/fdfs_shared_func.c | 101 ++++++++++++++++++++-------- tracker/fdfs_shared_func.h | 8 +++ tracker/tracker_mem.c | 22 +++--- tracker/tracker_service.c | 2 +- tracker/tracker_types.h | 10 ++- 13 files changed, 138 insertions(+), 70 deletions(-) diff --git a/HISTORY b/HISTORY index 5ae2c36..73e9214 100644 --- a/HISTORY +++ b/HISTORY @@ -1,4 +1,7 @@ +Version 6.03 2019-11-14 + * dual IPs support two different types of inner (intranet) IPs + Version 6.02 2019-11-12 * get_file_info calculate CRC32 for appender file type * disk recovery download file to local temp file then rename it diff --git a/client/storage_client.c b/client/storage_client.c index ec38e95..da87f81 100644 --- a/client/storage_client.c +++ b/client/storage_client.c @@ -2181,7 +2181,7 @@ int fdfs_get_file_info_ex(const char *group_name, const char *remote_filename, \ if (pStorageId != NULL) { strcpy(pFileInfo->source_ip_addr, - pStorageId->ip_addrs.ips[0]); + pStorageId->ip_addrs.ips[0].address); } else { diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index b36724a..7428edd 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -135,14 +135,14 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) while (g_continue_flag) { result = tracker_get_storage_max_status(&g_tracker_group, - g_group_name, g_tracker_client_ip.ips[0], + g_group_name, g_tracker_client_ip.ips[0].address, g_my_server_id_str, &saved_storage_status); if (result == ENOENT) { logWarning("file: "__FILE__", line: %d, " \ "current storage: %s does not exist " \ "in tracker server", __LINE__, \ - g_tracker_client_ip.ips[0]); + g_tracker_client_ip.ips[0].address); return ENOENT; } @@ -153,7 +153,7 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) logInfo("file: "__FILE__", line: %d, " \ "current storage: %s 's status is %d" \ ", does not need recovery", __LINE__, \ - g_tracker_client_ip.ips[0], \ + g_tracker_client_ip.ips[0].address, \ saved_storage_status); return ENOENT; } @@ -164,7 +164,8 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) logWarning("file: "__FILE__", line: %d, " \ "current storage: %s 's status is %d" \ ", does not need recovery", __LINE__, \ - g_tracker_client_ip.ips[0], saved_storage_status); + g_tracker_client_ip.ips[0].address, + saved_storage_status); return ENOENT; } @@ -861,7 +862,8 @@ int storage_disk_recovery_restore(const char *pBasePath) while (g_continue_flag) { if (storage_report_storage_status(g_my_server_id_str, \ - g_tracker_client_ip.ips[0], saved_storage_status) == 0) + g_tracker_client_ip.ips[0].address, + saved_storage_status) == 0) { break; } @@ -1147,7 +1149,8 @@ int storage_disk_recovery_start(const int store_path_index) while (g_continue_flag) { if (storage_report_storage_status(g_my_server_id_str, \ - g_tracker_client_ip.ips[0], FDFS_STORAGE_STATUS_RECOVERY) == 0) + g_tracker_client_ip.ips[0].address, + FDFS_STORAGE_STATUS_RECOVERY) == 0) { break; } diff --git a/storage/storage_func.c b/storage/storage_func.c index 3102a15..b5e9690 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -210,7 +210,7 @@ static int tracker_get_my_server_id() struct in_addr ip_addr; char ip_str[256]; - if (inet_pton(AF_INET, g_tracker_client_ip.ips[0], &ip_addr) == 1) + if (inet_pton(AF_INET, g_tracker_client_ip.ips[0].address, &ip_addr) == 1) { g_server_id_in_filename = ip_addr.s_addr; } @@ -218,7 +218,7 @@ static int tracker_get_my_server_id() { logError("file: "__FILE__", line: %d, " \ "call inet_pton for ip: %s fail", \ - __LINE__, g_tracker_client_ip.ips[0]); + __LINE__, g_tracker_client_ip.ips[0].address); g_server_id_in_filename = INADDR_NONE; } @@ -234,7 +234,7 @@ static int tracker_get_my_server_id() } result = tracker_get_storage_id(pTrackerServer, - g_group_name, g_tracker_client_ip.ips[0], + g_group_name, g_tracker_client_ip.ips[0].address, g_my_server_id_str); tracker_close_connection_ex(pTrackerServer, result != 0); if (result != 0) @@ -250,7 +250,7 @@ static int tracker_get_my_server_id() else { snprintf(g_my_server_id_str, sizeof(g_my_server_id_str), "%s", - g_tracker_client_ip.ips[0]); + g_tracker_client_ip.ips[0].address); } fdfs_multi_ips_to_string(&g_tracker_client_ip, @@ -2141,7 +2141,7 @@ int storage_set_tracker_client_ips(ConnectionInfo *conn, for (i = 0; i < multi_ip.count; i++) { result = storage_insert_ip_addr_to_multi_ips(&g_tracker_client_ip, - multi_ip.ips[i], multi_ip.count); + multi_ip.ips[i].address, multi_ip.count); if (result == 0) { if ((result=fdfs_check_and_format_ips(&g_tracker_client_ip, @@ -2152,12 +2152,12 @@ int storage_set_tracker_client_ips(ConnectionInfo *conn, "my ip: %s not valid, error info: %s. " "program exit!", __LINE__, conn->ip_addr, conn->port, - multi_ip.ips[i], error_info); + multi_ip.ips[i].address, error_info); return result; } - insert_into_local_host_ip(multi_ip.ips[i]); + insert_into_local_host_ip(multi_ip.ips[i].address); } else if (result != EEXIST) { @@ -2170,7 +2170,7 @@ int storage_set_tracker_client_ips(ConnectionInfo *conn, "my ip: %s not consistent with client ips: %s " "of other tracker client. program exit!", __LINE__, conn->ip_addr, conn->port, - multi_ip.ips[i], ip_str); + multi_ip.ips[i].address, ip_str); return result; } diff --git a/storage/storage_global.c b/storage/storage_global.c index 0d7f0a1..954a5f0 100644 --- a/storage/storage_global.c +++ b/storage/storage_global.c @@ -141,13 +141,14 @@ int storage_insert_ip_addr_to_multi_ips(FDFSMultiIP *multi_ip, if (multi_ip->count == 0) { multi_ip->count = 1; - strcpy(multi_ip->ips[0], ip_addr); + multi_ip->ips[0].type = fdfs_get_ip_type(ip_addr); + strcpy(multi_ip->ips[0].address, ip_addr); return 0; } for (i = 0; i < multi_ip->count; i++) { - if (strcmp(multi_ip->ips[i], ip_addr) == 0) + if (strcmp(multi_ip->ips[i].address, ip_addr) == 0) { return EEXIST; } @@ -158,7 +159,8 @@ int storage_insert_ip_addr_to_multi_ips(FDFSMultiIP *multi_ip, return ENOSPC; } - strcpy(multi_ip->ips[i], ip_addr); + multi_ip->ips[i].type = fdfs_get_ip_type(ip_addr); + strcpy(multi_ip->ips[i].address, ip_addr); multi_ip->count++; return 0; } diff --git a/storage/storage_ip_changed_dealer.c b/storage/storage_ip_changed_dealer.c index 12c1d99..91d7748 100644 --- a/storage/storage_ip_changed_dealer.c +++ b/storage/storage_ip_changed_dealer.c @@ -76,9 +76,9 @@ static int storage_report_ip_changed(ConnectionInfo *pTrackerServer) pHeader->cmd = TRACKER_PROTO_CMD_STORAGE_REPORT_IP_CHANGED; strcpy(out_buff + sizeof(TrackerHeader), g_group_name); strcpy(out_buff + sizeof(TrackerHeader) + FDFS_GROUP_NAME_MAX_LEN, \ - g_last_storage_ip.ips[0]); + g_last_storage_ip.ips[0].address); strcpy(out_buff + sizeof(TrackerHeader) + FDFS_GROUP_NAME_MAX_LEN + \ - IP_ADDRESS_SIZE, g_tracker_client_ip.ips[0]); + IP_ADDRESS_SIZE, g_tracker_client_ip.ips[0].address); if((result=tcpsenddata_nb(pTrackerServer->sock, out_buff, \ sizeof(out_buff), g_fdfs_network_timeout)) != 0) @@ -208,15 +208,15 @@ static int storage_report_storage_ip_addr() logDebug("file: "__FILE__", line: %d, " "last my ip is %s, current my ip is %s", - __LINE__, g_last_storage_ip.ips[0], - g_tracker_client_ip.ips[0]); + __LINE__, g_last_storage_ip.ips[0].address, + g_tracker_client_ip.ips[0].address); if (g_last_storage_ip.count == 0) { return storage_write_to_sync_ini_file(); } - else if (strcmp(g_tracker_client_ip.ips[0], - g_last_storage_ip.ips[0]) == 0) + else if (strcmp(g_tracker_client_ip.ips[0].address, + g_last_storage_ip.ips[0].address) == 0) { return 0; } diff --git a/storage/storage_sync_func.c b/storage/storage_sync_func.c index ec8b9d3..e1bd700 100644 --- a/storage/storage_sync_func.c +++ b/storage/storage_sync_func.c @@ -67,7 +67,8 @@ void storage_sync_connect_storage_server_ex(FDFSStorageBrief *pStorage, { ip_addrs.count = 1; ip_addrs.index = 0; - strcpy(ip_addrs.ips[0], pStorage->ip_addr); + ip_addrs.ips[0].type = fdfs_get_ip_type(pStorage->ip_addr); + strcpy(ip_addrs.ips[0].address, pStorage->ip_addr); } conn->sock = -1; @@ -81,7 +82,7 @@ void storage_sync_connect_storage_server_ex(FDFSStorageBrief *pStorage, { for (i=0; iip_addr, ip_addrs.ips[i]); + strcpy(conn->ip_addr, ip_addrs.ips[i].address); conn->sock = socketCreateExAuto(conn->ip_addr, g_fdfs_connect_timeout, O_NONBLOCK, g_client_bind_addr ? g_bind_addr : NULL, &result); @@ -148,7 +149,7 @@ void storage_sync_connect_storage_server_ex(FDFSStorageBrief *pStorage, logError("file: "__FILE__", line: %d, " "connect to storage server %s:%d fail, " "try count: %d, errno: %d, error info: %s", - __LINE__, ip_addrs.ips[i], g_server_port, avg_fails, + __LINE__, ip_addrs.ips[i].address, g_server_port, avg_fails, conn_results[i], STRERROR(conn_results[i])); } } diff --git a/tracker/fdfs_server_id_func.c b/tracker/fdfs_server_id_func.c index 3ddb489..6db18e1 100644 --- a/tracker/fdfs_server_id_func.c +++ b/tracker/fdfs_server_id_func.c @@ -177,7 +177,7 @@ static int fdfs_init_ip_array(FDFSStorageIdMapArray *mapArray, { idMap->idInfo = idInfo; idMap->group_name = idInfo->group_name; - idMap->ip_addr = idInfo->ip_addrs.ips[i]; + idMap->ip_addr = idInfo->ip_addrs.ips[i].address; idMap->port = idInfo->port; idMap++; } diff --git a/tracker/fdfs_shared_func.c b/tracker/fdfs_shared_func.c index 392a959..f99d303 100644 --- a/tracker/fdfs_shared_func.c +++ b/tracker/fdfs_shared_func.c @@ -17,6 +17,7 @@ #include "fdfs_global.h" #include "fdfs_shared_func.h" + bool fdfs_server_contain(TrackerServerInfo *pServerInfo, const char *target_ip, const int target_port) { @@ -495,11 +496,40 @@ int fdfs_server_info_to_string_ex(const TrackerServerInfo *pServer, return len; } +int fdfs_get_ip_type(const char* ip) +{ + if (ip == NULL || (int)strlen(ip) < 8) + { + return FDFS_IP_TYPE_UNKNOWN; + } + + if (memcmp(ip, "10.", 3) == 0) + { + return FDFS_IP_TYPE_PRIVATE_10; + } + if (memcmp(ip, "192.168.", 8) == 0) + { + return FDFS_IP_TYPE_PRIVATE_192; + } + + if (memcmp(ip, "172.", 4) == 0) + { + int b; + b = atoi(ip + 4); + if (b >= 16 && b < 32) + { + return FDFS_IP_TYPE_PRIVATE_172; + } + } + + return FDFS_IP_TYPE_OUTER; +} + int fdfs_check_server_ips(const TrackerServerInfo *pServer, char *error_info, const int error_size) { - int private0; - int private1; + int type0; + int type1; if (pServer->count == 1) { *error_info = '\0'; @@ -521,13 +551,14 @@ int fdfs_check_server_ips(const TrackerServerInfo *pServer, return EINVAL; } - private0 = is_private_ip(pServer->connections[0].ip_addr) ? 1 : 0; - private1 = is_private_ip(pServer->connections[1].ip_addr) ? 1 : 0; - if ((private0 ^ private1) == 0) + type0 = fdfs_get_ip_type(pServer->connections[0].ip_addr); + type1 = fdfs_get_ip_type(pServer->connections[1].ip_addr); + if (type0 == type1) { snprintf(error_info, error_size, "invalid ip addresses %s and %s, " - "one MUST be an inner IP and another is a outer IP", + "one MUST be an inner IP and another is a outer IP, " + "or two different types of inner IP addresses", pServer->connections[0].ip_addr, pServer->connections[1].ip_addr); return EINVAL; @@ -549,8 +580,8 @@ int fdfs_parse_multi_ips_ex(char *ip_str, FDFSMultiIP *ip_addrs, { if (resolve) { - if (getIpaddrByName(hosts[i], ip_addrs->ips[i], - sizeof(ip_addrs->ips[i])) == INADDR_NONE) + if (getIpaddrByName(hosts[i], ip_addrs->ips[i].address, + sizeof(ip_addrs->ips[i].address)) == INADDR_NONE) { snprintf(error_info, error_size, "host \"%s\" is invalid, error info: %s", @@ -560,7 +591,17 @@ int fdfs_parse_multi_ips_ex(char *ip_str, FDFSMultiIP *ip_addrs, } else { - snprintf(ip_addrs->ips[i], sizeof(ip_addrs->ips[i]), "%s", hosts[i]); + snprintf(ip_addrs->ips[i].address, + sizeof(ip_addrs->ips[i].address), "%s", hosts[i]); + } + + ip_addrs->ips[i].type = fdfs_get_ip_type(ip_addrs->ips[i].address); + if (ip_addrs->ips[i].type == FDFS_IP_TYPE_UNKNOWN) + { + snprintf(error_info, error_size, + "ip address \"%s\" is invalid", + ip_addrs->ips[i].address); + return EINVAL; } } @@ -582,14 +623,14 @@ int fdfs_multi_ips_to_string_ex(const FDFSMultiIP *ip_addrs, if (ip_addrs->count == 1) { return snprintf(buff, buffSize, "%s", - ip_addrs->ips[0]); + ip_addrs->ips[0].address); } - len = snprintf(buff, buffSize, "%s", ip_addrs->ips[0]); + len = snprintf(buff, buffSize, "%s", ip_addrs->ips[0].address); for (i=1; icount; i++) { len += snprintf(buff + len, buffSize - len, "%c%s", - seperator, ip_addrs->ips[i]); + seperator, ip_addrs->ips[i].address); } return len; } @@ -597,10 +638,11 @@ int fdfs_multi_ips_to_string_ex(const FDFSMultiIP *ip_addrs, const char *fdfs_get_ipaddr_by_peer_ip(const FDFSMultiIP *ip_addrs, const char *client_ip) { + int ip_type; int index; if (ip_addrs->count == 1) { - return ip_addrs->ips[0]; + return ip_addrs->ips[0].address; } if (ip_addrs->count <= 0) @@ -608,15 +650,16 @@ const char *fdfs_get_ipaddr_by_peer_ip(const FDFSMultiIP *ip_addrs, return ""; } - index = is_private_ip(client_ip) ? FDFS_MULTI_IP_INDEX_INNER : FDFS_MULTI_IP_INDEX_OUTER; - return ip_addrs->ips[index]; + ip_type = fdfs_get_ip_type(client_ip); + index = ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER].type == ip_type ? + FDFS_MULTI_IP_INDEX_INNER : FDFS_MULTI_IP_INDEX_OUTER; + return ip_addrs->ips[index].address; } int fdfs_check_and_format_ips(FDFSMultiIP *ip_addrs, char *error_info, const int error_size) { - int privates[FDFS_MULTI_IP_MAX_COUNT]; - char swap_ip[IP_ADDRESS_SIZE]; + FDFSIPInfo swap_ip; if (ip_addrs->count == 1) { *error_info = '\0'; @@ -638,23 +681,23 @@ int fdfs_check_and_format_ips(FDFSMultiIP *ip_addrs, return EINVAL; } - privates[0] = is_private_ip(ip_addrs->ips[0]) ? 1 : 0; - privates[1] = is_private_ip(ip_addrs->ips[1]) ? 1 : 0; - if ((privates[0] ^ privates[1]) == 0) + if (ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER].type == + ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER].type) { snprintf(error_info, error_size, "invalid ip addresses %s and %s, " - "one MUST be an inner IP and another is a outer IP", - ip_addrs->ips[0], ip_addrs->ips[1]); + "one MUST be an inner IP and another is a outer IP, " + "or two different types of inner IP addresses", + ip_addrs->ips[0].address, ip_addrs->ips[1].address); return EINVAL; } - if (!privates[FDFS_MULTI_IP_INDEX_INNER]) + if (ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER].type == FDFS_IP_TYPE_OUTER) { - strcpy(swap_ip, ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER]); - strcpy(ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER], - ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER]); - strcpy(ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER], swap_ip); + swap_ip = ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER]; + ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER] = + ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER]; + ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER] = swap_ip; } *error_info = '\0'; @@ -671,7 +714,7 @@ void fdfs_set_multi_ip_index(FDFSMultiIP *multi_ip, const char *target_ip) for (i=0; icount; i++) { - if (strcmp(multi_ip->ips[i], target_ip) == 0) + if (strcmp(multi_ip->ips[i].address, target_ip) == 0) { multi_ip->index = i; break; @@ -697,6 +740,6 @@ void fdfs_set_server_info_ex(TrackerServerInfo *pServer, for (i=0; icount; i++) { conn_pool_set_server_info(pServer->connections + i, - ip_addrs->ips[i], port); + ip_addrs->ips[i].address, port); } } diff --git a/tracker/fdfs_shared_func.h b/tracker/fdfs_shared_func.h index fdec711..dd6a026 100644 --- a/tracker/fdfs_shared_func.h +++ b/tracker/fdfs_shared_func.h @@ -17,6 +17,12 @@ #include "tracker_types.h" #include "fdfs_server_id_func.h" +#define FDFS_IP_TYPE_UNKNOWN 0 +#define FDFS_IP_TYPE_PRIVATE_10 1 +#define FDFS_IP_TYPE_PRIVATE_172 2 +#define FDFS_IP_TYPE_PRIVATE_192 3 +#define FDFS_IP_TYPE_OUTER 4 + #ifdef __cplusplus extern "C" { #endif @@ -124,6 +130,8 @@ static inline int fdfs_parse_multi_ips(char *ip_str, FDFSMultiIP *ip_addrs, error_info, error_size, resolve); } +int fdfs_get_ip_type(const char* ip); + int fdfs_check_server_ips(const TrackerServerInfo *pServer, char *error_info, const int error_size); diff --git a/tracker/tracker_mem.c b/tracker/tracker_mem.c index 859db57..827a8b8 100644 --- a/tracker/tracker_mem.c +++ b/tracker/tracker_mem.c @@ -3448,9 +3448,9 @@ int tracker_mem_delete_storage(FDFSGroupInfo *pGroup, const char *id) } } - logDebug("file: "__FILE__", line: %d, " \ - "delete storage server: %s:%d, group: %s", \ - __LINE__, pStorageServer->ip_addrs.ips[0], + logDebug("file: "__FILE__", line: %d, " + "delete storage server: %s:%d, group: %s", + __LINE__, pStorageServer->ip_addrs.ips[0].address, pStorageServer->storage_port, pGroup->group_name); tracker_mem_clear_storage_fields(pStorageServer); @@ -3536,16 +3536,18 @@ int tracker_mem_storage_ip_changed(FDFSGroupInfo *pGroup, \ pthread_mutex_lock(&mem_thread_lock); //exchange old and new storage server - snprintf(pOldStorageServer->id, sizeof(pOldStorageServer->id), \ + snprintf(pOldStorageServer->id, sizeof(pOldStorageServer->id), "%s", new_storage_ip); - snprintf(pOldStorageServer->ip_addrs.ips[0], - sizeof(pOldStorageServer->ip_addrs.ips[0]), "%s", new_storage_ip); + snprintf(pOldStorageServer->ip_addrs.ips[0].address, + sizeof(pOldStorageServer->ip_addrs.ips[0].address), + "%s", new_storage_ip); - snprintf(pNewStorageServer->id, sizeof(pNewStorageServer->id), \ + snprintf(pNewStorageServer->id, sizeof(pNewStorageServer->id), "%s", old_storage_ip); pNewStorageServer->ip_addrs.count = 1; - snprintf(pNewStorageServer->ip_addrs.ips[0], - sizeof(pNewStorageServer->ip_addrs.ips[0]), "%s", old_storage_ip); + snprintf(pNewStorageServer->ip_addrs.ips[0].address, + sizeof(pNewStorageServer->ip_addrs.ips[0].address), + "%s", old_storage_ip); pNewStorageServer->status = FDFS_STORAGE_STATUS_IP_CHANGED; pGroup->chg_count++; @@ -3684,7 +3686,7 @@ static int _tracker_mem_add_storage(FDFSGroupInfo *pGroup, { multi_ip.count = 1; multi_ip.index = 0; - strcpy(multi_ip.ips[0], ip_addr); + strcpy(multi_ip.ips[0].address, ip_addr); } if (id != NULL) diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 02ca9bb..5095860 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -1054,7 +1054,7 @@ static int tracker_deal_server_get_storage_status(struct fast_task_info *pTask) pDest = (FDFSStorageBrief *)(pTask->data + sizeof(TrackerHeader)); memset(pDest, 0, sizeof(FDFSStorageBrief)); strcpy(pDest->id, pStorage->id); - strcpy(pDest->ip_addr, pStorage->ip_addrs.ips[0]); + strcpy(pDest->ip_addr, pStorage->ip_addrs.ips[0].address); pDest->status = pStorage->status; int2buff(pGroup->storage_port, pDest->port); diff --git a/tracker/tracker_types.h b/tracker/tracker_types.h index 40cb765..3974f2c 100644 --- a/tracker/tracker_types.h +++ b/tracker/tracker_types.h @@ -276,15 +276,21 @@ typedef struct char sz_last_heart_beat_time[8]; } FDFSStorageStatBuff; +typedef struct StructFDFSIPInfo +{ + int type; //ip type + char address[IP_ADDRESS_SIZE]; +} FDFSIPInfo; + typedef struct StructFDFSMultiIP { int count; int index; - char ips[FDFS_MULTI_IP_MAX_COUNT][IP_ADDRESS_SIZE]; + FDFSIPInfo ips[FDFS_MULTI_IP_MAX_COUNT]; } FDFSMultiIP; #define FDFS_CURRENT_IP_ADDR(pServer) \ - (pServer)->ip_addrs.ips[(pServer)->ip_addrs.index] + (pServer)->ip_addrs.ips[(pServer)->ip_addrs.index].address typedef struct StructFDFSStorageDetail { From 41855a42479a38bfcf43f1e1654635677d78b717 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 14 Nov 2019 21:02:02 +0800 Subject: [PATCH 26/95] get_ipaddr_by_peer_ip refined --- tracker/fdfs_shared_func.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tracker/fdfs_shared_func.c b/tracker/fdfs_shared_func.c index f99d303..0d52ecc 100644 --- a/tracker/fdfs_shared_func.c +++ b/tracker/fdfs_shared_func.c @@ -651,8 +651,8 @@ const char *fdfs_get_ipaddr_by_peer_ip(const FDFSMultiIP *ip_addrs, } ip_type = fdfs_get_ip_type(client_ip); - index = ip_addrs->ips[FDFS_MULTI_IP_INDEX_INNER].type == ip_type ? - FDFS_MULTI_IP_INDEX_INNER : FDFS_MULTI_IP_INDEX_OUTER; + index = ip_addrs->ips[FDFS_MULTI_IP_INDEX_OUTER].type == ip_type ? + FDFS_MULTI_IP_INDEX_OUTER : FDFS_MULTI_IP_INDEX_INNER; return ip_addrs->ips[index].address; } From afc4fa23469fa22f8e876e72cbe61f005e1fe557 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 15 Nov 2019 08:19:36 +0800 Subject: [PATCH 27/95] code stype little adjust --- storage/tracker_client_thread.c | 7 ++++--- tracker/fdfs_shared_func.c | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index 05861e3..a394110 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -1922,9 +1922,10 @@ int tracker_report_join(ConnectionInfo *pTrackerServer, \ pTargetServer = &targetServer; strcpy(targetServer.server.id, g_my_server_id_str); - ppFound = (FDFSStorageServer **)bsearch(&pTargetServer, \ - g_sorted_storages, g_storage_count, \ - sizeof(FDFSStorageServer *), storage_cmp_by_server_id); + ppFound = (FDFSStorageServer **)bsearch(&pTargetServer, + g_sorted_storages, g_storage_count, + sizeof(FDFSStorageServer *), + storage_cmp_by_server_id); if (ppFound != NULL) { pReqBody->status = (*ppFound)->server.status; diff --git a/tracker/fdfs_shared_func.c b/tracker/fdfs_shared_func.c index 0d52ecc..f283c7c 100644 --- a/tracker/fdfs_shared_func.c +++ b/tracker/fdfs_shared_func.c @@ -151,7 +151,7 @@ void fdfs_server_sock_reset(TrackerServerInfo *pServerInfo) } } -int fdfs_get_tracker_leader_index_ex(TrackerServerGroup *pServerGroup, \ +int fdfs_get_tracker_leader_index_ex(TrackerServerGroup *pServerGroup, const char *leaderIp, const int leaderPort) { TrackerServerInfo *pServer; @@ -174,7 +174,7 @@ int fdfs_get_tracker_leader_index_ex(TrackerServerGroup *pServerGroup, \ return -1; } -int fdfs_parse_storage_reserved_space(IniContext *pIniContext, \ +int fdfs_parse_storage_reserved_space(IniContext *pIniContext, FDFSStorageReservedSpace *pStorageReservedSpace) { int result; From 6ea2f5e1ca6f8c5fcb813b63c03678c4e1e43fc3 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 15 Nov 2019 08:39:59 +0800 Subject: [PATCH 28/95] my_status change to my_result --- storage/storage_func.c | 8 +++---- storage/storage_global.h | 4 ++-- storage/tracker_client_thread.c | 39 ++++++++++++++++++--------------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/storage/storage_func.c b/storage/storage_func.c index b5e9690..7d7cb12 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -1097,7 +1097,7 @@ static int storage_check_tracker_ipaddr(const char *filename) return 0; } -static int init_my_status_per_tracker() +static int init_my_result_per_tracker() { int bytes; TrackerServerInfo *pTrackerServer; @@ -1121,8 +1121,8 @@ static int init_my_status_per_tracker() for (pTrackerServer=g_tracker_group.servers; pTrackerServermy_status = -1; - pReportStatus->src_storage_status = -1; + pReportStatus->my_result = -1; + pReportStatus->src_storage_result = -1; pReportStatus++; } @@ -1947,7 +1947,7 @@ int storage_func_init(const char *filename, \ return result; } - if ((result=init_my_status_per_tracker()) != 0) + if ((result=init_my_result_per_tracker()) != 0) { return result; } diff --git a/storage/storage_global.h b/storage/storage_global.h index f03114e..a2f24b0 100644 --- a/storage/storage_global.h +++ b/storage/storage_global.h @@ -59,8 +59,8 @@ typedef struct typedef struct { - signed char my_status; - signed char src_storage_status; + signed char my_result; + signed char src_storage_result; bool get_my_ip_done; } StorageStatusPerTracker; diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index a394110..f2a1e3b 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -390,14 +390,14 @@ static void *tracker_report_thread_entrance(void *arg) sync_old_done = true; } - g_my_report_status[tracker_index].src_storage_status = + g_my_report_status[tracker_index].src_storage_result = tracker_sync_notify(conn, tracker_index); - if (g_my_report_status[tracker_index].src_storage_status != 0) + if (g_my_report_status[tracker_index].src_storage_result != 0) { int k; for (k=0; kip_addr)) + { + } + + ppFound = (FDFSStorageServer **)bsearch(&pTargetServer, + g_sorted_storages, g_storage_count, sizeof(FDFSStorageServer *), storage_cmp_by_server_id); if (ppFound != NULL) { @@ -1936,14 +1939,14 @@ int tracker_report_join(ConnectionInfo *pTrackerServer, \ { for (i=0; iip_addr, \ pTrackerServer->port, \ (int)sizeof(respBody), in_bytes); - g_my_report_status[tracker_index].my_status = EINVAL; + g_my_report_status[tracker_index].my_result = EINVAL; return EINVAL; } From 22865e05424912bf53a9787f89e08f4a25946ec4 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 15 Nov 2019 13:19:26 +0800 Subject: [PATCH 29/95] storage server request tracker server to change it's status --- HISTORY | 5 +- client/tracker_client.c | 4 +- storage/storage_func.c | 1 + storage/storage_global.h | 6 +- storage/tracker_client_thread.c | 221 +++++++++++++++++++++++++++----- tracker/fdfs_shared_func.c | 1 + tracker/fdfs_shared_func.h | 5 + tracker/tracker_mem.c | 14 +- tracker/tracker_proto.h | 2 + tracker/tracker_service.c | 57 ++++++++ 10 files changed, 275 insertions(+), 41 deletions(-) diff --git a/HISTORY b/HISTORY index 73e9214..c682110 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,9 @@ -Version 6.03 2019-11-14 +Version 6.03 2019-11-15 * dual IPs support two different types of inner (intranet) IPs + * storage server request tracker server to change it's status + to that of tracker leader when the storage server found + it's status inconsistence Version 6.02 2019-11-12 * get_file_info calculate CRC32 for appender file type diff --git a/client/tracker_client.c b/client/tracker_client.c index bbd7ceb..3062b05 100644 --- a/client/tracker_client.c +++ b/client/tracker_client.c @@ -1429,8 +1429,8 @@ int tracker_set_trunk_server(TrackerServerGroup *pTrackerGroup, \ return result; } -int tracker_get_storage_status(ConnectionInfo *pTrackerServer, \ - const char *group_name, const char *ip_addr, \ +int tracker_get_storage_status(ConnectionInfo *pTrackerServer, + const char *group_name, const char *ip_addr, FDFSStorageBrief *pDestBuff) { TrackerHeader *pHeader; diff --git a/storage/storage_func.c b/storage/storage_func.c index 7d7cb12..722da52 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -1121,6 +1121,7 @@ static int init_my_result_per_tracker() for (pTrackerServer=g_tracker_group.servers; pTrackerServermy_status = -1; pReportStatus->my_result = -1; pReportStatus->src_storage_result = -1; pReportStatus++; diff --git a/storage/storage_global.h b/storage/storage_global.h index a2f24b0..3687700 100644 --- a/storage/storage_global.h +++ b/storage/storage_global.h @@ -59,9 +59,11 @@ typedef struct typedef struct { - signed char my_result; - signed char src_storage_result; + signed char my_status; //my status from tracker server + signed char my_result; //my report result + signed char src_storage_result; //src storage report result bool get_my_ip_done; + bool report_my_status; } StorageStatusPerTracker; extern volatile bool g_continue_flag; diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index f2a1e3b..8259830 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -45,12 +45,16 @@ static pthread_mutex_t reporter_thread_lock; static pthread_t *report_tids = NULL; static bool need_rejoin_tracker = false; -static int tracker_heart_beat(ConnectionInfo *pTrackerServer, \ - int *pstat_chg_sync_count, bool *bServerPortChanged); -static int tracker_report_df_stat(ConnectionInfo *pTrackerServer, \ - bool *bServerPortChanged); -static int tracker_report_sync_timestamp(ConnectionInfo *pTrackerServer, \ - bool *bServerPortChanged); +static int tracker_heart_beat(ConnectionInfo *pTrackerServer, + const int tracker_index, int *pstat_chg_sync_count, + bool *bServerPortChanged); +static int tracker_report_df_stat(ConnectionInfo *pTrackerServer, + const int tracker_index, bool *bServerPortChanged); +static int tracker_report_sync_timestamp(ConnectionInfo *pTrackerServer, + const int tracker_index, bool *bServerPortChanged); + +static int tracker_storage_change_status(ConnectionInfo *pTrackerServer, + const int tracker_index); static int tracker_sync_dest_req(ConnectionInfo *pTrackerServer); static int tracker_sync_dest_query(ConnectionInfo *pTrackerServer); @@ -440,11 +444,12 @@ static void *tracker_report_thread_entrance(void *arg) while (g_continue_flag) { current_time = g_current_time; - if (current_time - last_beat_time >= \ + if (current_time - last_beat_time >= g_heart_beat_interval) { - if (tracker_heart_beat(conn, &stat_chg_sync_count, - &bServerPortChanged) != 0) + if (tracker_heart_beat(conn, tracker_index, + &stat_chg_sync_count, + &bServerPortChanged) != 0) { break; } @@ -462,8 +467,9 @@ static void *tracker_report_thread_entrance(void *arg) current_time - last_sync_report_time >= g_heart_beat_interval) { - if (tracker_report_sync_timestamp( - conn, &bServerPortChanged)!=0) + if (tracker_report_sync_timestamp(conn, + tracker_index, + &bServerPortChanged) != 0) { break; } @@ -476,7 +482,8 @@ static void *tracker_report_thread_entrance(void *arg) g_stat_report_interval) { if (tracker_report_df_stat(conn, - &bServerPortChanged) != 0) + tracker_index, + &bServerPortChanged) != 0) { break; } @@ -484,6 +491,16 @@ static void *tracker_report_thread_entrance(void *arg) last_df_report_time = current_time; } + if (g_my_report_status[tracker_index].report_my_status) + { + if (tracker_storage_change_status(conn, tracker_index) == 0) + { + g_my_report_status[tracker_index].report_my_status = false; + } + + break; + } + if (g_if_trunker_self) { if (last_trunk_file_id < g_current_trunk_file_id) @@ -715,8 +732,48 @@ static int tracker_start_sync_threads(const FDFSStorageBrief *pStorage) return result; } +static void tracker_check_my_status(const int tracker_index) +{ + int my_status; + int leader_index; + int leader_status; + + leader_index = g_tracker_group.leader_index; + if ((leader_index < 0) || (tracker_index == leader_index)) + { + return; + } + + my_status = g_my_report_status[tracker_index].my_status; + leader_status = g_my_report_status[leader_index].my_status; + if (my_status < 0 || leader_status < 0) //NOT inited + { + return; + } + if (my_status == leader_status) + { + return; + } + + if (FDFS_IS_AVAILABLE_STATUS(my_status) && + FDFS_IS_AVAILABLE_STATUS(leader_status)) + { + return; + } + + g_my_report_status[tracker_index].report_my_status = true; + + logInfo("file: "__FILE__", line: %d, " + "my status: %d (%s) from tracker #%d != my status: %d (%s)" + "from leader tracker #%d, set report_my_status to true", + __LINE__, my_status, get_storage_status_caption( + my_status), tracker_index, leader_status, + get_storage_status_caption(leader_status), leader_index); +} + static int tracker_merge_servers(ConnectionInfo *pTrackerServer, - FDFSStorageBrief *briefServers, const int server_count) + const int tracker_index, FDFSStorageBrief *briefServers, + const int server_count) { FDFSStorageBrief *pServer; FDFSStorageBrief *pEnd; @@ -742,9 +799,10 @@ static int tracker_merge_servers(ConnectionInfo *pTrackerServer, { memcpy(&(targetServer.server),pServer,sizeof(FDFSStorageBrief)); - - if (is_local_host_ip(pServer->ip_addr)) + if (strcmp(pServer->id, g_my_server_id_str) == 0) { + g_my_report_status[tracker_index].my_status = pServer->status; + tracker_check_my_status(tracker_index); } ppFound = (FDFSStorageServer **)bsearch(&pTargetServer, @@ -1034,6 +1092,21 @@ static int notify_reselect_tracker_leader(TrackerServerInfo *pTrackerServer) return result; } +static void check_my_status_for_all_trackers() +{ + int tracker_index; + + if (g_tracker_group.leader_index < 0) + { + return; + } + for (tracker_index=0; tracker_indexip_addr, pTrackerServer->port, + old_status, get_storage_status_caption(old_status), + new_status, get_storage_status_caption(new_status)); + + body_len = 1; + memset(out_buff, 0, sizeof(out_buff)); + pHeader = (TrackerHeader *)out_buff; + long2buff(body_len, pHeader->pkg_len); + pHeader->cmd = TRACKER_PROTO_CMD_STORAGE_CHANGE_STATUS; + *(out_buff + sizeof(TrackerHeader)) = new_status; + + if((result=tcpsenddata_nb(pTrackerServer->sock, out_buff, + sizeof(TrackerHeader) + body_len, g_fdfs_network_timeout)) != 0) + { + logError("file: "__FILE__", line: %d, " + "tracker server %s:%d, send data fail, " + "errno: %d, error info: %s.", + __LINE__, pTrackerServer->ip_addr, + pTrackerServer->port, + result, STRERROR(result)); + return result; + } + + pInBuff = in_buff; + result = fdfs_recv_response(pTrackerServer, + &pInBuff, sizeof(in_buff), &nInPackLen); + if (result != 0) + { + logError("file: "__FILE__", line: %d, " + "fdfs_recv_response fail, result: %d", + __LINE__, result); + return result; + } + + if (nInPackLen != 0) + { + logError("file: "__FILE__", line: %d, " + "tracker server %s:%d, response body length: %d != 0", + __LINE__, pTrackerServer->ip_addr, pTrackerServer->port, + (int)nInPackLen); + return EINVAL; + } + + return 0; } static int tracker_storage_changelog_req(ConnectionInfo *pTrackerServer) diff --git a/tracker/fdfs_shared_func.c b/tracker/fdfs_shared_func.c index f283c7c..299e00d 100644 --- a/tracker/fdfs_shared_func.c +++ b/tracker/fdfs_shared_func.c @@ -743,3 +743,4 @@ void fdfs_set_server_info_ex(TrackerServerInfo *pServer, ip_addrs->ips[i].address, port); } } + diff --git a/tracker/fdfs_shared_func.h b/tracker/fdfs_shared_func.h index dd6a026..a82080f 100644 --- a/tracker/fdfs_shared_func.h +++ b/tracker/fdfs_shared_func.h @@ -23,6 +23,11 @@ #define FDFS_IP_TYPE_PRIVATE_192 3 #define FDFS_IP_TYPE_OUTER 4 +#define FDFS_IS_AVAILABLE_STATUS(status) \ + (status == FDFS_STORAGE_STATUS_OFFLINE || \ + status == FDFS_STORAGE_STATUS_ONLINE || \ + status == FDFS_STORAGE_STATUS_ACTIVE) + #ifdef __cplusplus extern "C" { #endif diff --git a/tracker/tracker_mem.c b/tracker/tracker_mem.c index 827a8b8..c29b699 100644 --- a/tracker/tracker_mem.c +++ b/tracker/tracker_mem.c @@ -4883,14 +4883,14 @@ int tracker_mem_sync_storages(FDFSGroupInfo *pGroup, \ continue; } - memcpy(target_storage.id, pServer->id, \ + memcpy(target_storage.id, pServer->id, FDFS_STORAGE_ID_MAX_SIZE); pTargetStorage = &target_storage; - if ((ppFound=(FDFSStorageDetail **)bsearch( \ - &pTargetStorage, \ - pGroup->sorted_servers, \ - pGroup->count, \ - sizeof(FDFSStorageDetail *), \ + if ((ppFound=(FDFSStorageDetail **)bsearch( + &pTargetStorage, + pGroup->sorted_servers, + pGroup->count, + sizeof(FDFSStorageDetail *), tracker_mem_cmp_by_storage_id)) != NULL) { if ((*ppFound)->status == pServer->status \ @@ -4948,7 +4948,7 @@ int tracker_mem_sync_storages(FDFSGroupInfo *pGroup, \ &pStorageServer, pServer->id, pServer->ip_addr, true, false, &bInserted); - if (result != 0) + if (result == 0 && bInserted) { pStorageServer->status = pServer->status; } diff --git a/tracker/tracker_proto.h b/tracker/tracker_proto.h index 2d754dc..c47ba85 100644 --- a/tracker/tracker_proto.h +++ b/tracker/tracker_proto.h @@ -35,6 +35,7 @@ #define TRACKER_PROTO_CMD_STORAGE_GET_STATUS 71 //get storage status from tracker #define TRACKER_PROTO_CMD_STORAGE_GET_SERVER_ID 70 //get storage server id from tracker #define TRACKER_PROTO_CMD_STORAGE_GET_MY_IP 60 //get storage server ip from tracker +#define TRACKER_PROTO_CMD_STORAGE_CHANGE_STATUS 59 //current storage can change it's status #define TRACKER_PROTO_CMD_STORAGE_FETCH_STORAGE_IDS 69 //get all storage ids from tracker #define TRACKER_PROTO_CMD_STORAGE_GET_GROUP_NAME 109 //get storage group name from tracker @@ -146,6 +147,7 @@ typedef struct typedef struct { + unsigned char my_status; //storage server status char src_id[FDFS_STORAGE_ID_MAX_SIZE]; //src storage id } TrackerStorageJoinBodyResp; diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 5095860..bb9e3b5 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -1382,6 +1382,59 @@ static int tracker_deal_storage_report_status(struct fast_task_info *pTask) return tracker_mem_sync_storages(pGroup, briefServers, 1); } +static int tracker_deal_storage_change_status(struct fast_task_info *pTask) +{ + TrackerClientInfo *pClientInfo; + int old_status; + int new_status; + + if (pTask->length - sizeof(TrackerHeader) != 1) + { + logError("file: "__FILE__", line: %d, " + "cmd=%d, client ip addr: %s, " + "body size "PKG_LEN_PRINTF_FORMAT" " + "is not correct", __LINE__, + TRACKER_PROTO_CMD_STORAGE_CHANGE_STATUS, + pTask->client_ip, pTask->length - + (int)sizeof(TrackerHeader)); + pTask->length = sizeof(TrackerHeader); + return EINVAL; + } + + pClientInfo = (TrackerClientInfo *)pTask->arg; + old_status = pClientInfo->pStorage->status; + pTask->length = sizeof(TrackerHeader); + + new_status = *(pTask->data + sizeof(TrackerHeader)); + if ((old_status == new_status) || + (FDFS_IS_AVAILABLE_STATUS(old_status) && + FDFS_IS_AVAILABLE_STATUS(new_status))) + { + logInfo("file: "__FILE__", line: %d, " + "client ip: %s, do NOT change storage status, " + "old status: %d (%s), new status: %d (%s)", + __LINE__, pTask->client_ip, + old_status, get_storage_status_caption(old_status), + new_status, get_storage_status_caption(new_status)); + return 0; + } + if (new_status == FDFS_STORAGE_STATUS_ONLINE || + new_status == FDFS_STORAGE_STATUS_ACTIVE) + { + new_status = FDFS_STORAGE_STATUS_OFFLINE; + } + + pClientInfo->pStorage->status = new_status; + tracker_save_storages(); + + logInfo("file: "__FILE__", line: %d, " + "client ip: %s, set storage status from %d (%s) " + "to %d (%s)", __LINE__, pTask->client_ip, + old_status, get_storage_status_caption(old_status), + new_status, get_storage_status_caption(new_status)); + return 0; +} + static int tracker_deal_storage_join(struct fast_task_info *pTask) { TrackerStorageJoinBodyResp *pJoinBodyResp; @@ -3863,6 +3916,10 @@ int tracker_deal_task(struct fast_task_info *pTask) case TRACKER_PROTO_CMD_STORAGE_REPORT_STATUS: result = tracker_deal_storage_report_status(pTask); break; + case TRACKER_PROTO_CMD_STORAGE_CHANGE_STATUS: + TRACKER_CHECK_LOGINED(pTask) + result = tracker_deal_storage_change_status(pTask); + break; case TRACKER_PROTO_CMD_STORAGE_GET_STATUS: result = tracker_deal_server_get_storage_status(pTask); break; From 017fff46f3e86360f8f395cf51f4ae8b92b2f9cf Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 15 Nov 2019 15:05:11 +0800 Subject: [PATCH 30/95] set my_status in storage join response --- HISTORY | 2 ++ tracker/tracker_service.c | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/HISTORY b/HISTORY index c682110..6525d97 100644 --- a/HISTORY +++ b/HISTORY @@ -5,6 +5,8 @@ Version 6.03 2019-11-15 to that of tracker leader when the storage server found it's status inconsistence + NOTE: the tracker and storage server must upgrade together + Version 6.02 2019-11-12 * get_file_info calculate CRC32 for appender file type * disk recovery download file to local temp file then rename it diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index bb9e3b5..1ae44bb 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -1605,17 +1605,18 @@ static int tracker_deal_storage_join(struct fast_task_info *pTask) return result; } - pJoinBodyResp = (TrackerStorageJoinBodyResp *)(pTask->data + \ + pJoinBodyResp = (TrackerStorageJoinBodyResp *)(pTask->data + sizeof(TrackerHeader)); memset(pJoinBodyResp, 0, sizeof(TrackerStorageJoinBodyResp)); + pJoinBodyResp->my_status = pClientInfo->pStorage->status; if (pClientInfo->pStorage->psync_src_server != NULL) { - strcpy(pJoinBodyResp->src_id, \ + strcpy(pJoinBodyResp->src_id, pClientInfo->pStorage->psync_src_server->id); } - pTask->length = sizeof(TrackerHeader) + \ + pTask->length = sizeof(TrackerHeader) + sizeof(TrackerStorageJoinBodyResp); return 0; } From 9dc6742b1eb976bd3452a67516815c1253a8c1e8 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 16 Nov 2019 09:25:57 +0800 Subject: [PATCH 31/95] bugfix: fdfs_monitor fix get index of the specified tracker server --- HISTORY | 3 ++- client/fdfs_monitor.c | 3 +-- client/test/fdfs_monitor.c | 9 ++++++--- common/fdfs_define.h | 2 +- storage/tracker_client_thread.c | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/HISTORY b/HISTORY index 6525d97..942a152 100644 --- a/HISTORY +++ b/HISTORY @@ -1,9 +1,10 @@ -Version 6.03 2019-11-15 +Version 6.03 2019-11-16 * dual IPs support two different types of inner (intranet) IPs * storage server request tracker server to change it's status to that of tracker leader when the storage server found it's status inconsistence + * bugfix: fdfs_monitor fix get index of the specified tracker server NOTE: the tracker and storage server must upgrade together diff --git a/client/fdfs_monitor.c b/client/fdfs_monitor.c index b0a45ca..6bcc703 100644 --- a/client/fdfs_monitor.c +++ b/client/fdfs_monitor.c @@ -131,8 +131,7 @@ int main(int argc, char *argv[]) for (i=0; ilast_synced_timestamp); + if (delay_seconds < 0) + { + delay_seconds = 0; + } day = delay_seconds / (24 * 3600); remain_seconds = delay_seconds % (24 * 3600); hour = remain_seconds / 3600; diff --git a/common/fdfs_define.h b/common/fdfs_define.h index ea71032..92ffe36 100644 --- a/common/fdfs_define.h +++ b/common/fdfs_define.h @@ -14,7 +14,7 @@ #include #include "fastcommon/common_define.h" -#define FDFS_TRACKER_SERVER_DEF_PORT 22000 +#define FDFS_TRACKER_SERVER_DEF_PORT 22122 #define FDFS_STORAGE_SERVER_DEF_PORT 23000 #define FDFS_DEF_STORAGE_RESERVED_MB 1024 #define TRACKER_ERROR_LOG_FILENAME "trackerd" diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index 8259830..096c558 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -764,7 +764,7 @@ static void tracker_check_my_status(const int tracker_index) g_my_report_status[tracker_index].report_my_status = true; logInfo("file: "__FILE__", line: %d, " - "my status: %d (%s) from tracker #%d != my status: %d (%s)" + "my status: %d (%s) from tracker #%d != my status: %d (%s) " "from leader tracker #%d, set report_my_status to true", __LINE__, my_status, get_storage_status_caption( my_status), tracker_index, leader_status, From cb24cd82e10435de9d8132c289d59c5f7da1be70 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 16 Nov 2019 10:53:19 +0800 Subject: [PATCH 32/95] storage server write to data_init_flag and mark file safely --- HISTORY | 2 + storage/fdfs_storaged.c | 2 +- storage/storage_disk_recovery.c | 62 +++++--------- storage/storage_func.c | 66 ++++++-------- storage/storage_func.h | 2 +- storage/storage_service.c | 9 +- storage/storage_sync.c | 147 +++++++++++++------------------- storage/storage_sync.h | 4 +- 8 files changed, 118 insertions(+), 176 deletions(-) diff --git a/HISTORY b/HISTORY index 942a152..a34d1ae 100644 --- a/HISTORY +++ b/HISTORY @@ -5,6 +5,8 @@ Version 6.03 2019-11-16 to that of tracker leader when the storage server found it's status inconsistence * bugfix: fdfs_monitor fix get index of the specified tracker server + * storage server write to data_init_flag and mark file safely + (write to temp file then rename) NOTE: the tracker and storage server must upgrade together diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index c2fc25b..5d49f30 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -107,7 +107,7 @@ int main(int argc, char *argv[]) return result; } - if ((result=storage_check_and_make_data_path()) != 0) + if ((result=storage_check_and_make_global_data_path()) != 0) { log_destroy(); return result; diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 7428edd..1c4c7bd 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -51,7 +51,7 @@ typedef struct { static int saved_storage_status = FDFS_STORAGE_STATUS_NONE; -static char *recovery_get_binlog_filename(const void *pArg, \ +static char *recovery_get_binlog_filename(const void *pArg, char *full_filename); static int storage_do_fetch_binlog(ConnectionInfo *pSrcStorage, \ @@ -289,35 +289,32 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) return 0; } -static char *recovery_get_full_filename(const void *pArg, \ +static char *recovery_get_full_filename(const char *pBasePath, const char *filename, char *full_filename) { - const char *pBasePath; static char buff[MAX_PATH_SIZE]; - pBasePath = (const char *)pArg; if (full_filename == NULL) { full_filename = buff; } - snprintf(full_filename, MAX_PATH_SIZE, \ + snprintf(full_filename, MAX_PATH_SIZE, "%s/data/%s", pBasePath, filename); - return full_filename; } -static char *recovery_get_binlog_filename(const void *pArg, \ - char *full_filename) +static char *recovery_get_binlog_filename(const void *pArg, + char *full_filename) { - return recovery_get_full_filename(pArg, \ + return recovery_get_full_filename((const char *)pArg, RECOVERY_BINLOG_FILENAME, full_filename); } -static char *recovery_get_mark_filename(const void *pArg, \ - char *full_filename) +static char *recovery_get_mark_filename(const char *pBasePath, + char *full_filename) { - return recovery_get_full_filename(pArg, \ + return recovery_get_full_filename(pBasePath, RECOVERY_MARK_FILENAME, full_filename); } @@ -362,16 +359,15 @@ static int recovery_write_to_mark_file(const char *pBasePath, \ char buff[128]; int len; - len = sprintf(buff, \ - "%s=%d\n" \ - "%s=%"PRId64"\n" \ - "%s=1\n", \ - MARK_ITEM_SAVED_STORAGE_STATUS, saved_storage_status, \ - MARK_ITEM_BINLOG_OFFSET, pReader->binlog_offset, \ + len = sprintf(buff, + "%s=%d\n" + "%s=%"PRId64"\n" + "%s=1\n", + MARK_ITEM_SAVED_STORAGE_STATUS, saved_storage_status, + MARK_ITEM_BINLOG_OFFSET, pReader->binlog_offset, MARK_ITEM_FETCH_BINLOG_DONE); - return storage_write_to_fd(pReader->mark_fd, \ - recovery_get_mark_filename, pBasePath, buff, len); + return safeWriteToFile(pReader->mark_filename, buff, len); } static int recovery_init_binlog_file(const char *pBasePath) @@ -406,12 +402,10 @@ static int recovery_init_mark_file(const char *pBasePath, \ static int recovery_reader_init(const char *pBasePath, \ StorageBinLogReader *pReader) { - char full_mark_filename[MAX_PATH_SIZE]; IniContext iniContext; int result; memset(pReader, 0, sizeof(StorageBinLogReader)); - pReader->mark_fd = -1; pReader->binlog_fd = -1; pReader->binlog_index = g_binlog_index + 1; @@ -428,14 +422,15 @@ static int recovery_reader_init(const char *pBasePath, \ } pReader->binlog_buff.current = pReader->binlog_buff.buffer; - recovery_get_mark_filename(pBasePath, full_mark_filename); + recovery_get_mark_filename(pBasePath, pReader->mark_filename); memset(&iniContext, 0, sizeof(IniContext)); - if ((result=iniLoadFromFile(full_mark_filename, &iniContext)) != 0) + if ((result=iniLoadFromFile(pReader->mark_filename, + &iniContext)) != 0) { logError("file: "__FILE__", line: %d, " \ "load from mark file \"%s\" fail, " \ "error code: %d", __LINE__, \ - full_mark_filename, result); + pReader->mark_filename, result); return result; } @@ -447,7 +442,7 @@ static int recovery_reader_init(const char *pBasePath, \ logInfo("file: "__FILE__", line: %d, " \ "mark file \"%s\", %s=0, " \ "need to fetch binlog again", __LINE__, \ - full_mark_filename, MARK_ITEM_FETCH_BINLOG_DONE); + pReader->mark_filename, MARK_ITEM_FETCH_BINLOG_DONE); return EAGAIN; } @@ -459,7 +454,7 @@ static int recovery_reader_init(const char *pBasePath, \ logError("file: "__FILE__", line: %d, " \ "in mark file \"%s\", %s: %d < 0", __LINE__, \ - full_mark_filename, MARK_ITEM_SAVED_STORAGE_STATUS, \ + pReader->mark_filename, MARK_ITEM_SAVED_STORAGE_STATUS, \ saved_storage_status); return EINVAL; } @@ -473,24 +468,13 @@ static int recovery_reader_init(const char *pBasePath, \ logError("file: "__FILE__", line: %d, " \ "in mark file \"%s\", %s: "\ "%"PRId64" < 0", __LINE__, \ - full_mark_filename, MARK_ITEM_BINLOG_OFFSET, \ + pReader->mark_filename, MARK_ITEM_BINLOG_OFFSET, \ pReader->binlog_offset); return EINVAL; } iniFreeContext(&iniContext); - pReader->mark_fd = open(full_mark_filename, O_WRONLY | O_CREAT, 0644); - if (pReader->mark_fd < 0) - { - logError("file: "__FILE__", line: %d, " \ - "open mark file \"%s\" fail, " \ - "error no: %d, error info: %s", \ - __LINE__, full_mark_filename, \ - errno, STRERROR(errno)); - return errno != 0 ? errno : ENOENT; - } - if ((result=storage_open_readable_binlog(pReader, \ recovery_get_binlog_filename, pBasePath)) != 0) { diff --git a/storage/storage_func.c b/storage/storage_func.c index 722da52..fdf39ab 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -628,63 +628,47 @@ int storage_write_to_stat_file() int storage_write_to_sync_ini_file() { char full_filename[MAX_PATH_SIZE]; - char buff[512]; + char buff[4 * 1024]; char ip_str[256]; - int fd; int len; + int result; - snprintf(full_filename, sizeof(full_filename), \ + snprintf(full_filename, sizeof(full_filename), "%s/data/%s", g_fdfs_base_path, DATA_DIR_INITED_FILENAME); - if ((fd=open(full_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) - { - logError("file: "__FILE__", line: %d, " \ - "open file \"%s\" fail, " \ - "errno: %d, error info: %s", \ - __LINE__, full_filename, \ - errno, STRERROR(errno)); - return errno != 0 ? errno : ENOENT; - } fdfs_multi_ips_to_string(&g_tracker_client_ip, ip_str, sizeof(ip_str)); - len = sprintf(buff, "%s=%d\n" \ - "%s=%d\n" \ - "%s=%s\n" \ - "%s=%d\n" \ - "%s=%s\n" \ - "%s=%d\n" \ - "%s=%d\n" \ - "%s=%d\n" \ - "%s=%d\n", \ - INIT_ITEM_STORAGE_JOIN_TIME, g_storage_join_time, \ - INIT_ITEM_SYNC_OLD_DONE, g_sync_old_done, \ - INIT_ITEM_SYNC_SRC_SERVER, g_sync_src_id, \ - INIT_ITEM_SYNC_UNTIL_TIMESTAMP, g_sync_until_timestamp, \ - INIT_ITEM_LAST_IP_ADDRESS, ip_str, \ - INIT_ITEM_LAST_SERVER_PORT, g_last_server_port, \ + len = sprintf(buff, "%s=%d\n" + "%s=%d\n" + "%s=%s\n" + "%s=%d\n" + "%s=%s\n" + "%s=%d\n" + "%s=%d\n" + "%s=%d\n" + "%s=%d\n", + INIT_ITEM_STORAGE_JOIN_TIME, g_storage_join_time, + INIT_ITEM_SYNC_OLD_DONE, g_sync_old_done, + INIT_ITEM_SYNC_SRC_SERVER, g_sync_src_id, + INIT_ITEM_SYNC_UNTIL_TIMESTAMP, g_sync_until_timestamp, + INIT_ITEM_LAST_IP_ADDRESS, ip_str, + INIT_ITEM_LAST_SERVER_PORT, g_last_server_port, INIT_ITEM_LAST_HTTP_PORT, g_last_http_port, - INIT_ITEM_CURRENT_TRUNK_FILE_ID, g_current_trunk_file_id, \ + INIT_ITEM_CURRENT_TRUNK_FILE_ID, g_current_trunk_file_id, INIT_ITEM_TRUNK_LAST_COMPRESS_TIME, (int)g_trunk_last_compress_time ); - if (fc_safe_write(fd, buff, len) != len) - { - logError("file: "__FILE__", line: %d, " \ - "write to file \"%s\" fail, " \ - "errno: %d, error info: %s", \ - __LINE__, full_filename, \ - errno, STRERROR(errno)); - close(fd); - return errno != 0 ? errno : EIO; - } - close(fd); + if ((result=safeWriteToFile(full_filename, buff, len)) != 0) + { + return result; + } STORAGE_CHOWN(full_filename, geteuid(), getegid()) return 0; } -int storage_check_and_make_data_path() +int storage_check_and_make_global_data_path() { char data_path[MAX_PATH_SIZE]; snprintf(data_path, sizeof(data_path), "%s/data", @@ -843,7 +827,7 @@ static int storage_check_and_make_data_dirs() } else { - if ((result=storage_check_and_make_data_path()) != 0) + if ((result=storage_check_and_make_global_data_path()) != 0) { return result; } diff --git a/storage/storage_func.h b/storage/storage_func.h index 712d394..fdc38a9 100644 --- a/storage/storage_func.h +++ b/storage/storage_func.h @@ -37,7 +37,7 @@ bool storage_id_is_myself(const char *storage_id); int storage_set_tracker_client_ips(ConnectionInfo *conn, const int tracker_index); -int storage_check_and_make_data_path(); +int storage_check_and_make_global_data_path(); int storage_logic_to_local_full_filename(const char *logic_filename, const int logic_filename_len, int *store_path_index, diff --git a/storage/storage_service.c b/storage/storage_service.c index 6245d83..8f2c806 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -4386,7 +4386,6 @@ static void fetch_one_path_binlog_finish_clean_up(struct fast_task_info *pTask) { StorageClientInfo *pClientInfo; StorageBinLogReader *pReader; - char full_filename[MAX_PATH_SIZE]; pClientInfo = (StorageClientInfo *)pTask->arg; pReader = (StorageBinLogReader *)pClientInfo->extra_arg; @@ -4398,13 +4397,13 @@ static void fetch_one_path_binlog_finish_clean_up(struct fast_task_info *pTask) pClientInfo->extra_arg = NULL; storage_reader_remove_from_list(pReader); - storage_reader_destroy(pReader); - get_mark_filename_by_reader(pReader, full_filename); - if (fileExists(full_filename)) + get_mark_filename_by_reader(pReader); + if (fileExists(pReader->mark_filename)) { - unlink(full_filename); + unlink(pReader->mark_filename); } + storage_reader_destroy(pReader); free(pReader); } diff --git a/storage/storage_sync.c b/storage/storage_sync.c index f771efe..6f10608 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -1944,53 +1944,45 @@ int storage_open_readable_binlog(StorageBinLogReader *pReader, \ return 0; } -static char *get_mark_filename_by_id_and_port(const char *storage_id, \ +static char *get_mark_filename_by_id_and_port(const char *storage_id, const int port, char *full_filename, const int filename_size) { if (g_use_storage_id) { - snprintf(full_filename, filename_size, \ - "%s/data/"SYNC_DIR_NAME"/%s%s", g_fdfs_base_path, \ + snprintf(full_filename, filename_size, + "%s/data/"SYNC_DIR_NAME"/%s%s", g_fdfs_base_path, storage_id, SYNC_MARK_FILE_EXT); } else { - snprintf(full_filename, filename_size, \ - "%s/data/"SYNC_DIR_NAME"/%s_%d%s", g_fdfs_base_path, \ + snprintf(full_filename, filename_size, + "%s/data/"SYNC_DIR_NAME"/%s_%d%s", g_fdfs_base_path, storage_id, port, SYNC_MARK_FILE_EXT); } return full_filename; } -static char *get_mark_filename_by_ip_and_port(const char *ip_addr, \ +static char *get_mark_filename_by_ip_and_port(const char *ip_addr, const int port, char *full_filename, const int filename_size) { - snprintf(full_filename, filename_size, \ - "%s/data/"SYNC_DIR_NAME"/%s_%d%s", g_fdfs_base_path, \ + snprintf(full_filename, filename_size, + "%s/data/"SYNC_DIR_NAME"/%s_%d%s", g_fdfs_base_path, ip_addr, port, SYNC_MARK_FILE_EXT); return full_filename; } -char *get_mark_filename_by_reader(const void *pArg, char *full_filename) +char *get_mark_filename_by_reader(StorageBinLogReader *pReader) { - const StorageBinLogReader *pReader; - static char buff[MAX_PATH_SIZE]; - - pReader = (const StorageBinLogReader *)pArg; - if (full_filename == NULL) - { - full_filename = buff; - } - - return get_mark_filename_by_id_and_port(pReader->storage_id, \ - g_server_port, full_filename, MAX_PATH_SIZE); + return get_mark_filename_by_id_and_port(pReader->storage_id, + g_server_port, pReader->mark_filename, + sizeof(pReader->mark_filename)); } -static char *get_mark_filename_by_id(const char *storage_id, \ +static char *get_mark_filename_by_id(const char *storage_id, char *full_filename, const int filename_size) { - return get_mark_filename_by_id_and_port(storage_id, g_server_port, \ - full_filename, filename_size); + return get_mark_filename_by_id_and_port(storage_id, + g_server_port, full_filename, filename_size); } int storage_report_storage_status(const char *storage_id, \ @@ -2199,7 +2191,6 @@ static int storage_reader_sync_init_req(StorageBinLogReader *pReader) int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader) { - char full_filename[MAX_PATH_SIZE]; IniContext iniContext; int result; bool bFileExist; @@ -2213,7 +2204,6 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader pReader->scan_row_count = 0; pReader->sync_row_count = 0; pReader->last_file_exist = 0; - pReader->mark_fd = -1; pReader->binlog_fd = -1; pReader->binlog_buff.buffer = (char *)malloc( \ @@ -2237,7 +2227,7 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader { strcpy(pReader->storage_id, pStorage->id); } - get_mark_filename_by_reader(pReader, full_filename); + get_mark_filename_by_reader(pReader); if (pStorage == NULL) { @@ -2249,22 +2239,23 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader } else { - bFileExist = fileExists(full_filename); + bFileExist = fileExists(pReader->mark_filename); if (!bFileExist && (g_use_storage_id && pStorage != NULL)) { char old_mark_filename[MAX_PATH_SIZE]; - get_mark_filename_by_ip_and_port(pStorage->ip_addr, \ - g_server_port, old_mark_filename, \ + get_mark_filename_by_ip_and_port(pStorage->ip_addr, + g_server_port, old_mark_filename, sizeof(old_mark_filename)); if (fileExists(old_mark_filename)) { - if (rename(old_mark_filename, full_filename)!=0) + if (rename(old_mark_filename, + pReader->mark_filename) !=0 ) { - logError("file: "__FILE__", line: %d, "\ - "rename file %s to %s fail" \ - ", errno: %d, error info: %s", \ - __LINE__, old_mark_filename, \ - full_filename, errno, \ + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail" + ", errno: %d, error info: %s", + __LINE__, old_mark_filename, + pReader->mark_filename, errno, STRERROR(errno)); return errno != 0 ? errno : EACCES; } @@ -2284,29 +2275,30 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader if (bFileExist) { memset(&iniContext, 0, sizeof(IniContext)); - if ((result=iniLoadFromFile(full_filename, &iniContext)) \ - != 0) + if ((result=iniLoadFromFile(pReader->mark_filename, + &iniContext)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "load from mark file \"%s\" fail, " \ - "error code: %d", \ - __LINE__, full_filename, result); + logError("file: "__FILE__", line: %d, " + "load from mark file \"%s\" fail, " + "error code: %d", __LINE__, + pReader->mark_filename, result); return result; } if (iniContext.global.count < 7) { iniFreeContext(&iniContext); - logError("file: "__FILE__", line: %d, " \ - "in mark file \"%s\", item count: %d < 7", \ - __LINE__, full_filename, iniContext.global.count); + logError("file: "__FILE__", line: %d, " + "in mark file \"%s\", item count: %d < 7", + __LINE__, pReader->mark_filename, + iniContext.global.count); return ENOENT; } - bNeedSyncOld = iniGetBoolValue(NULL, \ - MARK_ITEM_NEED_SYNC_OLD, \ + bNeedSyncOld = iniGetBoolValue(NULL, + MARK_ITEM_NEED_SYNC_OLD, &iniContext, false); - if (pStorage != NULL && pStorage->status == \ + if (pStorage != NULL && pStorage->status == FDFS_STORAGE_STATUS_SYNCING) { if ((result=storage_reader_sync_init_req(pReader)) != 0) @@ -2356,17 +2348,17 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader logError("file: "__FILE__", line: %d, " \ "in mark file \"%s\", " \ "binlog_index: %d < 0", \ - __LINE__, full_filename, \ + __LINE__, pReader->mark_filename, \ pReader->binlog_index); return EINVAL; } if (pReader->binlog_offset < 0) { iniFreeContext(&iniContext); - logError("file: "__FILE__", line: %d, " \ - "in mark file \"%s\", binlog_offset: "\ - "%"PRId64" < 0", \ - __LINE__, full_filename, \ + logError("file: "__FILE__", line: %d, " + "in mark file \"%s\", binlog_offset: " + "%"PRId64" < 0", __LINE__, + pReader->mark_filename, pReader->binlog_offset); return EINVAL; } @@ -2378,18 +2370,6 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader pReader->last_scan_rows = pReader->scan_row_count; pReader->last_sync_rows = pReader->sync_row_count; - pReader->mark_fd = open(full_filename, O_WRONLY | O_CREAT, 0644); - if (pReader->mark_fd < 0) - { - logError("file: "__FILE__", line: %d, " \ - "open mark file \"%s\" fail, " \ - "error no: %d, error info: %s", \ - __LINE__, full_filename, \ - errno, STRERROR(errno)); - return errno != 0 ? errno : ENOENT; - } - STORAGE_FCHOWN(pReader->mark_fd, full_filename, geteuid(), getegid()) - if ((result=storage_open_readable_binlog(pReader, \ get_binlog_readable_filename, pReader)) != 0) { @@ -2423,12 +2403,6 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader void storage_reader_destroy(StorageBinLogReader *pReader) { - if (pReader->mark_fd >= 0) - { - close(pReader->mark_fd); - pReader->mark_fd = -1; - } - if (pReader->binlog_fd >= 0) { close(pReader->binlog_fd); @@ -2450,25 +2424,25 @@ static int storage_write_to_mark_file(StorageBinLogReader *pReader) int len; int result; - len = sprintf(buff, \ - "%s=%d\n" \ - "%s=%"PRId64"\n" \ - "%s=%d\n" \ - "%s=%d\n" \ - "%s=%d\n" \ - "%s=%"PRId64"\n" \ - "%s=%"PRId64"\n", \ - 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, \ + len = sprintf(buff, + "%s=%d\n" + "%s=%"PRId64"\n" + "%s=%d\n" + "%s=%d\n" + "%s=%d\n" + "%s=%"PRId64"\n" + "%s=%"PRId64"\n", + 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); - if ((result=storage_write_to_fd(pReader->mark_fd, \ - get_mark_filename_by_reader, pReader, buff, len)) == 0) + if ((result=safeWriteToFile(pReader->mark_filename, buff, len)) == 0) { + STORAGE_CHOWN(pReader->mark_filename, geteuid(), getegid()) pReader->last_scan_rows = pReader->scan_row_count; pReader->last_sync_rows = pReader->sync_row_count; } @@ -2959,7 +2933,6 @@ static void* storage_sync_thread_entrance(void* arg) } memset(pReader, 0, sizeof(StorageBinLogReader)); - pReader->mark_fd = -1; pReader->binlog_fd = -1; storage_reader_add_to_list(pReader); diff --git a/storage/storage_sync.h b/storage/storage_sync.h index 96b2230..aaf4bca 100644 --- a/storage/storage_sync.h +++ b/storage/storage_sync.h @@ -42,12 +42,12 @@ typedef struct { struct fc_list_head link; char storage_id[FDFS_STORAGE_ID_MAX_SIZE]; + char mark_filename[MAX_PATH_SIZE]; bool need_sync_old; bool sync_old_done; bool last_file_exist; //if the last file exist on the dest server BinLogBuffer binlog_buff; time_t until_timestamp; - int mark_fd; int binlog_index; int binlog_fd; int64_t binlog_offset; @@ -92,7 +92,7 @@ int storage_sync_thread_start(const FDFSStorageBrief *pStorage); int kill_storage_sync_threads(); int fdfs_binlog_sync_func(void *args); -char *get_mark_filename_by_reader(const void *pArg, char *full_filename); +char *get_mark_filename_by_reader(StorageBinLogReader *pReader); int storage_unlink_mark_file(const char *storage_id); int storage_rename_mark_file(const char *old_ip_addr, const int old_port, \ const char *new_ip_addr, const int new_port); From 3b1045268e05f3860212ba570aeb211ceef9acaa Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 16 Nov 2019 11:30:20 +0800 Subject: [PATCH 33/95] add func fdfs_set_server_info_index1 --- client/fdfs_monitor.c | 1 + conf/client.conf | 6 ++++-- conf/storage.conf | 6 ++++-- conf/storage_ids.conf | 4 +++- tracker/fdfs_shared_func.c | 22 +++++++++++++++++++++- tracker/fdfs_shared_func.h | 10 ++++++++++ 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/client/fdfs_monitor.c b/client/fdfs_monitor.c index 6bcc703..ef1c53f 100644 --- a/client/fdfs_monitor.c +++ b/client/fdfs_monitor.c @@ -133,6 +133,7 @@ int main(int argc, char *argv[]) { if (fdfs_server_contain1(g_tracker_group.servers + i, &conn)) { + fdfs_set_server_info_index1(g_tracker_group.servers + i, &conn); g_tracker_group.server_index = i; break; } diff --git a/conf/client.conf b/conf/client.conf index a34e56e..c1173ec 100644 --- a/conf/client.conf +++ b/conf/client.conf @@ -13,8 +13,10 @@ base_path=/home/yuqing/fastdfs # the value format of tracker_server is "HOST:PORT", # the HOST can be hostname or ip address, # and the HOST can be dual IPs or hostnames seperated by comma, -# the dual IPS must be an intranet IP and an extranet IP. -# such as: 192.168.2.100,122.244.141.46 +# the dual IPS must be an inner (intranet) IP and an outer (extranet) IP, +# or two different types of inner (intranet) IPs. +# for example: 192.168.2.100,122.244.141.46:22122 +# another eg.: 192.168.1.10,172.17.4.21:22122 tracker_server=192.168.0.196:22122 tracker_server=192.168.0.197:22122 diff --git a/conf/storage.conf b/conf/storage.conf index 82dc876..31fb080 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -119,8 +119,10 @@ subdir_count_per_path=256 # the value format of tracker_server is "HOST:PORT", # the HOST can be hostname or ip address, # and the HOST can be dual IPs or hostnames seperated by comma, -# the dual IPS must be an intranet IP and an extranet IP. -# such as: 192.168.2.100,122.244.141.46 +# the dual IPS must be an inner (intranet) IP and an outer (extranet) IP, +# or two different types of inner (intranet) IPs. +# for example: 192.168.2.100,122.244.141.46:22122 +# another eg.: 192.168.1.10,172.17.4.21:22122 tracker_server=192.168.209.121:22122 tracker_server=192.168.209.122:22122 diff --git a/conf/storage_ids.conf b/conf/storage_ids.conf index b444377..1075a43 100644 --- a/conf/storage_ids.conf +++ b/conf/storage_ids.conf @@ -1,7 +1,9 @@ # # storage ip or hostname can be dual IPs seperated by comma, -# one is an intranet IP and another is an extranet IP. +# one is an inner (intranet) IP and another is an outer (extranet) IP, +# or two different types of inner (intranet) IPs # for example: 192.168.2.100,122.244.141.46 +# another eg.: 192.168.1.10,172.17.4.21 # # the port is optional. if you run more than one storaged instances # in a server, you must specified the port to distinguish different instances. diff --git a/tracker/fdfs_shared_func.c b/tracker/fdfs_shared_func.c index 299e00d..6e76ea9 100644 --- a/tracker/fdfs_shared_func.c +++ b/tracker/fdfs_shared_func.c @@ -707,7 +707,7 @@ int fdfs_check_and_format_ips(FDFSMultiIP *ip_addrs, void fdfs_set_multi_ip_index(FDFSMultiIP *multi_ip, const char *target_ip) { int i; - if (multi_ip->count == 1) + if (multi_ip->count <= 1) { return; } @@ -722,6 +722,26 @@ void fdfs_set_multi_ip_index(FDFSMultiIP *multi_ip, const char *target_ip) } } +void fdfs_set_server_info_index(TrackerServerInfo *pServer, + const char *target_ip, const int target_port) +{ + int i; + if (pServer->count <= 1) + { + return; + } + + for (i=0; icount; i++) + { + if (FC_CONNECTION_SERVER_EQUAL(pServer->connections[i], + target_ip, target_port)) + { + pServer->index = i; + break; + } + } +} + void fdfs_set_server_info(TrackerServerInfo *pServer, const char *ip_addr, const int port) { diff --git a/tracker/fdfs_shared_func.h b/tracker/fdfs_shared_func.h index a82080f..b8903ec 100644 --- a/tracker/fdfs_shared_func.h +++ b/tracker/fdfs_shared_func.h @@ -148,6 +148,16 @@ const char *fdfs_get_ipaddr_by_peer_ip(const FDFSMultiIP *ip_addrs, void fdfs_set_multi_ip_index(FDFSMultiIP *multi_ip, const char *target_ip); +void fdfs_set_server_info_index(TrackerServerInfo *pServer, + const char *target_ip, const int target_port); + +static inline void fdfs_set_server_info_index1(TrackerServerInfo *pServer, + const ConnectionInfo *target) +{ + return fdfs_set_server_info_index(pServer, + target->ip_addr, target->port); +} + void fdfs_set_server_info(TrackerServerInfo *pServer, const char *ip_addr, const int port); From 1ac2ced8730cac41607345e5a1f012a1523fa751 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 16 Nov 2019 11:34:50 +0800 Subject: [PATCH 34/95] upgrade version to v6.03 --- common/fdfs_global.c | 2 +- fastdfs.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/fdfs_global.c b/common/fdfs_global.c index 9963451..0176051 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -23,7 +23,7 @@ int g_fdfs_connect_timeout = DEFAULT_CONNECT_TIMEOUT; int g_fdfs_network_timeout = DEFAULT_NETWORK_TIMEOUT; char g_fdfs_base_path[MAX_PATH_SIZE] = {'/', 't', 'm', 'p', '\0'}; -Version g_fdfs_version = {6, 2}; +Version g_fdfs_version = {6, 3}; bool g_use_connection_pool = false; ConnectionPool g_connection_pool; int g_connection_pool_max_idle_time = 3600; diff --git a/fastdfs.spec b/fastdfs.spec index ff51a9d..86f8259 100644 --- a/fastdfs.spec +++ b/fastdfs.spec @@ -3,7 +3,7 @@ %define FDFSClient libfdfsclient %define FDFSClientDevel libfdfsclient-devel %define FDFSTool fastdfs-tool -%define FDFSVersion 6.0.2 +%define FDFSVersion 6.0.3 %define CommitVersion %(echo $COMMIT_VERSION) Name: %{FastDFS} From af4f0754e482ff882294f6d045887546542eefb5 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 17 Nov 2019 19:21:55 +0800 Subject: [PATCH 35/95] code refine: extent struct FDFSStorePathInfo --- HISTORY | 6 +- storage/storage_disk_recovery.c | 15 +++-- storage/storage_dump.c | 6 +- storage/storage_func.c | 25 ++----- storage/storage_global.c | 1 - storage/storage_global.h | 9 --- storage/storage_service.c | 108 +++++++++++++++---------------- storage/storage_sync.c | 12 ++-- storage/tracker_client_thread.c | 16 ++--- storage/trunk_mgr/trunk_mem.c | 4 +- storage/trunk_mgr/trunk_shared.c | 90 ++++++++++++++------------ storage/trunk_mgr/trunk_shared.h | 20 +++++- tracker/tracker_types.h | 5 -- 13 files changed, 155 insertions(+), 162 deletions(-) diff --git a/HISTORY b/HISTORY index a34d1ae..a09218c 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.03 2019-11-16 +Version 6.03 2019-11-17 * dual IPs support two different types of inner (intranet) IPs * storage server request tracker server to change it's status to that of tracker leader when the storage server found @@ -7,7 +7,9 @@ Version 6.03 2019-11-16 * bugfix: fdfs_monitor fix get index of the specified tracker server * storage server write to data_init_flag and mark file safely (write to temp file then rename) - + * code refine: combine g_fdfs_store_paths and g_path_space_list, + and extent struct FDFSStorePathInfo + NOTE: the tracker and storage server must upgrade together Version 6.02 2019-11-12 diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 1c4c7bd..4cf8096 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -30,6 +30,7 @@ #include "tracker_types.h" #include "tracker_proto.h" #include "storage_global.h" +#include "trunk_mgr/trunk_shared.h" #include "storage_func.h" #include "storage_sync.h" #include "tracker_client.h" @@ -65,7 +66,7 @@ static int storage_do_fetch_binlog(ConnectionInfo *pSrcStorage, \ int64_t file_bytes; int result; - pBasePath = g_fdfs_store_paths.paths[store_path_index]; + pBasePath = g_fdfs_store_paths.paths[store_path_index].path; recovery_get_binlog_filename(pBasePath, full_binlog_filename); memset(out_buff, 0, sizeof(out_buff)); @@ -534,7 +535,7 @@ static int recovery_download_file_to_local(StorageBinLogRecord *pRecord, { bTrunkFile = false; sprintf(local_filename, "%s/data/%s", - g_fdfs_store_paths.paths[pRecord->store_path_index], + g_fdfs_store_paths.paths[pRecord->store_path_index].path, pRecord->true_filename); } @@ -684,7 +685,7 @@ static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pRead break; } sprintf(local_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], \ + g_fdfs_store_paths.paths[store_path_index].path, \ record.true_filename); if ((result=storage_split_filename_ex( \ @@ -695,7 +696,7 @@ static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pRead break; } sprintf(src_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], \ + g_fdfs_store_paths.paths[store_path_index].path, \ record.true_filename); if (symlink(src_filename, local_filename) == 0) { @@ -916,7 +917,7 @@ static int storage_do_split_trunk_binlog(const int store_path_index, int record_length; int result; - pBasePath = g_fdfs_store_paths.paths[store_path_index]; + pBasePath = g_fdfs_store_paths.paths[store_path_index].path; recovery_get_full_filename(pBasePath, \ RECOVERY_BINLOG_FILENAME".tmp", tmpFullFilename); fp = fopen(tmpFullFilename, "w"); @@ -1087,7 +1088,7 @@ static int storage_disk_recovery_split_trunk_binlog(const int store_path_index) StorageBinLogReader reader; int result; - pBasePath = g_fdfs_store_paths.paths[store_path_index]; + pBasePath = g_fdfs_store_paths.paths[store_path_index].path; if ((result=recovery_reader_init(pBasePath, &reader)) != 0) { storage_reader_destroy(&reader); @@ -1107,7 +1108,7 @@ int storage_disk_recovery_start(const int store_path_index) int result; char *pBasePath; - pBasePath = g_fdfs_store_paths.paths[store_path_index]; + pBasePath = g_fdfs_store_paths.paths[store_path_index].path; if ((result=recovery_init_mark_file(pBasePath, false)) != 0) { return result; diff --git a/storage/storage_dump.c b/storage/storage_dump.c index c7707d7..badefef 100644 --- a/storage/storage_dump.c +++ b/storage/storage_dump.c @@ -246,9 +246,9 @@ static int fdfs_dump_global_vars(char *buff, const int buffSize) total_len += snprintf(buff + total_len, buffSize - total_len, "\tg_fdfs_store_paths.paths[%d]=%s, " \ "total=%d MB, free=%d MB\n", i, \ - g_fdfs_store_paths.paths[i], \ - g_path_space_list[i].total_mb, \ - g_path_space_list[i].free_mb); + g_fdfs_store_paths.paths[i].path, \ + g_fdfs_store_paths.paths[i].total_mb, \ + g_fdfs_store_paths.paths[i].free_mb); } if (total_len < buffSize - 1) diff --git a/storage/storage_func.c b/storage/storage_func.c index fdf39ab..53d8977 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -842,7 +842,7 @@ static int storage_check_and_make_data_dirs() for (i=0; ifilename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], \ + g_fdfs_store_paths.paths[store_path_index].path, \ true_filename); } @@ -2012,15 +2012,15 @@ int storage_get_storage_path_index(int *store_path_index) *store_path_index = 0; } - if (!storage_check_reserved_space_path(g_path_space_list \ - [*store_path_index].total_mb, g_path_space_list \ + if (!storage_check_reserved_space_path(g_fdfs_store_paths.paths \ + [*store_path_index].total_mb, g_fdfs_store_paths.paths \ [*store_path_index].free_mb, g_avg_storage_reserved_mb)) { for (i=0; ifilename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[master_store_path_index], \ + "%s/data/%s", g_fdfs_store_paths.paths[master_store_path_index].path, \ filename); sprintf(pFileContext->fname2log, \ "%c"FDFS_STORAGE_DATA_DIR_FORMAT"/%s", \ @@ -2878,12 +2878,12 @@ static int storage_service_do_create_link(struct fast_task_info *pTask, \ else { sprintf(full_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], \ + g_fdfs_store_paths.paths[store_path_index].path, \ filename); } sprintf(src_full_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], \ + g_fdfs_store_paths.paths[store_path_index].path, \ pSrcFileInfo->src_true_filename); if (symlink(src_full_filename, full_filename) != 0) { @@ -3337,7 +3337,7 @@ static int storage_server_set_metadata(struct fast_task_info *pTask) pFileContext->timestamp2log = g_current_time; sprintf(pFileContext->filename, "%s/data/%s%s", \ - g_fdfs_store_paths.paths[store_path_index], true_filename, \ + g_fdfs_store_paths.paths[store_path_index].path, true_filename, \ FDFS_STORAGE_META_FILE_EXT); sprintf(pFileContext->fname2log,"%s%s", \ filename, FDFS_STORAGE_META_FILE_EXT); @@ -3595,7 +3595,7 @@ static int query_file_info_deal_response(struct fast_task_info *pTask, snprintf(pFileContext->fname2log, sizeof(pFileContext->fname2log), "%s", filename); snprintf(pFileContext->filename, sizeof(pFileContext->filename), - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], + "%s/data/%s", g_fdfs_store_paths.paths[store_path_index].path, true_filename); return push_calc_crc32_to_dio_queue(pTask, @@ -3772,7 +3772,7 @@ static int storage_server_query_file_info(struct fast_task_info *pTask) { char full_filename[MAX_PATH_SIZE + 128]; sprintf(full_filename, "%s/data/%s", - g_fdfs_store_paths.paths[store_path_index], + g_fdfs_store_paths.paths[store_path_index].path, true_filename); if ((len=readlink(full_filename, src_filename, \ sizeof(src_filename))) < 0) @@ -4156,7 +4156,6 @@ static int storage_server_fetch_one_path_binlog_dealer( \ char *pBasePath; int result; int record_len; - int base_path_len; int len; int store_path_index; struct stat stat_buf; @@ -4180,8 +4179,7 @@ static int storage_server_fetch_one_path_binlog_dealer( \ store_path_index = pFileContext->extra_info.upload.trunk_info. \ path.store_path_index; - pBasePath = g_fdfs_store_paths.paths[store_path_index]; - base_path_len = strlen(pBasePath); + pBasePath = g_fdfs_store_paths.paths[store_path_index].path; pOutBuff = pTask->data; bLast = false; @@ -4202,7 +4200,8 @@ static int storage_server_fetch_one_path_binlog_dealer( \ break; } - if (g_fdfs_store_paths.paths[record.store_path_index] != pBasePath) + if (g_fdfs_store_paths.paths[record.store_path_index]. + path != pBasePath) { continue; } @@ -4231,7 +4230,7 @@ static int storage_server_fetch_one_path_binlog_dealer( \ else { snprintf(full_filename, sizeof(full_filename), "%s/data/%s", - g_fdfs_store_paths.paths[record.store_path_index], + g_fdfs_store_paths.paths[record.store_path_index].path, record.true_filename); if (lstat(full_filename, &stat_buf) != 0) { @@ -4326,7 +4325,7 @@ static int storage_server_fetch_one_path_binlog_dealer( \ break; } - if (len <= base_path_len) + if (len <= g_fdfs_store_paths.paths[store_path_index].path_len) { logWarning("file: "__FILE__", line: %d, " \ "invalid symbol link file: %s, " \ @@ -4346,11 +4345,11 @@ static int storage_server_fetch_one_path_binlog_dealer( \ } //full filename format: ${base_path}/data/filename - pOutBuff += sprintf(pOutBuff, "%d %c %s %s/%s\n", \ - (int)record.timestamp, \ - record.op_type, record.filename, \ - diskLogicPath, \ - src_filename + base_path_len + 6); + pOutBuff += sprintf(pOutBuff, "%d %c %s %s/%s\n", + (int)record.timestamp, + record.op_type, record.filename, + diskLogicPath, src_filename + g_fdfs_store_paths. + paths[store_path_index].path_len + 6); } if (pTask->size - (pOutBuff - pTask->data) < @@ -4663,8 +4662,8 @@ static int storage_upload_file(struct fast_task_info *pTask, bool bAppenderFile) { char reserved_space_str[32]; - if (!storage_check_reserved_space_path(g_path_space_list \ - [store_path_index].total_mb, g_path_space_list \ + if (!storage_check_reserved_space_path(g_fdfs_store_paths.paths \ + [store_path_index].total_mb, g_fdfs_store_paths.paths \ [store_path_index].free_mb - (file_bytes/FDFS_ONE_MB), \ g_avg_storage_reserved_mb)) { @@ -4672,12 +4671,12 @@ static int storage_upload_file(struct fast_task_info *pTask, bool bAppenderFile) "no space to upload file, " "free space: %d MB is too small, file bytes: " \ "%"PRId64", reserved space: %s", \ - __LINE__, g_path_space_list[store_path_index].\ + __LINE__, g_fdfs_store_paths.paths[store_path_index].\ free_mb, file_bytes, \ fdfs_storage_reserved_space_to_string_ex( \ g_storage_reserved_space.flag, \ g_avg_storage_reserved_mb, \ - g_path_space_list[store_path_index]. \ + g_fdfs_store_paths.paths[store_path_index]. \ total_mb, g_storage_reserved_space.rs.ratio,\ reserved_space_str)); return ENOSPC; @@ -4758,7 +4757,7 @@ static int storage_server_check_appender_file(struct fast_task_info *pTask, } snprintf(pFileContext->filename, sizeof(pFileContext->filename), - "%s/data/%s", g_fdfs_store_paths.paths[*store_path_index], + "%s/data/%s", g_fdfs_store_paths.paths[*store_path_index].path, true_filename); if (lstat(pFileContext->filename, stat_buf) == 0) { @@ -5517,8 +5516,8 @@ static int storage_upload_slave_file(struct fast_task_info *pTask) return result; } - if (!storage_check_reserved_space_path(g_path_space_list \ - [store_path_index].total_mb, g_path_space_list \ + if (!storage_check_reserved_space_path(g_fdfs_store_paths.paths \ + [store_path_index].total_mb, g_fdfs_store_paths.paths \ [store_path_index].free_mb - (file_bytes / FDFS_ONE_MB), \ g_avg_storage_reserved_mb)) { @@ -5526,11 +5525,11 @@ static int storage_upload_slave_file(struct fast_task_info *pTask) "no space to upload file, " "free space: %d MB is too small, file bytes: " \ "%"PRId64", reserved space: %s", __LINE__,\ - g_path_space_list[store_path_index].free_mb, \ + g_fdfs_store_paths.paths[store_path_index].free_mb, \ file_bytes, fdfs_storage_reserved_space_to_string_ex(\ g_storage_reserved_space.flag, \ g_avg_storage_reserved_mb, \ - g_path_space_list[store_path_index].total_mb, \ + g_fdfs_store_paths.paths[store_path_index].total_mb, \ g_storage_reserved_space.rs.ratio, \ reserved_space_str)); return ENOSPC; @@ -5573,7 +5572,7 @@ static int storage_upload_slave_file(struct fast_task_info *pTask) } snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], filename); + "%s/data/%s", g_fdfs_store_paths.paths[store_path_index].path, filename); if (fileExists(pFileContext->filename)) { logError("file: "__FILE__", line: %d, " \ @@ -5871,7 +5870,7 @@ static int storage_sync_copy_file(struct fast_task_info *pTask, \ sprintf(pFileContext->filename, "%s/data/.cp" \ "%"PRId64".tmp", \ - g_fdfs_store_paths.paths[store_path_index], \ + g_fdfs_store_paths.paths[store_path_index].path, \ temp_file_sequence++); pthread_mutex_unlock(&g_storage_thread_lock); @@ -6732,11 +6731,11 @@ static int storage_do_sync_link_file(struct fast_task_info *pTask) else { snprintf(pFileContext->filename, sizeof(pFileContext->filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[dest_store_path_index], \ + "%s/data/%s", g_fdfs_store_paths.paths[dest_store_path_index].path, \ dest_true_filename); snprintf(src_full_filename, sizeof(src_full_filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[src_store_path_index], \ + "%s/data/%s", g_fdfs_store_paths.paths[src_store_path_index].path, \ src_true_filename); if (symlink(src_full_filename, pFileContext->filename) != 0) { @@ -7106,7 +7105,7 @@ static int storage_server_get_metadata(struct fast_task_info *pTask) } sprintf(pFileContext->filename, "%s/data/%s%s", \ - g_fdfs_store_paths.paths[store_path_index], \ + g_fdfs_store_paths.paths[store_path_index].path, \ true_filename, FDFS_STORAGE_META_FILE_EXT); if (lstat(pFileContext->filename, &stat_buf) == 0) { @@ -7308,7 +7307,7 @@ static int storage_server_download_file(struct fast_task_info *pTask) else { sprintf(pFileContext->filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], true_filename); + g_fdfs_store_paths.paths[store_path_index].path, true_filename); } pFileContext->calc_crc32 = false; @@ -7560,7 +7559,7 @@ static int storage_sync_delete_file(struct fast_task_info *pTask) { pClientInfo->deal_func = dio_delete_normal_file; sprintf(pFileContext->filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], true_filename); + g_fdfs_store_paths.paths[store_path_index].path, true_filename); } strcpy(pFileContext->fname2log, filename); @@ -7695,7 +7694,7 @@ static int storage_server_delete_file(struct fast_task_info *pTask) { pClientInfo->deal_func = dio_delete_normal_file; sprintf(pFileContext->filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], true_filename); + g_fdfs_store_paths.paths[store_path_index].path, true_filename); } if ((pFileContext->extra_info.upload.file_type & _FILE_TYPE_LINK) && \ @@ -7706,12 +7705,11 @@ static int storage_server_delete_file(struct fast_task_info *pTask) char src_fname2log[128]; char *src_true_filename; int src_filename_len; - int base_path_len; int src_store_path_index; int i; sprintf(full_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index], true_filename); + g_fdfs_store_paths.paths[store_path_index].path, true_filename); do { if ((src_filename_len=readlink(full_filename, \ @@ -7742,11 +7740,11 @@ static int storage_server_delete_file(struct fast_task_info *pTask) return result; } - base_path_len = strlen(g_fdfs_store_paths.paths \ - [store_path_index]); - if (src_filename_len > base_path_len && memcmp( \ - src_filename, g_fdfs_store_paths.paths \ - [store_path_index], base_path_len) == 0) + if (src_filename_len > g_fdfs_store_paths.paths + [store_path_index].path_len && memcmp( + src_filename, g_fdfs_store_paths.paths + [store_path_index].path, g_fdfs_store_paths. + paths[store_path_index].path_len) == 0) { src_store_path_index = store_path_index; } @@ -7755,10 +7753,9 @@ static int storage_server_delete_file(struct fast_task_info *pTask) src_store_path_index = -1; for (i=0; i base_path_len && \ - memcmp(src_filename, g_fdfs_store_paths.paths\ - [i], base_path_len) == 0) + if (src_filename_len > g_fdfs_store_paths.paths[i].path_len && + memcmp(src_filename, g_fdfs_store_paths.paths + [i].path, g_fdfs_store_paths.paths[i].path_len) == 0) { src_store_path_index = i; break; @@ -7774,8 +7771,9 @@ static int storage_server_delete_file(struct fast_task_info *pTask) } } - src_true_filename = src_filename + (base_path_len + \ - (sizeof("/data/") -1)); + src_true_filename = src_filename + (g_fdfs_store_paths. + paths[src_store_path_index].path_len + + (sizeof("/data/") -1)); snprintf(src_fname2log, sizeof(src_fname2log), \ "%c"FDFS_STORAGE_DATA_DIR_FORMAT"/%s", \ FDFS_STORAGE_STORE_PATH_PREFIX_CHAR, \ @@ -7916,7 +7914,7 @@ static int storage_create_link_core(struct fast_task_info *pTask, \ } snprintf(full_filename, sizeof(full_filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[store_path_index], \ + "%s/data/%s", g_fdfs_store_paths.paths[store_path_index].path, \ filename); if (fileExists(full_filename)) { diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 6f10608..1265640 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -185,7 +185,7 @@ static int storage_sync_copy_file(ConnectionInfo *pStorageServer, \ { file_offset = 0; sprintf(full_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[pRecord->store_path_index], \ + g_fdfs_store_paths.paths[pRecord->store_path_index].path, \ pRecord->true_filename); } @@ -350,7 +350,7 @@ static int storage_sync_modify_file(ConnectionInfo *pStorageServer, \ } snprintf(full_filename, sizeof(full_filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[pRecord->store_path_index], \ + "%s/data/%s", g_fdfs_store_paths.paths[pRecord->store_path_index].path, \ pRecord->true_filename); if (lstat(full_filename, &stat_buf) != 0) { @@ -520,7 +520,7 @@ static int storage_sync_truncate_file(ConnectionInfo *pStorageServer, \ } snprintf(full_filename, sizeof(full_filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[pRecord->store_path_index], \ + "%s/data/%s", g_fdfs_store_paths.paths[pRecord->store_path_index].path, \ pRecord->true_filename); if (lstat(full_filename, &stat_buf) != 0) { @@ -841,7 +841,7 @@ static int storage_sync_link_file(ConnectionInfo *pStorageServer, \ int src_filename_len; snprintf(full_filename, sizeof(full_filename), \ - "%s/data/%s", g_fdfs_store_paths.paths[pRecord->store_path_index], \ + "%s/data/%s", g_fdfs_store_paths.paths[pRecord->store_path_index].path, \ pRecord->true_filename); src_filename_len = readlink(full_filename, src_full_filename, \ sizeof(src_full_filename) - 1); @@ -884,7 +884,7 @@ static int storage_sync_link_file(ConnectionInfo *pStorageServer, \ src_path_index++) { if (strcmp(src_full_filename, \ - g_fdfs_store_paths.paths[src_path_index]) == 0) + g_fdfs_store_paths.paths[src_path_index].path) == 0) { break; } @@ -998,7 +998,7 @@ static int storage_sync_rename_file(ConnectionInfo *pStorageServer, } snprintf(full_filename, sizeof(full_filename), "%s/data/%s", - g_fdfs_store_paths.paths[pRecord->store_path_index], + g_fdfs_store_paths.paths[pRecord->store_path_index].path, pRecord->true_filename); if (lstat(full_filename, &stat_buf) != 0) { diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index 096c558..0133e08 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -2203,7 +2203,7 @@ static int tracker_report_df_stat(ConnectionInfo *pTrackerServer, for (i=0; isz_total_mb); - long2buff(g_path_space_list[i].free_mb, pStatBuff->sz_free_mb); + long2buff(g_fdfs_store_paths.paths[i].total_mb, pStatBuff->sz_total_mb); + long2buff(g_fdfs_store_paths.paths[i].free_mb, pStatBuff->sz_free_mb); pStatBuff++; } @@ -2235,12 +2235,12 @@ static int tracker_report_df_stat(ConnectionInfo *pTrackerServer, store_path_index = -1; for (i=0; i \ + if (g_fdfs_store_paths.paths[i].free_mb > \ g_avg_storage_reserved_mb \ - && g_path_space_list[i].free_mb > max_free_mb) + && g_fdfs_store_paths.paths[i].free_mb > max_free_mb) { store_path_index = i; - max_free_mb = g_path_space_list[i].free_mb; + max_free_mb = g_fdfs_store_paths.paths[i].free_mb; } } if (g_store_path_index != store_path_index) diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index ce301a3..5aa8812 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -1953,8 +1953,8 @@ int trunk_create_trunk_file_advance(void *args) free_mb_sum = 0; for (i=0; ipaths[pTrunkInfo->path.store_path_index]; + pStorePath = pStorePaths->paths[pTrunkInfo->path.store_path_index].path; TRUNK_GET_FILENAME(pTrunkInfo->file.id, short_filename); snprintf(full_filename, buff_size, \ @@ -547,7 +551,7 @@ int trunk_file_do_lstat_func_ex(const FDFSStorePaths *pStorePaths, \ if (filename_len != FDFS_TRUNK_FILENAME_LENGTH) //not trunk file { snprintf(full_filename, sizeof(full_filename), "%s/data/%s", \ - pStorePaths->paths[store_path_index], true_filename); + pStorePaths->paths[store_path_index].path, true_filename); if (stat_func == FDFS_STAT_FUNC_STAT) { @@ -576,7 +580,7 @@ int trunk_file_do_lstat_func_ex(const FDFSStorePaths *pStorePaths, \ if (!IS_TRUNK_FILE(file_size)) //slave file { snprintf(full_filename, sizeof(full_filename), "%s/data/%s", \ - pStorePaths->paths[store_path_index], true_filename); + pStorePaths->paths[store_path_index].path, true_filename); if (stat_func == FDFS_STAT_FUNC_STAT) { diff --git a/storage/trunk_mgr/trunk_shared.h b/storage/trunk_mgr/trunk_shared.h index db2513c..9fcab70 100644 --- a/storage/trunk_mgr/trunk_shared.h +++ b/storage/trunk_mgr/trunk_shared.h @@ -48,6 +48,20 @@ #define TRUNK_GET_FILENAME(file_id, filename) \ sprintf(filename, "%06d", file_id) +typedef struct +{ + int total_mb; //total spaces + int free_mb; //free spaces + int path_len; //the length of store path + char *path; //file store path + char *mark; //path mark to avoid confusion +} FDFSStorePathInfo; + +typedef struct { + int count; //store path count + FDFSStorePathInfo *paths; //file store paths +} FDFSStorePaths; + #ifdef __cplusplus extern "C" { #endif @@ -84,9 +98,9 @@ typedef struct tagFDFSTrunkFullInfo { FDFSTrunkFileInfo file; } FDFSTrunkFullInfo; -char **storage_load_paths_from_conf_file_ex(IniContext *pItemContext, \ - const char *szSectionName, const bool bUseBasePath, \ - int *path_count, int *err_no); +FDFSStorePathInfo *storage_load_paths_from_conf_file_ex( + IniContext *pItemContext, const char *szSectionName, + const bool bUseBasePath, int *path_count, int *err_no); int storage_load_paths_from_conf_file(IniContext *pItemContext); void trunk_shared_init(); diff --git a/tracker/tracker_types.h b/tracker/tracker_types.h index 3974f2c..8e7bb75 100644 --- a/tracker/tracker_types.h +++ b/tracker/tracker_types.h @@ -464,11 +464,6 @@ typedef struct { } rs; } FDFSStorageReservedSpace; -typedef struct { - int count; //store path count - char **paths; //file store paths -} FDFSStorePaths; - typedef struct { TrackerServerInfo *pTrackerServer; int running_time; //running seconds, more means higher weight From 4ed56c5f692ff9cec65e444296d69229ee5b5187 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 17 Nov 2019 22:19:29 +0800 Subject: [PATCH 36/95] check store path's mark file to prevent confusion --- HISTORY | 1 + storage/storage_func.c | 297 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 289 insertions(+), 9 deletions(-) diff --git a/HISTORY b/HISTORY index a09218c..755a31a 100644 --- a/HISTORY +++ b/HISTORY @@ -9,6 +9,7 @@ Version 6.03 2019-11-17 (write to temp file then rename) * code refine: combine g_fdfs_store_paths and g_path_space_list, and extent struct FDFSStorePathInfo + * check store path's mark file to prevent confusion NOTE: the tracker and storage server must upgrade together diff --git a/storage/storage_func.c b/storage/storage_func.c index 53d8977..c67bdf6 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -51,8 +51,18 @@ #include "fdfs_http_shared.h" #endif +typedef struct +{ + char ip_addr[IP_ADDRESS_SIZE]; + short port; + unsigned char store_path_index; + char padding; + int create_time; +} FDFSStorePathMarkInfo; + #define DATA_DIR_INITED_FILENAME ".data_init_flag" #define STORAGE_STAT_FILENAME "storage_stat.dat" +#define STORE_PATH_MARK_FILENAME ".fastdfs_vars" #define INIT_ITEM_STORAGE_JOIN_TIME "storage_join_time" #define INIT_ITEM_SYNC_OLD_DONE "sync_old_done" @@ -63,6 +73,7 @@ #define INIT_ITEM_LAST_HTTP_PORT "last_http_port" #define INIT_ITEM_CURRENT_TRUNK_FILE_ID "current_trunk_file_id" #define INIT_ITEM_TRUNK_LAST_COMPRESS_TIME "trunk_last_compress_time" +#define INIT_ITEM_STORE_PATH_MARK_PREFIX "store_path_mark" #define STAT_ITEM_TOTAL_UPLOAD "total_upload_count" #define STAT_ITEM_SUCCESS_UPLOAD "success_upload_count" @@ -632,6 +643,7 @@ int storage_write_to_sync_ini_file() char ip_str[256]; int len; int result; + int i; snprintf(full_filename, sizeof(full_filename), "%s/data/%s", g_fdfs_base_path, DATA_DIR_INITED_FILENAME); @@ -658,6 +670,17 @@ int storage_write_to_sync_ini_file() INIT_ITEM_TRUNK_LAST_COMPRESS_TIME, (int)g_trunk_last_compress_time ); + + for (i=0; i 0) + { + char time_str[32]; + + mark_info.ip_addr[IP_ADDRESS_SIZE - 1] = '\0'; + formatDatetime(mark_info.create_time, + "%Y-%m-%d %H:%M:%S", + time_str, sizeof(time_str)); + + logCrit("file: "__FILE__", line: %d, " + "the store path #%d: %s maybe used by other " + "storage server. fields in the mark file: " + "{ ip_addr: %s, port: %d," + " store_path_index: %d," + " create_time: %s }, " + "if you confirm that it is NOT " + "used by other storage server, you can delete " + "the mark file %s then try again", __LINE__, + store_path_index, g_fdfs_store_paths. + paths[store_path_index].path, + mark_info.ip_addr, mark_info.port, + mark_info.store_path_index, time_str, + full_filename); + } + else + { + logCrit("file: "__FILE__", line: %d, " + "the store path #%d: %s maybe used by other " + "storage server. if you confirm that it is NOT " + "used by other storage server, you can delete " + "the mark file %s then try again", __LINE__, + store_path_index, g_fdfs_store_paths. + paths[store_path_index].path, full_filename); + } + + free(mark); + return EINVAL; + } + } + else + { + if (!bPathCreated) + { + logWarning("file: "__FILE__", line: %d, " + "the mark file of store path #%d: %s is missed, " + "try to re-create the mark file: %s", __LINE__, + store_path_index, g_fdfs_store_paths. + paths[store_path_index].path, full_filename); + } + } + } + + if ((result=storage_generate_store_path_mark(store_path_index)) != 0) + { + return result; + } + + return storage_write_to_sync_ini_file(); +} + static int storage_check_and_make_data_dirs() { int result; @@ -700,9 +975,9 @@ static int storage_check_and_make_data_dirs() char error_info[256]; bool pathCreated; - snprintf(data_path, sizeof(data_path), "%s/data", \ + snprintf(data_path, sizeof(data_path), "%s/data", g_fdfs_base_path); - snprintf(full_filename, sizeof(full_filename), "%s/%s", \ + snprintf(full_filename, sizeof(full_filename), "%s/%s", data_path, DATA_DIR_INITED_FILENAME); if (fileExists(full_filename)) { @@ -790,6 +1065,12 @@ static int storage_check_and_make_data_dirs() g_trunk_last_compress_time = iniGetIntValue(NULL, \ INIT_ITEM_TRUNK_LAST_COMPRESS_TIME , &iniContext, 0); + if ((result=storage_load_store_path_marks(&iniContext)) != 0) + { + iniFreeContext(&iniContext); + return result; + } + iniFreeContext(&iniContext); if (g_last_server_port == 0 || g_last_http_port == 0) @@ -848,6 +1129,11 @@ static int storage_check_and_make_data_dirs() return result; } + if ((result=storage_check_store_path_mark(i, pathCreated)) != 0) + { + return result; + } + if (g_sync_old_done && pathCreated) //repair damaged disk { if ((result=storage_disk_recovery_start(i)) != 0) @@ -1125,13 +1411,6 @@ int storage_func_init(const char *filename, \ int64_t rotate_access_log_size; int64_t rotate_error_log_size; - /* - while (nThreadCount > 0) - { - sleep(1); - } - */ - if ((result=iniLoadFromFile(filename, &iniContext)) != 0) { logError("file: "__FILE__", line: %d, " \ From 132dbc095082026c89c22d1b321d02f87d37d8b7 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 18 Nov 2019 08:36:51 +0800 Subject: [PATCH 37/95] change comments in the config files --- conf/storage.conf | 26 ++++++++++++++++---------- conf/tracker.conf | 12 +++++++++--- storage/storage_func.c | 7 ++++++- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/conf/storage.conf b/conf/storage.conf index 31fb080..9346182 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -7,7 +7,7 @@ disabled=false # # comment or remove this item for fetching from tracker server, # in this case, use_storage_id must set to true in tracker.conf, -# and storage_ids.conf must be configed correctly. +# and storage_ids.conf must be configured correctly. group_name=group1 # bind an address of this host @@ -16,7 +16,7 @@ bind_addr= # if bind an address of this host when connect to other servers # (this storage server as a client) -# true for binding the address configed by above parameter: "bind_addr" +# true for binding the address configured by the above parameter: "bind_addr" # false for binding any address of this host client_bind=true @@ -25,9 +25,10 @@ port=23000 # connect timeout in seconds # default value is 30s +# Note: in the intranet network (LAN), 2 seconds is enough. connect_timeout=10 -# network timeout in seconds +# network timeout in seconds for send and recv # default value is 30s network_timeout=60 @@ -46,7 +47,7 @@ base_path=/home/yuqing/fastdfs # you should set this parameter larger, eg. 10240 max_connections=1024 -# the buff size to recv / send data +# the buff size to recv / send data from/to network # this parameter must more than 8KB # default value is 64KB # since V2.00 @@ -57,7 +58,7 @@ buff_size = 256KB # since V4.07 accept_threads=1 -# work thread count, should <= max_connections +# work thread count # work thread deal network io # default value is 4 # since V2.00 @@ -70,13 +71,13 @@ work_threads=4 # since V2.00 disk_rw_separated = true -# disk reader thread count per store base path +# disk reader thread count per store path # for mixed read / write, this parameter can be 0 # default value is 1 # since V2.00 disk_reader_threads = 1 -# disk writer thread count per store base path +# disk writer thread count per store path # for mixed read / write, this parameter can be 0 # default value is 1 # since V2.00 @@ -102,12 +103,17 @@ sync_end_time=23:59 # default value is 500 write_mark_file_freq=500 -# path(disk or mount point) count, default value is 1 +# store path (disk or mount point) count, default value is 1 store_path_count=1 -# store_path#, based 0, if store_path0 not exists, it's value is base_path +# store_path#, based on 0, to configure the store paths to store file +# if store_path0 not exists, it's value is base_path (NOT recommended) # the paths must be exist -# NOTE: the store paths' order is very important, don't mess up. +# +# IMPORTANT NOTE: +# the store paths' order is very important, don't mess up!!! +# the base_path should be independent (different) of the store paths + store_path0=/home/yuqing/fastdfs #store_path1=/home/yuqing/fastdfs2 diff --git a/conf/tracker.conf b/conf/tracker.conf index c2d645b..6c5f8c5 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -12,9 +12,10 @@ port=22122 # connect timeout in seconds # default value is 30s +# Note: in the intranet network (LAN), 2 seconds is enough. connect_timeout=10 -# network timeout in seconds +# network timeout in seconds for send and recv # default value is 30s network_timeout=60 @@ -196,18 +197,23 @@ trunk_init_reload_from_binlog = false # the min interval for compressing the trunk binlog file # unit: second -# default value is 0, 0 means never compress # FastDFS compress the trunk binlog when trunk init and trunk destroy # recommand to set this parameter to 86400 (one day) +# default value is 0, 0 means never compress # since V5.01 trunk_compress_binlog_min_interval = 0 -# if use storage ID instead of IP address +# if use storage server ID instead of IP address +# if you want to use dual IPs for storage server, you MUST set +# this parameter to true, and configure the dual IPs in the file +# configured by following item "storage_ids_filename", +# such as storage_ids.conf # default value is false # since V4.00 use_storage_id = false # specify storage ids filename, can use relative or absolute path +# this parameter is valid only when use_storage_id set to true # since V4.00 storage_ids_filename = storage_ids.conf diff --git a/storage/storage_func.c b/storage/storage_func.c index c67bdf6..acd369e 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -877,7 +877,12 @@ static int storage_check_store_path_mark(const int store_path_index, if (mark != NULL) { if (strcmp(g_fdfs_store_paths.paths[store_path_index].mark, - mark) != 0) + mark) == 0) + { + free(mark); + return 0; + } + else { FDFSStorePathMarkInfo mark_info; int mark_len; From afff529a9b402804f90b589e8083757c2ddb0758 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 18 Nov 2019 18:34:22 +0800 Subject: [PATCH 38/95] skip status FDFS_STORAGE_STATUS_DELETED --- tracker/tracker_mem.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tracker/tracker_mem.c b/tracker/tracker_mem.c index c29b699..e5917f4 100644 --- a/tracker/tracker_mem.c +++ b/tracker/tracker_mem.c @@ -4710,16 +4710,21 @@ int tracker_mem_add_group_and_storage(TrackerClientInfo *pClientInfo, \ } else { - if (pClientInfo->pGroup->store_path_count != \ + if (pClientInfo->pGroup->store_path_count != pJoinBody->store_path_count) { - ppEnd = pClientInfo->pGroup->all_servers + \ + ppEnd = pClientInfo->pGroup->all_servers + pClientInfo->pGroup->count; - for (ppServer=pClientInfo->pGroup->all_servers; \ + for (ppServer=pClientInfo->pGroup->all_servers; ppServerstore_path_count != \ - pJoinBody->store_path_count) + if ((*ppServer)->status == FDFS_STORAGE_STATUS_DELETED) + { + continue; + } + + if ((*ppServer)->store_path_count != + pJoinBody->store_path_count) { break; } @@ -4727,9 +4732,9 @@ int tracker_mem_add_group_and_storage(TrackerClientInfo *pClientInfo, \ if (ppServer == ppEnd) //all servers are same, adjust { - if ((result=tracker_realloc_group_path_mbs( \ - pClientInfo->pGroup, \ - pJoinBody->store_path_count))!=0) + if ((result=tracker_realloc_group_path_mbs( + pClientInfo->pGroup, pJoinBody-> + store_path_count)) != 0) { return result; } From 358fff4ac84212e33baa414e837a7bf209987a0d Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 18 Nov 2019 22:30:10 +0800 Subject: [PATCH 39/95] new selected tracker leader do NOT notify self by network --- HISTORY | 1 + tracker/fdfs_shared_func.c | 19 ++++++ tracker/fdfs_shared_func.h | 3 + tracker/tracker_mem.c | 3 +- tracker/tracker_relationship.c | 112 +++++++++++++++++++++++++++------ tracker/tracker_relationship.h | 3 + tracker/tracker_service.c | 21 ++----- 7 files changed, 127 insertions(+), 35 deletions(-) diff --git a/HISTORY b/HISTORY index 755a31a..a22fd10 100644 --- a/HISTORY +++ b/HISTORY @@ -10,6 +10,7 @@ Version 6.03 2019-11-17 * code refine: combine g_fdfs_store_paths and g_path_space_list, and extent struct FDFSStorePathInfo * check store path's mark file to prevent confusion + * new selected tracker leader do NOT notify self by network NOTE: the tracker and storage server must upgrade together diff --git a/tracker/fdfs_shared_func.c b/tracker/fdfs_shared_func.c index 6e76ea9..61397db 100644 --- a/tracker/fdfs_shared_func.c +++ b/tracker/fdfs_shared_func.c @@ -13,6 +13,7 @@ #include "fastcommon/logger.h" #include "fastcommon/sockopt.h" #include "fastcommon/shared_func.h" +#include "fastcommon/local_ip_func.h" #include "tracker_proto.h" #include "fdfs_global.h" #include "fdfs_shared_func.h" @@ -109,6 +110,24 @@ bool fdfs_server_equal(TrackerServerInfo *pServer1, return true; } +bool fdfs_server_contain_local_service(TrackerServerInfo *pServerInfo, + const int target_port) +{ + const char *current_ip; + + current_ip = get_first_local_ip(); + while (current_ip != NULL) + { + if (fdfs_server_contain(pServerInfo, current_ip, target_port)) + { + return true; + } + current_ip = get_next_local_ip(current_ip); + } + + return false; +} + TrackerServerInfo *fdfs_tracker_group_get_server(TrackerServerGroup *pGroup, const char *target_ip, const int target_port) { diff --git a/tracker/fdfs_shared_func.h b/tracker/fdfs_shared_func.h index b8903ec..38a79ac 100644 --- a/tracker/fdfs_shared_func.h +++ b/tracker/fdfs_shared_func.h @@ -80,6 +80,9 @@ bool fdfs_server_contain_ex(TrackerServerInfo *pServer1, bool fdfs_server_equal(TrackerServerInfo *pServer1, TrackerServerInfo *pServer2); +bool fdfs_server_contain_local_service(TrackerServerInfo *pServerInfo, + const int target_port); + /** * tracker group get server * params: diff --git a/tracker/tracker_mem.c b/tracker/tracker_mem.c index e5917f4..90c574d 100644 --- a/tracker/tracker_mem.c +++ b/tracker/tracker_mem.c @@ -4275,8 +4275,7 @@ static int tracker_mem_get_tracker_server(FDFSStorageJoinBody *pJoinBody, \ for (pTrackerServer=pJoinBody->tracker_servers; pTrackerServerconnections[0].port == g_server_port && - is_local_host_ip(pTrackerServer->connections[0].ip_addr)) + if (fdfs_server_contain_local_service(pTrackerServer, g_server_port)) { continue; } diff --git a/tracker/tracker_relationship.c b/tracker/tracker_relationship.c index 7f5721a..64991d1 100644 --- a/tracker/tracker_relationship.c +++ b/tracker/tracker_relationship.c @@ -177,6 +177,21 @@ static int relationship_cmp_tracker_status(const void *p1, const void *p2) return conn1->port - conn2->port; } +static int relationship_get_tracker_status(TrackerRunningStatus *pStatus) +{ + if (fdfs_server_contain_local_service(pStatus->pTrackerServer, + g_server_port)) + { + tracker_calc_running_times(pStatus); + pStatus->if_leader = g_if_leader_self; + return 0; + } + else + { + return fdfs_get_tracker_status(pStatus->pTrackerServer, pStatus); + } +} + static int relationship_get_tracker_leader(TrackerRunningStatus *pTrackerStatus) { TrackerServerInfo *pTrackerServer; @@ -196,7 +211,7 @@ static int relationship_get_tracker_leader(TrackerRunningStatus *pTrackerStatus) pTrackerServerpTrackerServer = pTrackerServer; - r = fdfs_get_tracker_status(pTrackerServer, pStatus); + r = relationship_get_tracker_status(pStatus); if (r == 0) { pStatus++; @@ -237,14 +252,6 @@ static int relationship_get_tracker_leader(TrackerRunningStatus *pTrackerStatus) return 0; } -#define relationship_notify_next_leader(pTrackerServer, pLeader, bConnectFail) \ - do_notify_leader_changed(pTrackerServer, pLeader, \ - TRACKER_PROTO_CMD_TRACKER_NOTIFY_NEXT_LEADER, bConnectFail) - -#define relationship_commit_next_leader(pTrackerServer, pLeader, bConnectFail) \ - do_notify_leader_changed(pTrackerServer, pLeader, \ - TRACKER_PROTO_CMD_TRACKER_COMMIT_NEXT_LEADER, bConnectFail) - static int do_notify_leader_changed(TrackerServerInfo *pTrackerServer, \ ConnectionInfo *pLeader, const char cmd, bool *bConnectFail) { @@ -320,7 +327,74 @@ static int do_notify_leader_changed(TrackerServerInfo *pTrackerServer, \ return result; } -static int relationship_notify_leader_changed(ConnectionInfo *pLeader) +void relationship_set_tracker_leader(const int server_index, + ConnectionInfo *pLeader, const bool leader_self) +{ + g_tracker_servers.leader_index = server_index; + g_next_leader_index = -1; + + if (leader_self) + { + g_if_leader_self = true; + g_tracker_leader_chg_count++; + } + else + { + logInfo("file: "__FILE__", line: %d, " + "the tracker leader is %s:%d", __LINE__, + pLeader->ip_addr, pLeader->port); + } +} + +static int relationship_notify_next_leader(TrackerServerInfo *pTrackerServer, + TrackerRunningStatus *pTrackerStatus, bool *bConnectFail) +{ + if (pTrackerStatus->pTrackerServer == pTrackerServer) + { + g_next_leader_index = pTrackerServer - g_tracker_servers.servers; + return 0; + } + else + { + ConnectionInfo *pLeader; + pLeader = pTrackerStatus->pTrackerServer->connections; + return do_notify_leader_changed(pTrackerServer, pLeader, + TRACKER_PROTO_CMD_TRACKER_NOTIFY_NEXT_LEADER, bConnectFail); + } +} + +static int relationship_commit_next_leader(TrackerServerInfo *pTrackerServer, + TrackerRunningStatus *pTrackerStatus, bool *bConnectFail) +{ + ConnectionInfo *pLeader; + + pLeader = pTrackerStatus->pTrackerServer->connections; + if (pTrackerStatus->pTrackerServer == pTrackerServer) + { + int server_index; + int expect_index; + server_index = g_next_leader_index; + expect_index = pTrackerServer - g_tracker_servers.servers; + if (server_index != expect_index) + { + logError("file: "__FILE__", line: %d, " + "g_next_leader_index: %d != expected: %d", + __LINE__, server_index, expect_index); + g_next_leader_index = -1; + return EBUSY; + } + + relationship_set_tracker_leader(server_index, pLeader, true); + return 0; + } + else + { + return do_notify_leader_changed(pTrackerServer, pLeader, + TRACKER_PROTO_CMD_TRACKER_COMMIT_NEXT_LEADER, bConnectFail); + } +} + +static int relationship_notify_leader_changed(TrackerRunningStatus *pTrackerStatus) { TrackerServerInfo *pTrackerServer; TrackerServerInfo *pTrackerEnd; @@ -331,11 +405,11 @@ static int relationship_notify_leader_changed(ConnectionInfo *pLeader) result = ENOENT; pTrackerEnd = g_tracker_servers.servers + g_tracker_servers.server_count; success_count = 0; - for (pTrackerServer=g_tracker_servers.servers; \ + for (pTrackerServer=g_tracker_servers.servers; pTrackerServerconnections; - if (conn->port == g_server_port && is_local_host_ip(conn->ip_addr)) + if (fdfs_server_contain_local_service(trackerStatus. + pTrackerServer, g_server_port)) { - if ((result=relationship_notify_leader_changed(conn)) != 0) + if ((result=relationship_notify_leader_changed( + &trackerStatus)) != 0) { return result; } diff --git a/tracker/tracker_relationship.h b/tracker/tracker_relationship.h index 69fd9f3..4e0dc47 100644 --- a/tracker/tracker_relationship.h +++ b/tracker/tracker_relationship.h @@ -24,6 +24,9 @@ extern bool g_if_leader_self; //if I am leader int tracker_relationship_init(); int tracker_relationship_destroy(); +void relationship_set_tracker_leader(const int server_index, + ConnectionInfo *pLeader, const bool if_leader_self); + #ifdef __cplusplus } #endif diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 1ae44bb..7f56eab 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -897,8 +897,8 @@ static int tracker_deal_notify_next_leader(struct fast_task_info *pTask) return ENOENT; } - if (g_if_leader_self && (leader.port != g_server_port || \ - !is_local_host_ip(leader.ip_addr))) + if (g_if_leader_self && !(leader.port == g_server_port && + is_local_host_ip(leader.ip_addr))) { g_if_leader_self = false; g_tracker_servers.leader_index = -1; @@ -922,6 +922,7 @@ static int tracker_deal_commit_next_leader(struct fast_task_info *pTask) char *ipAndPort[2]; ConnectionInfo leader; int server_index; + bool leader_self; if (pTask->length - sizeof(TrackerHeader) != FDFS_PROTO_IP_PORT_SIZE) { @@ -969,19 +970,9 @@ static int tracker_deal_commit_next_leader(struct fast_task_info *pTask) return EINVAL; } - g_tracker_servers.leader_index = server_index; - g_next_leader_index = -1; - if (leader.port == g_server_port && is_local_host_ip(leader.ip_addr)) - { - g_if_leader_self = true; - g_tracker_leader_chg_count++; - } - else - { - logInfo("file: "__FILE__", line: %d, " \ - "the tracker leader is %s:%d", __LINE__, \ - leader.ip_addr, leader.port); - } + leader_self = (leader.port == g_server_port) && + is_local_host_ip(leader.ip_addr); + relationship_set_tracker_leader(server_index, &leader, leader_self); return 0; } From e4c2644db25fd9fe742ac4657d548b1463fd1e1a Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 19 Nov 2019 08:49:08 +0800 Subject: [PATCH 40/95] change sleep seconds when ping tracker leader fail --- tracker/tracker_relationship.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tracker/tracker_relationship.c b/tracker/tracker_relationship.c index 64991d1..c638c70 100644 --- a/tracker/tracker_relationship.c +++ b/tracker/tracker_relationship.c @@ -562,9 +562,9 @@ static void *relationship_thread_entrance(void* arg) int sleep_seconds; fail_count = 0; + sleep_seconds = 1; while (g_continue_flag) { - sleep_seconds = 1; if (g_tracker_servers.servers != NULL) { if (g_tracker_servers.leader_index < 0) @@ -574,20 +574,26 @@ static void *relationship_thread_entrance(void* arg) sleep_seconds = 1 + (int)((double)rand() * (double)MAX_SLEEP_SECONDS / RAND_MAX); } + else + { + sleep_seconds = 1; + } } else { if (relationship_ping_leader() == 0) { fail_count = 0; + sleep_seconds = 1; } else { - fail_count++; - if (fail_count >= 3) + sleep_seconds *= 2; + if (++fail_count >= 3) { g_tracker_servers.leader_index = -1; fail_count = 0; + sleep_seconds = 1; } } } From 5557429899e51c3186a1f7605c6df784c78507da Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 19 Nov 2019 09:17:55 +0800 Subject: [PATCH 41/95] log more info when ping tracker leader fail --- tracker/tracker_relationship.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tracker/tracker_relationship.c b/tracker/tracker_relationship.c index c638c70..c8a5516 100644 --- a/tracker/tracker_relationship.c +++ b/tracker/tracker_relationship.c @@ -581,6 +581,8 @@ static void *relationship_thread_entrance(void* arg) } else { + int leader_index; + leader_index = g_tracker_servers.leader_index; if (relationship_ping_leader() == 0) { fail_count = 0; @@ -588,8 +590,28 @@ static void *relationship_thread_entrance(void* arg) } else { + char leader_str[64]; + ConnectionInfo *pLeader; + + if (leader_index < 0) + { + strcpy(leader_str, "unknown leader"); + } + else + { + pLeader = g_tracker_servers.servers + [leader_index].connections; + sprintf(leader_str, "leader %s:%d", + pLeader->ip_addr, pLeader->port); + } + + ++fail_count; + logError("file: "__FILE__", line: %d, " + "%dth ping %s fail", __LINE__, + fail_count, leader_str); + sleep_seconds *= 2; - if (++fail_count >= 3) + if (fail_count >= 3) { g_tracker_servers.leader_index = -1; fail_count = 0; From 0551999135ef72c4d913214cde53cb1c957cf725 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 19 Nov 2019 10:40:17 +0800 Subject: [PATCH 42/95] storage.conf add parameter check_store_path_mark --- HISTORY | 2 +- conf/storage.conf | 8 ++++++++ storage/storage_func.c | 37 ++++++++++++++++++++++++++++--------- storage/storage_global.c | 1 + storage/storage_global.h | 1 + 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/HISTORY b/HISTORY index a22fd10..16d8cef 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.03 2019-11-17 +Version 6.03 2019-11-19 * dual IPs support two different types of inner (intranet) IPs * storage server request tracker server to change it's status to that of tracker leader when the storage server found diff --git a/conf/storage.conf b/conf/storage.conf index 9346182..0a3b599 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -303,6 +303,14 @@ compress_binlog = false # since V6.01 compress_binlog_time=01:30 +# if check the mark of store path to prevent confusion +# recommend to set this parameter to true +# if two storage servers (instances) MUST use a same store path for +# some specific purposes, you should set this parameter to false +# default value is true +# since V6.03 +check_store_path_mark = true + # use the ip address of this storage server if domain_name is empty, # else this domain name will ocur in the url redirected by the tracker server http.domain_name= diff --git a/storage/storage_func.c b/storage/storage_func.c index acd369e..0823442 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -671,13 +671,16 @@ int storage_write_to_sync_ini_file() ); - for (i=0; i Date: Tue, 19 Nov 2019 11:03:55 +0800 Subject: [PATCH 43/95] remove debug info --- storage/storage_func.c | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/storage/storage_func.c b/storage/storage_func.c index 0823442..a698926 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -778,25 +778,6 @@ static int storage_generate_store_path_mark(const int store_path_index) base64_encode_ex(&g_fdfs_base64_context, (char *)&mark_info, sizeof(mark_info), mark_str, &mark_len, false); - { - char time_str[32]; - - mark_info.ip_addr[IP_ADDRESS_SIZE - 1] = '\0'; - formatDatetime(mark_info.create_time, - "%Y-%m-%d %H:%M:%S", - time_str, sizeof(time_str)); - - logInfo("file: "__FILE__", line: %d, " - "the store path #%d, fields in the mark file: " - "{ ip_addr: %s, port: %d," - " store_path_index: %d," - " create_time: %s }, mark_str: %s", - __LINE__, store_path_index, - mark_info.ip_addr, mark_info.port, - mark_info.store_path_index, - time_str, mark_str); - } - snprintf(full_filename, sizeof(full_filename), "%s/data/%s", g_fdfs_store_paths.paths[store_path_index].path, STORE_PATH_MARK_FILENAME); From 9a29048ae5283e0c7ed93547ba3210414410ea92 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 20 Nov 2019 08:38:38 +0800 Subject: [PATCH 44/95] larger network_timeout for fetching one-store-path binlog --- HISTORY | 4 ++- storage/storage_disk_recovery.c | 43 ++++++++++++++++++++------------- storage/storage_service.c | 22 ++++++++--------- tracker/tracker_proto.c | 25 ++++++++++--------- tracker/tracker_proto.h | 11 ++++++++- 5 files changed, 63 insertions(+), 42 deletions(-) diff --git a/HISTORY b/HISTORY index 16d8cef..df6660b 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.03 2019-11-19 +Version 6.03 2019-11-20 * dual IPs support two different types of inner (intranet) IPs * storage server request tracker server to change it's status to that of tracker leader when the storage server found @@ -11,6 +11,8 @@ Version 6.03 2019-11-19 and extent struct FDFSStorePathInfo * check store path's mark file to prevent confusion * new selected tracker leader do NOT notify self by network + * larger network_timeout for fetching one-store-path binlog + when disk recovery NOTE: the tracker and storage server must upgrade together diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 4cf8096..985ed5d 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -65,6 +65,7 @@ static int storage_do_fetch_binlog(ConnectionInfo *pSrcStorage, \ int64_t in_bytes; int64_t file_bytes; int result; + int network_timeout; pBasePath = g_fdfs_store_paths.paths[store_path_index].path; recovery_get_binlog_filename(pBasePath, full_binlog_filename); @@ -75,21 +76,30 @@ static int storage_do_fetch_binlog(ConnectionInfo *pSrcStorage, \ long2buff(FDFS_GROUP_NAME_MAX_LEN + 1, pHeader->pkg_len); pHeader->cmd = STORAGE_PROTO_CMD_FETCH_ONE_PATH_BINLOG; strcpy(out_buff + sizeof(TrackerHeader), g_group_name); - *(out_buff + sizeof(TrackerHeader) + FDFS_GROUP_NAME_MAX_LEN) = \ + *(out_buff + sizeof(TrackerHeader) + FDFS_GROUP_NAME_MAX_LEN) = store_path_index; - if((result=tcpsenddata_nb(pSrcStorage->sock, out_buff, \ + if((result=tcpsenddata_nb(pSrcStorage->sock, out_buff, sizeof(out_buff), g_fdfs_network_timeout)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "tracker server %s:%d, send data fail, " \ - "errno: %d, error info: %s.", \ - __LINE__, pSrcStorage->ip_addr, pSrcStorage->port, \ + logError("file: "__FILE__", line: %d, " + "storage server %s:%d, send data fail, " + "errno: %d, error info: %s.", + __LINE__, pSrcStorage->ip_addr, pSrcStorage->port, result, STRERROR(result)); return result; } - if ((result=fdfs_recv_header(pSrcStorage, &in_bytes)) != 0) + if (g_fdfs_network_timeout >= 600) + { + network_timeout = g_fdfs_network_timeout; + } + else + { + network_timeout = 600; + } + if ((result=fdfs_recv_header_ex(pSrcStorage, network_timeout, + &in_bytes)) != 0) { logError("file: "__FILE__", line: %d, " "fdfs_recv_header fail, result: %d", @@ -97,21 +107,20 @@ static int storage_do_fetch_binlog(ConnectionInfo *pSrcStorage, \ return result; } - if ((result=tcprecvfile(pSrcStorage->sock, full_binlog_filename, \ - in_bytes, 0, g_fdfs_network_timeout, \ - &file_bytes)) != 0) + if ((result=tcprecvfile(pSrcStorage->sock, full_binlog_filename, + in_bytes, 0, network_timeout, &file_bytes)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "tracker server %s:%d, tcprecvfile fail, " \ - "errno: %d, error info: %s.", \ - __LINE__, pSrcStorage->ip_addr, pSrcStorage->port, \ + logError("file: "__FILE__", line: %d, " + "storage server %s:%d, tcprecvfile fail, " + "errno: %d, error info: %s.", + __LINE__, pSrcStorage->ip_addr, pSrcStorage->port, result, STRERROR(result)); return result; } - logInfo("file: "__FILE__", line: %d, " \ - "recovery binlog file size: %"PRId64, \ - __LINE__, file_bytes); + logInfo("file: "__FILE__", line: %d, " + "recovery binlog from %s:%d, file size: %"PRId64, __LINE__, + pSrcStorage->ip_addr, pSrcStorage->port, file_bytes); return 0; } diff --git a/storage/storage_service.c b/storage/storage_service.c index e7e2dd9..2abc477 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -4144,7 +4144,7 @@ static int storage_server_trunk_confirm_or_free(struct fast_task_info *pTask) } } -static int storage_server_fetch_one_path_binlog_dealer( \ +static int storage_server_fetch_one_path_binlog_dealer( struct fast_task_info *pTask) { #define STORAGE_LAST_AHEAD_BYTES (2 * FDFS_PROTO_PKG_LEN_SIZE) @@ -4167,7 +4167,7 @@ static int storage_server_fetch_one_path_binlog_dealer( \ int64_t pkg_len; pClientInfo = (StorageClientInfo *)pTask->arg; - if (pClientInfo->total_length - pClientInfo->total_offset <= \ + if (pClientInfo->total_length - pClientInfo->total_offset <= STORAGE_LAST_AHEAD_BYTES) //finished, close the connection { STORAGE_NIO_NOTIFY_CLOSE(pTask); @@ -4177,13 +4177,13 @@ static int storage_server_fetch_one_path_binlog_dealer( \ pFileContext = &(pClientInfo->file_context); pReader = (StorageBinLogReader *)pClientInfo->extra_arg; - store_path_index = pFileContext->extra_info.upload.trunk_info. \ + store_path_index = pFileContext->extra_info.upload.trunk_info. path.store_path_index; pBasePath = g_fdfs_store_paths.paths[store_path_index].path; pOutBuff = pTask->data; bLast = false; - sprintf(diskLogicPath, "%c"FDFS_STORAGE_DATA_DIR_FORMAT, \ + sprintf(diskLogicPath, "%c"FDFS_STORAGE_DATA_DIR_FORMAT, FDFS_STORAGE_STORE_PATH_PREFIX_CHAR, store_path_index); do @@ -4368,12 +4368,12 @@ static int storage_server_fetch_one_path_binlog_dealer( \ pTask->length = pOutBuff - pTask->data; if (bLast) { - pkg_len = pClientInfo->total_offset + pTask->length - \ + pkg_len = pClientInfo->total_offset + pTask->length - sizeof(TrackerHeader); long2buff(pkg_len, pOutBuff); pTask->length += FDFS_PROTO_PKG_LEN_SIZE; - pClientInfo->total_length = pkg_len + FDFS_PROTO_PKG_LEN_SIZE \ + pClientInfo->total_length = pkg_len + FDFS_PROTO_PKG_LEN_SIZE + STORAGE_LAST_AHEAD_BYTES; } @@ -4406,7 +4406,7 @@ static void fetch_one_path_binlog_finish_clean_up(struct fast_task_info *pTask) free(pReader); } -static int storage_server_do_fetch_one_path_binlog( \ +static int storage_server_do_fetch_one_path_binlog( struct fast_task_info *pTask, const int store_path_index) { StorageClientInfo *pClientInfo; @@ -4441,20 +4441,20 @@ static int storage_server_do_fetch_one_path_binlog( \ pFileContext->fd = -1; pFileContext->op = FDFS_STORAGE_FILE_OP_READ; - pFileContext->dio_thread_index = storage_dio_get_thread_index( \ + pFileContext->dio_thread_index = storage_dio_get_thread_index( pTask, store_path_index, pFileContext->op); pFileContext->extra_info.upload.trunk_info.path.store_path_index = store_path_index; pClientInfo->extra_arg = pReader; - pClientInfo->total_length = INFINITE_FILE_SIZE + \ + pClientInfo->total_length = INFINITE_FILE_SIZE + sizeof(TrackerHeader); pClientInfo->total_offset = 0; pTask->length = sizeof(TrackerHeader); pHeader = (TrackerHeader *)pTask->data; pHeader->status = 0; pHeader->cmd = STORAGE_PROTO_CMD_RESP; - long2buff(pClientInfo->total_length - sizeof(TrackerHeader), \ + long2buff(pClientInfo->total_length - sizeof(TrackerHeader), pHeader->pkg_len); storage_nio_notify(pTask); @@ -4512,7 +4512,7 @@ static int storage_server_fetch_one_path_binlog(struct fast_task_info *pTask) return EINVAL; } - return storage_server_do_fetch_one_path_binlog( \ + return storage_server_do_fetch_one_path_binlog( pTask, store_path_index); } diff --git a/tracker/tracker_proto.c b/tracker/tracker_proto.c index 3e737b7..94afadc 100644 --- a/tracker/tracker_proto.c +++ b/tracker/tracker_proto.c @@ -24,19 +24,20 @@ #include "tracker_proto.h" #include "fdfs_shared_func.h" -int fdfs_recv_header(ConnectionInfo *pTrackerServer, int64_t *in_bytes) +int fdfs_recv_header_ex(ConnectionInfo *pTrackerServer, + const int network_timeout, int64_t *in_bytes) { TrackerHeader resp; int result; - if ((result=tcprecvdata_nb(pTrackerServer->sock, &resp, \ - sizeof(resp), g_fdfs_network_timeout)) != 0) + if ((result=tcprecvdata_nb(pTrackerServer->sock, &resp, + sizeof(resp), network_timeout)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "server: %s:%d, recv data fail, " \ - "errno: %d, error info: %s", \ - __LINE__, pTrackerServer->ip_addr, \ - pTrackerServer->port, \ + logError("file: "__FILE__", line: %d, " + "server: %s:%d, recv data fail, " + "errno: %d, error info: %s", + __LINE__, pTrackerServer->ip_addr, + pTrackerServer->port, result, STRERROR(result)); *in_bytes = 0; return result; @@ -56,10 +57,10 @@ int fdfs_recv_header(ConnectionInfo *pTrackerServer, int64_t *in_bytes) *in_bytes = buff2long(resp.pkg_len); if (*in_bytes < 0) { - logError("file: "__FILE__", line: %d, " \ - "server: %s:%d, recv package size " \ - "%"PRId64" is not correct", \ - __LINE__, pTrackerServer->ip_addr, \ + logError("file: "__FILE__", line: %d, " + "server: %s:%d, recv package size " + "%"PRId64" is not correct", + __LINE__, pTrackerServer->ip_addr, pTrackerServer->port, *in_bytes); *in_bytes = 0; return EINVAL; diff --git a/tracker/tracker_proto.h b/tracker/tracker_proto.h index c47ba85..7106d5b 100644 --- a/tracker/tracker_proto.h +++ b/tracker/tracker_proto.h @@ -12,6 +12,7 @@ #define _TRACKER_PROTO_H_ #include "tracker_types.h" +#include "fdfs_global.h" #include "fastcommon/connection_pool.h" #include "fastcommon/ini_file_reader.h" @@ -287,7 +288,15 @@ int metadata_cmp_by_name(const void *p1, const void *p2); const char *get_storage_status_caption(const int status); -int fdfs_recv_header(ConnectionInfo *pTrackerServer, int64_t *in_bytes); +int fdfs_recv_header_ex(ConnectionInfo *pTrackerServer, + const int network_timeout, int64_t *in_bytes); + +static inline int fdfs_recv_header(ConnectionInfo *pTrackerServer, + int64_t *in_bytes) +{ + return fdfs_recv_header_ex(pTrackerServer, + g_fdfs_network_timeout, in_bytes); +} int fdfs_recv_response(ConnectionInfo *pTrackerServer, \ char **buff, const int buff_size, \ From add02e734882a236fa0d3057adc36e283339c8f6 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 20 Nov 2019 17:02:21 +0800 Subject: [PATCH 45/95] a little change --- storage/tracker_client_thread.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index 0133e08..27ea40b 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -2022,8 +2022,8 @@ int tracker_report_join(ConnectionInfo *pTrackerServer, \ if (g_my_report_status[i].my_result == -1) { logInfo("file: "__FILE__", line: %d, " - "tracker server: #%d. %s:%d, g_my_report_status: %d", - __LINE__, i, + "tracker server: #%d. %s:%d, " + "my_report_result: %d", __LINE__, i, g_tracker_group.servers[i].connections[0].ip_addr, g_tracker_group.servers[i].connections[0].port, g_my_report_status[i].my_result); From aeb171dca7a3b951386862fb3eaf68fafcfe57e7 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 22 Nov 2019 07:54:16 +0800 Subject: [PATCH 46/95] change .gitignore --- .gitignore | 52 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 383a7e7..458784e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ client/fdfs_link_library.sh # Compiled Dynamic libraries *.so *.dylib +*.dSYM *.dll # Fortran module files @@ -39,19 +40,38 @@ client/fdfs_link_library.sh *.exe *.out *.app -client/fdfs_append_file* -client/fdfs_appender_test* -client/fdfs_crc32* -client/fdfs_delete_file* -client/fdfs_download_file* -client/fdfs_file_info* -client/fdfs_monitor* -client/fdfs_test* -client/fdfs_test1* -client/fdfs_upload_appender* -client/fdfs_upload_file* -storage/fdfs_storaged* -tracker/fdfs_trackerd* +client/fdfs_append_file +client/fdfs_appender_test +client/fdfs_appender_test1 +client/fdfs_crc32 +client/fdfs_delete_file +client/fdfs_download_file +client/fdfs_file_info +client/fdfs_monitor +client/fdfs_test +client/fdfs_test1 +client/fdfs_upload_appender +client/fdfs_upload_file +client/fdfs_regenerate_filename +client/test/fdfs_monitor +client/test/fdfs_test +client/test/fdfs_test1 +storage/fdfs_storaged +tracker/fdfs_trackerd +test/combine_result +test/100M +test/10M +test/1M +test/200K +test/50K +test/5K +test/gen_files +test/test_delete +test/test_download +test/test_upload +test/upload/ +test/download/ +test/delete/ # other php_client/.deps @@ -79,4 +99,8 @@ php_client/libtool php_client/ltmain.sh php_client/missing php_client/mkinstalldirs -php_client/run-tests.php \ No newline at end of file +php_client/run-tests.php + +# fastdfs runtime paths +data/ +logs/ From da7300bc9b67d64b185ee56e9b52c185751de9c8 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 22 Nov 2019 09:26:47 +0800 Subject: [PATCH 47/95] change comment/information in config files and codes --- conf/storage.conf | 18 ++++++++++++------ conf/tracker.conf | 9 +++++---- storage/storage_service.c | 8 +++++--- tracker/tracker_nio.c | 9 +++++---- tracker/tracker_service.c | 6 +++--- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/conf/storage.conf b/conf/storage.conf index 0a3b599..76f2529 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -24,31 +24,37 @@ client_bind=true port=23000 # connect timeout in seconds -# default value is 30s +# default value is 30 # Note: in the intranet network (LAN), 2 seconds is enough. connect_timeout=10 # network timeout in seconds for send and recv -# default value is 30s +# default value is 30 network_timeout=60 -# heart beat interval in seconds +# the heart beat interval in seconds +# the storage server send heartbeat to tracker server periodically +# default value is 30 heart_beat_interval=30 # disk usage report interval in seconds +# the storage server send disk usage report to tracker server periodically +# default value is 300 stat_report_interval=60 # the base path to store data and log files +# NOTE: the binlog files maybe are large, make sure +# the base path has enough disk space base_path=/home/yuqing/fastdfs # max concurrent connections the server supported -# default value is 256 -# more max_connections means more memory will be used # you should set this parameter larger, eg. 10240 +# default value is 256 max_connections=1024 # the buff size to recv / send data from/to network # this parameter must more than 8KB +# 256KB or 512KB is recommended # default value is 64KB # since V2.00 buff_size = 256KB @@ -295,7 +301,7 @@ connection_pool_max_idle_time = 3600 # if compress the binlog files by gzip # default value is false # since V6.01 -compress_binlog = false +compress_binlog = true # try to compress binlog time, time format: Hour:Minute # Hour from 0 to 23, Minute from 0 to 59 diff --git a/conf/tracker.conf b/conf/tracker.conf index 6c5f8c5..52f5f8b 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -11,19 +11,20 @@ bind_addr= port=22122 # connect timeout in seconds -# default value is 30s +# default value is 30 # Note: in the intranet network (LAN), 2 seconds is enough. connect_timeout=10 # network timeout in seconds for send and recv -# default value is 30s +# default value is 30 network_timeout=60 # the base path to store data and log files base_path=/home/yuqing/fastdfs -# max concurrent connections this server supported -# you should set this parameter larger, eg. 102400 +# max concurrent connections this server support +# you should set this parameter larger, eg. 10240 +# default value is 256 max_connections=1024 # accept thread count diff --git a/storage/storage_service.c b/storage/storage_service.c index 2abc477..5ee573d 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -1834,9 +1834,11 @@ static void *accept_thread_entrance(void* arg) pTask = free_queue_pop(); if (pTask == NULL) { - logError("file: "__FILE__", line: %d, " \ - "malloc task buff failed", \ - __LINE__); + logError("file: "__FILE__", line: %d, " + "malloc task buff fail, you should " + "increase the parameter \"max_connections\" " + "in storage.conf, or check your applications " + "for connection leaks", __LINE__); close(incomesock); continue; } diff --git a/tracker/tracker_nio.c b/tracker/tracker_nio.c index 796843e..9d1f69f 100644 --- a/tracker/tracker_nio.c +++ b/tracker/tracker_nio.c @@ -134,10 +134,11 @@ void recv_notify_read(int sock, short event, void *arg) pTask = free_queue_pop(); if (pTask == NULL) { - logError("file: "__FILE__", line: %d, " \ - "malloc task buff failed, you should " \ - "increase the parameter: max_connections", \ - __LINE__); + logError("file: "__FILE__", line: %d, " + "malloc task buff fail, you should " + "increase the parameter \"max_connections\" " + "in tracker.conf, or check your applications " + "for connection leaks", __LINE__); close(incomesock); continue; } diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 7f56eab..7316740 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -588,14 +588,14 @@ static int tracker_deal_changelog_req(struct fast_task_info *pTask) break; } - memcpy(group_name, pTask->data + sizeof(TrackerHeader), \ + memcpy(group_name, pTask->data + sizeof(TrackerHeader), FDFS_GROUP_NAME_MAX_LEN); *(group_name + FDFS_GROUP_NAME_MAX_LEN) = '\0'; pGroup = tracker_mem_get_group(group_name); if (pGroup == NULL) { - logError("file: "__FILE__", line: %d, " \ - "client ip: %s, invalid group_name: %s", \ + logError("file: "__FILE__", line: %d, " + "client ip: %s, group_name: %s not exist", __LINE__, pTask->client_ip, group_name); result = ENOENT; break; From ad22505fd201d3065c429ab72e1931bd130d8483 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 22 Nov 2019 21:04:34 +0800 Subject: [PATCH 48/95] change sync_log_buff_interval from 10 to 1 --- conf/storage.conf | 2 +- conf/tracker.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/storage.conf b/conf/storage.conf index 76f2529..9adfa6a 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -186,7 +186,7 @@ fsync_after_written_bytes=0 # sync log buff to disk every interval seconds # must > 0, default value is 10 seconds -sync_log_buff_interval=10 +sync_log_buff_interval=1 # sync binlog buff / cache to disk every interval seconds # default value is 60 seconds diff --git a/conf/tracker.conf b/conf/tracker.conf index 52f5f8b..9e1d641 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -115,7 +115,7 @@ allow_hosts=* # sync log buff to disk every interval seconds # default value is 10 seconds -sync_log_buff_interval = 10 +sync_log_buff_interval = 1 # check storage server alive interval seconds check_active_interval = 120 From 949f53b15de16b79d9968ce3244edb9734faf301 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 23 Nov 2019 09:56:35 +0800 Subject: [PATCH 49/95] INSTALL changed and modify website name --- INSTALL | 76 ++++++++++++-------- client/client_func.c | 2 +- client/client_func.h | 2 +- client/client_global.c | 2 +- client/client_global.h | 2 +- client/fdfs_append_file.c | 2 +- client/fdfs_appender_test.c | 4 +- client/fdfs_appender_test1.c | 4 +- client/fdfs_client.h | 2 +- client/fdfs_crc32.c | 2 +- client/fdfs_delete_file.c | 2 +- client/fdfs_download_file.c | 2 +- client/fdfs_file_info.c | 2 +- client/fdfs_monitor.c | 2 +- client/fdfs_regenerate_filename.c | 2 +- client/fdfs_test.c | 4 +- client/fdfs_test1.c | 4 +- client/fdfs_upload_appender.c | 2 +- client/fdfs_upload_file.c | 2 +- client/storage_client.c | 2 +- client/storage_client.h | 2 +- client/storage_client1.h | 2 +- client/test/fdfs_monitor.c | 2 +- client/test/fdfs_test.c | 4 +- client/test/fdfs_test1.c | 4 +- client/tracker_client.c | 2 +- client/tracker_client.h | 2 +- common/fdfs_define.h | 2 +- common/fdfs_global.c | 2 +- common/fdfs_global.h | 2 +- common/fdfs_http_shared.c | 2 +- common/fdfs_http_shared.h | 2 +- common/mime_file_parser.c | 2 +- common/mime_file_parser.h | 2 +- setup.sh | 32 +++++++++ storage/fdfs_storaged.c | 2 +- storage/fdht_client/fdht_client.c | 2 +- storage/fdht_client/fdht_client.h | 2 +- storage/fdht_client/fdht_define.h | 2 +- storage/fdht_client/fdht_func.c | 2 +- storage/fdht_client/fdht_func.h | 2 +- storage/fdht_client/fdht_global.c | 2 +- storage/fdht_client/fdht_global.h | 2 +- storage/fdht_client/fdht_proto.c | 2 +- storage/fdht_client/fdht_proto.h | 2 +- storage/fdht_client/fdht_proto_types.h | 2 +- storage/fdht_client/fdht_types.h | 2 +- storage/storage_dio.c | 2 +- storage/storage_dio.h | 2 +- storage/storage_disk_recovery.c | 2 +- storage/storage_disk_recovery.h | 2 +- storage/storage_dump.c | 2 +- storage/storage_dump.h | 2 +- storage/storage_func.c | 2 +- storage/storage_func.h | 2 +- storage/storage_global.c | 2 +- storage/storage_global.h | 2 +- storage/storage_ip_changed_dealer.c | 2 +- storage/storage_ip_changed_dealer.h | 2 +- storage/storage_nio.c | 2 +- storage/storage_nio.h | 2 +- storage/storage_param_getter.c | 2 +- storage/storage_param_getter.h | 2 +- storage/storage_service.c | 2 +- storage/storage_service.h | 2 +- storage/storage_sync.c | 2 +- storage/storage_sync.h | 2 +- storage/storage_sync_func.c | 2 +- storage/storage_sync_func.h | 2 +- storage/tracker_client_thread.c | 2 +- storage/tracker_client_thread.h | 2 +- storage/trunk_mgr/trunk_client.c | 2 +- storage/trunk_mgr/trunk_client.h | 2 +- storage/trunk_mgr/trunk_free_block_checker.c | 2 +- storage/trunk_mgr/trunk_free_block_checker.h | 2 +- storage/trunk_mgr/trunk_mem.c | 2 +- storage/trunk_mgr/trunk_mem.h | 2 +- storage/trunk_mgr/trunk_shared.c | 2 +- storage/trunk_mgr/trunk_shared.h | 2 +- storage/trunk_mgr/trunk_sync.c | 2 +- storage/trunk_mgr/trunk_sync.h | 2 +- tracker/fdfs_server_id_func.c | 2 +- tracker/fdfs_server_id_func.h | 2 +- tracker/fdfs_shared_func.c | 2 +- tracker/fdfs_shared_func.h | 2 +- tracker/fdfs_trackerd.c | 2 +- tracker/tracker_dump.c | 2 +- tracker/tracker_dump.h | 2 +- tracker/tracker_func.c | 2 +- tracker/tracker_func.h | 2 +- tracker/tracker_global.c | 2 +- tracker/tracker_global.h | 2 +- tracker/tracker_http_check.h | 2 +- tracker/tracker_mem.c | 2 +- tracker/tracker_mem.h | 2 +- tracker/tracker_nio.c | 2 +- tracker/tracker_nio.h | 2 +- tracker/tracker_proto.c | 2 +- tracker/tracker_proto.h | 2 +- tracker/tracker_relationship.c | 2 +- tracker/tracker_relationship.h | 2 +- tracker/tracker_service.c | 2 +- tracker/tracker_service.h | 2 +- tracker/tracker_status.c | 2 +- tracker/tracker_status.h | 2 +- tracker/tracker_types.h | 2 +- 106 files changed, 187 insertions(+), 141 deletions(-) create mode 100755 setup.sh diff --git a/INSTALL b/INSTALL index 36e452f..5eaa9de 100644 --- a/INSTALL +++ b/INSTALL @@ -3,50 +3,65 @@ Copy right 2009 Happy Fish / YuQing FastDFS may be copied only under the terms of the GNU General Public License V3, which may be found in the FastDFS source kit. Please visit the FastDFS Home Page for more detail. -English language: http://english.csource.org/ -Chinese language: http://www.csource.org/ +Chinese language: http://www.fastken.com/ -#step 1. download libfastcommon source package from github and install it, - the github address: - https://github.com/happyfish100/libfastcommon.git +# step 1. download libfastcommon source codes and install it, +# github address: https://github.com/happyfish100/libfastcommon.git +# gitee address: https://gitee.com/fastdfs100/libfastcommon.git +# get source codes as: + git clone https://github.com/happyfish100/libfastcommon.git + cd libfastcommon; git checkout V1.0.41 + ./make.sh clean && ./make.sh && ./make.sh install -#step 2. download FastDFS source package and unpack it, -tar xzf FastDFS_v5.x.tar.gz -#for example: -tar xzf FastDFS_v5.08.tar.gz -#step 3. enter the FastDFS dir -cd FastDFS +# step 2. download fastdfs source codes and install it, +# github address: https://github.com/happyfish100/fastdfs.git +# gitee address: https://gitee.com/fastdfs100/fastdfs.git +# get source codes as: + git clone https://github.com/happyfish100/fastdfs.git + cd fastdfs; git checkout V6.03 + ./make.sh clean && ./make.sh && ./make.sh install -#step 4. execute: -./make.sh -#step 5. make install -./make.sh install +# step 3. setup the config files + the setup script does NOT overwrite existing config files, + please feel free to execute this script (take easy :) +./setup.sh /etc/fdfs -#step 6. edit/modify the config file of tracker and storage -#step 7. run server programs -#start the tracker server: +# step 4. edit or modify the config files of tracker, storage and client +such as: + vi /etc/fdfs/tracker.conf + vi /etc/fdfs/storage.conf + vi /etc/fdfs/client.conf + + and so on ... + + +# step 5. run the server programs +# start the tracker server: /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart -#in Linux, you can start fdfs_trackerd as a service: -/sbin/service fdfs_trackerd start -#start the storage server: +# start the storage server: /usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart -#in Linux, you can start fdfs_storaged as a service: -/sbin/service fdfs_storaged start -#step 8. run test program -#run the client test program: +# (optional) in Linux, you can start fdfs_trackerd and fdfs_storaged as a service: +/sbin/service fdfs_trackerd restart +/sbin/service fdfs_storaged restart + + +# step 6. (optional) run monitor program +# such as: +/usr/bin/fdfs_monitor /etc/fdfs/client.conf + + +# step 7. (optional) run the test program +# such as: /usr/bin/fdfs_test /usr/bin/fdfs_test1 -#for example, upload a file: -/usr/bin/fdfs_test conf/client.conf upload /usr/include/stdlib.h -#step 9. run monitor program -#run the monitor program: -/usr/bin/fdfs_monitor +# for example, upload a file for test: +/usr/bin/fdfs_test /etc/fdfs/client.conf upload /usr/include/stdlib.h tracker server config file sample please see conf/tracker.conf @@ -55,7 +70,6 @@ storage server config file sample please see conf/storage.conf client config file sample please see conf/client.conf - Item detail 1. server common items --------------------------------------------------- diff --git a/client/client_func.c b/client/client_func.c index 1b8fe05..610abf2 100644 --- a/client/client_func.c +++ b/client/client_func.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //client_func.c diff --git a/client/client_func.h b/client/client_func.h index f3f2dfd..e0f317e 100644 --- a/client/client_func.h +++ b/client/client_func.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //client_func.h diff --git a/client/client_global.c b/client/client_global.c index 19964b4..fa2553c 100644 --- a/client/client_global.c +++ b/client/client_global.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/client_global.h b/client/client_global.h index 35f5818..5c72442 100644 --- a/client/client_global.h +++ b/client/client_global.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //client_global.h diff --git a/client/fdfs_append_file.c b/client/fdfs_append_file.c index 39f9b73..1b1e451 100644 --- a/client/fdfs_append_file.c +++ b/client/fdfs_append_file.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_appender_test.c b/client/fdfs_appender_test.c index 13821fb..f49e0be 100644 --- a/client/fdfs_appender_test.c +++ b/client/fdfs_appender_test.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) "\nCopyright (C) 2008, Happy Fish / YuQing\n" \ "\nFastDFS may be copied only under the terms of the GNU General\n" \ "Public License V3, which may be found in the FastDFS source kit.\n" \ -"Please visit the FastDFS Home Page http://www.csource.org/ \n" \ +"Please visit the FastDFS Home Page http://www.fastken.com/ \n" \ "for more detail.\n\n" \ , g_fdfs_version.major, g_fdfs_version.minor); diff --git a/client/fdfs_appender_test1.c b/client/fdfs_appender_test1.c index e713053..7fb0f22 100644 --- a/client/fdfs_appender_test1.c +++ b/client/fdfs_appender_test1.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) "\nCopyright (C) 2008, Happy Fish / YuQing\n" \ "\nFastDFS may be copied only under the terms of the GNU General\n" \ "Public License V3, which may be found in the FastDFS source kit.\n" \ -"Please visit the FastDFS Home Page http://www.csource.org/ \n" \ +"Please visit the FastDFS Home Page http://www.fastken.com/ \n" \ "for more detail.\n\n" \ , g_fdfs_version.major, g_fdfs_version.minor); diff --git a/client/fdfs_client.h b/client/fdfs_client.h index aaf9a39..d77b165 100644 --- a/client/fdfs_client.h +++ b/client/fdfs_client.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #ifndef FDFS_CLIENT_H diff --git a/client/fdfs_crc32.c b/client/fdfs_crc32.c index 7de0e9e..94b1d9f 100644 --- a/client/fdfs_crc32.c +++ b/client/fdfs_crc32.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_delete_file.c b/client/fdfs_delete_file.c index 83636ab..7d9369c 100644 --- a/client/fdfs_delete_file.c +++ b/client/fdfs_delete_file.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_download_file.c b/client/fdfs_download_file.c index 331ed2d..7cf5f93 100644 --- a/client/fdfs_download_file.c +++ b/client/fdfs_download_file.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_file_info.c b/client/fdfs_file_info.c index f8b41ae..0d2a49d 100644 --- a/client/fdfs_file_info.c +++ b/client/fdfs_file_info.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_monitor.c b/client/fdfs_monitor.c index ef1c53f..d648cab 100644 --- a/client/fdfs_monitor.c +++ b/client/fdfs_monitor.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_regenerate_filename.c b/client/fdfs_regenerate_filename.c index ef07462..e3a19d7 100644 --- a/client/fdfs_regenerate_filename.c +++ b/client/fdfs_regenerate_filename.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_test.c b/client/fdfs_test.c index 639bac8..16850b9 100644 --- a/client/fdfs_test.c +++ b/client/fdfs_test.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) "\nCopyright (C) 2008, Happy Fish / YuQing\n" \ "\nFastDFS may be copied only under the terms of the GNU General\n" \ "Public License V3, which may be found in the FastDFS source kit.\n" \ -"Please visit the FastDFS Home Page http://www.csource.org/ \n" \ +"Please visit the FastDFS Home Page http://www.fastken.com/ \n" \ "for more detail.\n\n" \ , g_fdfs_version.major, g_fdfs_version.minor); diff --git a/client/fdfs_test1.c b/client/fdfs_test1.c index 124fc49..9b015a8 100644 --- a/client/fdfs_test1.c +++ b/client/fdfs_test1.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) "\nCopyright (C) 2008, Happy Fish / YuQing\n" \ "\nFastDFS may be copied only under the terms of the GNU General\n" \ "Public License V3, which may be found in the FastDFS source kit.\n" \ -"Please visit the FastDFS Home Page http://www.csource.org/ \n" \ +"Please visit the FastDFS Home Page http://www.fastken.com/ \n" \ "for more detail.\n\n" \ , g_fdfs_version.major, g_fdfs_version.minor); diff --git a/client/fdfs_upload_appender.c b/client/fdfs_upload_appender.c index 81ea98d..a195553 100644 --- a/client/fdfs_upload_appender.c +++ b/client/fdfs_upload_appender.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/fdfs_upload_file.c b/client/fdfs_upload_file.c index f121203..4bac235 100644 --- a/client/fdfs_upload_file.c +++ b/client/fdfs_upload_file.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/storage_client.c b/client/storage_client.c index da87f81..576990b 100644 --- a/client/storage_client.c +++ b/client/storage_client.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ diff --git a/client/storage_client.h b/client/storage_client.h index fd4849e..11e2e47 100644 --- a/client/storage_client.h +++ b/client/storage_client.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #ifndef STORAGE_CLIENT_H diff --git a/client/storage_client1.h b/client/storage_client1.h index 7a4bf77..a0358f2 100644 --- a/client/storage_client1.h +++ b/client/storage_client1.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #ifndef STORAGE_CLIENT1_H diff --git a/client/test/fdfs_monitor.c b/client/test/fdfs_monitor.c index 6bcc703..742cf56 100644 --- a/client/test/fdfs_monitor.c +++ b/client/test/fdfs_monitor.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/client/test/fdfs_test.c b/client/test/fdfs_test.c index 639bac8..16850b9 100644 --- a/client/test/fdfs_test.c +++ b/client/test/fdfs_test.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) "\nCopyright (C) 2008, Happy Fish / YuQing\n" \ "\nFastDFS may be copied only under the terms of the GNU General\n" \ "Public License V3, which may be found in the FastDFS source kit.\n" \ -"Please visit the FastDFS Home Page http://www.csource.org/ \n" \ +"Please visit the FastDFS Home Page http://www.fastken.com/ \n" \ "for more detail.\n\n" \ , g_fdfs_version.major, g_fdfs_version.minor); diff --git a/client/test/fdfs_test1.c b/client/test/fdfs_test1.c index 124fc49..9b015a8 100644 --- a/client/test/fdfs_test1.c +++ b/client/test/fdfs_test1.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) "\nCopyright (C) 2008, Happy Fish / YuQing\n" \ "\nFastDFS may be copied only under the terms of the GNU General\n" \ "Public License V3, which may be found in the FastDFS source kit.\n" \ -"Please visit the FastDFS Home Page http://www.csource.org/ \n" \ +"Please visit the FastDFS Home Page http://www.fastken.com/ \n" \ "for more detail.\n\n" \ , g_fdfs_version.major, g_fdfs_version.minor); diff --git a/client/tracker_client.c b/client/tracker_client.c index 3062b05..7f6a533 100644 --- a/client/tracker_client.c +++ b/client/tracker_client.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ diff --git a/client/tracker_client.h b/client/tracker_client.h index 58b37a1..93f0b99 100644 --- a/client/tracker_client.h +++ b/client/tracker_client.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #ifndef TRACKER_CLIENT_H diff --git a/common/fdfs_define.h b/common/fdfs_define.h index 92ffe36..0579bc4 100644 --- a/common/fdfs_define.h +++ b/common/fdfs_define.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdfs_define.h diff --git a/common/fdfs_global.c b/common/fdfs_global.c index 0176051..d17aa3d 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/common/fdfs_global.h b/common/fdfs_global.h index 98a99df..ca0e3d3 100644 --- a/common/fdfs_global.h +++ b/common/fdfs_global.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdfs_global.h diff --git a/common/fdfs_http_shared.c b/common/fdfs_http_shared.c index ff79fb6..43197e1 100644 --- a/common/fdfs_http_shared.c +++ b/common/fdfs_http_shared.c @@ -4,7 +4,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/common/fdfs_http_shared.h b/common/fdfs_http_shared.h index 5da53ad..679eb95 100644 --- a/common/fdfs_http_shared.h +++ b/common/fdfs_http_shared.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #ifndef _FDFS_HTTP_SHARED_H diff --git a/common/mime_file_parser.c b/common/mime_file_parser.c index aa7461c..e7bcfce 100644 --- a/common/mime_file_parser.c +++ b/common/mime_file_parser.c @@ -4,7 +4,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/common/mime_file_parser.h b/common/mime_file_parser.h index ecb2c4f..681f388 100644 --- a/common/mime_file_parser.h +++ b/common/mime_file_parser.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #ifndef _MINE_FILE_PARSER_H diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..3a8d5c6 --- /dev/null +++ b/setup.sh @@ -0,0 +1,32 @@ + +if [ -n "$1" ]; then + TARGET_CONF_PATH=$1 +else + TARGET_CONF_PATH=/etc/fdfs +fi + +mkdir -p $TARGET_CONF_PATH + +if [ ! -f $TARGET_CONF_PATH/tracker.conf ]; then + cp -f conf/tracker.conf $TARGET_CONF_PATH/tracker.conf +fi + +if [ ! -f $TARGET_CONF_PATH/storage.conf ]; then + cp -f conf/storage.conf $TARGET_CONF_PATH/storage.conf +fi + +if [ ! -f $TARGET_CONF_PATH/client.conf ]; then + cp -f conf/client.conf $TARGET_CONF_PATH/client.conf +fi + +if [ ! -f $TARGET_CONF_PATH/storage_ids.conf ]; then + cp -f conf/storage_ids.conf $TARGET_CONF_PATH/storage_ids.conf +fi + +if [ ! -f $TARGET_CONF_PATH/http.conf ]; then + cp -f conf/http.conf $TARGET_CONF_PATH/http.conf +fi + +if [ ! -f $TARGET_CONF_PATH/mime.types ]; then + cp -f conf/mime.types $TARGET_CONF_PATH/mime.types +fi diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 5d49f30..3e57b54 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/fdht_client/fdht_client.c b/storage/fdht_client/fdht_client.c index 7ae6718..bd57d06 100644 --- a/storage/fdht_client/fdht_client.c +++ b/storage/fdht_client/fdht_client.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/fdht_client/fdht_client.h b/storage/fdht_client/fdht_client.h index 07222a8..2658ee1 100644 --- a/storage/fdht_client/fdht_client.h +++ b/storage/fdht_client/fdht_client.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_client.h diff --git a/storage/fdht_client/fdht_define.h b/storage/fdht_client/fdht_define.h index c61afd9..5a479f8 100644 --- a/storage/fdht_client/fdht_define.h +++ b/storage/fdht_client/fdht_define.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_define.h diff --git a/storage/fdht_client/fdht_func.c b/storage/fdht_client/fdht_func.c index 765fd16..fa3730d 100644 --- a/storage/fdht_client/fdht_func.c +++ b/storage/fdht_client/fdht_func.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_func.c diff --git a/storage/fdht_client/fdht_func.h b/storage/fdht_client/fdht_func.h index 7bfee30..592f0f4 100644 --- a/storage/fdht_client/fdht_func.h +++ b/storage/fdht_client/fdht_func.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_func.h diff --git a/storage/fdht_client/fdht_global.c b/storage/fdht_client/fdht_global.c index ce93a63..5cd8c7b 100644 --- a/storage/fdht_client/fdht_global.c +++ b/storage/fdht_client/fdht_global.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/fdht_client/fdht_global.h b/storage/fdht_client/fdht_global.h index c6fa467..580ae92 100644 --- a/storage/fdht_client/fdht_global.h +++ b/storage/fdht_client/fdht_global.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_global.h diff --git a/storage/fdht_client/fdht_proto.c b/storage/fdht_client/fdht_proto.c index 418ca62..8d6a6c4 100644 --- a/storage/fdht_client/fdht_proto.c +++ b/storage/fdht_client/fdht_proto.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/fdht_client/fdht_proto.h b/storage/fdht_client/fdht_proto.h index 93e87b9..69b667a 100644 --- a/storage/fdht_client/fdht_proto.h +++ b/storage/fdht_client/fdht_proto.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_proto.h diff --git a/storage/fdht_client/fdht_proto_types.h b/storage/fdht_client/fdht_proto_types.h index cdf2588..98c5504 100644 --- a/storage/fdht_client/fdht_proto_types.h +++ b/storage/fdht_client/fdht_proto_types.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_proto_types.h diff --git a/storage/fdht_client/fdht_types.h b/storage/fdht_client/fdht_types.h index 37b0ec1..673e0e7 100644 --- a/storage/fdht_client/fdht_types.h +++ b/storage/fdht_client/fdht_types.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdht_types.h diff --git a/storage/storage_dio.c b/storage/storage_dio.c index 90caa76..0b9ccd5 100644 --- a/storage/storage_dio.c +++ b/storage/storage_dio.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/storage_dio.h b/storage/storage_dio.h index 9d517fa..e58b90b 100644 --- a/storage/storage_dio.h +++ b/storage/storage_dio.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_dio.h diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 985ed5d..dc0b59a 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ diff --git a/storage/storage_disk_recovery.h b/storage/storage_disk_recovery.h index 053f1dd..9a8a06c 100644 --- a/storage/storage_disk_recovery.h +++ b/storage/storage_disk_recovery.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_disk_recovery.h diff --git a/storage/storage_dump.c b/storage/storage_dump.c index badefef..e7c8dd6 100644 --- a/storage/storage_dump.c +++ b/storage/storage_dump.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/storage_dump.h b/storage/storage_dump.h index 4da03a0..2fc090c 100644 --- a/storage/storage_dump.h +++ b/storage/storage_dump.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_dump.h diff --git a/storage/storage_func.c b/storage/storage_func.c index a698926..701fbac 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_func.c diff --git a/storage/storage_func.h b/storage/storage_func.h index fdc38a9..8eb4c9e 100644 --- a/storage/storage_func.h +++ b/storage/storage_func.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_func.h diff --git a/storage/storage_global.c b/storage/storage_global.c index 166a3dd..55d54e6 100644 --- a/storage/storage_global.c +++ b/storage/storage_global.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/storage_global.h b/storage/storage_global.h index 718e8f0..b7e938a 100644 --- a/storage/storage_global.h +++ b/storage/storage_global.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_global.h diff --git a/storage/storage_ip_changed_dealer.c b/storage/storage_ip_changed_dealer.c index 91d7748..58e4af6 100644 --- a/storage/storage_ip_changed_dealer.c +++ b/storage/storage_ip_changed_dealer.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ diff --git a/storage/storage_ip_changed_dealer.h b/storage/storage_ip_changed_dealer.h index f5639ec..0947711 100644 --- a/storage/storage_ip_changed_dealer.h +++ b/storage/storage_ip_changed_dealer.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_ip_changed_dealer.h diff --git a/storage/storage_nio.c b/storage/storage_nio.c index 911ff0a..b4b04d6 100644 --- a/storage/storage_nio.c +++ b/storage/storage_nio.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/storage/storage_nio.h b/storage/storage_nio.h index d33870f..bb79ecb 100644 --- a/storage/storage_nio.h +++ b/storage/storage_nio.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_nio.h diff --git a/storage/storage_param_getter.c b/storage/storage_param_getter.c index d4ffd17..cca4055 100644 --- a/storage/storage_param_getter.c +++ b/storage/storage_param_getter.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ diff --git a/storage/storage_param_getter.h b/storage/storage_param_getter.h index 7d6e020..16dd480 100644 --- a/storage/storage_param_getter.h +++ b/storage/storage_param_getter.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_param_getter.h diff --git a/storage/storage_service.c b/storage/storage_service.c index 5ee573d..b5c92a0 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_service.c diff --git a/storage/storage_service.h b/storage/storage_service.h index ea96ce5..f24a2d2 100644 --- a/storage/storage_service.h +++ b/storage/storage_service.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_service.h diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 1265640..5c4cf99 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -2,7 +2,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_sync.c diff --git a/storage/storage_sync.h b/storage/storage_sync.h index aaf4bca..7dfa99d 100644 --- a/storage/storage_sync.h +++ b/storage/storage_sync.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_sync.h diff --git a/storage/storage_sync_func.c b/storage/storage_sync_func.c index e1bd700..72a7fb8 100644 --- a/storage/storage_sync_func.c +++ b/storage/storage_sync_func.c @@ -2,7 +2,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_sync_func.c diff --git a/storage/storage_sync_func.h b/storage/storage_sync_func.h index 70cdb1b..487ec3b 100644 --- a/storage/storage_sync_func.h +++ b/storage/storage_sync_func.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //storage_sync_func.h diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index 27ea40b..a7f4fe3 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ diff --git a/storage/tracker_client_thread.h b/storage/tracker_client_thread.h index c6ce687..38704dc 100644 --- a/storage/tracker_client_thread.h +++ b/storage/tracker_client_thread.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_client_thread.h diff --git a/storage/trunk_mgr/trunk_client.c b/storage/trunk_mgr/trunk_client.c index 240ca1b..1ea60ff 100644 --- a/storage/trunk_mgr/trunk_client.c +++ b/storage/trunk_mgr/trunk_client.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_client.c diff --git a/storage/trunk_mgr/trunk_client.h b/storage/trunk_mgr/trunk_client.h index e094c8f..5b457e6 100644 --- a/storage/trunk_mgr/trunk_client.h +++ b/storage/trunk_mgr/trunk_client.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_client.h diff --git a/storage/trunk_mgr/trunk_free_block_checker.c b/storage/trunk_mgr/trunk_free_block_checker.c index 46ac87f..0b8a2d9 100644 --- a/storage/trunk_mgr/trunk_free_block_checker.c +++ b/storage/trunk_mgr/trunk_free_block_checker.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_free_block_checker.c diff --git a/storage/trunk_mgr/trunk_free_block_checker.h b/storage/trunk_mgr/trunk_free_block_checker.h index e539365..4f83b63 100644 --- a/storage/trunk_mgr/trunk_free_block_checker.h +++ b/storage/trunk_mgr/trunk_free_block_checker.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_free_block_checker.h diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 5aa8812..1add39d 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_mem.c diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index dc3cac4..d47de3a 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_mem.h diff --git a/storage/trunk_mgr/trunk_shared.c b/storage/trunk_mgr/trunk_shared.c index 9eeefc6..d65758e 100644 --- a/storage/trunk_mgr/trunk_shared.c +++ b/storage/trunk_mgr/trunk_shared.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_shared.c diff --git a/storage/trunk_mgr/trunk_shared.h b/storage/trunk_mgr/trunk_shared.h index 9fcab70..85f11e2 100644 --- a/storage/trunk_mgr/trunk_shared.h +++ b/storage/trunk_mgr/trunk_shared.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_shared.h diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index e359ac1..ca86522 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_sync.c diff --git a/storage/trunk_mgr/trunk_sync.h b/storage/trunk_mgr/trunk_sync.h index 015392c..58e4b6a 100644 --- a/storage/trunk_mgr/trunk_sync.h +++ b/storage/trunk_mgr/trunk_sync.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //trunk_sync.h diff --git a/tracker/fdfs_server_id_func.c b/tracker/fdfs_server_id_func.c index 6db18e1..8d76415 100644 --- a/tracker/fdfs_server_id_func.c +++ b/tracker/fdfs_server_id_func.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/fdfs_server_id_func.h b/tracker/fdfs_server_id_func.h index 44557dc..a0f6007 100644 --- a/tracker/fdfs_server_id_func.h +++ b/tracker/fdfs_server_id_func.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdfs_server_id_func.h diff --git a/tracker/fdfs_shared_func.c b/tracker/fdfs_shared_func.c index 61397db..8d07163 100644 --- a/tracker/fdfs_shared_func.c +++ b/tracker/fdfs_shared_func.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/fdfs_shared_func.h b/tracker/fdfs_shared_func.h index 38a79ac..aced559 100644 --- a/tracker/fdfs_shared_func.h +++ b/tracker/fdfs_shared_func.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //fdfs_shared_func.h diff --git a/tracker/fdfs_trackerd.c b/tracker/fdfs_trackerd.c index 85f97d9..3985169 100644 --- a/tracker/fdfs_trackerd.c +++ b/tracker/fdfs_trackerd.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/tracker_dump.c b/tracker/tracker_dump.c index 732d43a..8a8c444 100644 --- a/tracker/tracker_dump.c +++ b/tracker/tracker_dump.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/tracker_dump.h b/tracker/tracker_dump.h index 8a16368..f737a19 100644 --- a/tracker/tracker_dump.h +++ b/tracker/tracker_dump.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_dump.h diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index 1041e8b..e357c04 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_func.c diff --git a/tracker/tracker_func.h b/tracker/tracker_func.h index cb044f6..67a0737 100644 --- a/tracker/tracker_func.h +++ b/tracker/tracker_func.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_func.h diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index 98967b3..4b8999b 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include "tracker_global.h" diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index af3d010..70f01df 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_global.h diff --git a/tracker/tracker_http_check.h b/tracker/tracker_http_check.h index 6bfd23a..39e8743 100644 --- a/tracker/tracker_http_check.h +++ b/tracker/tracker_http_check.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_httpd.h diff --git a/tracker/tracker_mem.c b/tracker/tracker_mem.c index 90c574d..0161e0a 100644 --- a/tracker/tracker_mem.c +++ b/tracker/tracker_mem.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/tracker_mem.h b/tracker/tracker_mem.h index 0d8273f..4083bfb 100644 --- a/tracker/tracker_mem.h +++ b/tracker/tracker_mem.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_mem.h diff --git a/tracker/tracker_nio.c b/tracker/tracker_nio.c index 9d1f69f..92f230f 100644 --- a/tracker/tracker_nio.c +++ b/tracker/tracker_nio.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/tracker_nio.h b/tracker/tracker_nio.h index e4625d4..85140a8 100644 --- a/tracker/tracker_nio.h +++ b/tracker/tracker_nio.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_nio.h diff --git a/tracker/tracker_proto.c b/tracker/tracker_proto.c index 94afadc..a319193 100644 --- a/tracker/tracker_proto.c +++ b/tracker/tracker_proto.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/tracker_proto.h b/tracker/tracker_proto.h index 7106d5b..41069dd 100644 --- a/tracker/tracker_proto.h +++ b/tracker/tracker_proto.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_proto.h diff --git a/tracker/tracker_relationship.c b/tracker/tracker_relationship.c index c8a5516..dd38457 100644 --- a/tracker/tracker_relationship.c +++ b/tracker/tracker_relationship.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ #include diff --git a/tracker/tracker_relationship.h b/tracker/tracker_relationship.h index 4e0dc47..b51ee81 100644 --- a/tracker/tracker_relationship.h +++ b/tracker/tracker_relationship.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_relationship.h diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 7316740..5926344 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_service.c diff --git a/tracker/tracker_service.h b/tracker/tracker_service.h index 62d56f3..b52d5db 100644 --- a/tracker/tracker_service.h +++ b/tracker/tracker_service.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_service.h diff --git a/tracker/tracker_status.c b/tracker/tracker_status.c index ce4e5ab..347d4ab 100644 --- a/tracker/tracker_status.c +++ b/tracker/tracker_status.c @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_func.c diff --git a/tracker/tracker_status.h b/tracker/tracker_status.h index c45b5e9..91629cc 100644 --- a/tracker/tracker_status.h +++ b/tracker/tracker_status.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_status.h diff --git a/tracker/tracker_types.h b/tracker/tracker_types.h index 8e7bb75..b92002f 100644 --- a/tracker/tracker_types.h +++ b/tracker/tracker_types.h @@ -3,7 +3,7 @@ * * FastDFS may be copied only under the terms of the GNU General * Public License V3, which may be found in the FastDFS source kit. -* Please visit the FastDFS Home Page http://www.csource.org/ for more detail. +* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail. **/ //tracker_types.h From 80c9930f22de9f53e8d024fd133c858259ba7dd2 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 23 Nov 2019 10:07:11 +0800 Subject: [PATCH 50/95] INSTALL file changed --- INSTALL | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/INSTALL b/INSTALL index 5eaa9de..9ef8eb9 100644 --- a/INSTALL +++ b/INSTALL @@ -8,7 +8,8 @@ Chinese language: http://www.fastken.com/ # step 1. download libfastcommon source codes and install it, # github address: https://github.com/happyfish100/libfastcommon.git # gitee address: https://gitee.com/fastdfs100/libfastcommon.git -# get source codes as: +# command lines as: + git clone https://github.com/happyfish100/libfastcommon.git cd libfastcommon; git checkout V1.0.41 ./make.sh clean && ./make.sh && ./make.sh install @@ -17,15 +18,17 @@ Chinese language: http://www.fastken.com/ # step 2. download fastdfs source codes and install it, # github address: https://github.com/happyfish100/fastdfs.git # gitee address: https://gitee.com/fastdfs100/fastdfs.git -# get source codes as: +# command lines as: + git clone https://github.com/happyfish100/fastdfs.git cd fastdfs; git checkout V6.03 ./make.sh clean && ./make.sh && ./make.sh install # step 3. setup the config files - the setup script does NOT overwrite existing config files, - please feel free to execute this script (take easy :) +# the setup script does NOT overwrite existing config files, +# please feel free to execute this script (take easy :) + ./setup.sh /etc/fdfs From df2fd2069b085bd78c7876d7967ccc5e7c489659 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 27 Nov 2019 20:33:56 +0800 Subject: [PATCH 51/95] storage_report_ip_changed ignore result EEXIST --- conf/storage.conf | 5 +++-- storage/storage_ip_changed_dealer.c | 15 ++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/conf/storage.conf b/conf/storage.conf index 9adfa6a..0bc0019 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -173,8 +173,9 @@ allow_hosts=* # 1: random, distributted by hash code file_distribute_path_mode=0 -# valid when file_distribute_to_path is set to 0 (round robin), -# when the written file count reaches this number, then rotate to next path +# valid when file_distribute_to_path is set to 0 (round robin). +# when the written file count reaches this number, then rotate to next path. +# rotate to the first path (00/00) after the last path (such as FF/FF). # default value is 100 file_distribute_rotate_count=100 diff --git a/storage/storage_ip_changed_dealer.c b/storage/storage_ip_changed_dealer.c index 58e4af6..e550314 100644 --- a/storage/storage_ip_changed_dealer.c +++ b/storage/storage_ip_changed_dealer.c @@ -92,10 +92,11 @@ static int storage_report_ip_changed(ConnectionInfo *pTrackerServer) } pInBuff = in_buff; - result = fdfs_recv_response(pTrackerServer, \ + result = fdfs_recv_response(pTrackerServer, &pInBuff, 0, &in_bytes); - if (result == 0 || result == EALREADY || result == ENOENT) + if (result == 0 || result == EALREADY || result == ENOENT + || result == EEXIST) { if (result != 0) { @@ -107,11 +108,11 @@ static int storage_report_ip_changed(ConnectionInfo *pTrackerServer) } else { - logError("file: "__FILE__", line: %d, " \ - "tracker server %s:%d, recv data fail or " \ - "response status != 0, " \ - "errno: %d, error info: %s", \ - __LINE__, pTrackerServer->ip_addr, \ + logError("file: "__FILE__", line: %d, " + "tracker server %s:%d, recv data fail or " + "response status != 0, " + "errno: %d, error info: %s", + __LINE__, pTrackerServer->ip_addr, pTrackerServer->port, result, STRERROR(result)); return result == EBUSY ? 0 : result; } From 634d85eaae7cecd9465633e6570500d90a53cc55 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 30 Nov 2019 16:12:18 +0800 Subject: [PATCH 52/95] support compress error log and access log --- HISTORY | 5 +++++ common/fdfs_global.c | 2 +- conf/storage.conf | 10 ++++++++++ conf/tracker.conf | 7 ++++++- storage/storage_func.c | 23 +++++++++++++++++++++-- storage/storage_global.c | 2 ++ storage/storage_global.h | 2 ++ storage/storage_sync.c | 6 ++++-- tracker/tracker_func.c | 13 +++++++++++-- tracker/tracker_global.c | 1 + tracker/tracker_global.h | 5 +++-- 11 files changed, 66 insertions(+), 10 deletions(-) diff --git a/HISTORY b/HISTORY index df6660b..abac97e 100644 --- a/HISTORY +++ b/HISTORY @@ -1,4 +1,9 @@ +Version 6.04 2019-11-30 + * storage_report_ip_changed ignore result EEXIST + * use get_gzip_command_filename from libfastcommon v1.42 + * support compress error log and access log + Version 6.03 2019-11-20 * dual IPs support two different types of inner (intranet) IPs * storage server request tracker server to change it's status diff --git a/common/fdfs_global.c b/common/fdfs_global.c index d17aa3d..87d8c22 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -23,7 +23,7 @@ int g_fdfs_connect_timeout = DEFAULT_CONNECT_TIMEOUT; int g_fdfs_network_timeout = DEFAULT_NETWORK_TIMEOUT; char g_fdfs_base_path[MAX_PATH_SIZE] = {'/', 't', 'm', 'p', '\0'}; -Version g_fdfs_version = {6, 3}; +Version g_fdfs_version = {6, 4}; bool g_use_connection_pool = false; ConnectionPool g_connection_pool; int g_connection_pool_max_idle_time = 3600; diff --git a/conf/storage.conf b/conf/storage.conf index 0bc0019..aa1d142 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -255,6 +255,11 @@ rotate_access_log = false # since V4.00 access_log_rotate_time=00:00 +# if compress the old access log by gzip +# default value is false +# since V6.04 +compress_old_access_log = false + # if rotate the error log every day # default value is false # since V4.02 @@ -266,6 +271,11 @@ rotate_error_log = false # since V4.02 error_log_rotate_time=00:00 +# if compress the old error log by gzip +# default value is false +# since V6.04 +compress_old_error_log = false + # rotate access log when the log file exceeds this size # 0 means never rotates log file by log file size # default value is 0 diff --git a/conf/tracker.conf b/conf/tracker.conf index 9e1d641..6545625 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -240,7 +240,12 @@ rotate_error_log = false # Hour from 0 to 23, Minute from 0 to 59 # default value is 00:00 # since V4.02 -error_log_rotate_time=00:00 +error_log_rotate_time = 00:00 + +# if compress the old error log by gzip +# default value is false +# since V6.04 +compress_old_error_log = false # rotate error log when the log file exceeds this size # 0 means never rotates log file by log file size diff --git a/storage/storage_func.c b/storage/storage_func.c index 701fbac..f23d9d7 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -1994,6 +1994,23 @@ int storage_func_init(const char *filename, \ g_rotate_error_log = iniGetBoolValue(NULL, "rotate_error_log",\ &iniContext, false); + g_compress_old_access_log = iniGetBoolValue(NULL, "compress_old_access_log", + &iniContext, false); + g_compress_old_error_log = iniGetBoolValue(NULL, "compress_old_error_log", + &iniContext, false); + + if (g_compress_old_error_log) + { + log_set_compress_log_flags(LOG_COMPRESS_FLAGS_ENABLED | + LOG_COMPRESS_FLAGS_NEW_THREAD); + } + if (g_use_access_log && g_compress_old_access_log) + { + log_set_compress_log_flags_ex(&g_access_log_context, + LOG_COMPRESS_FLAGS_ENABLED | + LOG_COMPRESS_FLAGS_NEW_THREAD); + } + if ((result=get_time_item_from_conf(&iniContext, \ "error_log_rotate_time", &g_error_log_rotate_time, \ 0, 0)) != 0) @@ -2124,8 +2141,10 @@ int storage_func_init(const char *filename, \ "HTTP server port=%d, domain name=%s, " \ "use_access_log=%d, rotate_access_log=%d, " \ "access_log_rotate_time=%02d:%02d, " \ + "compress_old_access_log=%d, " \ "rotate_error_log=%d, " \ "error_log_rotate_time=%02d:%02d, " \ + "compress_old_error_log=%d, " \ "rotate_access_log_size=%"PRId64", " \ "rotate_error_log_size=%"PRId64", " \ "log_file_keep_days=%d, " \ @@ -2163,9 +2182,9 @@ int storage_func_init(const char *filename, \ g_key_namespace, g_keep_alive, \ g_http_port, g_http_domain, g_use_access_log, \ g_rotate_access_log, g_access_log_rotate_time.hour, \ - g_access_log_rotate_time.minute, \ + g_access_log_rotate_time.minute, g_compress_old_access_log, \ g_rotate_error_log, g_error_log_rotate_time.hour, \ - g_error_log_rotate_time.minute, \ + g_error_log_rotate_time.minute, g_compress_old_error_log, \ g_access_log_context.rotate_size, \ g_log_context.rotate_size, g_log_file_keep_days, \ g_file_sync_skip_invalid_record, \ diff --git a/storage/storage_global.c b/storage/storage_global.c index 55d54e6..1ff6db7 100644 --- a/storage/storage_global.c +++ b/storage/storage_global.c @@ -79,6 +79,8 @@ in_addr_t g_server_id_in_filename = 0; bool g_use_access_log = false; //if log to access log bool g_rotate_access_log = false; //if rotate the access log every day bool g_rotate_error_log = false; //if rotate the error log every day +bool g_compress_old_access_log = false; //if compress the old access log +bool g_compress_old_error_log = false; //if compress the old error log bool g_use_storage_id = false; //identify storage by ID instead of IP address byte g_id_type_in_filename = FDFS_ID_TYPE_IP_ADDRESS; //id type of the storage server in the filename bool g_store_slave_file_use_link = false; //if store slave file use symbol link diff --git a/storage/storage_global.h b/storage/storage_global.h index b7e938a..93ae5ba 100644 --- a/storage/storage_global.h +++ b/storage/storage_global.h @@ -128,6 +128,8 @@ extern byte g_id_type_in_filename; //id type of the storage server in the filena extern bool g_use_access_log; //if log to access log extern bool g_rotate_access_log; //if rotate the access log every day extern bool g_rotate_error_log; //if rotate the error log every day +extern bool g_compress_old_access_log; //if compress the old access log +extern bool g_compress_old_error_log; //if compress the old error log extern TimeInfo g_access_log_rotate_time; //rotate access log time base extern TimeInfo g_error_log_rotate_time; //rotate error log time base diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 5c4cf99..b1f41a2 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -1792,7 +1792,8 @@ static int uncompress_binlog_file(StorageBinLogReader *pReader, logInfo("file: "__FILE__", line: %d, " "try to uncompress binlog %s", __LINE__, gzip_filename); - snprintf(command, sizeof(command), "gzip -d %s 2>&1", gzip_filename); + snprintf(command, sizeof(command), "%s -d %s 2>&1", + get_gzip_command_filename(), gzip_filename); result = getExecResult(command, output, sizeof(output)); unlink(flag_filename); if (result != 0) @@ -1873,7 +1874,8 @@ static int compress_binlog_file(const char *filename) "try to compress binlog %s", __LINE__, filename); - snprintf(command, sizeof(command), "gzip %s 2>&1", filename); + snprintf(command, sizeof(command), "%s %s 2>&1", + get_gzip_command_filename(), filename); result = getExecResult(command, output, sizeof(output)); unlink(flag_filename); if (result != 0) diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index e357c04..8ac3e35 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -579,8 +579,16 @@ int tracker_load_from_conf_file(const char *filename, \ return result; } - g_rotate_error_log = iniGetBoolValue(NULL, "rotate_error_log",\ + g_rotate_error_log = iniGetBoolValue(NULL, "rotate_error_log", &iniContext, false); + g_compress_old_error_log = iniGetBoolValue(NULL, "compress_old_error_log", + &iniContext, false); + if (g_compress_old_error_log) + { + log_set_compress_log_flags(LOG_COMPRESS_FLAGS_ENABLED | + LOG_COMPRESS_FLAGS_NEW_THREAD); + } + if ((result=get_time_item_from_conf(&iniContext, \ "error_log_rotate_time", &g_error_log_rotate_time, \ 0, 0)) != 0) @@ -744,6 +752,7 @@ int tracker_load_from_conf_file(const char *filename, \ "storage_id/ip_count=%d / %d, " \ "rotate_error_log=%d, " \ "error_log_rotate_time=%02d:%02d, " \ + "compress_old_error_log=%d, " \ "rotate_error_log_size=%"PRId64", " \ "log_file_keep_days=%d, " \ "store_slave_file_use_link=%d, " \ @@ -780,7 +789,7 @@ int tracker_load_from_conf_file(const char *filename, \ FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", \ g_storage_ids_by_id.count, g_storage_ids_by_ip.count, \ g_rotate_error_log, g_error_log_rotate_time.hour, \ - g_error_log_rotate_time.minute, \ + g_error_log_rotate_time.minute, g_compress_old_error_log, \ g_log_context.rotate_size, g_log_file_keep_days, g_store_slave_file_use_link, \ g_use_connection_pool, g_connection_pool_max_idle_time); diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index 4b8999b..13430af 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -39,6 +39,7 @@ bool g_storage_ip_changed_auto_adjust = true; bool g_use_storage_id = false; //if use storage ID instead of IP address byte g_id_type_in_filename = FDFS_ID_TYPE_IP_ADDRESS; //id type of the storage server in the filename bool g_rotate_error_log = false; //if rotate the error log every day +bool g_compress_old_error_log = false; //if compress the old error log TimeInfo g_error_log_rotate_time = {0, 0, 0}; //rotate error log time base int g_thread_stack_size = 64 * 1024; diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index 70f01df..3159ae2 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -62,8 +62,9 @@ extern char g_run_by_user[32]; extern bool g_storage_ip_changed_auto_adjust; extern bool g_use_storage_id; //identify storage by ID instead of IP address extern byte g_id_type_in_filename; //id type of the storage server in the filename -extern bool g_rotate_error_log; //if rotate the error log every day -extern TimeInfo g_error_log_rotate_time; //rotate error log time base +extern bool g_rotate_error_log; //if rotate the error log every day +extern bool g_compress_old_error_log; //if compress the old error log +extern TimeInfo g_error_log_rotate_time; //rotate error log time base extern int g_thread_stack_size; extern int g_storage_sync_file_max_delay; From 46171f2c646541e577594f2c47f22dcd4563af71 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 1 Dec 2019 20:39:31 +0800 Subject: [PATCH 53/95] add parameter compress_error_log_days_before --- HISTORY | 2 +- conf/storage.conf | 8 ++++++++ conf/tracker.conf | 4 ++++ storage/storage_func.c | 11 +++++++++++ storage/storage_global.c | 2 ++ storage/storage_global.h | 2 ++ tracker/tracker_func.c | 5 +++++ tracker/tracker_global.c | 1 + tracker/tracker_global.h | 1 + 9 files changed, 35 insertions(+), 1 deletion(-) diff --git a/HISTORY b/HISTORY index abac97e..a9c28ef 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.04 2019-11-30 +Version 6.04 2019-12-01 * storage_report_ip_changed ignore result EEXIST * use get_gzip_command_filename from libfastcommon v1.42 * support compress error log and access log diff --git a/conf/storage.conf b/conf/storage.conf index aa1d142..f2233ed 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -260,6 +260,10 @@ access_log_rotate_time=00:00 # since V6.04 compress_old_access_log = false +# compress the access log days before +# default value is 1 +compress_access_log_days_before = 1 + # if rotate the error log every day # default value is false # since V4.02 @@ -276,6 +280,10 @@ error_log_rotate_time=00:00 # since V6.04 compress_old_error_log = false +# compress the error log days before +# default value is 1 +compress_error_log_days_before = 1 + # rotate access log when the log file exceeds this size # 0 means never rotates log file by log file size # default value is 0 diff --git a/conf/tracker.conf b/conf/tracker.conf index 6545625..78331fe 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -247,6 +247,10 @@ error_log_rotate_time = 00:00 # since V6.04 compress_old_error_log = false +# compress the error log days before +# default value is 1 +compress_error_log_days_before = 1 + # rotate error log when the log file exceeds this size # 0 means never rotates log file by log file size # default value is 0 diff --git a/storage/storage_func.c b/storage/storage_func.c index f23d9d7..4d9bab2 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -1998,17 +1998,24 @@ int storage_func_init(const char *filename, \ &iniContext, false); g_compress_old_error_log = iniGetBoolValue(NULL, "compress_old_error_log", &iniContext, false); + g_compress_error_log_days_before = iniGetIntValue(NULL, + "compress_error_log_days_before", &iniContext, 1); + g_compress_access_log_days_before = iniGetIntValue(NULL, + "compress_access_log_days_before", &iniContext, 1); if (g_compress_old_error_log) { log_set_compress_log_flags(LOG_COMPRESS_FLAGS_ENABLED | LOG_COMPRESS_FLAGS_NEW_THREAD); + log_set_compress_log_days_before(g_compress_error_log_days_before); } if (g_use_access_log && g_compress_old_access_log) { log_set_compress_log_flags_ex(&g_access_log_context, LOG_COMPRESS_FLAGS_ENABLED | LOG_COMPRESS_FLAGS_NEW_THREAD); + log_set_compress_log_days_before_ex(&g_access_log_context, + g_compress_access_log_days_before); } if ((result=get_time_item_from_conf(&iniContext, \ @@ -2142,9 +2149,11 @@ int storage_func_init(const char *filename, \ "use_access_log=%d, rotate_access_log=%d, " \ "access_log_rotate_time=%02d:%02d, " \ "compress_old_access_log=%d, " \ + "compress_access_log_days_before=%d, " \ "rotate_error_log=%d, " \ "error_log_rotate_time=%02d:%02d, " \ "compress_old_error_log=%d, " \ + "compress_error_log_days_before=%d, " \ "rotate_access_log_size=%"PRId64", " \ "rotate_error_log_size=%"PRId64", " \ "log_file_keep_days=%d, " \ @@ -2183,8 +2192,10 @@ int storage_func_init(const char *filename, \ g_http_port, g_http_domain, g_use_access_log, \ g_rotate_access_log, g_access_log_rotate_time.hour, \ g_access_log_rotate_time.minute, g_compress_old_access_log, \ + g_compress_access_log_days_before, \ g_rotate_error_log, g_error_log_rotate_time.hour, \ g_error_log_rotate_time.minute, g_compress_old_error_log, \ + g_compress_error_log_days_before, \ g_access_log_context.rotate_size, \ g_log_context.rotate_size, g_log_file_keep_days, \ g_file_sync_skip_invalid_record, \ diff --git a/storage/storage_global.c b/storage/storage_global.c index 1ff6db7..f8a1a48 100644 --- a/storage/storage_global.c +++ b/storage/storage_global.c @@ -126,6 +126,8 @@ char g_exe_name[256] = {0}; #endif int g_log_file_keep_days = 0; +int g_compress_access_log_days_before = 0; +int g_compress_error_log_days_before = 0; struct storage_nio_thread_data *g_nio_thread_data = NULL; struct storage_dio_thread_data *g_dio_thread_data = NULL; diff --git a/storage/storage_global.h b/storage/storage_global.h index 93ae5ba..a1849e4 100644 --- a/storage/storage_global.h +++ b/storage/storage_global.h @@ -175,6 +175,8 @@ extern char g_exe_name[256]; #endif extern int g_log_file_keep_days; +extern int g_compress_access_log_days_before; +extern int g_compress_error_log_days_before; extern struct storage_nio_thread_data *g_nio_thread_data; //network io thread data extern struct storage_dio_thread_data *g_dio_thread_data; //disk io thread data diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index 8ac3e35..6b3e397 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -583,10 +583,13 @@ int tracker_load_from_conf_file(const char *filename, \ &iniContext, false); g_compress_old_error_log = iniGetBoolValue(NULL, "compress_old_error_log", &iniContext, false); + g_compress_error_log_days_before = iniGetIntValue(NULL, + "compress_error_log_days_before", &iniContext, 1); if (g_compress_old_error_log) { log_set_compress_log_flags(LOG_COMPRESS_FLAGS_ENABLED | LOG_COMPRESS_FLAGS_NEW_THREAD); + log_set_compress_log_days_before(g_compress_error_log_days_before); } if ((result=get_time_item_from_conf(&iniContext, \ @@ -753,6 +756,7 @@ int tracker_load_from_conf_file(const char *filename, \ "rotate_error_log=%d, " \ "error_log_rotate_time=%02d:%02d, " \ "compress_old_error_log=%d, " \ + "compress_error_log_days_before=%d, " \ "rotate_error_log_size=%"PRId64", " \ "log_file_keep_days=%d, " \ "store_slave_file_use_link=%d, " \ @@ -790,6 +794,7 @@ int tracker_load_from_conf_file(const char *filename, \ g_storage_ids_by_id.count, g_storage_ids_by_ip.count, \ g_rotate_error_log, g_error_log_rotate_time.hour, \ g_error_log_rotate_time.minute, g_compress_old_error_log, \ + g_compress_error_log_days_before, \ g_log_context.rotate_size, g_log_file_keep_days, g_store_slave_file_use_link, \ g_use_connection_pool, g_connection_pool_max_idle_time); diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index 13430af..fddc830 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -75,5 +75,6 @@ char g_exe_name[256] = {0}; #endif int g_log_file_keep_days = 0; +int g_compress_error_log_days_before = 0; FDFSConnectionStat g_connection_stat = {0, 0}; diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index 3159ae2..85502b1 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -99,6 +99,7 @@ extern char g_exe_name[256]; #endif extern int g_log_file_keep_days; +extern int g_compress_error_log_days_before; extern FDFSConnectionStat g_connection_stat; #ifdef __cplusplus From 33b539eac6a2636d567e2ee38ab56c2946bae7ce Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 4 Dec 2019 10:47:32 +0800 Subject: [PATCH 54/95] disk recovery support multi-threads to speed up --- HISTORY | 10 +- conf/storage.conf | 11 +- conf/tracker.conf | 3 +- storage/fdfs_storaged.c | 178 ++-- storage/storage_disk_recovery.c | 1338 ++++++++++++++++++++++--------- storage/storage_disk_recovery.h | 4 +- storage/storage_func.c | 26 +- storage/storage_global.c | 1 + storage/storage_global.h | 1 + storage/storage_service.c | 21 +- storage/storage_sync.c | 2 +- 11 files changed, 1109 insertions(+), 486 deletions(-) diff --git a/HISTORY b/HISTORY index a9c28ef..16f2f0b 100644 --- a/HISTORY +++ b/HISTORY @@ -1,8 +1,12 @@ -Version 6.04 2019-12-01 +Version 6.04 2019-12-04 * storage_report_ip_changed ignore result EEXIST * use get_gzip_command_filename from libfastcommon v1.42 * support compress error log and access log + * disk recovery support multi-threads to speed up + + NOTE: you MUST upgrade libfastcommon to V1.42 or later + Version 6.03 2019-11-20 * dual IPs support two different types of inner (intranet) IPs @@ -21,6 +25,7 @@ Version 6.03 2019-11-20 NOTE: the tracker and storage server must upgrade together + Version 6.02 2019-11-12 * get_file_info calculate CRC32 for appender file type * disk recovery download file to local temp file then rename it @@ -28,12 +33,14 @@ Version 6.02 2019-11-12 * support regenerate filename for appender file NOTE: the regenerated file will be a normal file! + Version 6.01 2019-10-25 * compress and uncompress binlog file by gzip when need, config items in storage.conf: compress_binlog and compress_binlog_time * bugfix: must check and create data path before write_to_pid_file in fdfs_storaged.c + Version 6.00 2019-10-16 * tracker and storage server support dual IPs 1. you can config dual tracker IPs in storage.conf and client.conf, @@ -49,6 +56,7 @@ Version 6.00 2019-10-16 * tracker server check tracker list when storage server join * use socketCreateExAuto and socketClientExAuto exported by libfastcommon + Version 5.12 2018-06-07 * code refine for rare case * replace print format OFF_PRINTF_FORMAT to PRId64 diff --git a/conf/storage.conf b/conf/storage.conf index f2233ed..c27059f 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -109,8 +109,13 @@ sync_end_time=23:59 # default value is 500 write_mark_file_freq=500 +# disk recovery thread count +# default value is 1 +# since V6.04 +disk_recovery_threads = 1 + # store path (disk or mount point) count, default value is 1 -store_path_count=1 +store_path_count = 1 # store_path#, based on 0, to configure the store paths to store file # if store_path0 not exists, it's value is base_path (NOT recommended) @@ -262,6 +267,7 @@ compress_old_access_log = false # compress the access log days before # default value is 1 +# since V6.04 compress_access_log_days_before = 1 # if rotate the error log every day @@ -282,6 +288,7 @@ compress_old_error_log = false # compress the error log days before # default value is 1 +# since V6.04 compress_error_log_days_before = 1 # rotate access log when the log file exceeds this size @@ -309,7 +316,7 @@ file_sync_skip_invalid_record=false # if use connection pool # default value is false # since V4.05 -use_connection_pool = false +use_connection_pool = true # connections whose the idle time exceeds this time will be closed # unit: second diff --git a/conf/tracker.conf b/conf/tracker.conf index 78331fe..f93b6c0 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -249,6 +249,7 @@ compress_old_error_log = false # compress the error log days before # default value is 1 +# since V6.04 compress_error_log_days_before = 1 # rotate error log when the log file exceeds this size @@ -265,7 +266,7 @@ log_file_keep_days = 0 # if use connection pool # default value is false # since V4.05 -use_connection_pool = false +use_connection_pool = true # connections whose the idle time exceeds this time will be closed # unit: second diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 3e57b54..874809f 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -49,8 +49,12 @@ #include "storage_dump.h" #endif +#define ACCEPT_STAGE_NONE 0 +#define ACCEPT_STAGE_DOING 1 +#define ACCEPT_STAGE_DONE 2 + static bool bTerminateFlag = false; -static bool bAcceptEndFlag = false; +static char accept_stage = ACCEPT_STAGE_NONE; static void sigQuitHandler(int sig); static void sigHupHandler(int sig); @@ -58,6 +62,7 @@ static void sigUsrHandler(int sig); static void sigAlarmHandler(int sig); static int setupSchedules(pthread_t *schedule_tid); +static int setupSignalHandlers(); #if defined(DEBUG_FLAG) @@ -83,7 +88,6 @@ int main(int argc, char *argv[]) int sock; int wait_count; pthread_t schedule_tid; - struct sigaction act; char pidFilename[MAX_PATH_SIZE]; bool stop; @@ -148,6 +152,13 @@ int main(int argc, char *argv[]) return result; } + if ((result=setupSignalHandlers()) != 0) + { + logCrit("exit abnormally!\n"); + log_destroy(); + return result; + } + memset(g_bind_addr, 0, sizeof(g_bind_addr)); if ((result=storage_func_init(conf_filename, \ g_bind_addr, sizeof(g_bind_addr))) != 0) @@ -207,84 +218,6 @@ int main(int argc, char *argv[]) return result; } - memset(&act, 0, sizeof(act)); - sigemptyset(&act.sa_mask); - - act.sa_handler = sigUsrHandler; - if(sigaction(SIGUSR1, &act, NULL) < 0 || \ - sigaction(SIGUSR2, &act, NULL) < 0) - { - logCrit("file: "__FILE__", line: %d, " \ - "call sigaction fail, errno: %d, error info: %s", \ - __LINE__, errno, STRERROR(errno)); - logCrit("exit abnormally!\n"); - return errno; - } - - act.sa_handler = sigHupHandler; - if(sigaction(SIGHUP, &act, NULL) < 0) - { - logCrit("file: "__FILE__", line: %d, " \ - "call sigaction fail, errno: %d, error info: %s", \ - __LINE__, errno, STRERROR(errno)); - logCrit("exit abnormally!\n"); - return errno; - } - - act.sa_handler = SIG_IGN; - if(sigaction(SIGPIPE, &act, NULL) < 0) - { - logCrit("file: "__FILE__", line: %d, " \ - "call sigaction fail, errno: %d, error info: %s", \ - __LINE__, errno, STRERROR(errno)); - logCrit("exit abnormally!\n"); - return errno; - } - - act.sa_handler = sigQuitHandler; - if(sigaction(SIGINT, &act, NULL) < 0 || \ - sigaction(SIGTERM, &act, NULL) < 0 || \ - sigaction(SIGQUIT, &act, NULL) < 0) - { - logCrit("file: "__FILE__", line: %d, " \ - "call sigaction fail, errno: %d, error info: %s", \ - __LINE__, errno, STRERROR(errno)); - logCrit("exit abnormally!\n"); - return errno; - } - -#if defined(DEBUG_FLAG) - -/* -#if defined(OS_LINUX) - memset(&act, 0, sizeof(act)); - act.sa_sigaction = sigSegvHandler; - act.sa_flags = SA_SIGINFO; - if (sigaction(SIGSEGV, &act, NULL) < 0 || \ - sigaction(SIGABRT, &act, NULL) < 0) - { - logCrit("file: "__FILE__", line: %d, " \ - "call sigaction fail, errno: %d, error info: %s", \ - __LINE__, errno, STRERROR(errno)); - logCrit("exit abnormally!\n"); - return errno; - } -#endif -*/ - - memset(&act, 0, sizeof(act)); - sigemptyset(&act.sa_mask); - act.sa_handler = sigDumpHandler; - if(sigaction(SIGUSR1, &act, NULL) < 0 || \ - sigaction(SIGUSR2, &act, NULL) < 0) - { - logCrit("file: "__FILE__", line: %d, " \ - "call sigaction fail, errno: %d, error info: %s", \ - __LINE__, errno, STRERROR(errno)); - logCrit("exit abnormally!\n"); - return errno; - } -#endif #ifdef WITH_HTTPD if (!g_http_params.disabled) @@ -333,10 +266,10 @@ int main(int argc, char *argv[]) log_set_cache(true); bTerminateFlag = false; - bAcceptEndFlag = false; + accept_stage = ACCEPT_STAGE_DOING; storage_accept_loop(sock); - bAcceptEndFlag = true; + accept_stage = ACCEPT_STAGE_DONE; fdfs_binlog_sync_func(NULL); //binlog fsync @@ -412,7 +345,7 @@ static void sigAlarmHandler(int sig) { ConnectionInfo server; - if (bAcceptEndFlag) + if (accept_stage != ACCEPT_STAGE_DOING) { return; } @@ -583,3 +516,82 @@ static int setupSchedules(pthread_t *schedule_tid) return 0; } +static int setupSignalHandlers() +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + sigemptyset(&act.sa_mask); + + act.sa_handler = sigUsrHandler; + if(sigaction(SIGUSR1, &act, NULL) < 0 || \ + sigaction(SIGUSR2, &act, NULL) < 0) + { + logCrit("file: "__FILE__", line: %d, " \ + "call sigaction fail, errno: %d, error info: %s", \ + __LINE__, errno, STRERROR(errno)); + return errno != 0 ? errno : EFAULT; + } + + act.sa_handler = sigHupHandler; + if(sigaction(SIGHUP, &act, NULL) < 0) + { + logCrit("file: "__FILE__", line: %d, " \ + "call sigaction fail, errno: %d, error info: %s", \ + __LINE__, errno, STRERROR(errno)); + return errno != 0 ? errno : EFAULT; + } + + act.sa_handler = SIG_IGN; + if(sigaction(SIGPIPE, &act, NULL) < 0) + { + logCrit("file: "__FILE__", line: %d, " \ + "call sigaction fail, errno: %d, error info: %s", \ + __LINE__, errno, STRERROR(errno)); + return errno != 0 ? errno : EFAULT; + } + + act.sa_handler = sigQuitHandler; + if(sigaction(SIGINT, &act, NULL) < 0 || \ + sigaction(SIGTERM, &act, NULL) < 0 || \ + sigaction(SIGQUIT, &act, NULL) < 0) + { + logCrit("file: "__FILE__", line: %d, " \ + "call sigaction fail, errno: %d, error info: %s", \ + __LINE__, errno, STRERROR(errno)); + return errno != 0 ? errno : EFAULT; + } + +#if defined(DEBUG_FLAG) + +/* +#if defined(OS_LINUX) + memset(&act, 0, sizeof(act)); + act.sa_sigaction = sigSegvHandler; + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGSEGV, &act, NULL) < 0 || \ + sigaction(SIGABRT, &act, NULL) < 0) + { + logCrit("file: "__FILE__", line: %d, " \ + "call sigaction fail, errno: %d, error info: %s", \ + __LINE__, errno, STRERROR(errno)); + return errno != 0 ? errno : EFAULT; + } +#endif +*/ + + memset(&act, 0, sizeof(act)); + sigemptyset(&act.sa_mask); + act.sa_handler = sigDumpHandler; + if(sigaction(SIGUSR1, &act, NULL) < 0 || \ + sigaction(SIGUSR2, &act, NULL) < 0) + { + logCrit("file: "__FILE__", line: %d, " \ + "call sigaction fail, errno: %d, error info: %s", \ + __LINE__, errno, STRERROR(errno)); + return errno != 0 ? errno : EFAULT; + } +#endif + + return 0; +} diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index dc0b59a..5f95cbd 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -43,17 +43,74 @@ typedef struct { int id; //trunk file id } FDFSTrunkFileIdInfo; +typedef struct { + int thread_index; //-1 for global + int result; + bool done; + const char *base_path; +} RecoveryThreadData; + #define RECOVERY_BINLOG_FILENAME ".binlog.recovery" +#define RECOVERY_FLAG_FILENAME ".recovery.flag" #define RECOVERY_MARK_FILENAME ".recovery.mark" -#define MARK_ITEM_BINLOG_OFFSET "binlog_offset" -#define MARK_ITEM_FETCH_BINLOG_DONE "fetch_binlog_done" -#define MARK_ITEM_SAVED_STORAGE_STATUS "saved_storage_status" +#define FLAG_ITEM_RECOVERY_THREADS "recovery_threads" +#define FLAG_ITEM_SAVED_STORAGE_STATUS "saved_storage_status" +#define FLAG_ITEM_FETCH_BINLOG_DONE "fetch_binlog_done" +#define MARK_ITEM_BINLOG_OFFSET "binlog_offset" + +static int last_recovery_threads = -1; //for rebalance binlog data +static volatile int current_recovery_thread_count = 0; static int saved_storage_status = FDFS_STORAGE_STATUS_NONE; static char *recovery_get_binlog_filename(const void *pArg, - char *full_filename); + char *full_filename); + +static int disk_recovery_write_to_binlog(FILE *fp, + const char *binlog_filename, StorageBinLogRecord *pRecord); + +static char *recovery_get_full_filename_ex(const char *pBasePath, + const int thread_index, const char *filename, char *full_filename) +{ + static char buff[MAX_PATH_SIZE]; + int len; + + if (full_filename == NULL) + { + full_filename = buff; + } + + len = snprintf(full_filename, MAX_PATH_SIZE, + "%s/data/%s", pBasePath, filename); + if (thread_index >= 0) + { + snprintf(full_filename + len, MAX_PATH_SIZE - len, + ".%d", thread_index); + } + return full_filename; +} + +static inline char *recovery_get_full_filename(const RecoveryThreadData + *pThreadData, const char *filename, char *full_filename) +{ + return recovery_get_full_filename_ex(pThreadData->base_path, + pThreadData->thread_index, filename, full_filename); +} + +static inline char *recovery_get_global_full_filename(const char *pBasePath, + const char *filename, char *full_filename) +{ + return recovery_get_full_filename_ex(pBasePath, -1, + RECOVERY_FLAG_FILENAME, full_filename); +} + +static inline char *recovery_get_global_binlog_filename(const char *pBasePath, + char *full_filename) +{ + return recovery_get_global_full_filename(pBasePath, + RECOVERY_BINLOG_FILENAME, full_filename); +} static int storage_do_fetch_binlog(ConnectionInfo *pSrcStorage, \ const int store_path_index) @@ -68,7 +125,8 @@ static int storage_do_fetch_binlog(ConnectionInfo *pSrcStorage, \ int network_timeout; pBasePath = g_fdfs_store_paths.paths[store_path_index].path; - recovery_get_binlog_filename(pBasePath, full_binlog_filename); + recovery_get_full_filename_ex(pBasePath, 0, + RECOVERY_BINLOG_FILENAME, full_binlog_filename); memset(out_buff, 0, sizeof(out_buff)); pHeader = (TrackerHeader *)out_buff; @@ -129,12 +187,14 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) { int result; int storage_count; + int i; + static unsigned int current_index = 0; TrackerServerInfo trackerServer; ConnectionInfo *pTrackerConn; FDFSGroupStat groupStat; FDFSStorageInfo storageStats[FDFS_MAX_SERVERS_EACH_GROUP]; FDFSStorageInfo *pStorageStat; - FDFSStorageInfo *pStorageEnd; + bool found; memset(pSrcStorage, 0, sizeof(ConnectionInfo)); pSrcStorage->sock = -1; @@ -185,6 +245,7 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) sleep(1); } + found = false; while (g_continue_flag) { if ((pTrackerConn=tracker_get_connection_r(&trackerServer, \ @@ -243,8 +304,8 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) continue; } - result = tracker_list_servers(pTrackerConn, \ - g_group_name, NULL, storageStats, \ + result = tracker_list_servers(pTrackerConn, + g_group_name, NULL, storageStats, FDFS_MAX_SERVERS_EACH_GROUP, &storage_count); tracker_close_connection_ex(pTrackerConn, result != 0); if (result != 0) @@ -263,10 +324,9 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) continue; } - pStorageEnd = storageStats + storage_count; - for (pStorageStat=storageStats; pStorageStatid, g_my_server_id_str) == 0) { continue; @@ -274,13 +334,14 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) if (pStorageStat->status == FDFS_STORAGE_STATUS_ACTIVE) { + found = true; strcpy(pSrcStorage->ip_addr, pStorageStat->ip_addr); pSrcStorage->port = pStorageStat->storage_port; break; } } - if (pStorageStat < pStorageEnd) //found src storage server + if (found) //found src storage server { break; } @@ -299,117 +360,205 @@ static int recovery_get_src_storage_server(ConnectionInfo *pSrcStorage) return 0; } -static char *recovery_get_full_filename(const char *pBasePath, - const char *filename, char *full_filename) -{ - static char buff[MAX_PATH_SIZE]; - - if (full_filename == NULL) - { - full_filename = buff; - } - - snprintf(full_filename, MAX_PATH_SIZE, - "%s/data/%s", pBasePath, filename); - return full_filename; -} - static char *recovery_get_binlog_filename(const void *pArg, char *full_filename) { - return recovery_get_full_filename((const char *)pArg, + return recovery_get_full_filename((const RecoveryThreadData *)pArg, RECOVERY_BINLOG_FILENAME, full_filename); } -static char *recovery_get_mark_filename(const char *pBasePath, +static char *recovery_get_flag_filename(const char *pBasePath, char *full_filename) { - return recovery_get_full_filename(pBasePath, + return recovery_get_global_full_filename(pBasePath, + RECOVERY_FLAG_FILENAME, full_filename); +} + +static char *recovery_get_mark_filename(const RecoveryThreadData *pThreadData, + char *full_filename) +{ + return recovery_get_full_filename(pThreadData, RECOVERY_MARK_FILENAME, full_filename); } +static int storage_disk_recovery_delete_thread_files(const char *pBasePath, + const int index_start, const int index_end) +{ + int i; + char mark_filename[MAX_PATH_SIZE]; + char binlog_filename[MAX_PATH_SIZE]; + + for (i=index_start; ibinlog_offset, - MARK_ITEM_FETCH_BINLOG_DONE); + "%s=%"PRId64"\n", + MARK_ITEM_BINLOG_OFFSET, binlog_offset); - return safeWriteToFile(pReader->mark_filename, buff, len); + return safeWriteToFile(mark_filename, buff, len); } -static int recovery_init_binlog_file(const char *pBasePath) +static inline int recovery_write_to_mark_file(StorageBinLogReader *pReader) +{ + return do_write_to_mark_file(pReader->mark_filename, + pReader->binlog_offset); +} + +static int recovery_init_global_binlog_file(const char *pBasePath) { char full_binlog_filename[MAX_PATH_SIZE]; char buff[1]; *buff = '\0'; - recovery_get_binlog_filename(pBasePath, full_binlog_filename); + recovery_get_full_filename_ex(pBasePath, 0, + RECOVERY_BINLOG_FILENAME, full_binlog_filename); return writeToFile(full_binlog_filename, buff, 0); } -static int recovery_init_mark_file(const char *pBasePath, \ - const bool fetch_binlog_done) +static int recovery_init_flag_file_ex(const char *pBasePath, + const bool fetch_binlog_done, const int recovery_threads) { char full_filename[MAX_PATH_SIZE]; - char buff[128]; - int len; - recovery_get_mark_filename(pBasePath, full_filename); - - len = sprintf(buff, \ - "%s=%d\n" \ - "%s=0\n" \ - "%s=%d\n", \ - MARK_ITEM_SAVED_STORAGE_STATUS, saved_storage_status, \ - MARK_ITEM_BINLOG_OFFSET, \ - MARK_ITEM_FETCH_BINLOG_DONE, fetch_binlog_done); - return writeToFile(full_filename, buff, len); + recovery_get_flag_filename(pBasePath, full_filename); + return do_write_to_flag_file(full_filename, + fetch_binlog_done, recovery_threads); } -static int recovery_reader_init(const char *pBasePath, \ +static inline int recovery_init_flag_file(const char *pBasePath, + const bool fetch_binlog_done, const int recovery_threads) +{ + return recovery_init_flag_file_ex(pBasePath, + fetch_binlog_done, recovery_threads); +} + +static int recovery_load_params_from_flag_file(const char *full_flag_filename) +{ + IniContext iniContext; + int result; + + memset(&iniContext, 0, sizeof(IniContext)); + if ((result=iniLoadFromFile(full_flag_filename, + &iniContext)) != 0) + { + logError("file: "__FILE__", line: %d, " + "load from flag file \"%s\" fail, " + "error code: %d", __LINE__, + full_flag_filename, result); + return result; + } + + if (!iniGetBoolValue(NULL, FLAG_ITEM_FETCH_BINLOG_DONE, + &iniContext, false)) + { + iniFreeContext(&iniContext); + + logInfo("file: "__FILE__", line: %d, " + "flag file \"%s\", %s=0, " + "need to fetch binlog again", __LINE__, + full_flag_filename, FLAG_ITEM_FETCH_BINLOG_DONE); + return EAGAIN; + } + + saved_storage_status = iniGetIntValue(NULL, + FLAG_ITEM_SAVED_STORAGE_STATUS, &iniContext, -1); + if (saved_storage_status < 0) + { + iniFreeContext(&iniContext); + + logError("file: "__FILE__", line: %d, " + "in flag file \"%s\", %s: %d < 0", __LINE__, + full_flag_filename, FLAG_ITEM_SAVED_STORAGE_STATUS, + saved_storage_status); + return EINVAL; + } + + last_recovery_threads = iniGetIntValue(NULL, + FLAG_ITEM_RECOVERY_THREADS, &iniContext, -1); + + iniFreeContext(&iniContext); + return 0; +} + +static int recovery_reader_init(const RecoveryThreadData *pThreadData, StorageBinLogReader *pReader) { IniContext iniContext; @@ -419,57 +568,32 @@ static int recovery_reader_init(const char *pBasePath, \ pReader->binlog_fd = -1; pReader->binlog_index = g_binlog_index + 1; - pReader->binlog_buff.buffer = (char *)malloc( \ + pReader->binlog_buff.buffer = (char *)malloc( STORAGE_BINLOG_BUFFER_SIZE); if (pReader->binlog_buff.buffer == NULL) { - logError("file: "__FILE__", line: %d, " \ - "malloc %d bytes fail, " \ - "errno: %d, error info: %s", \ - __LINE__, STORAGE_BINLOG_BUFFER_SIZE, \ + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail, " + "errno: %d, error info: %s", + __LINE__, STORAGE_BINLOG_BUFFER_SIZE, errno, STRERROR(errno)); return errno != 0 ? errno : ENOMEM; } pReader->binlog_buff.current = pReader->binlog_buff.buffer; - recovery_get_mark_filename(pBasePath, pReader->mark_filename); + recovery_get_mark_filename(pThreadData, pReader->mark_filename); memset(&iniContext, 0, sizeof(IniContext)); if ((result=iniLoadFromFile(pReader->mark_filename, &iniContext)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "load from mark file \"%s\" fail, " \ - "error code: %d", __LINE__, \ + logError("file: "__FILE__", line: %d, " + "load from mark file \"%s\" fail, " + "error code: %d", __LINE__, pReader->mark_filename, result); return result; } - if (!iniGetBoolValue(NULL, MARK_ITEM_FETCH_BINLOG_DONE, \ - &iniContext, false)) - { - iniFreeContext(&iniContext); - - logInfo("file: "__FILE__", line: %d, " \ - "mark file \"%s\", %s=0, " \ - "need to fetch binlog again", __LINE__, \ - pReader->mark_filename, MARK_ITEM_FETCH_BINLOG_DONE); - return EAGAIN; - } - - saved_storage_status = iniGetIntValue(NULL, \ - MARK_ITEM_SAVED_STORAGE_STATUS, &iniContext, -1); - if (saved_storage_status < 0) - { - iniFreeContext(&iniContext); - - logError("file: "__FILE__", line: %d, " \ - "in mark file \"%s\", %s: %d < 0", __LINE__, \ - pReader->mark_filename, MARK_ITEM_SAVED_STORAGE_STATUS, \ - saved_storage_status); - return EINVAL; - } - - pReader->binlog_offset = iniGetInt64Value(NULL, \ + pReader->binlog_offset = iniGetInt64Value(NULL, MARK_ITEM_BINLOG_OFFSET, &iniContext, -1); if (pReader->binlog_offset < 0) { @@ -485,8 +609,8 @@ static int recovery_reader_init(const char *pBasePath, \ iniFreeContext(&iniContext); - if ((result=storage_open_readable_binlog(pReader, \ - recovery_get_binlog_filename, pBasePath)) != 0) + if ((result=storage_open_readable_binlog(pReader, + recovery_get_binlog_filename, pThreadData)) != 0) { return result; } @@ -494,15 +618,15 @@ static int recovery_reader_init(const char *pBasePath, \ return 0; } -static int recovery_reader_check_init(const char *pBasePath, \ - StorageBinLogReader *pReader) +static int recovery_reader_check_init(const RecoveryThreadData *pThreadData, + StorageBinLogReader *pReader) { if (pReader->binlog_fd >= 0 && pReader->binlog_buff.buffer != NULL) { return 0; } - return recovery_reader_init(pBasePath, pReader); + return recovery_reader_init(pThreadData, pReader); } static int recovery_download_file_to_local(StorageBinLogRecord *pRecord, @@ -584,8 +708,8 @@ static int recovery_download_file_to_local(StorageBinLogRecord *pRecord, return result; } -static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pReader, \ - ConnectionInfo *pSrcStorage) +static int storage_do_recovery(RecoveryThreadData *pThreadData, + StorageBinLogReader *pReader, ConnectionInfo *pSrcStorage) { TrackerServerInfo trackerServer; ConnectionInfo *pTrackerServer; @@ -619,262 +743,670 @@ static int storage_do_recovery(const char *pBasePath, StorageBinLogReader *pRead result = 0; logInfo("file: "__FILE__", line: %d, " - "disk recovery: recovering files of data path: %s ...", - __LINE__, pBasePath); + "disk recovery thread #%d, src storage server %s:%d, " + "recovering files of data path: %s ...", __LINE__, + pThreadData->thread_index, pSrcStorage->ip_addr, + pSrcStorage->port, pThreadData->base_path); bContinueFlag = true; while (bContinueFlag) - { - if ((result=recovery_reader_check_init(pBasePath, pReader)) != 0) { - break; - } - if ((pStorageConn=tracker_make_connection(pSrcStorage, &result)) == NULL) - { - sleep(5); - continue; - } + if ((result=recovery_reader_check_init(pThreadData, pReader)) != 0) + { + break; + } + if ((pStorageConn=tracker_make_connection(pSrcStorage, + &result)) == NULL) + { + sleep(5); + continue; + } - while (g_continue_flag) - { - result=storage_binlog_read(pReader, &record, &record_length); - if (result != 0) - { - if (result == ENOENT) - { - result = 0; - } - bContinueFlag = false; - break; - } - - total_count++; - if (record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_FILE - || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_FILE) - { - result = recovery_download_file_to_local(&record, - pTrackerServer, pStorageConn); - if (result == 0) - { - success_count++; - } - else if (result == -EINVAL) - { - result = 0; - } - else if (result == ENOENT) - { - result = 0; - noent_count++; - } - else + while (g_continue_flag) + { + result = storage_binlog_read(pReader, &record, &record_length); + if (result != 0) { - break; - } - } - else if (record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_LINK - || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_LINK) - { - if (record.src_filename_len == 0) - { - logError("file: "__FILE__", line: %d, " \ - "invalid binlog line, filename: %s, " \ - "expect src filename", __LINE__, \ - record.filename); - result = EINVAL; - bContinueFlag = false; - break; - } + if (result == ENOENT) + { + pThreadData->done = true; + result = 0; + } + bContinueFlag = false; + break; + } - if ((result=storage_split_filename_ex(record.filename, \ - &record.filename_len, record.true_filename, \ - &store_path_index)) != 0) - { - bContinueFlag = false; - break; - } - sprintf(local_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index].path, \ - record.true_filename); - - if ((result=storage_split_filename_ex( \ - record.src_filename, &record.src_filename_len,\ - record.true_filename, &store_path_index)) != 0) - { - bContinueFlag = false; - break; - } - sprintf(src_filename, "%s/data/%s", \ - g_fdfs_store_paths.paths[store_path_index].path, \ - record.true_filename); - if (symlink(src_filename, local_filename) == 0) - { - success_count++; - } - else - { - result = errno != 0 ? errno : ENOENT; - if (result == ENOENT || result == EEXIST) - { - log_level = LOG_DEBUG; - } - else - { - log_level = LOG_ERR; - } - - log_it_ex(&g_log_context, log_level, \ - "file: "__FILE__", line: %d, " \ - "link file %s to %s fail, " \ - "errno: %d, error info: %s", __LINE__,\ - src_filename, local_filename, \ - result, STRERROR(result)); - - if (result != ENOENT && result != EEXIST) - { - bContinueFlag = false; - break; - } - else + total_count++; + if (record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_FILE + || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_FILE) + { + result = recovery_download_file_to_local(&record, + pTrackerServer, pStorageConn); + if (result == 0) + { + success_count++; + } + else if (result == -EINVAL) { result = 0; } - } - } - else - { - logError("file: "__FILE__", line: %d, " \ - "invalid file op type: %d", \ - __LINE__, record.op_type); - result = EINVAL; - bContinueFlag = false; - break; - } + else if (result == ENOENT) + { + result = 0; + noent_count++; + } + else + { + break; + } + } + else if (record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_LINK + || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_LINK) + { + if (record.src_filename_len == 0) + { + logError("file: "__FILE__", line: %d, " \ + "invalid binlog line, filename: %s, " \ + "expect src filename", __LINE__, \ + record.filename); + result = EINVAL; + bContinueFlag = false; + break; + } - pReader->binlog_offset += record_length; - count++; - if (count == 1000) - { - logDebug("file: "__FILE__", line: %d, " \ - "disk recovery: recover path: %s, " \ - "file count: %"PRId64 \ - ", success count: %"PRId64", noent_count: %"PRId64, \ - __LINE__, pBasePath, total_count, \ - success_count, noent_count); - recovery_write_to_mark_file(pBasePath, pReader); - count = 0; - } - } + if ((result=storage_split_filename_ex(record.filename, \ + &record.filename_len, record.true_filename, \ + &store_path_index)) != 0) + { + bContinueFlag = false; + break; + } + sprintf(local_filename, "%s/data/%s", \ + g_fdfs_store_paths.paths[store_path_index].path, \ + record.true_filename); - tracker_close_connection_ex(pStorageConn, result != 0); - recovery_write_to_mark_file(pBasePath, pReader); - if (bContinueFlag) - { - storage_reader_destroy(pReader); + if ((result=storage_split_filename_ex( \ + record.src_filename, &record.src_filename_len,\ + record.true_filename, &store_path_index)) != 0) + { + bContinueFlag = false; + break; + } + sprintf(src_filename, "%s/data/%s", \ + g_fdfs_store_paths.paths[store_path_index].path, \ + record.true_filename); + if (symlink(src_filename, local_filename) == 0) + { + success_count++; + } + else + { + result = errno != 0 ? errno : ENOENT; + if (result == ENOENT || result == EEXIST) + { + log_level = LOG_DEBUG; + } + else + { + log_level = LOG_ERR; + } + + log_it_ex(&g_log_context, log_level, \ + "file: "__FILE__", line: %d, " \ + "link file %s to %s fail, " \ + "errno: %d, error info: %s", __LINE__,\ + src_filename, local_filename, \ + result, STRERROR(result)); + + if (result != ENOENT && result != EEXIST) + { + bContinueFlag = false; + break; + } + else + { + result = 0; + } + } + } + else + { + logError("file: "__FILE__", line: %d, " \ + "invalid file op type: %d", \ + __LINE__, record.op_type); + result = EINVAL; + bContinueFlag = false; + break; + } + + pReader->binlog_offset += record_length; + count++; + if (count == 1000) + { + logDebug("file: "__FILE__", line: %d, " + "disk recovery thread #%d recover path: %s, " + "file count: %"PRId64", success count: %"PRId64 + ", noent_count: %"PRId64, __LINE__, + pThreadData->thread_index, + pThreadData->base_path, total_count, + success_count, noent_count); + recovery_write_to_mark_file(pReader); + count = 0; + } + } + + tracker_close_connection_ex(pStorageConn, result != 0); + recovery_write_to_mark_file(pReader); + + if (!g_continue_flag) + { + bContinueFlag = false; + } + else if (bContinueFlag) + { + storage_reader_destroy(pReader); + } + + if (count > 0) + { + logInfo("file: "__FILE__", line: %d, " + "disk recovery thread #%d, recover path: %s, " + "file count: %"PRId64", success count: " + "%"PRId64", noent_count: %"PRId64, + __LINE__, pThreadData->thread_index, + pThreadData->base_path, total_count, + success_count, noent_count); + count = 0; + } + + if (bContinueFlag) + { + sleep(5); + } } - if (count > 0) - { - count = 0; - - logInfo("file: "__FILE__", line: %d, " \ - "disk recovery: recover path: %s, " \ - "file count: %"PRId64 \ - ", success count: %"PRId64", noent_count: %"PRId64, \ - __LINE__, pBasePath, total_count, success_count, noent_count); - } - else - { - sleep(5); - } - } - tracker_close_connection_ex(pTrackerServer, true); - if (result == 0) + if (pThreadData->done) { - logInfo("file: "__FILE__", line: %d, " \ - "disk recovery: recover files of data path: %s done", \ - __LINE__, pBasePath); + logInfo("file: "__FILE__", line: %d, " + "disk recovery thread #%d, src storage server %s:%d, " + "recover files of data path: %s done", __LINE__, + pThreadData->thread_index, pSrcStorage->ip_addr, + pSrcStorage->port, pThreadData->base_path); } - return result; + return g_continue_flag ? result :EINTR; } -int storage_disk_recovery_restore(const char *pBasePath) +static void *storage_disk_recovery_restore_entrance(void *arg) { - char full_binlog_filename[MAX_PATH_SIZE]; - char full_mark_filename[MAX_PATH_SIZE]; - ConnectionInfo srcStorage; - int result; StorageBinLogReader reader; + RecoveryThreadData *pThreadData; + ConnectionInfo srcStorage; - recovery_get_binlog_filename(pBasePath, full_binlog_filename); - recovery_get_mark_filename(pBasePath, full_mark_filename); + pThreadData = (RecoveryThreadData *)arg; + __sync_add_and_fetch(¤t_recovery_thread_count, 1); - if (!(fileExists(full_mark_filename) && \ - fileExists(full_binlog_filename))) + do + { + if ((pThreadData->result=recovery_get_src_storage_server(&srcStorage)) != 0) + { + if (pThreadData->result == ENOENT) + { + logWarning("file: "__FILE__", line: %d, " + "no source storage server, " + "disk recovery finished!", __LINE__); + pThreadData->result = 0; + } + break; + } + + if ((pThreadData->result=recovery_reader_init(pThreadData, &reader)) != 0) + { + storage_reader_destroy(&reader); + break; + } + + pThreadData->result = storage_do_recovery(pThreadData, &reader, &srcStorage); + + recovery_write_to_mark_file(&reader); + storage_reader_destroy(&reader); + + } while (0); + + __sync_sub_and_fetch(¤t_recovery_thread_count, 1); + + return NULL; +} + +static int storage_disk_recovery_old_version_migrate(const char *pBasePath) +{ + char old_binlog_filename[MAX_PATH_SIZE]; + char old_mark_filename[MAX_PATH_SIZE]; + char new_binlog_filename[MAX_PATH_SIZE]; + char new_mark_filename[MAX_PATH_SIZE]; + int result; + + recovery_get_global_binlog_filename(pBasePath, old_binlog_filename); + recovery_get_global_full_filename(pBasePath, + RECOVERY_MARK_FILENAME, old_mark_filename); + + if (!(fileExists(old_mark_filename) && + fileExists(old_binlog_filename))) { - return 0; + return ENOENT; } - logInfo("file: "__FILE__", line: %d, " \ - "disk recovery: begin recovery data path: %s ...", \ - __LINE__, pBasePath); + logInfo("file: "__FILE__", line: %d, " + "try to migrate data from old version ...", __LINE__); - if ((result=recovery_get_src_storage_server(&srcStorage)) != 0) + result = recovery_load_params_from_flag_file(old_mark_filename); + if (result != 0) + { + if (result == EAGAIN) + { + unlink(old_mark_filename); + } + return result; + } + + if ((result=recovery_init_flag_file_ex(pBasePath, true, 1)) != 0) + { + return result; + } + + recovery_get_full_filename_ex(pBasePath, 0, + RECOVERY_MARK_FILENAME, new_mark_filename); + if (rename(old_mark_filename, new_mark_filename) != 0) { - if (result == ENOENT) - { - logWarning("file: "__FILE__", line: %d, " \ - "no source storage server, " \ - "disk recovery finished!", __LINE__); - return storage_disk_recovery_finish(pBasePath); - } - else - { - return result; - } + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", __LINE__, + old_mark_filename, new_mark_filename, + errno, STRERROR(errno)); + return errno != 0 ? errno : EPERM; } - if ((result=recovery_reader_init(pBasePath, &reader)) != 0) + recovery_get_full_filename_ex(pBasePath, 0, + RECOVERY_BINLOG_FILENAME, new_binlog_filename); + if (rename(old_binlog_filename, new_binlog_filename) != 0) { - storage_reader_destroy(&reader); - return result; + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", __LINE__, + old_binlog_filename, new_binlog_filename, + errno, STRERROR(errno)); + return errno != 0 ? errno : EPERM; } - result = storage_do_recovery(pBasePath, &reader, &srcStorage); + logInfo("file: "__FILE__", line: %d, " + "migrate data from old version successfully.", __LINE__); + return 0; +} - recovery_write_to_mark_file(pBasePath, &reader); - storage_reader_destroy(&reader); +static int do_dispatch_binlog_for_threads(const char *pBasePath) +{ + typedef struct { + FILE *fp; + int64_t count; + char binlog_filename[MAX_PATH_SIZE]; + char temp_filename[MAX_PATH_SIZE]; + } RecoveryDispatchInfo; - if (result != 0) - { - return result; - } + char mark_filename[MAX_PATH_SIZE]; + string_t log_buff; + char buff[2 * 1024]; + struct stat file_stat; + RecoveryThreadData thread_data; + StorageBinLogReader reader; + StorageBinLogRecord record; + int record_length; + RecoveryDispatchInfo *dispatchs; + RecoveryDispatchInfo *disp; + int64_t total_count; + int bytes; + int result; + int i; - while (g_continue_flag) - { - if (storage_report_storage_status(g_my_server_id_str, \ - g_tracker_client_ip.ips[0].address, - saved_storage_status) == 0) - { - break; - } + bytes = sizeof(RecoveryDispatchInfo) * g_disk_recovery_threads; + dispatchs = (RecoveryDispatchInfo *)malloc(bytes); + if (dispatchs == NULL) + { + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail", + __LINE__, bytes); + return ENOMEM; + } + memset(dispatchs, 0, bytes); - sleep(5); - } + result = 0; + for (i=0; ifp, + disp->temp_filename, &record)) != 0) + { + break; + } + disp->count++; + } + + storage_reader_destroy(&reader); + if (result != 0) + { + break; + } + } + + total_count = 0; + *buff = '\0'; + log_buff.str = buff; + log_buff.len = 0; + for (i=0; i total lines: %"PRId64"%s", + __LINE__, total_count, log_buff.str); + return result; +} + +static int storage_disk_recovery_dispatch_binlog_for_threads( + const char *pBasePath) +{ + int result; + int i; + char binlog_filename[MAX_PATH_SIZE]; + + if (last_recovery_threads <= 0) + { + logError("file: "__FILE__", line: %d, " + "invalid last recovery threads: %d, " + "retry restore data for %s again ...", + __LINE__, last_recovery_threads, pBasePath); + return EAGAIN; + } + + for (i=0; i 0); + + if (__sync_fetch_and_add(¤t_recovery_thread_count, 0) > 0) + { + for (i=0; i<60; i++) + { + if ((thread_count=__sync_fetch_and_add( + ¤t_recovery_thread_count, 0)) == 0) + { + break; + } + + logInfo("file: "__FILE__", line: %d, " + "waiting for recovery threads exit, " + "waiting count: %d, current thread count: %d", + __LINE__, i+1, thread_count); + sleep(1); + } + } + + free(thread_data); + free(args); + free(recovery_tids); + + if (!g_continue_flag) + { + return EINTR; + } + + while (g_continue_flag) + { + if (storage_report_storage_status(g_my_server_id_str, \ + g_tracker_client_ip.ips[0].address, + saved_storage_status) == 0) + { + break; + } + + sleep(5); + } + + if (!g_continue_flag) + { + return EINTR; + } + + for (i=0; iline) > 0) { return 0; @@ -903,14 +1435,54 @@ static int tree_write_file_walk_callback(void *data, void *args) else { result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "write to binlog file fail, " \ - "errno: %d, error info: %s.", \ + logError("file: "__FILE__", line: %d, " + "write to binlog file fail, " + "errno: %d, error info: %s.", __LINE__, result, STRERROR(result)); return EIO; } } +static int disk_recovery_write_to_binlog(FILE *fp, + const char *binlog_filename, StorageBinLogRecord *pRecord) +{ + int result; + if (pRecord->op_type == STORAGE_OP_TYPE_SOURCE_CREATE_FILE + || pRecord->op_type == STORAGE_OP_TYPE_REPLICA_CREATE_FILE) + { + if (fprintf(fp, "%d %c %s\n", + (int)pRecord->timestamp, + pRecord->op_type, pRecord->filename) < 0) + { + result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " + "write to file: %s fail, " + "errno: %d, error info: %s.", + __LINE__, binlog_filename, + result, STRERROR(result)); + return result; + } + } + else + { + if (fprintf(fp, "%d %c %s %s\n", + (int)pRecord->timestamp, + pRecord->op_type, pRecord->filename, + pRecord->src_filename) < 0) + { + result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " + "write to file: %s fail, " + "errno: %d, error info: %s.", + __LINE__, binlog_filename, + result, STRERROR(result)); + return result; + } + } + + return 0; +} + static int storage_do_split_trunk_binlog(const int store_path_index, StorageBinLogReader *pReader) { @@ -927,7 +1499,7 @@ static int storage_do_split_trunk_binlog(const int store_path_index, int result; pBasePath = g_fdfs_store_paths.paths[store_path_index].path; - recovery_get_full_filename(pBasePath, \ + recovery_get_full_filename_ex(pBasePath, -1, RECOVERY_BINLOG_FILENAME".tmp", tmpFullFilename); fp = fopen(tmpFullFilename, "w"); if (fp == NULL) @@ -1014,38 +1586,11 @@ static int storage_do_split_trunk_binlog(const int store_path_index, } else { - if (record.op_type == STORAGE_OP_TYPE_SOURCE_CREATE_FILE - || record.op_type == STORAGE_OP_TYPE_REPLICA_CREATE_FILE) - { - if (fprintf(fp, "%d %c %s\n", \ - (int)record.timestamp, \ - record.op_type, record.filename) < 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "write to file: %s fail, " \ - "errno: %d, error info: %s.", \ - __LINE__, tmpFullFilename, - result, STRERROR(result)); - break; - } - } - else - { - if (fprintf(fp, "%d %c %s %s\n", \ - (int)record.timestamp, \ - record.op_type, record.filename, \ - record.src_filename) < 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "write to file: %s fail, " \ - "errno: %d, error info: %s.", \ - __LINE__, tmpFullFilename, - result, STRERROR(result)); - break; - } - } + if ((result=disk_recovery_write_to_binlog(fp, + tmpFullFilename, &record)) != 0) + { + break; + } } } @@ -1076,7 +1621,7 @@ static int storage_do_split_trunk_binlog(const int store_path_index, return result; } - recovery_get_full_filename(pBasePath, \ + recovery_get_full_filename_ex(pBasePath, 0, RECOVERY_BINLOG_FILENAME, binlogFullFilename); if (rename(tmpFullFilename, binlogFullFilename) != 0) { @@ -1093,24 +1638,32 @@ static int storage_do_split_trunk_binlog(const int store_path_index, static int storage_disk_recovery_split_trunk_binlog(const int store_path_index) { - char *pBasePath; + char mark_filename[MAX_PATH_SIZE]; + RecoveryThreadData thread_data; StorageBinLogReader reader; int result; - pBasePath = g_fdfs_store_paths.paths[store_path_index].path; - if ((result=recovery_reader_init(pBasePath, &reader)) != 0) + thread_data.base_path = g_fdfs_store_paths.paths[store_path_index].path; + thread_data.thread_index = 0; + + recovery_get_mark_filename(&thread_data, mark_filename); + if ((result=do_write_to_mark_file(mark_filename, 0)) != 0) + { + return result; + } + + if ((result=recovery_reader_init(&thread_data, &reader)) != 0) { storage_reader_destroy(&reader); return result; } result = storage_do_split_trunk_binlog(store_path_index, &reader); - storage_reader_destroy(&reader); return result; } -int storage_disk_recovery_start(const int store_path_index) +int storage_disk_recovery_prepare(const int store_path_index) { ConnectionInfo srcStorage; ConnectionInfo *pStorageConn; @@ -1118,12 +1671,12 @@ int storage_disk_recovery_start(const int store_path_index) char *pBasePath; pBasePath = g_fdfs_store_paths.paths[store_path_index].path; - if ((result=recovery_init_mark_file(pBasePath, false)) != 0) + if ((result=recovery_init_flag_file(pBasePath, false, -1)) != 0) { return result; } - if ((result=recovery_init_binlog_file(pBasePath)) != 0) + if ((result=recovery_init_global_binlog_file(pBasePath)) != 0) { return result; } @@ -1160,6 +1713,10 @@ int storage_disk_recovery_start(const int store_path_index) return result; } + logInfo("file: "__FILE__", line: %d, " + "try to fetch binlog from %s:%d ...", __LINE__, + pStorageConn->ip_addr, pStorageConn->port); + result = storage_do_fetch_binlog(pStorageConn, store_path_index); tracker_close_connection_ex(pStorageConn, true); if (result != 0) @@ -1167,20 +1724,23 @@ int storage_disk_recovery_start(const int store_path_index) return result; } - //set fetch binlog done - if ((result=recovery_init_mark_file(pBasePath, true)) != 0) + logInfo("file: "__FILE__", line: %d, " + "fetch binlog from %s:%d successfully.", __LINE__, + pStorageConn->ip_addr, pStorageConn->port); + + if ((result=storage_disk_recovery_split_trunk_binlog( + store_path_index)) != 0) { + char flagFullFilename[MAX_PATH_SIZE]; + unlink(recovery_get_flag_filename(pBasePath, flagFullFilename)); return result; } - if ((result=storage_disk_recovery_split_trunk_binlog( \ - store_path_index)) != 0) + //set fetch binlog done + if ((result=recovery_init_flag_file(pBasePath, true, 1)) != 0) { - char markFullFilename[MAX_PATH_SIZE]; - unlink(recovery_get_mark_filename(pBasePath, markFullFilename)); return result; } return 0; } - diff --git a/storage/storage_disk_recovery.h b/storage/storage_disk_recovery.h index 9a8a06c..b07ab63 100644 --- a/storage/storage_disk_recovery.h +++ b/storage/storage_disk_recovery.h @@ -18,8 +18,8 @@ extern "C" { #endif -int storage_disk_recovery_start(const int store_path_index); -int storage_disk_recovery_restore(const char *pBasePath); +int storage_disk_recovery_prepare(const int store_path_index); +int storage_disk_recovery_check_restore(const char *pBasePath); #ifdef __cplusplus } diff --git a/storage/storage_func.c b/storage/storage_func.c index 4d9bab2..7d167c6 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -1138,21 +1138,23 @@ static int storage_check_and_make_data_dirs() if (g_sync_old_done && pathCreated) //repair damaged disk { - if ((result=storage_disk_recovery_start(i)) != 0) + if ((result=storage_disk_recovery_prepare(i)) != 0) { return result; } } - result = storage_disk_recovery_restore(g_fdfs_store_paths.paths[i].path); + result = storage_disk_recovery_check_restore( + g_fdfs_store_paths.paths[i].path); if (result == EAGAIN) //need to re-fetch binlog { - if ((result=storage_disk_recovery_start(i)) != 0) + if ((result=storage_disk_recovery_prepare(i)) != 0) { return result; } - result=storage_disk_recovery_restore(g_fdfs_store_paths.paths[i].path); + result = storage_disk_recovery_check_restore( + g_fdfs_store_paths.paths[i].path); } if (result != 0) @@ -1712,6 +1714,18 @@ int storage_func_init(const char *filename, \ break; } + g_disk_recovery_threads = iniGetIntValue(NULL, + "disk_recovery_threads", &iniContext, 1); + if (g_disk_recovery_threads <= 0) + { + logError("file: "__FILE__", line: %d, " + "item \"disk_recovery_threads\" is invalid, " + "value: %d <= 0!", __LINE__, + g_disk_recovery_threads); + result = EINVAL; + break; + } + /* g_disk_rw_direct = iniGetBoolValue(NULL, \ "disk_rw_direct", &iniContext, false); @@ -2127,7 +2141,7 @@ int storage_func_init(const char *filename, \ "max_connections=%d, accept_threads=%d, " \ "work_threads=%d, " \ "disk_rw_separated=%d, disk_reader_threads=%d, " \ - "disk_writer_threads=%d, " \ + "disk_writer_threads=%d, disk_recovery_threads=%d, " \ "buff_size=%d KB, heart_beat_interval=%ds, " \ "stat_report_interval=%ds, tracker_server_count=%d, " \ "sync_wait_msec=%dms, sync_interval=%dms, " \ @@ -2172,7 +2186,7 @@ int storage_func_init(const char *filename, \ g_client_bind_addr, g_max_connections, \ g_accept_threads, g_work_threads, g_disk_rw_separated, \ g_disk_reader_threads, g_disk_writer_threads, \ - g_buff_size / 1024, \ + g_disk_recovery_threads, g_buff_size / 1024, \ g_heart_beat_interval, g_stat_report_interval, \ g_tracker_group.server_count, g_sync_wait_usec / 1000, \ g_sync_interval / 1000, \ diff --git a/storage/storage_global.c b/storage/storage_global.c index f8a1a48..74e2232 100644 --- a/storage/storage_global.c +++ b/storage/storage_global.c @@ -31,6 +31,7 @@ bool g_disk_rw_direct = false; bool g_disk_rw_separated = true; int g_disk_reader_threads = DEFAULT_DISK_READER_THREADS; int g_disk_writer_threads = DEFAULT_DISK_WRITER_THREADS; +int g_disk_recovery_threads = 1; int g_extra_open_file_flags = 0; int g_file_distribute_path_mode = FDFS_FILE_DIST_PATH_ROUND_ROBIN; diff --git a/storage/storage_global.h b/storage/storage_global.h index a1849e4..a79c649 100644 --- a/storage/storage_global.h +++ b/storage/storage_global.h @@ -78,6 +78,7 @@ extern bool g_disk_rw_direct; //if file read / write directly extern bool g_disk_rw_separated; //if disk read / write separated extern int g_disk_reader_threads; //disk reader thread count per store base path extern int g_disk_writer_threads; //disk writer thread count per store base path +extern int g_disk_recovery_threads; //disk recovery thread count extern int g_extra_open_file_flags; //extra open file flags extern int g_file_distribute_path_mode; diff --git a/storage/storage_service.c b/storage/storage_service.c index b5c92a0..a94be37 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -4359,7 +4359,15 @@ static int storage_server_fetch_one_path_binlog_dealer( { break; } - } while(1); + } while (g_continue_flag); + + if (!g_continue_flag) + { + if (result == 0) + { + result = EINTR; + } + } if (result != 0) //error occurs { @@ -4386,6 +4394,7 @@ static int storage_server_fetch_one_path_binlog_dealer( static void fetch_one_path_binlog_finish_clean_up(struct fast_task_info *pTask) { StorageClientInfo *pClientInfo; + StorageFileContext *pFileContext; StorageBinLogReader *pReader; pClientInfo = (StorageClientInfo *)pTask->arg; @@ -4404,6 +4413,12 @@ static void fetch_one_path_binlog_finish_clean_up(struct fast_task_info *pTask) unlink(pReader->mark_filename); } + pFileContext = &(pClientInfo->file_context); + logInfo("file: "__FILE__", line: %d, " + "client ip: %s, fetch binlog of store path #%d done", + __LINE__, pTask->client_ip, pFileContext->extra_info. + upload.trunk_info.path.store_path_index); + storage_reader_destroy(pReader); free(pReader); } @@ -4427,6 +4442,10 @@ static int storage_server_do_fetch_one_path_binlog( return errno != 0 ? errno : ENOMEM; } + logInfo("file: "__FILE__", line: %d, " + "client ip: %s, fetch binlog of store path #%d ...", + __LINE__, pTask->client_ip, store_path_index); + pClientInfo = (StorageClientInfo *)pTask->arg; pFileContext = &(pClientInfo->file_context); diff --git a/storage/storage_sync.c b/storage/storage_sync.c index b1f41a2..08b0645 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -2372,7 +2372,7 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader pReader->last_scan_rows = pReader->scan_row_count; pReader->last_sync_rows = pReader->sync_row_count; - if ((result=storage_open_readable_binlog(pReader, \ + if ((result=storage_open_readable_binlog(pReader, get_binlog_readable_filename, pReader)) != 0) { return result; From 867dc291117e9e51666f8b9b631812ba1284e0bf Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 4 Dec 2019 15:59:31 +0800 Subject: [PATCH 55/95] use fdfs_get_ipaddr_by_peer_ip --- storage/storage_disk_recovery.c | 9 ++++++--- tracker/tracker_service.c | 14 +++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 5f95cbd..a4e73ec 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -23,10 +23,11 @@ #include #include "fdfs_define.h" #include "fastcommon/logger.h" -#include "fdfs_global.h" +#include "fastcommon/fc_list.h" #include "fastcommon/sockopt.h" #include "fastcommon/avl_tree.h" #include "fastcommon/shared_func.h" +#include "fdfs_global.h" #include "tracker_types.h" #include "tracker_proto.h" #include "storage_global.h" @@ -43,11 +44,13 @@ typedef struct { int id; //trunk file id } FDFSTrunkFileIdInfo; -typedef struct { +typedef struct recovery_thread_data { + struct fc_list_head link; int thread_index; //-1 for global int result; bool done; const char *base_path; + pthread_t tid; } RecoveryThreadData; #define RECOVERY_BINLOG_FILENAME ".binlog.recovery" @@ -1321,7 +1324,7 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) if (__sync_fetch_and_add(¤t_recovery_thread_count, 0) > 0) { - for (i=0; i<60; i++) + for (i=0; i<10; i++) { if ((thread_count=__sync_fetch_and_add( ¤t_recovery_thread_count, 0)) == 0) diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 5926344..1f30432 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -413,8 +413,10 @@ static int tracker_check_and_sync(struct fast_task_info *pTask, \ pDestServer->status = pServer->status; memcpy(pDestServer->id, pServer->id, FDFS_STORAGE_ID_MAX_SIZE); - memcpy(pDestServer->ip_addr, FDFS_CURRENT_IP_ADDR(pServer), - IP_ADDRESS_SIZE); + memcpy(pDestServer->ip_addr, + fdfs_get_ipaddr_by_peer_ip(&pServer->ip_addrs, + pTask->client_ip), IP_ADDRESS_SIZE); + int2buff(pClientInfo->pGroup->storage_port, pDestServer->port); } @@ -438,8 +440,9 @@ static int tracker_check_and_sync(struct fast_task_info *pTask, \ pDestServer->status = (*ppServer)->status; memcpy(pDestServer->id, (*ppServer)->id, FDFS_STORAGE_ID_MAX_SIZE); - memcpy(pDestServer->ip_addr, FDFS_CURRENT_IP_ADDR(*ppServer), - IP_ADDRESS_SIZE); + memcpy(pDestServer->ip_addr, + fdfs_get_ipaddr_by_peer_ip(&(*ppServer)->ip_addrs, + pTask->client_ip), IP_ADDRESS_SIZE); int2buff(pClientInfo->pGroup->storage_port, pDestServer->port); pDestServer++; @@ -2400,7 +2403,8 @@ static int tracker_deal_server_list_group_storages(struct fast_task_info *pTask) pStorageStat = &((*ppServer)->stat); pDest->status = (*ppServer)->status; strcpy(pDest->id, (*ppServer)->id); - strcpy(pDest->ip_addr, FDFS_CURRENT_IP_ADDR(*ppServer)); + strcpy(pDest->ip_addr, fdfs_get_ipaddr_by_peer_ip( + &(*ppServer)->ip_addrs, pTask->client_ip)); if ((*ppServer)->psync_src_server != NULL) { strcpy(pDest->src_id, \ From edb3f6bb4d0fc727a1c4245b6a305cea29fc1e6f Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 4 Dec 2019 16:29:08 +0800 Subject: [PATCH 56/95] pthread_kill alive recovery threads --- storage/storage_disk_recovery.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index a4e73ec..2345ef3 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -23,7 +23,6 @@ #include #include "fdfs_define.h" #include "fastcommon/logger.h" -#include "fastcommon/fc_list.h" #include "fastcommon/sockopt.h" #include "fastcommon/avl_tree.h" #include "fastcommon/shared_func.h" @@ -45,9 +44,9 @@ typedef struct { } FDFSTrunkFileIdInfo; typedef struct recovery_thread_data { - struct fc_list_head link; int thread_index; //-1 for global int result; + volatile int alive; bool done; const char *base_path; pthread_t tid; @@ -949,6 +948,8 @@ static void *storage_disk_recovery_restore_entrance(void *arg) ConnectionInfo srcStorage; pThreadData = (RecoveryThreadData *)arg; + pThreadData->tid = pthread_self(); + __sync_add_and_fetch(&pThreadData->alive, 1); __sync_add_and_fetch(¤t_recovery_thread_count, 1); do @@ -979,6 +980,8 @@ static void *storage_disk_recovery_restore_entrance(void *arg) } while (0); __sync_sub_and_fetch(¤t_recovery_thread_count, 1); + __sync_sub_and_fetch(&pThreadData->alive, 1); + sleep(1); return NULL; } @@ -1262,6 +1265,7 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) int thread_count; int bytes; int i; + int k; pthread_t *recovery_tids; void **args; RecoveryThreadData *thread_data; @@ -1305,6 +1309,7 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) thread_data[i].thread_index = i; thread_data[i].result = EINTR; thread_data[i].done = false; + thread_data[i].alive = 0; args[i] = thread_data + i; } @@ -1324,7 +1329,7 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) if (__sync_fetch_and_add(¤t_recovery_thread_count, 0) > 0) { - for (i=0; i<10; i++) + for (i=0; i<30; i++) { if ((thread_count=__sync_fetch_and_add( ¤t_recovery_thread_count, 0)) == 0) @@ -1332,6 +1337,14 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) break; } + for (k=0; k 0) + { + pthread_kill(thread_data[k].tid, SIGINT); + } + } + logInfo("file: "__FILE__", line: %d, " "waiting for recovery threads exit, " "waiting count: %d, current thread count: %d", @@ -1340,6 +1353,7 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) } } + sleep(1); //wait for thread exit free(thread_data); free(args); free(recovery_tids); @@ -1351,7 +1365,7 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) while (g_continue_flag) { - if (storage_report_storage_status(g_my_server_id_str, \ + if (storage_report_storage_status(g_my_server_id_str, g_tracker_client_ip.ips[0].address, saved_storage_status) == 0) { From 856ef15ab7c3ccb817a9871156a07d1b11543077 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 4 Dec 2019 20:00:34 +0800 Subject: [PATCH 57/95] fix recovery_get_global_full_filename --- storage/storage_disk_recovery.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 2345ef3..6d334cd 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -104,7 +104,7 @@ static inline char *recovery_get_global_full_filename(const char *pBasePath, const char *filename, char *full_filename) { return recovery_get_full_filename_ex(pBasePath, -1, - RECOVERY_FLAG_FILENAME, full_filename); + filename, full_filename); } static inline char *recovery_get_global_binlog_filename(const char *pBasePath, @@ -1266,6 +1266,7 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) int bytes; int i; int k; + int sig; pthread_t *recovery_tids; void **args; RecoveryThreadData *thread_data; @@ -1329,7 +1330,10 @@ static int storage_disk_recovery_do_restore(const char *pBasePath) if (__sync_fetch_and_add(¤t_recovery_thread_count, 0) > 0) { - for (i=0; i<30; i++) +#define MAX_WAIT_COUNT 30 + + sig = SIGINT; + for (i=0; i= MAX_WAIT_COUNT / 2) + { + sig = SIGTERM; + } + for (k=0; k 0) { - pthread_kill(thread_data[k].tid, SIGINT); + pthread_kill(thread_data[k].tid, sig); } } From 01041705ba998b53cfde1ed8d6d6230e143a7bfe Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 4 Dec 2019 20:19:43 +0800 Subject: [PATCH 58/95] calc hash use src_filename when it not empty --- storage/storage_disk_recovery.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 6d334cd..9163f5d 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -1071,6 +1071,7 @@ static int do_dispatch_binlog_for_threads(const char *pBasePath) RecoveryDispatchInfo *dispatchs; RecoveryDispatchInfo *disp; int64_t total_count; + int hash_code; int bytes; int result; int i; @@ -1128,8 +1129,18 @@ static int do_dispatch_binlog_for_threads(const char *pBasePath) break; } - disp = dispatchs + (unsigned int)(Time33Hash(record.filename, - record.filename_len)) % g_disk_recovery_threads; + if (record.src_filename_len > 0) + { + hash_code = Time33Hash(record.src_filename, + record.src_filename_len); + } + else + { + hash_code = Time33Hash(record.filename, + record.filename_len); + } + disp = dispatchs + ((unsigned int)hash_code) % + g_disk_recovery_threads; if ((result=disk_recovery_write_to_binlog(disp->fp, disp->temp_filename, &record)) != 0) { From 22824e5f07fee2a26cf67bc475e38a24eeaa0527 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 4 Dec 2019 22:59:24 +0800 Subject: [PATCH 59/95] bugfix: init pReader->binlog_buff.version/length to 0 --- HISTORY | 1 + storage/storage_sync.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY b/HISTORY index 16f2f0b..6cd7a18 100644 --- a/HISTORY +++ b/HISTORY @@ -4,6 +4,7 @@ Version 6.04 2019-12-04 * use get_gzip_command_filename from libfastcommon v1.42 * support compress error log and access log * disk recovery support multi-threads to speed up + * bugfix: init pReader->binlog_buff.version/length to 0 NOTE: you MUST upgrade libfastcommon to V1.42 or later diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 08b0645..9d6103d 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -2207,6 +2207,8 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader pReader->sync_row_count = 0; pReader->last_file_exist = 0; pReader->binlog_fd = -1; + pReader->binlog_buff.version = 0; + pReader->binlog_buff.length = 0; pReader->binlog_buff.buffer = (char *)malloc( \ STORAGE_BINLOG_BUFFER_SIZE); @@ -2477,7 +2479,7 @@ static int storage_binlog_preread(StorageBinLogReader *pReader) int bytes_read; int saved_binlog_write_version; - if (pReader->binlog_buff.version == binlog_write_version && \ + if (pReader->binlog_buff.version == binlog_write_version && pReader->binlog_buff.length == 0) { return ENOENT; From 6bfb8215ff40db20b4d0e11c641c0701dfea619d Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 5 Dec 2019 08:54:39 +0800 Subject: [PATCH 60/95] upgrade version to 6.04 --- HISTORY | 2 +- INSTALL | 4 ++-- fastdfs.spec | 6 +++--- storage/storage_disk_recovery.c | 21 +++++++++++++-------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/HISTORY b/HISTORY index 6cd7a18..b1e6d12 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.04 2019-12-04 +Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST * use get_gzip_command_filename from libfastcommon v1.42 * support compress error log and access log diff --git a/INSTALL b/INSTALL index 9ef8eb9..840735c 100644 --- a/INSTALL +++ b/INSTALL @@ -11,7 +11,7 @@ Chinese language: http://www.fastken.com/ # command lines as: git clone https://github.com/happyfish100/libfastcommon.git - cd libfastcommon; git checkout V1.0.41 + cd libfastcommon; git checkout V1.0.42 ./make.sh clean && ./make.sh && ./make.sh install @@ -21,7 +21,7 @@ Chinese language: http://www.fastken.com/ # command lines as: git clone https://github.com/happyfish100/fastdfs.git - cd fastdfs; git checkout V6.03 + cd fastdfs; git checkout V6.04 ./make.sh clean && ./make.sh && ./make.sh install diff --git a/fastdfs.spec b/fastdfs.spec index 86f8259..1943e16 100644 --- a/fastdfs.spec +++ b/fastdfs.spec @@ -3,7 +3,7 @@ %define FDFSClient libfdfsclient %define FDFSClientDevel libfdfsclient-devel %define FDFSTool fastdfs-tool -%define FDFSVersion 6.0.3 +%define FDFSVersion 6.0.4 %define CommitVersion %(echo $COMMIT_VERSION) Name: %{FastDFS} @@ -18,14 +18,14 @@ Source: http://perso.orange.fr/sebastien.godard/%{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: %__cp %__mv %__chmod %__grep %__mkdir %__install %__id -BuildRequires: libfastcommon-devel >= 1.0.41 +BuildRequires: libfastcommon-devel >= 1.0.42 %description This package provides tracker & storage of fastdfs commit version: %{CommitVersion} %package -n %{FDFSServer} -Requires: libfastcommon >= 1.0.41 +Requires: libfastcommon >= 1.0.42 Summary: fastdfs tracker & storage %package -n %{FDFSTool} diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 9163f5d..0e3dbb0 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -1091,7 +1091,8 @@ static int do_dispatch_binlog_for_threads(const char *pBasePath) for (i=0; i total lines: %"PRId64"%s", - __LINE__, total_count, log_buff.str); + if (result == 0) + { + logInfo("file: "__FILE__", line: %d, " + "dispatch stats => record count: %"PRId64"%s", + __LINE__, total_count, log_buff.str); + } return result; } From a424a06cf3810dce6a4f42d90623b50d5af6c6a5 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 5 Dec 2019 09:51:36 +0800 Subject: [PATCH 61/95] should use memset to init pReader --- HISTORY | 3 ++- storage/storage_sync.c | 23 +++++++---------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/HISTORY b/HISTORY index b1e6d12..65c41d3 100644 --- a/HISTORY +++ b/HISTORY @@ -4,7 +4,8 @@ Version 6.04 2019-12-05 * use get_gzip_command_filename from libfastcommon v1.42 * support compress error log and access log * disk recovery support multi-threads to speed up - * bugfix: init pReader->binlog_buff.version/length to 0 + * bugfix: should use memset to init pReader in function + storage_reader_init, this bug is caused by v6.01 NOTE: you MUST upgrade libfastcommon to V1.42 or later diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 9d6103d..4037fd5 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -2198,26 +2198,17 @@ int storage_reader_init(FDFSStorageBrief *pStorage, StorageBinLogReader *pReader bool bFileExist; bool bNeedSyncOld; - pReader->binlog_index = 0; - pReader->binlog_offset = 0; - pReader->need_sync_old = 0; - pReader->sync_old_done = 0; - pReader->until_timestamp = 0; - pReader->scan_row_count = 0; - pReader->sync_row_count = 0; - pReader->last_file_exist = 0; - pReader->binlog_fd = -1; - pReader->binlog_buff.version = 0; - pReader->binlog_buff.length = 0; + memset(pReader, 0, sizeof(StorageBinLogReader)); + pReader->binlog_fd = -1; - pReader->binlog_buff.buffer = (char *)malloc( \ + pReader->binlog_buff.buffer = (char *)malloc( STORAGE_BINLOG_BUFFER_SIZE); if (pReader->binlog_buff.buffer == NULL) { - logError("file: "__FILE__", line: %d, " \ - "malloc %d bytes fail, " \ - "errno: %d, error info: %s", \ - __LINE__, STORAGE_BINLOG_BUFFER_SIZE, \ + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail, " + "errno: %d, error info: %s", + __LINE__, STORAGE_BINLOG_BUFFER_SIZE, errno, STRERROR(errno)); return errno != 0 ? errno : ENOMEM; } From 983a21ba518a9af96c72c4bfb4a266bed06116fd Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 5 Dec 2019 12:04:30 +0800 Subject: [PATCH 62/95] remove recovery_init_flag_file_ex --- storage/storage_disk_recovery.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/storage/storage_disk_recovery.c b/storage/storage_disk_recovery.c index 0e3dbb0..372a365 100644 --- a/storage/storage_disk_recovery.c +++ b/storage/storage_disk_recovery.c @@ -495,7 +495,7 @@ static int recovery_init_global_binlog_file(const char *pBasePath) return writeToFile(full_binlog_filename, buff, 0); } -static int recovery_init_flag_file_ex(const char *pBasePath, +static int recovery_init_flag_file(const char *pBasePath, const bool fetch_binlog_done, const int recovery_threads) { char full_filename[MAX_PATH_SIZE]; @@ -505,13 +505,6 @@ static int recovery_init_flag_file_ex(const char *pBasePath, fetch_binlog_done, recovery_threads); } -static inline int recovery_init_flag_file(const char *pBasePath, - const bool fetch_binlog_done, const int recovery_threads) -{ - return recovery_init_flag_file_ex(pBasePath, - fetch_binlog_done, recovery_threads); -} - static int recovery_load_params_from_flag_file(const char *full_flag_filename) { IniContext iniContext; @@ -1017,7 +1010,7 @@ static int storage_disk_recovery_old_version_migrate(const char *pBasePath) return result; } - if ((result=recovery_init_flag_file_ex(pBasePath, true, 1)) != 0) + if ((result=recovery_init_flag_file(pBasePath, true, 1)) != 0) { return result; } From e0d3d44f64905794bf51af1a6e09649b1a453c1e Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 5 Dec 2019 12:13:53 +0800 Subject: [PATCH 63/95] sigQuitHandler: tcp_set_try_again_when_interrupt to false --- storage/fdfs_storaged.c | 1 + tracker/fdfs_trackerd.c | 1 + 2 files changed, 2 insertions(+) diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 874809f..1e69752 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -330,6 +330,7 @@ static void sigQuitHandler(int sig) { if (!bTerminateFlag) { + tcp_set_try_again_when_interrupt(false); set_timer(1, 1, sigAlarmHandler); bTerminateFlag = true; diff --git a/tracker/fdfs_trackerd.c b/tracker/fdfs_trackerd.c index 3985169..1d40141 100644 --- a/tracker/fdfs_trackerd.c +++ b/tracker/fdfs_trackerd.c @@ -441,6 +441,7 @@ static void sigQuitHandler(int sig) { if (!bTerminateFlag) { + tcp_set_try_again_when_interrupt(false); set_timer(1, 1, sigAlarmHandler); bTerminateFlag = true; From 322ee15cbe6f46185de68b59085e41b542ea7901 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 6 Dec 2019 08:52:08 +0800 Subject: [PATCH 64/95] beautify config files --- conf/client.conf | 17 +++---- conf/http.conf | 10 ++--- conf/storage.conf | 101 +++++++++++++++++++++--------------------- conf/storage_ids.conf | 8 +++- conf/tracker.conf | 46 +++++++++---------- 5 files changed, 94 insertions(+), 88 deletions(-) diff --git a/conf/client.conf b/conf/client.conf index c1173ec..cdb84c8 100644 --- a/conf/client.conf +++ b/conf/client.conf @@ -1,13 +1,14 @@ # connect timeout in seconds # default value is 30s -connect_timeout=10 +# Note: in the intranet network (LAN), 2 seconds is enough. +connect_timeout = 5 # network timeout in seconds # default value is 30s -network_timeout=60 +network_timeout = 60 # the base path to store log files -base_path=/home/yuqing/fastdfs +base_path = /home/yuqing/fastdfs # tracker_server can ocur more than once for multi tracker servers. # the value format of tracker_server is "HOST:PORT", @@ -18,8 +19,8 @@ base_path=/home/yuqing/fastdfs # for example: 192.168.2.100,122.244.141.46:22122 # another eg.: 192.168.1.10,172.17.4.21:22122 -tracker_server=192.168.0.196:22122 -tracker_server=192.168.0.197:22122 +tracker_server = 192.168.0.196:22122 +tracker_server = 192.168.0.197:22122 #standard log level as syslog, case insensitive, value list: ### emerg for emergency @@ -30,7 +31,7 @@ tracker_server=192.168.0.197:22122 ### notice ### info ### debug -log_level=info +log_level = info # if use connection pool # default value is false @@ -46,7 +47,7 @@ connection_pool_max_idle_time = 3600 # if load FastDFS parameters from tracker server # since V4.05 # default value is false -load_fdfs_parameters_from_tracker=false +load_fdfs_parameters_from_tracker = false # if use storage ID instead of IP address # same as tracker.conf @@ -63,7 +64,7 @@ storage_ids_filename = storage_ids.conf #HTTP settings -http.tracker_server_port=80 +http.tracker_server_port = 80 #use "#include" directive to include HTTP other settiongs ##include http.conf diff --git a/conf/http.conf b/conf/http.conf index 4d055f9..26603dd 100644 --- a/conf/http.conf +++ b/conf/http.conf @@ -5,24 +5,24 @@ http.default_content_type = application/octet-stream # MIME types file format: MIME_type extensions # such as: image/jpeg jpeg jpg jpe # you can use apache's MIME file: mime.types -http.mime_types_filename=mime.types +http.mime_types_filename = mime.types # if use token to anti-steal # default value is false (0) -http.anti_steal.check_token=false +http.anti_steal.check_token = false # token TTL (time to live), seconds # default value is 600 -http.anti_steal.token_ttl=900 +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 +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 +http.anti_steal.token_check_fail = /home/yuqing/fastdfs/conf/anti-steal.jpg # if support multi regions for HTTP Range # default value is true diff --git a/conf/storage.conf b/conf/storage.conf index c27059f..1d864eb 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -1,56 +1,57 @@ # is this config file disabled # false for enabled # true for disabled -disabled=false +disabled = false # the name of the group this storage server belongs to # # comment or remove this item for fetching from tracker server, # in this case, use_storage_id must set to true in tracker.conf, # and storage_ids.conf must be configured correctly. -group_name=group1 +group_name = group1 # bind an address of this host # empty for bind all addresses of this host -bind_addr= +bind_addr = # if bind an address of this host when connect to other servers # (this storage server as a client) # true for binding the address configured by the above parameter: "bind_addr" # false for binding any address of this host -client_bind=true +client_bind = true # the storage server port -port=23000 +port = 23000 # connect timeout in seconds # default value is 30 # Note: in the intranet network (LAN), 2 seconds is enough. -connect_timeout=10 +connect_timeout = 5 # network timeout in seconds for send and recv # default value is 30 -network_timeout=60 +network_timeout = 60 # the heart beat interval in seconds # the storage server send heartbeat to tracker server periodically # default value is 30 -heart_beat_interval=30 +heart_beat_interval = 30 # disk usage report interval in seconds # the storage server send disk usage report to tracker server periodically # default value is 300 -stat_report_interval=60 +stat_report_interval = 60 # the base path to store data and log files # NOTE: the binlog files maybe are large, make sure -# the base path has enough disk space -base_path=/home/yuqing/fastdfs +# the base path has enough disk space, +# eg. the disk free space should > 50GB +base_path = /home/yuqing/fastdfs # max concurrent connections the server supported # you should set this parameter larger, eg. 10240 # default value is 256 -max_connections=1024 +max_connections = 1024 # the buff size to recv / send data from/to network # this parameter must more than 8KB @@ -62,13 +63,13 @@ buff_size = 256KB # accept thread count # default value is 1 # since V4.07 -accept_threads=1 +accept_threads = 1 # work thread count # work thread deal network io # default value is 4 # since V2.00 -work_threads=4 +work_threads = 4 # if disk read / write separated ## false for mixed read and write @@ -91,23 +92,23 @@ disk_writer_threads = 1 # when no entry to sync, try read binlog again after X milliseconds # must > 0, default value is 200ms -sync_wait_msec=50 +sync_wait_msec = 50 # after sync a file, usleep milliseconds # 0 for sync successively (never call usleep) -sync_interval=0 +sync_interval = 0 # storage sync start time of a day, time format: Hour:Minute # Hour from 0 to 23, Minute from 0 to 59 -sync_start_time=00:00 +sync_start_time = 00:00 # storage sync end time of a day, time format: Hour:Minute # Hour from 0 to 23, Minute from 0 to 59 -sync_end_time=23:59 +sync_end_time = 23:59 # write to the mark file after sync N files # default value is 500 -write_mark_file_freq=500 +write_mark_file_freq = 500 # disk recovery thread count # default value is 1 @@ -117,7 +118,7 @@ disk_recovery_threads = 1 # store path (disk or mount point) count, default value is 1 store_path_count = 1 -# store_path#, based on 0, to configure the store paths to store file +# store_path#, based on 0, to configure the store paths to store files # if store_path0 not exists, it's value is base_path (NOT recommended) # the paths must be exist # @@ -125,12 +126,12 @@ store_path_count = 1 # the store paths' order is very important, don't mess up!!! # the base_path should be independent (different) of the store paths -store_path0=/home/yuqing/fastdfs -#store_path1=/home/yuqing/fastdfs2 +store_path0 = /home/yuqing/fastdfs +#store_path1 = /home/yuqing/fastdfs2 # subdir_count * subdir_count directories will be auto created under each # store_path (disk), value can be 1 to 256, default value is 256 -subdir_count_per_path=256 +subdir_count_per_path = 256 # tracker_server can ocur more than once for multi tracker servers. # the value format of tracker_server is "HOST:PORT", @@ -141,8 +142,8 @@ subdir_count_per_path=256 # for example: 192.168.2.100,122.244.141.46:22122 # another eg.: 192.168.1.10,172.17.4.21:22122 -tracker_server=192.168.209.121:22122 -tracker_server=192.168.209.122:22122 +tracker_server = 192.168.209.121:22122 +tracker_server = 192.168.209.122:22122 #standard log level as syslog, case insensitive, value list: ### emerg for emergency @@ -153,15 +154,15 @@ tracker_server=192.168.209.122:22122 ### notice ### info ### debug -log_level=info +log_level = info #unix group name to run this program, #not set (empty) means run by the group of current user -run_by_group= +run_by_group = #unix username to run this program, #not set (empty) means run by current user -run_by_user= +run_by_user = # allow_hosts can ocur more than once, host can be hostname or ip address, # "*" (only one asterisk) means match all ip addresses @@ -171,71 +172,71 @@ run_by_user= # allow_hosts=10.0.1.[1-15,20] # allow_hosts=host[01-08,20-25].domain.com # allow_hosts=192.168.5.64/26 -allow_hosts=* +allow_hosts = * # the mode of the files distributed to the data path # 0: round robin(default) # 1: random, distributted by hash code -file_distribute_path_mode=0 +file_distribute_path_mode = 0 # valid when file_distribute_to_path is set to 0 (round robin). # when the written file count reaches this number, then rotate to next path. # rotate to the first path (00/00) after the last path (such as FF/FF). # default value is 100 -file_distribute_rotate_count=100 +file_distribute_rotate_count = 100 # call fsync to disk when write big file # 0: never call fsync # other: call fsync when written bytes >= this bytes # default value is 0 (never call fsync) -fsync_after_written_bytes=0 +fsync_after_written_bytes = 0 # sync log buff to disk every interval seconds # must > 0, default value is 10 seconds -sync_log_buff_interval=1 +sync_log_buff_interval = 1 # sync binlog buff / cache to disk every interval seconds # default value is 60 seconds -sync_binlog_buff_interval=1 +sync_binlog_buff_interval = 1 # sync storage stat info to disk every interval seconds # default value is 300 seconds -sync_stat_file_interval=300 +sync_stat_file_interval = 300 # thread stack size, should >= 512KB # default value is 512KB -thread_stack_size=512KB +thread_stack_size = 512KB # the priority as a source server for uploading file. # the lower this value, the higher its uploading priority. # default value is 10 -upload_priority=10 +upload_priority = 10 # the NIC alias prefix, such as eth in Linux, you can see it by ifconfig -a # multi aliases split by comma. empty value means auto set by OS type # default values is empty -if_alias_prefix= +if_alias_prefix = # if check file duplicate, when set to true, use FastDHT to store file indexes # 1 or yes: need check # 0 or no: do not check # default value is 0 -check_file_duplicate=0 +check_file_duplicate = 0 # file signature method for check file duplicate ## hash: four 32 bits hash code ## md5: MD5 signature # default value is hash # since V4.01 -file_signature_method=hash +file_signature_method = hash # namespace for storing file indexes (key-value pairs) # this item must be set when check_file_duplicate is true / on -key_namespace=FastDFS +key_namespace = FastDFS # set keep_alive to 1 to enable persistent connection with FastDHT servers # default value is 0 (short connection) -keep_alive=0 +keep_alive = 0 # you can use "#include filename" (not include double quotes) directive to # load FastDHT server list, when the filename is a relative path such as @@ -258,7 +259,7 @@ rotate_access_log = false # Hour from 0 to 23, Minute from 0 to 59 # default value is 00:00 # since V4.00 -access_log_rotate_time=00:00 +access_log_rotate_time = 00:00 # if compress the old access log by gzip # default value is false @@ -268,7 +269,7 @@ compress_old_access_log = false # compress the access log days before # default value is 1 # since V6.04 -compress_access_log_days_before = 1 +compress_access_log_days_before = 7 # if rotate the error log every day # default value is false @@ -279,7 +280,7 @@ rotate_error_log = false # Hour from 0 to 23, Minute from 0 to 59 # default value is 00:00 # since V4.02 -error_log_rotate_time=00:00 +error_log_rotate_time = 00:00 # if compress the old error log by gzip # default value is false @@ -289,7 +290,7 @@ compress_old_error_log = false # compress the error log days before # default value is 1 # since V6.04 -compress_error_log_days_before = 1 +compress_error_log_days_before = 7 # rotate access log when the log file exceeds this size # 0 means never rotates log file by log file size @@ -311,7 +312,7 @@ log_file_keep_days = 0 # if skip the invalid record when sync file # default value is false # since V4.02 -file_sync_skip_invalid_record=false +file_sync_skip_invalid_record = false # if use connection pool # default value is false @@ -333,7 +334,7 @@ compress_binlog = true # Hour from 0 to 23, Minute from 0 to 59 # default value is 01:30 # since V6.01 -compress_binlog_time=01:30 +compress_binlog_time = 01:30 # if check the mark of store path to prevent confusion # recommend to set this parameter to true @@ -345,8 +346,8 @@ check_store_path_mark = true # use the ip address of this storage server if domain_name is empty, # else this domain name will ocur in the url redirected by the tracker server -http.domain_name= +http.domain_name = # the port of the web server on this storage server -http.server_port=8888 +http.server_port = 8888 diff --git a/conf/storage_ids.conf b/conf/storage_ids.conf index 1075a43..8f6911f 100644 --- a/conf/storage_ids.conf +++ b/conf/storage_ids.conf @@ -1,4 +1,8 @@ # +# +# id is a natural number (1, 2, 3 etc.), +# 6 bits of the id length is enough, such as 100001 +# # storage ip or hostname can be dual IPs seperated by comma, # one is an inner (intranet) IP and another is an outer (extranet) IP, # or two different types of inner (intranet) IPs @@ -8,5 +12,5 @@ # the port is optional. if you run more than one storaged instances # in a server, you must specified the port to distinguish different instances. -# 100001 group1 192.168.0.196 -# 100002 group1 192.168.0.116 +100001 group1 192.168.0.196 +100002 group1 192.168.0.197 diff --git a/conf/tracker.conf b/conf/tracker.conf index f93b6c0..c06fcaf 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -1,41 +1,41 @@ # is this config file disabled # false for enabled # true for disabled -disabled=false +disabled = false # bind an address of this host # empty for bind all addresses of this host -bind_addr= +bind_addr = # the tracker server port -port=22122 +port = 22122 # connect timeout in seconds # default value is 30 # Note: in the intranet network (LAN), 2 seconds is enough. -connect_timeout=10 +connect_timeout = 5 # network timeout in seconds for send and recv # default value is 30 -network_timeout=60 +network_timeout = 60 # the base path to store data and log files -base_path=/home/yuqing/fastdfs +base_path = /home/yuqing/fastdfs # max concurrent connections this server support # you should set this parameter larger, eg. 10240 # default value is 256 -max_connections=1024 +max_connections = 1024 # accept thread count # default value is 1 # since V4.07 -accept_threads=1 +accept_threads = 1 -# work thread count, should <= max_connections +# work thread count # default value is 4 # since V2.00 -work_threads=4 +work_threads = 4 # min buff size # default value 8KB @@ -49,28 +49,28 @@ max_buff_size = 128KB # 0: round robin # 1: specify group # 2: load balance, select the max free space group to upload file -store_lookup=2 +store_lookup = 2 # which group to upload file # when store_lookup set to 1, must set store_group to the group name -store_group=group2 +store_group = group2 # which storage server to upload file # 0: round robin (default) # 1: the first server order by ip address # 2: the first server order by priority (the minimal) # Note: if use_trunk_file set to true, must set store_server to 1 or 2 -store_server=0 +store_server = 0 # which path(means disk or mount point) of the storage server to upload file # 0: round robin # 2: load balance, select the max free space path to upload file -store_path=0 +store_path = 0 # which storage server to download file # 0: round robin (default) # 1: the source storage server which the current file uploaded to -download_server=0 +download_server = 0 # reserved storage space for system or other applications. # if the free(available) space of any stoarge server in @@ -93,7 +93,7 @@ reserved_storage_space = 20% ### notice ### info ### debug -log_level=info +log_level = info #unix group name to run this program, #not set (empty) means run by the group of current user @@ -101,7 +101,7 @@ run_by_group= #unix username to run this program, #not set (empty) means run by current user -run_by_user= +run_by_user = # allow_hosts can ocur more than once, host can be hostname or ip address, # "*" (only one asterisk) means match all ip addresses @@ -111,7 +111,7 @@ run_by_user= # allow_hosts=10.0.1.[1-15,20] # allow_hosts=host[01-08,20-25].domain.com # allow_hosts=192.168.5.64/26 -allow_hosts=* +allow_hosts = * # sync log buff to disk every interval seconds # default value is 10 seconds @@ -250,7 +250,7 @@ compress_old_error_log = false # compress the error log days before # default value is 1 # since V6.04 -compress_error_log_days_before = 1 +compress_error_log_days_before = 7 # rotate error log when the log file exceeds this size # 0 means never rotates log file by log file size @@ -275,21 +275,21 @@ use_connection_pool = true connection_pool_max_idle_time = 3600 # HTTP port on this tracker server -http.server_port=8080 +http.server_port = 8080 # check storage HTTP server alive interval seconds # <= 0 for never check # default value is 30 -http.check_alive_interval=30 +http.check_alive_interval = 30 # check storage HTTP server alive type, values are: # tcp : connect to the storge server with HTTP port only, # do not request and get response # http: storage check alive url must return http status 200 # default value is tcp -http.check_alive_type=tcp +http.check_alive_type = tcp # check storage HTTP server alive uri/url # NOTE: storage embed HTTP server support uri: /status.html -http.check_alive_uri=/status.html +http.check_alive_uri = /status.html From 24bb1e97b54000d8cf07bf6ee164269379d1b491 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 8 Dec 2019 10:17:16 +0800 Subject: [PATCH 65/95] change config files --- conf/storage.conf | 10 +++++----- conf/tracker.conf | 27 +++++++++++++-------------- storage/fdfs_storaged.c | 2 +- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/conf/storage.conf b/conf/storage.conf index 1d864eb..8ce66fd 100644 --- a/conf/storage.conf +++ b/conf/storage.conf @@ -48,7 +48,7 @@ stat_report_interval = 60 # eg. the disk free space should > 50GB base_path = /home/yuqing/fastdfs -# max concurrent connections the server supported +# max concurrent connections the server supported, # you should set this parameter larger, eg. 10240 # default value is 256 max_connections = 1024 @@ -61,12 +61,12 @@ max_connections = 1024 buff_size = 256KB # accept thread count -# default value is 1 +# default value is 1 which is recommended # since V4.07 accept_threads = 1 # work thread count -# work thread deal network io +# work threads to deal network io # default value is 4 # since V2.00 work_threads = 4 @@ -113,14 +113,14 @@ write_mark_file_freq = 500 # disk recovery thread count # default value is 1 # since V6.04 -disk_recovery_threads = 1 +disk_recovery_threads = 3 # store path (disk or mount point) count, default value is 1 store_path_count = 1 # store_path#, based on 0, to configure the store paths to store files # if store_path0 not exists, it's value is base_path (NOT recommended) -# the paths must be exist +# the paths must be exist. # # IMPORTANT NOTE: # the store paths' order is very important, don't mess up!!! diff --git a/conf/tracker.conf b/conf/tracker.conf index c06fcaf..a92e0aa 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -28,24 +28,25 @@ base_path = /home/yuqing/fastdfs max_connections = 1024 # accept thread count -# default value is 1 +# default value is 1 which is recommended # since V4.07 accept_threads = 1 # work thread count +# work threads to deal network io # default value is 4 # since V2.00 work_threads = 4 -# min buff size +# the min network buff size # default value 8KB min_buff_size = 8KB -# max buff size +# the max network buff size # default value 128KB max_buff_size = 128KB -# the method of selecting group to upload files +# the method for selecting group to upload files # 0: round robin # 1: specify group # 2: load balance, select the max free space group to upload file @@ -62,7 +63,7 @@ store_group = group2 # Note: if use_trunk_file set to true, must set store_server to 1 or 2 store_server = 0 -# which path(means disk or mount point) of the storage server to upload file +# which path (means disk or mount point) of the storage server to upload file # 0: round robin # 2: load balance, select the max free space path to upload file store_path = 0 @@ -74,14 +75,13 @@ download_server = 0 # reserved storage space for system or other applications. # if the free(available) space of any stoarge server in -# a group <= reserved_storage_space, -# no file can be uploaded to this group. +# a group <= reserved_storage_space, no file can be uploaded to this group. # bytes unit can be one of follows: ### G or g for gigabyte(GB) ### M or m for megabyte(MB) ### K or k for kilobyte(KB) ### no unit for byte(B) -### XX.XX% as ratio such as reserved_storage_space = 10% +### XX.XX% as ratio such as: reserved_storage_space = 10% reserved_storage_space = 20% #standard log level as syslog, case insensitive, value list: @@ -152,7 +152,7 @@ slot_min_size = 256 # store the upload file to trunk file when it's size <= this value # default value is 16MB # since V3.00 -slot_max_size = 16MB +slot_max_size = 4MB # the trunk file size, should >= 4MB # default value is 64MB @@ -176,8 +176,8 @@ trunk_create_file_time_base = 02:00 trunk_create_file_interval = 86400 # the threshold to create trunk file -# when the free trunk file size less than the threshold, will create -# the trunk files +# when the free trunk file size less than the threshold, +# will create he trunk files # default value is 0 # since V3.06 trunk_create_file_space_threshold = 20G @@ -202,13 +202,12 @@ trunk_init_reload_from_binlog = false # recommand to set this parameter to 86400 (one day) # default value is 0, 0 means never compress # since V5.01 -trunk_compress_binlog_min_interval = 0 +trunk_compress_binlog_min_interval = 86400 # if use storage server ID instead of IP address # if you want to use dual IPs for storage server, you MUST set # this parameter to true, and configure the dual IPs in the file -# configured by following item "storage_ids_filename", -# such as storage_ids.conf +# configured by following item "storage_ids_filename", such as storage_ids.conf # default value is false # since V4.00 use_storage_id = false diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 1e69752..69326c8 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -301,7 +301,7 @@ int main(int argc, char *argv[]) */ usleep(10000); - if (++wait_count > 6000) + if (++wait_count > 9000) { logWarning("waiting timeout, exit!"); break; From a49735ae5a672336c5ee902b84b6fc3b7d251f4e Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 13 Dec 2019 10:59:07 +0800 Subject: [PATCH 66/95] fdfs_trackerd and fdfs_storaged print the server version in usage --- HISTORY | 5 +++++ client/storage_client.c | 2 +- storage/fdfs_storaged.c | 14 ++++++++++++-- tracker/fdfs_trackerd.c | 14 ++++++++++++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/HISTORY b/HISTORY index 65c41d3..5592bab 100644 --- a/HISTORY +++ b/HISTORY @@ -1,4 +1,9 @@ +Version 6.05 2019-12-13 + * fdfs_trackerd and fdfs_storaged print the server version in usage. + you can execute fdfs_trackerd or fdfs_storaged without parameters + to show the server version + Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST * use get_gzip_command_filename from libfastcommon v1.42 diff --git a/client/storage_client.c b/client/storage_client.c index 576990b..18d528e 100644 --- a/client/storage_client.c +++ b/client/storage_client.c @@ -1808,7 +1808,7 @@ int storage_do_append_file(ConnectionInfo *pTrackerServer, \ } /** -STORAGE_PROTO_CMD_APPEND_FILE: +STORAGE_PROTO_CMD_MODIFY_FILE: 8 bytes: appender filename length 8 bytes: file offset 8 bytes: file size diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 69326c8..376b4b1 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -77,8 +77,10 @@ static void sigDumpHandler(int sig); static void usage(const char *program) { - fprintf(stderr, "Usage: %s [start | stop | restart]\n", - program); + fprintf(stderr, "FastDFS server v%d.%02d\n" + "Usage: %s [start | stop | restart]\n", + g_fdfs_version.major, g_fdfs_version.minor, + program); } int main(int argc, char *argv[]) @@ -104,6 +106,14 @@ int main(int argc, char *argv[]) trunk_shared_init(); conf_filename = argv[1]; + if (!fileExists(conf_filename)) + { + if (starts_with(conf_filename, "-")) + { + usage(argv[0]); + return 0; + } + } if ((result=get_base_path_from_conf_file(conf_filename, g_fdfs_base_path, sizeof(g_fdfs_base_path))) != 0) { diff --git a/tracker/fdfs_trackerd.c b/tracker/fdfs_trackerd.c index 1d40141..da914e7 100644 --- a/tracker/fdfs_trackerd.c +++ b/tracker/fdfs_trackerd.c @@ -70,8 +70,10 @@ static void sigDumpHandler(int sig); static void usage(const char *program) { - fprintf(stderr, "Usage: %s [start | stop | restart]\n", - program); + fprintf(stderr, "FastDFS server v%d.%02d\n" + "Usage: %s [start | stop | restart]\n", + g_fdfs_version.major, g_fdfs_version.minor, + program); } int main(int argc, char *argv[]) @@ -100,6 +102,14 @@ int main(int argc, char *argv[]) log_init2(); conf_filename = argv[1]; + if (!fileExists(conf_filename)) + { + if (starts_with(conf_filename, "-")) + { + usage(argv[0]); + return 0; + } + } if ((result=get_base_path_from_conf_file(conf_filename, g_fdfs_base_path, sizeof(g_fdfs_base_path))) != 0) { From cf0ec7e4cf3f4c26791512582c13c06d3556b22c Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 14 Dec 2019 21:03:35 +0800 Subject: [PATCH 67/95] trunk server support compress the trunk binlog periodically --- HISTORY | 5 +- conf/tracker.conf | 17 ++- storage/storage_func.c | 15 +- storage/storage_param_getter.c | 19 ++- storage/storage_sync.c | 9 +- storage/tracker_client_thread.c | 148 ++++++++++--------- storage/trunk_mgr/trunk_mem.c | 143 +++++++++++++++--- storage/trunk_mgr/trunk_mem.h | 5 + storage/trunk_mgr/trunk_sync.c | 247 ++++++++++++++++++++------------ storage/trunk_mgr/trunk_sync.h | 1 + tracker/tracker_func.c | 18 ++- tracker/tracker_global.c | 2 + tracker/tracker_global.h | 2 + tracker/tracker_service.c | 5 + 14 files changed, 438 insertions(+), 198 deletions(-) diff --git a/HISTORY b/HISTORY index 5592bab..05c1522 100644 --- a/HISTORY +++ b/HISTORY @@ -1,8 +1,11 @@ -Version 6.05 2019-12-13 +Version 6.05 2019-12-14 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version + * trunk server support compress the trunk binlog periodically, + config items in tracker.conf: trunk_compress_binlog_interval + and trunk_compress_binlog_time_base Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST diff --git a/conf/tracker.conf b/conf/tracker.conf index a92e0aa..adccc65 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -197,13 +197,26 @@ trunk_init_check_occupying = false trunk_init_reload_from_binlog = false # the min interval for compressing the trunk binlog file -# unit: second +# unit: second, 0 means never compress # FastDFS compress the trunk binlog when trunk init and trunk destroy # recommand to set this parameter to 86400 (one day) -# default value is 0, 0 means never compress +# default value is 0 # since V5.01 trunk_compress_binlog_min_interval = 86400 +# the interval for compressing the trunk binlog file +# unit: second, 0 means never compress +# recommand to set this parameter to 86400 (one day) +# default value is 0 +# since V6.05 +trunk_compress_binlog_interval = 86400 + +# compress the trunk binlog time base, time format: Hour:Minute +# Hour from 0 to 23, Minute from 0 to 59 +# default value is 03:00 +# since V6.05 +trunk_compress_binlog_time_base = 03:00 + # if use storage server ID instead of IP address # if you want to use dual IPs for storage server, you MUST set # this parameter to true, and configure the dual IPs in the file diff --git a/storage/storage_func.c b/storage/storage_func.c index 7d167c6..44acd99 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -73,6 +73,8 @@ typedef struct #define INIT_ITEM_LAST_HTTP_PORT "last_http_port" #define INIT_ITEM_CURRENT_TRUNK_FILE_ID "current_trunk_file_id" #define INIT_ITEM_TRUNK_LAST_COMPRESS_TIME "trunk_last_compress_time" +#define INIT_ITEM_TRUNK_BINLOG_COMPRESS_IN_PROGRESS \ + "trunk_binlog_compress_in_progress" #define INIT_ITEM_STORE_PATH_MARK_PREFIX "store_path_mark" #define STAT_ITEM_TOTAL_UPLOAD "total_upload_count" @@ -658,6 +660,7 @@ int storage_write_to_sync_ini_file() "%s=%d\n" "%s=%d\n" "%s=%d\n" + "%s=%d\n" "%s=%d\n", INIT_ITEM_STORAGE_JOIN_TIME, g_storage_join_time, INIT_ITEM_SYNC_OLD_DONE, g_sync_old_done, @@ -667,10 +670,11 @@ int storage_write_to_sync_ini_file() INIT_ITEM_LAST_SERVER_PORT, g_last_server_port, INIT_ITEM_LAST_HTTP_PORT, g_last_http_port, INIT_ITEM_CURRENT_TRUNK_FILE_ID, g_current_trunk_file_id, - INIT_ITEM_TRUNK_LAST_COMPRESS_TIME, (int)g_trunk_last_compress_time + INIT_ITEM_TRUNK_LAST_COMPRESS_TIME, (int)g_trunk_last_compress_time, + INIT_ITEM_TRUNK_BINLOG_COMPRESS_IN_PROGRESS, + g_trunk_binlog_compress_in_progress ); - if (g_check_store_path_mark) { for (i=0; iip_addr, local_ip_addr); */ - if (is_local_host_ip(pStorage->ip_addr)) + if (strcmp(pStorage->id, g_my_server_id_str) == 0 || + is_local_host_ip(pStorage->ip_addr)) { //can't self sync to self logError("file: "__FILE__", line: %d, " \ "ip_addr %s belong to the local host," \ @@ -3263,11 +3264,11 @@ int storage_sync_thread_start(const FDFSStorageBrief *pStorage) return 0; } - if (storage_server_is_myself(pStorage) || \ + if (strcmp(pStorage->id, g_my_server_id_str) == 0 || is_local_host_ip(pStorage->ip_addr)) //can't self sync to self { - logWarning("file: "__FILE__", line: %d, " \ - "storage id: %s is myself, can't start sync thread!", \ + logWarning("file: "__FILE__", line: %d, " + "storage id: %s is myself, can't start sync thread!", __LINE__, pStorage->id); return 0; } diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index a7f4fe3..cd48015 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -37,7 +37,8 @@ #include "trunk_sync.h" #include "storage_param_getter.h" -#define TRUNK_FILE_CREATOR_TASK_ID 88 +#define TRUNK_FILE_CREATOR_TASK_ID 88 +#define TRUNK_BINLOG_COMPRESS_TASK_ID 89 static pthread_mutex_t reporter_thread_lock; @@ -876,9 +877,11 @@ static int tracker_merge_servers(ConnectionInfo *pTrackerServer, FDFS_STORAGE_STATUS_SYNCING)) && \ ((*ppFound)->server.status > pServer->status)) { + pServer->id[FDFS_STORAGE_ID_MAX_SIZE - 1] = '\0'; *(pServer->ip_addr + IP_ADDRESS_SIZE - 1) = '\0'; - if (is_local_host_ip(pServer->ip_addr) && \ - buff2int(pServer->port) == g_server_port) + if ((strcmp(pServer->id, g_my_server_id_str) == 0) || + (is_local_host_ip(pServer->ip_addr) && + buff2int(pServer->port) == g_server_port)) { need_rejoin_tracker = true; logWarning("file: "__FILE__", line: %d, " \ @@ -1204,6 +1207,74 @@ static void set_trunk_server(const char *ip_addr, const int port) } } +static int do_set_trunk_server_myself(ConnectionInfo *pTrackerServer) +{ + int result; + ScheduleArray scheduleArray; + ScheduleEntry entries[2]; + ScheduleEntry *entry; + + tracker_fetch_trunk_fid(pTrackerServer); + g_if_trunker_self = true; + + if ((result=storage_trunk_init()) != 0) + { + return result; + } + + scheduleArray.entries = entries; + entry = entries; + if (g_trunk_create_file_advance && + g_trunk_create_file_interval > 0) + { + INIT_SCHEDULE_ENTRY_EX(*entry, TRUNK_FILE_CREATOR_TASK_ID, + g_trunk_create_file_time_base, + g_trunk_create_file_interval, + trunk_create_trunk_file_advance, NULL); + entry->new_thread = true; + entry++; + } + + if (g_trunk_compress_binlog_interval > 0) + { + INIT_SCHEDULE_ENTRY_EX(*entry, TRUNK_BINLOG_COMPRESS_TASK_ID, + g_trunk_compress_binlog_time_base, + g_trunk_compress_binlog_interval, + trunk_binlog_compress_func, NULL); + entry->new_thread = true; + entry++; + } + + scheduleArray.count = entry - entries; + if (scheduleArray.count > 0) + { + sched_add_entries(&scheduleArray); + } + + trunk_sync_thread_start_all(); + return 0; +} + +static void do_unset_trunk_server_myself(ConnectionInfo *pTrackerServer) +{ + tracker_report_trunk_fid(pTrackerServer); + g_if_trunker_self = false; + + trunk_waiting_sync_thread_exit(); + + storage_trunk_destroy_ex(true); + if (g_trunk_create_file_advance && + g_trunk_create_file_interval > 0) + { + sched_del_entry(TRUNK_FILE_CREATOR_TASK_ID); + } + + if (g_trunk_compress_binlog_interval > 0) + { + sched_del_entry(TRUNK_BINLOG_COMPRESS_TASK_ID); + } +} + static int tracker_check_response(ConnectionInfo *pTrackerServer, const int tracker_index, bool *bServerPortChanged) { @@ -1384,11 +1455,13 @@ static int tracker_check_response(ConnectionInfo *pTrackerServer, { int port; + pBriefServers->id[FDFS_STORAGE_ID_MAX_SIZE - 1] = '\0'; pBriefServers->ip_addr[IP_ADDRESS_SIZE - 1] = '\0'; port = buff2int(pBriefServers->port); set_trunk_server(pBriefServers->ip_addr, port); - if (is_local_host_ip(pBriefServers->ip_addr) && - port == g_server_port) + if ((strcmp(pBriefServers->id, g_my_server_id_str) == 0) || + (is_local_host_ip(pBriefServers->ip_addr) && + port == g_server_port)) { if (g_if_trunker_self) { @@ -1403,32 +1476,10 @@ static int tracker_check_response(ConnectionInfo *pTrackerServer, "I am the the trunk server %s:%d", __LINE__, pBriefServers->ip_addr, port); - tracker_fetch_trunk_fid(pTrackerServer); - g_if_trunker_self = true; - - if ((result=storage_trunk_init()) != 0) - { - return result; - } - - if (g_trunk_create_file_advance && - g_trunk_create_file_interval > 0) - { - ScheduleArray scheduleArray; - ScheduleEntry entries[1]; - - entries[0].id = TRUNK_FILE_CREATOR_TASK_ID; - entries[0].time_base = g_trunk_create_file_time_base; - entries[0].interval = g_trunk_create_file_interval; - entries[0].task_func = trunk_create_trunk_file_advance; - entries[0].func_args = NULL; - - scheduleArray.count = 1; - scheduleArray.entries = entries; - sched_add_entries(&scheduleArray); - } - - trunk_sync_thread_start_all(); + if ((result=do_set_trunk_server_myself(pTrackerServer)) != 0) + { + return result; + } } } else @@ -1440,46 +1491,13 @@ static int tracker_check_response(ConnectionInfo *pTrackerServer, if (g_if_trunker_self) { - int saved_trunk_sync_thread_count; - logWarning("file: "__FILE__", line: %d, " \ "I am the old trunk server, " \ "the new trunk server is %s:%d", \ __LINE__, g_trunk_server.connections[0].ip_addr, \ g_trunk_server.connections[0].port); - tracker_report_trunk_fid(pTrackerServer); - g_if_trunker_self = false; - - saved_trunk_sync_thread_count = \ - g_trunk_sync_thread_count; - if (saved_trunk_sync_thread_count > 0) - { - logInfo("file: "__FILE__", line: %d, "\ - "waiting %d trunk sync " \ - "threads exit ...", __LINE__, \ - saved_trunk_sync_thread_count); - } - - while (g_trunk_sync_thread_count > 0) - { - usleep(50000); - } - - if (saved_trunk_sync_thread_count > 0) - { - logInfo("file: "__FILE__", line: %d, " \ - "%d trunk sync threads exited",\ - __LINE__, \ - saved_trunk_sync_thread_count); - } - - storage_trunk_destroy_ex(true); - if (g_trunk_create_file_advance && \ - g_trunk_create_file_interval > 0) - { - sched_del_entry(TRUNK_FILE_CREATOR_TASK_ID); - } + do_unset_trunk_server_myself(pTrackerServer); } } } diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 1add39d..882a82d 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -52,14 +52,18 @@ int g_avg_storage_reserved_mb = FDFS_DEF_STORAGE_RESERVED_MB; int g_store_path_index = 0; int g_current_trunk_file_id = 0; TimeInfo g_trunk_create_file_time_base = {0, 0}; +TimeInfo g_trunk_compress_binlog_time_base = {0, 0}; int g_trunk_create_file_interval = 86400; int g_trunk_compress_binlog_min_interval = 0; +int g_trunk_compress_binlog_interval = 0; TrackerServerInfo g_trunk_server = {0, 0}; bool g_if_use_trunk_file = false; bool g_if_trunker_self = false; bool g_trunk_create_file_advance = false; bool g_trunk_init_check_occupying = false; bool g_trunk_init_reload_from_binlog = false; +volatile int g_trunk_binlog_compress_in_progress = 0; +volatile int g_trunk_data_save_in_progress = 0; static byte trunk_init_flag = STORAGE_TRUNK_INIT_FLAG_NONE; int64_t g_trunk_total_free_space = 0; int64_t g_trunk_create_file_space_threshold = 0; @@ -387,7 +391,7 @@ static int tree_walk_callback(void *data, void *args) return 0; } -static int storage_trunk_do_save() +static int do_save_trunk_data() { int64_t trunk_binlog_size; char trunk_data_filename[MAX_PATH_SIZE]; @@ -493,40 +497,133 @@ static int storage_trunk_do_save() return result; } -static int storage_trunk_save() +static int storage_trunk_do_save() { int result; + if (__sync_add_and_fetch(&g_trunk_data_save_in_progress, 1) != 1) + { + __sync_sub_and_fetch(&g_trunk_data_save_in_progress, 1); + logError("file: "__FILE__", line: %d, " + "trunk binlog compress already in progress, " + "g_trunk_data_save_in_progress=%d", __LINE__, + g_trunk_data_save_in_progress); + return EINPROGRESS; + } - if (!(g_trunk_compress_binlog_min_interval > 0 && \ + result = do_save_trunk_data(); + __sync_sub_and_fetch(&g_trunk_data_save_in_progress, 1); + + return result; +} + +static int storage_trunk_compress() +{ + int result; + + if (__sync_add_and_fetch(&g_trunk_binlog_compress_in_progress, 1) != 1) + { + __sync_sub_and_fetch(&g_trunk_binlog_compress_in_progress, 1); + logError("file: "__FILE__", line: %d, " + "trunk binlog compress already in progress, " + "g_trunk_binlog_compress_in_progress=%d", + __LINE__, g_trunk_binlog_compress_in_progress); + return EINPROGRESS; + } + + storage_write_to_sync_ini_file(); + + logInfo("file: "__FILE__", line: %d, " + "start compress trunk binlog ...", __LINE__); + do + { + if ((result=trunk_binlog_compress_apply()) != 0) + { + break; + } + + if ((result=storage_trunk_do_save()) != 0) + { + trunk_binlog_compress_rollback(); + break; + } + + if ((result=trunk_binlog_compress_commit()) != 0) + { + trunk_binlog_compress_rollback(); + break; + } + + g_trunk_last_compress_time = g_current_time; + } while (0); + + __sync_sub_and_fetch(&g_trunk_binlog_compress_in_progress, 1); + storage_write_to_sync_ini_file(); + + if (result == 0) + { + logInfo("file: "__FILE__", line: %d, " + "compress trunk binlog successfully.", __LINE__); + return trunk_unlink_all_mark_files(); //because the binlog file be compressed + } + else + { + logError("file: "__FILE__", line: %d, " + "compress trunk binlog fail.", __LINE__); + } + + return result; +} + +static int storage_trunk_save() +{ + if (!(g_trunk_compress_binlog_min_interval > 0 && g_current_time - g_trunk_last_compress_time > g_trunk_compress_binlog_min_interval)) { - return storage_trunk_do_save(); + if (__sync_add_and_fetch(&g_trunk_binlog_compress_in_progress, 0) == 0) + { + return storage_trunk_do_save(); + } + else + { + logWarning("file: "__FILE__", line: %d, " + "trunk binlog compress already in progress, " + "g_trunk_binlog_compress_in_progress=%d", + __LINE__, g_trunk_binlog_compress_in_progress); + return 0; + } } + + return storage_trunk_compress(); +} - logInfo("start compress trunk binlog ..."); - if ((result=trunk_binlog_compress_apply()) != 0) - { - return result; - } +int trunk_binlog_compress_func(void *args) +{ + int result; - if ((result=storage_trunk_do_save()) != 0) - { - trunk_binlog_compress_rollback(); - return result; - } + if (!g_if_trunker_self) + { + return 0; + } - if ((result=trunk_binlog_compress_commit()) != 0) - { - trunk_binlog_compress_rollback(); - return result; - } + result = storage_trunk_compress(); + if (result != 0) + { + return result; + } - g_trunk_last_compress_time = g_current_time; - storage_write_to_sync_ini_file(); + if (!g_if_trunker_self) + { + return 0; + } - logInfo("compress trunk binlog done."); - return trunk_unlink_all_mark_files(); //because the binlog file be compressed + g_if_trunker_self = false; //for sync thread exit + trunk_waiting_sync_thread_exit(); + + g_if_trunker_self = true; //restore to true + trunk_sync_thread_start_all(); + + return 0; } static bool storage_trunk_is_space_occupied(const FDFSTrunkFullInfo *pTrunkInfo) diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index d47de3a..7cd1f85 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -35,13 +35,16 @@ extern int g_avg_storage_reserved_mb; //calc by above var: g_storage_reserved_m extern int g_store_path_index; //store to which path extern int g_current_trunk_file_id; //current trunk file id extern TimeInfo g_trunk_create_file_time_base; +extern TimeInfo g_trunk_compress_binlog_time_base; extern int g_trunk_create_file_interval; extern int g_trunk_compress_binlog_min_interval; +extern int g_trunk_compress_binlog_interval; extern TrackerServerInfo g_trunk_server; //the trunk server extern bool g_if_use_trunk_file; //if use trunk file extern bool g_trunk_create_file_advance; extern bool g_trunk_init_check_occupying; extern bool g_trunk_init_reload_from_binlog; +extern volatile int g_trunk_binlog_compress_in_progress; extern bool g_if_trunker_self; //if am i trunk server extern int64_t g_trunk_create_file_space_threshold; extern int64_t g_trunk_total_free_space; //trunk total free space in bytes @@ -87,6 +90,8 @@ int trunk_file_delete(const char *trunk_filename, \ int trunk_create_trunk_file_advance(void *args); +int trunk_binlog_compress_func(void *args); + int storage_delete_trunk_data_file(); char *storage_trunk_get_data_filename(char *full_filename); diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index ca86522..b08adc2 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -313,29 +313,35 @@ int trunk_binlog_compress_apply() return 0; } - if ((result=trunk_binlog_close_writer(true)) != 0) - { - return result; - } + pthread_mutex_lock(&trunk_sync_thread_lock); - if (rename(binlog_filename, rollback_filename) != 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "rename %s to %s fail, " \ - "errno: %d, error info: %s", - __LINE__, binlog_filename, rollback_filename, - result, STRERROR(result)); - return result; - } + do + { + if ((result=trunk_binlog_close_writer(false)) != 0) + { + break; + } - if ((result=trunk_binlog_open_writer(binlog_filename)) != 0) - { - rename(rollback_filename, binlog_filename); //rollback - return result; - } + if (rename(binlog_filename, rollback_filename) != 0) + { + result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " \ + "rename %s to %s fail, " \ + "errno: %d, error info: %s", + __LINE__, binlog_filename, rollback_filename, + result, STRERROR(result)); + break; + } - return 0; + if ((result=trunk_binlog_open_writer(binlog_filename)) != 0) + { + rename(rollback_filename, binlog_filename); //rollback + break; + } + } while (0); + + pthread_mutex_unlock(&trunk_sync_thread_lock); + return result; } static int trunk_binlog_open_read(const char *filename, @@ -478,46 +484,53 @@ int trunk_binlog_compress_commit() return errno != 0 ? errno : ENOENT; } + pthread_mutex_lock(&trunk_sync_thread_lock); if (need_open_binlog) { - trunk_binlog_close_writer(true); + trunk_binlog_close_writer(false); } - result = trunk_binlog_merge_file(data_fd); - close(data_fd); - if (result != 0) - { - return result; - } - if (unlink(data_filename) != 0) - { - result = errno != 0 ? errno : EPERM; - logError("file: "__FILE__", line: %d, " \ - "unlink %s fail, errno: %d, error info: %s", - __LINE__, data_filename, - result, STRERROR(result)); - return result; - } + do + { + result = trunk_binlog_merge_file(data_fd); + close(data_fd); + if (result != 0) + { + break; + } + if (unlink(data_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + logError("file: "__FILE__", line: %d, " + "unlink %s fail, errno: %d, error info: %s", + __LINE__, data_filename, + result, STRERROR(result)); + break; + } - get_trunk_rollback_filename(rollback_filename); - if (access(rollback_filename, F_OK) == 0) - { - if (unlink(rollback_filename) != 0) - { - result = errno != 0 ? errno : EPERM; - logWarning("file: "__FILE__", line: %d, " \ - "unlink %s fail, errno: %d, error info: %s", - __LINE__, rollback_filename, - result, STRERROR(result)); - } - } + get_trunk_rollback_filename(rollback_filename); + if (access(rollback_filename, F_OK) == 0) + { + if (unlink(rollback_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + logWarning("file: "__FILE__", line: %d, " + "unlink %s fail, errno: %d, error info: %s", + __LINE__, rollback_filename, + result, STRERROR(result)); + break; + } + } - if (need_open_binlog) - { - return trunk_binlog_open_writer(binlog_filename); - } + if (need_open_binlog) + { + result = trunk_binlog_open_writer(binlog_filename); + } + } while (0); - return 0; + pthread_mutex_unlock(&trunk_sync_thread_lock); + + return result; } int trunk_binlog_compress_rollback() @@ -557,7 +570,7 @@ int trunk_binlog_compress_rollback() { return 0; } - logError("file: "__FILE__", line: %d, " \ + logError("file: "__FILE__", line: %d, " "stat file %s fail, errno: %d, error info: %s", __LINE__, rollback_filename, result, STRERROR(result)); @@ -570,38 +583,46 @@ int trunk_binlog_compress_rollback() return 0; } - if ((result=trunk_binlog_close_writer(true)) != 0) - { - return result; - } + pthread_mutex_lock(&trunk_sync_thread_lock); + do + { + if ((result=trunk_binlog_close_writer(false)) != 0) + { + break; + } - if ((rollback_fd=trunk_binlog_open_read(rollback_filename, - false)) < 0) - { - return errno != 0 ? errno : ENOENT; - } + if ((rollback_fd=trunk_binlog_open_read(rollback_filename, + false)) < 0) + { + result = errno != 0 ? errno : ENOENT; + break; + } - result = trunk_binlog_merge_file(rollback_fd); - close(rollback_fd); - if (result == 0) - { - if (unlink(rollback_filename) != 0) - { - result = errno != 0 ? errno : EPERM; - logWarning("file: "__FILE__", line: %d, " \ - "unlink %s fail, " \ - "errno: %d, error info: %s", - __LINE__, rollback_filename, - result, STRERROR(result)); - } + result = trunk_binlog_merge_file(rollback_fd); + close(rollback_fd); + if (result == 0) + { + if (unlink(rollback_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + logWarning("file: "__FILE__", line: %d, " \ + "unlink %s fail, " \ + "errno: %d, error info: %s", + __LINE__, rollback_filename, + result, STRERROR(result)); + break; + } - return trunk_binlog_open_writer(binlog_filename); - } - else - { - trunk_binlog_open_writer(binlog_filename); - return result; - } + result = trunk_binlog_open_writer(binlog_filename); + } + else + { + result = trunk_binlog_open_writer(binlog_filename); + } + } while (0); + + pthread_mutex_unlock(&trunk_sync_thread_lock); + return result; } static int trunk_binlog_fsync_ex(const bool bNeedLock, \ @@ -1221,23 +1242,23 @@ int trunk_unlink_mark_file(const char *storage_id) t = g_current_time; localtime_r(&t, &tm); - trunk_get_mark_filename_by_id(storage_id, old_filename, \ + trunk_get_mark_filename_by_id(storage_id, old_filename, sizeof(old_filename)); if (!fileExists(old_filename)) { return ENOENT; } - snprintf(new_filename, sizeof(new_filename), \ - "%s.%04d%02d%02d%02d%02d%02d", old_filename, \ - tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, \ + snprintf(new_filename, sizeof(new_filename), + "%s.%04d%02d%02d%02d%02d%02d", old_filename, + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); if (rename(old_filename, new_filename) != 0) { - logError("file: "__FILE__", line: %d, " \ - "rename file %s to %s fail" \ - ", errno: %d, error info: %s", \ - __LINE__, old_filename, new_filename, \ + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", + __LINE__, old_filename, new_filename, errno, STRERROR(errno)); return errno != 0 ? errno : EACCES; } @@ -1470,7 +1491,8 @@ static void* trunk_sync_thread_entrance(void* arg) __LINE__, pStorage->ip_addr, local_ip_addr); */ - if (is_local_host_ip(pStorage->ip_addr)) + if ((strcmp(pStorage->id, g_my_server_id_str) == 0) || + is_local_host_ip(pStorage->ip_addr)) { //can't self sync to self logError("file: "__FILE__", line: %d, " \ "ip_addr %s belong to the local host," \ @@ -1630,7 +1652,8 @@ int trunk_sync_thread_start(const FDFSStorageBrief *pStorage) return 0; } - if (is_local_host_ip(pStorage->ip_addr)) //can't self sync to self + if ((strcmp(pStorage->id, g_my_server_id_str) == 0) || + is_local_host_ip(pStorage->ip_addr)) //can't self sync to self { return 0; } @@ -1695,6 +1718,42 @@ int trunk_sync_thread_start(const FDFSStorageBrief *pStorage) return 0; } +void trunk_waiting_sync_thread_exit() +{ + int saved_trunk_sync_thread_count; + int count; + + saved_trunk_sync_thread_count = g_trunk_sync_thread_count; + if (saved_trunk_sync_thread_count > 0) + { + logInfo("file: "__FILE__", line: %d, " + "waiting %d trunk sync threads exit ...", + __LINE__, saved_trunk_sync_thread_count); + } + + count = 0; + while (g_trunk_sync_thread_count > 0 && count < 20) + { + usleep(50000); + count++; + } + + if (g_trunk_sync_thread_count > 0) + { + logWarning("file: "__FILE__", line: %d, " + "kill %d trunk sync threads.", + __LINE__, g_trunk_sync_thread_count); + kill_trunk_sync_threads(); + } + + if (saved_trunk_sync_thread_count > 0) + { + logInfo("file: "__FILE__", line: %d, " + "%d trunk sync threads exited", + __LINE__, saved_trunk_sync_thread_count); + } +} + int trunk_unlink_all_mark_files() { FDFSStorageServer *pStorageServer; @@ -1710,7 +1769,7 @@ int trunk_unlink_all_mark_files() continue; } - if ((result=trunk_unlink_mark_file( \ + if ((result=trunk_unlink_mark_file( pStorageServer->server.id)) != 0) { if (result != ENOENT) diff --git a/storage/trunk_mgr/trunk_sync.h b/storage/trunk_mgr/trunk_sync.h index 58e4b6a..40ad243 100644 --- a/storage/trunk_mgr/trunk_sync.h +++ b/storage/trunk_mgr/trunk_sync.h @@ -61,6 +61,7 @@ int trunk_sync_thread_start_all(); int trunk_sync_thread_start(const FDFSStorageBrief *pStorage); int kill_trunk_sync_threads(); int trunk_binlog_sync_func(void *args); +void trunk_waiting_sync_thread_exit(); char *get_trunk_binlog_filename(char *full_filename); char *trunk_mark_filename_by_reader(const void *pArg, char *full_filename); diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index 6b3e397..79917c8 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -563,9 +563,18 @@ int tracker_load_from_conf_file(const char *filename, \ { return result; } - g_trunk_compress_binlog_min_interval = iniGetIntValue(NULL, \ - "trunk_compress_binlog_min_interval", \ + g_trunk_compress_binlog_min_interval = iniGetIntValue(NULL, + "trunk_compress_binlog_min_interval", &iniContext, 0); + g_trunk_compress_binlog_interval = iniGetIntValue(NULL, + "trunk_compress_binlog_interval", + &iniContext, 0); + if ((result=get_time_item_from_conf(&iniContext, + "trunk_compress_binlog_time_base", + &g_trunk_compress_binlog_time_base, 3, 0)) != 0) + { + return result; + } g_trunk_init_check_occupying = iniGetBoolValue(NULL, \ "trunk_init_check_occupying", &iniContext, false); @@ -750,6 +759,8 @@ int tracker_load_from_conf_file(const char *filename, \ "trunk_init_check_occupying=%d, " \ "trunk_init_reload_from_binlog=%d, " \ "trunk_compress_binlog_min_interval=%d, " \ + "trunk_compress_binlog_interval=%d, " \ + "trunk_compress_binlog_time_base=%02d:%02d, " \ "use_storage_id=%d, " \ "id_type_in_filename=%s, " \ "storage_id/ip_count=%d / %d, " \ @@ -789,6 +800,9 @@ int tracker_load_from_conf_file(const char *filename, \ (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, \ g_trunk_init_reload_from_binlog, \ g_trunk_compress_binlog_min_interval, \ + g_trunk_compress_binlog_interval, \ + g_trunk_compress_binlog_time_base.hour, \ + g_trunk_compress_binlog_time_base.minute, \ g_use_storage_id, g_id_type_in_filename == \ FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", \ g_storage_ids_by_id.count, g_storage_ids_by_ip.count, \ diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index fddc830..de47160 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -55,7 +55,9 @@ int g_slot_min_size = 256; //slot min size, such as 256 bytes int g_slot_max_size = 16 * 1024 * 1024; //slot max size, such as 16MB int g_trunk_file_size = 64 * 1024 * 1024; //the trunk file size, such as 64MB TimeInfo g_trunk_create_file_time_base = {0, 0}; +TimeInfo g_trunk_compress_binlog_time_base = {0, 0}; int g_trunk_create_file_interval = 86400; +int g_trunk_compress_binlog_interval = 0; int g_trunk_compress_binlog_min_interval = 0; int64_t g_trunk_create_file_space_threshold = 0; diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index 85502b1..1bec35c 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -79,7 +79,9 @@ extern int g_slot_min_size; //slot min size, such as 256 bytes extern int g_slot_max_size; //slot max size, such as 16MB extern int g_trunk_file_size; //the trunk file size, such as 64MB extern TimeInfo g_trunk_create_file_time_base; +extern TimeInfo g_trunk_compress_binlog_time_base; extern int g_trunk_create_file_interval; +extern int g_trunk_compress_binlog_interval; extern int g_trunk_compress_binlog_min_interval; extern int64_t g_trunk_create_file_space_threshold; diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 1f30432..f75d0b4 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -695,6 +695,8 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) "trunk_init_check_occupying=%d\n" \ "trunk_init_reload_from_binlog=%d\n" \ "trunk_compress_binlog_min_interval=%d\n" \ + "trunk_compress_binlog_interval=%d\n" \ + "trunk_compress_binlog_time_base=%02d:%02d\n" \ "store_slave_file_use_link=%d\n", \ g_use_storage_id, g_id_type_in_filename == \ FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", \ @@ -712,6 +714,9 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) g_trunk_init_check_occupying, \ g_trunk_init_reload_from_binlog, \ g_trunk_compress_binlog_min_interval, \ + g_trunk_compress_binlog_interval, \ + g_trunk_compress_binlog_time_base.hour, \ + g_trunk_compress_binlog_time_base.minute, \ g_store_slave_file_use_link); return 0; From cab3a90d7f545079e188c5df1520676dc2f62726 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 15 Dec 2019 18:49:02 +0800 Subject: [PATCH 68/95] compress the trunk binlog gracefully --- HISTORY | 2 +- storage/storage_sync_func.c | 2 +- storage/storage_sync_func.h | 4 +- storage/trunk_mgr/trunk_mem.c | 26 ++- storage/trunk_mgr/trunk_sync.c | 365 ++++++++++++++++++++++++--------- storage/trunk_mgr/trunk_sync.h | 5 +- 6 files changed, 291 insertions(+), 113 deletions(-) diff --git a/HISTORY b/HISTORY index 05c1522..4919bc3 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.05 2019-12-14 +Version 6.05 2019-12-15 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version diff --git a/storage/storage_sync_func.c b/storage/storage_sync_func.c index 72a7fb8..34cb47a 100644 --- a/storage/storage_sync_func.c +++ b/storage/storage_sync_func.c @@ -29,7 +29,7 @@ #include "storage_func.h" #include "storage_sync_func.h" -void storage_sync_connect_storage_server_ex(FDFSStorageBrief *pStorage, +void storage_sync_connect_storage_server_ex(const FDFSStorageBrief *pStorage, ConnectionInfo *conn, bool *check_flag) { int nContinuousFail; diff --git a/storage/storage_sync_func.h b/storage/storage_sync_func.h index 487ec3b..94b2b08 100644 --- a/storage/storage_sync_func.h +++ b/storage/storage_sync_func.h @@ -17,11 +17,11 @@ extern "C" { #endif -void storage_sync_connect_storage_server_ex(FDFSStorageBrief *pStorage, +void storage_sync_connect_storage_server_ex(const FDFSStorageBrief *pStorage, ConnectionInfo *conn, bool *check_flag); static inline void storage_sync_connect_storage_server( - FDFSStorageBrief *pStorage, ConnectionInfo *conn) + const FDFSStorageBrief *pStorage, ConnectionInfo *conn) { bool check_flag = true; storage_sync_connect_storage_server_ex(pStorage, diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 882a82d..a32137d 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -520,6 +520,15 @@ static int storage_trunk_compress() { int result; + if (g_current_time - g_up_time < 600) + { + logWarning("file: "__FILE__", line: %d, " + "too little time lapse: %ds afer startup, " + "skip trunk binlog compress", __LINE__, + (int)(g_current_time - g_up_time)); + return EBUSY; + } + if (__sync_add_and_fetch(&g_trunk_binlog_compress_in_progress, 1) != 1) { __sync_sub_and_fetch(&g_trunk_binlog_compress_in_progress, 1); @@ -563,7 +572,6 @@ static int storage_trunk_compress() { logInfo("file: "__FILE__", line: %d, " "compress trunk binlog successfully.", __LINE__); - return trunk_unlink_all_mark_files(); //because the binlog file be compressed } else { @@ -576,6 +584,8 @@ static int storage_trunk_compress() static int storage_trunk_save() { + int result; + if (!(g_trunk_compress_binlog_min_interval > 0 && g_current_time - g_trunk_last_compress_time > g_trunk_compress_binlog_min_interval)) @@ -594,7 +604,12 @@ static int storage_trunk_save() } } - return storage_trunk_compress(); + if ((result=storage_trunk_compress()) == 0) + { + return trunk_unlink_all_mark_files(); //because the binlog file be compressed + } + + return result; } int trunk_binlog_compress_func(void *args) @@ -617,12 +632,7 @@ int trunk_binlog_compress_func(void *args) return 0; } - g_if_trunker_self = false; //for sync thread exit - trunk_waiting_sync_thread_exit(); - - g_if_trunker_self = true; //restore to true - trunk_sync_thread_start_all(); - + trunk_sync_notify_thread_reset_offset(); return 0; } diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index b08adc2..65909a0 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -52,8 +52,22 @@ static char *trunk_binlog_write_cache_buff = NULL; static int trunk_binlog_write_cache_len = 0; static int trunk_binlog_write_version = 1; +typedef struct +{ + bool running; + bool reset_binlog_offset; + const FDFSStorageBrief *pStorage; + pthread_t tid; +} TrunkSyncThreadInfo; + +typedef struct +{ + TrunkSyncThreadInfo **thread_data; + int alloc_count; +} TrunkSyncThreadInfoArray; + /* save sync thread ids */ -static pthread_t *trunk_sync_tids = NULL; +static TrunkSyncThreadInfoArray sync_thread_info_array = {NULL, 0}; static int trunk_write_to_mark_file(TrunkBinLogReader *pReader); static int trunk_binlog_fsync_ex(const bool bNeedLock, \ @@ -199,27 +213,43 @@ int kill_trunk_sync_threads() { int result; int kill_res; + TrunkSyncThreadInfo **thread_info; + TrunkSyncThreadInfo **info_end; - if (trunk_sync_tids == NULL) + if (sync_thread_info_array.thread_data == NULL) { return 0; } if ((result=pthread_mutex_lock(&trunk_sync_thread_lock)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_lock fail, " \ - "errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_lock fail, " + "errno: %d, error info: %s", __LINE__, result, STRERROR(result)); } - kill_res = kill_work_threads(trunk_sync_tids, g_trunk_sync_thread_count); + kill_res = 0; + info_end = sync_thread_info_array.thread_data + + sync_thread_info_array.alloc_count; + for (thread_info=sync_thread_info_array.thread_data; + thread_inforunning && (kill_res=pthread_kill( + (*thread_info)->tid, SIGINT)) != 0) + { + logError("file: "__FILE__", line: %d, " + "kill thread failed, " + "errno: %d, error info: %s", + __LINE__, kill_res, STRERROR(kill_res)); + } + } if ((result=pthread_mutex_unlock(&trunk_sync_thread_lock)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_unlock fail, " \ - "errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_unlock fail, " + "errno: %d, error info: %s", __LINE__, result, STRERROR(result)); } @@ -231,6 +261,47 @@ int kill_trunk_sync_threads() return kill_res; } +int trunk_sync_notify_thread_reset_offset() +{ + int result; + TrunkSyncThreadInfo **thread_info; + TrunkSyncThreadInfo **info_end; + + if (sync_thread_info_array.thread_data == NULL) + { + return 0; + } + + if ((result=pthread_mutex_lock(&trunk_sync_thread_lock)) != 0) + { + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_lock fail, " + "errno: %d, error info: %s", + __LINE__, result, STRERROR(result)); + } + + info_end = sync_thread_info_array.thread_data + + sync_thread_info_array.alloc_count; + for (thread_info=sync_thread_info_array.thread_data; + thread_inforunning) + { + (*thread_info)->reset_binlog_offset = true; + } + } + + if ((result=pthread_mutex_unlock(&trunk_sync_thread_lock)) != 0) + { + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_unlock fail, " + "errno: %d, error info: %s", + __LINE__, result, STRERROR(result)); + } + + return result; +} + int trunk_binlog_sync_func(void *args) { if (trunk_binlog_write_cache_len > 0) @@ -809,6 +880,7 @@ int trunk_open_readable_binlog(TrunkBinLogReader *pReader, \ get_filename_func filename_func, const void *pArg) { char full_filename[MAX_PATH_SIZE]; + struct stat file_stat; if (pReader->binlog_fd >= 0) { @@ -819,14 +891,34 @@ int trunk_open_readable_binlog(TrunkBinLogReader *pReader, \ pReader->binlog_fd = open(full_filename, O_RDONLY); if (pReader->binlog_fd < 0) { - logError("file: "__FILE__", line: %d, " \ - "open binlog file \"%s\" fail, " \ - "errno: %d, error info: %s", \ - __LINE__, full_filename, \ + logError("file: "__FILE__", line: %d, " + "open binlog file \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, full_filename, errno, STRERROR(errno)); return errno != 0 ? errno : ENOENT; } + if (fstat(pReader->binlog_fd, &file_stat) != 0) + { + logError("file: "__FILE__", line: %d, " + "stat binlog file \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, full_filename, + errno, STRERROR(errno)); + return errno != 0 ? errno : ENOENT; + } + + if (pReader->binlog_offset > file_stat.st_size) + { + logWarning("file: "__FILE__", line: %d, " + "binlog file \"%s\", binlog_offset: %"PRId64 + " > file size: %"PRId64", set binlog_offset to 0", + __LINE__, full_filename, pReader->binlog_offset, + (int64_t)file_stat.st_size); + pReader->binlog_offset = 0; + } + if (pReader->binlog_offset > 0 && \ lseek(pReader->binlog_fd, pReader->binlog_offset, SEEK_SET) < 0) { @@ -895,7 +987,8 @@ static char *trunk_get_mark_filename_by_id(const char *storage_id, full_filename, filename_size); } -int trunk_reader_init(FDFSStorageBrief *pStorage, TrunkBinLogReader *pReader) +int trunk_reader_init(const FDFSStorageBrief *pStorage, + TrunkBinLogReader *pReader) { char full_filename[MAX_PATH_SIZE]; IniContext iniContext; @@ -1067,11 +1160,11 @@ static int trunk_write_to_mark_file(TrunkBinLogReader *pReader) int len; int result; - len = sprintf(buff, \ - "%s=%"PRId64"\n", \ + len = sprintf(buff, + "%s=%"PRId64"\n", MARK_ITEM_BINLOG_FILE_OFFSET, pReader->binlog_offset); - if ((result=storage_write_to_fd(pReader->mark_fd, \ + if ((result=storage_write_to_fd(pReader->mark_fd, trunk_mark_filename_by_reader, pReader, buff, len)) == 0) { pReader->last_binlog_offset = pReader->binlog_offset; @@ -1303,48 +1396,33 @@ int trunk_rename_mark_file(const char *old_ip_addr, const int old_port, \ return 0; } -static void trunk_sync_thread_exit(ConnectionInfo *pStorage) +static void trunk_sync_thread_exit(TrunkSyncThreadInfo *thread_data, + const int port) { int result; - int i; - pthread_t tid; if ((result=pthread_mutex_lock(&trunk_sync_thread_lock)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_lock fail, " \ - "errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_lock fail, " + "errno: %d, error info: %s", __LINE__, result, STRERROR(result)); } - - tid = pthread_self(); - for (i=0; irunning = false; g_trunk_sync_thread_count--; if ((result=pthread_mutex_unlock(&trunk_sync_thread_lock)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_unlock fail, " \ - "errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_unlock fail, " + "errno: %d, error info: %s", __LINE__, result, STRERROR(result)); } - logInfo("file: "__FILE__", line: %d, " \ - "trunk sync thread to storage server %s:%d exit", - __LINE__, pStorage->ip_addr, pStorage->port); + logInfo("file: "__FILE__", line: %d, " + "trunk sync thread to storage server %s:%d exit", + __LINE__, thread_data->pStorage->ip_addr, port); } static int trunk_sync_data(TrunkBinLogReader *pReader, \ @@ -1419,9 +1497,10 @@ static int trunk_sync_data(TrunkBinLogReader *pReader, \ return 0; } -static void* trunk_sync_thread_entrance(void* arg) +static void *trunk_sync_thread_entrance(void* arg) { - FDFSStorageBrief *pStorage; + TrunkSyncThreadInfo *thread_data; + const FDFSStorageBrief *pStorage; TrunkBinLogReader reader; ConnectionInfo storage_server; char local_ip_addr[IP_ADDRESS_SIZE]; @@ -1439,7 +1518,8 @@ static void* trunk_sync_thread_entrance(void* arg) current_time = g_current_time; last_keep_alive_time = 0; - pStorage = (FDFSStorageBrief *)arg; + thread_data = (TrunkSyncThreadInfo *)arg; + pStorage = thread_data->pStorage; strcpy(storage_server.ip_addr, pStorage->ip_addr); storage_server.port = g_server_port; @@ -1503,6 +1583,16 @@ static void* trunk_sync_thread_entrance(void* arg) break; } + if (thread_data->reset_binlog_offset) + { + thread_data->reset_binlog_offset = false; + if (reader.binlog_offset > 0) + { + reader.binlog_offset = 0; + trunk_write_to_mark_file(&reader); + } + } + if (reader.binlog_offset == 0) { if ((result=fdfs_deal_no_body_cmd(&storage_server, \ @@ -1520,9 +1610,9 @@ static void* trunk_sync_thread_entrance(void* arg) } sync_result = 0; - while (g_continue_flag && \ - pStorage->status != FDFS_STORAGE_STATUS_DELETED && \ - pStorage->status != FDFS_STORAGE_STATUS_IP_CHANGED && \ + while (g_continue_flag && !thread_data->reset_binlog_offset && + pStorage->status != FDFS_STORAGE_STATUS_DELETED && + pStorage->status != FDFS_STORAGE_STATUS_IP_CHANGED && pStorage->status != FDFS_STORAGE_STATUS_NONE) { read_result = trunk_binlog_preread(&reader); @@ -1613,7 +1703,7 @@ static void* trunk_sync_thread_entrance(void* arg) } trunk_reader_destroy(&reader); - trunk_sync_thread_exit(&storage_server); + trunk_sync_thread_exit(thread_data, storage_server.port); return NULL; } @@ -1639,14 +1729,98 @@ int trunk_sync_thread_start_all() return result; } +TrunkSyncThreadInfo *trunk_sync_alloc_thread_data() +{ + TrunkSyncThreadInfo **thread_info; + TrunkSyncThreadInfo **info_end; + TrunkSyncThreadInfo **new_thread_data; + TrunkSyncThreadInfo **new_data_start; + int alloc_count; + int bytes; + + if (g_trunk_sync_thread_count + 1 < sync_thread_info_array.alloc_count) + { + info_end = sync_thread_info_array.thread_data + + sync_thread_info_array.alloc_count; + for (thread_info=sync_thread_info_array.thread_data; + thread_inforunning) + { + return *thread_info; + } + } + } + + if (sync_thread_info_array.alloc_count == 0) + { + alloc_count = 1; + } + else + { + alloc_count = sync_thread_info_array.alloc_count * 2; + } + + bytes = sizeof(TrunkSyncThreadInfo *) * alloc_count; + new_thread_data = (TrunkSyncThreadInfo **)malloc(bytes); + if (new_thread_data == NULL) + { + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail, " + "errno: %d, error info: %s", + __LINE__, bytes, errno, STRERROR(errno)); + return NULL; + } + + logInfo("file: "__FILE__", line: %d, " + "alloc %d thread data entries", + __LINE__, alloc_count); + + if (sync_thread_info_array.alloc_count > 0) + { + memcpy(new_thread_data, sync_thread_info_array.thread_data, + sizeof(TrunkSyncThreadInfo *) * + sync_thread_info_array.alloc_count); + } + + new_data_start = new_thread_data + sync_thread_info_array.alloc_count; + info_end = new_thread_data + alloc_count; + for (thread_info=new_data_start; thread_infostatus == FDFS_STORAGE_STATUS_DELETED || \ - pStorage->status == FDFS_STORAGE_STATUS_IP_CHANGED || \ + if (pStorage->status == FDFS_STORAGE_STATUS_DELETED || + pStorage->status == FDFS_STORAGE_STATUS_IP_CHANGED || pStorage->status == FDFS_STORAGE_STATUS_NONE) { return 0; @@ -1663,59 +1837,50 @@ int trunk_sync_thread_start(const FDFSStorageBrief *pStorage) return result; } - /* - //printf("start storage ip_addr: %s, g_trunk_sync_thread_count=%d\n", - pStorage->ip_addr, g_trunk_sync_thread_count); - */ - - if ((result=pthread_create(&tid, &pattr, trunk_sync_thread_entrance, \ - (void *)pStorage)) != 0) + if ((lock_res=pthread_mutex_lock(&trunk_sync_thread_lock)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "create thread failed, errno: %d, " \ - "error info: %s", \ - __LINE__, result, STRERROR(result)); - - pthread_attr_destroy(&pattr); - return result; + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_lock fail, " + "errno: %d, error info: %s", + __LINE__, lock_res, STRERROR(lock_res)); } - if ((result=pthread_mutex_lock(&trunk_sync_thread_lock)) != 0) - { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_lock fail, " \ - "errno: %d, error info: %s", \ - __LINE__, result, STRERROR(result)); - } + do + { + thread_data = trunk_sync_alloc_thread_data(); + if (thread_data == NULL) + { + result = ENOMEM; + break; + } - g_trunk_sync_thread_count++; - trunk_sync_tids = (pthread_t *)realloc(trunk_sync_tids, sizeof(pthread_t) * \ - g_trunk_sync_thread_count); - if (trunk_sync_tids == NULL) - { - logError("file: "__FILE__", line: %d, " \ - "malloc %d bytes fail, " \ - "errno: %d, error info: %s", \ - __LINE__, (int)sizeof(pthread_t) * \ - g_trunk_sync_thread_count, \ - errno, STRERROR(errno)); - } - else - { - trunk_sync_tids[g_trunk_sync_thread_count - 1] = tid; - } + thread_data->running = true; + thread_data->pStorage = pStorage; + if ((result=pthread_create(&thread_data->tid, &pattr, + trunk_sync_thread_entrance, + (void *)thread_data)) != 0) + { + thread_data->running = false; + logError("file: "__FILE__", line: %d, " + "create thread failed, errno: %d, " + "error info: %s", + __LINE__, result, STRERROR(result)); + break; + } - if ((result=pthread_mutex_unlock(&trunk_sync_thread_lock)) != 0) - { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_unlock fail, " \ - "errno: %d, error info: %s", \ - __LINE__, result, STRERROR(result)); - } + g_trunk_sync_thread_count++; + } while (0); + + if ((lock_res=pthread_mutex_unlock(&trunk_sync_thread_lock)) != 0) + { + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_unlock fail, " + "errno: %d, error info: %s", + __LINE__, lock_res, STRERROR(lock_res)); + } pthread_attr_destroy(&pattr); - - return 0; + return result; } void trunk_waiting_sync_thread_exit() diff --git a/storage/trunk_mgr/trunk_sync.h b/storage/trunk_mgr/trunk_sync.h index 40ad243..e94c471 100644 --- a/storage/trunk_mgr/trunk_sync.h +++ b/storage/trunk_mgr/trunk_sync.h @@ -73,7 +73,8 @@ int trunk_rename_mark_file(const char *old_ip_addr, const int old_port, \ int trunk_open_readable_binlog(TrunkBinLogReader *pReader, \ get_filename_func filename_func, const void *pArg); -int trunk_reader_init(FDFSStorageBrief *pStorage, TrunkBinLogReader *pReader); +int trunk_reader_init(const FDFSStorageBrief *pStorage, + TrunkBinLogReader *pReader); void trunk_reader_destroy(TrunkBinLogReader *pReader); //trunk binlog compress @@ -81,6 +82,8 @@ int trunk_binlog_compress_apply(); int trunk_binlog_compress_commit(); int trunk_binlog_compress_rollback(); +int trunk_sync_notify_thread_reset_offset(); + #ifdef __cplusplus } #endif From fd8772976d6585ff88bca8ae35759eb755b1db6d Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 15 Dec 2019 21:26:13 +0800 Subject: [PATCH 69/95] check trunk binlog version before compressing --- storage/trunk_mgr/trunk_mem.c | 19 +++++++++++++++--- storage/trunk_mgr/trunk_sync.c | 36 ++++++++++++++++++++++------------ storage/trunk_mgr/trunk_sync.h | 3 ++- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index a32137d..23481ae 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -518,6 +518,8 @@ static int storage_trunk_do_save() static int storage_trunk_compress() { + static int last_write_version = 0; + int current_write_version; int result; if (g_current_time - g_up_time < 600) @@ -526,7 +528,16 @@ static int storage_trunk_compress() "too little time lapse: %ds afer startup, " "skip trunk binlog compress", __LINE__, (int)(g_current_time - g_up_time)); - return EBUSY; + return EAGAIN; + } + + current_write_version = trunk_binlog_get_write_version(); + if (current_write_version == last_write_version) + { + logInfo("file: "__FILE__", line: %d, " + "binlog NOT changed, do NOT need compress", + __LINE__); + return EALREADY; } if (__sync_add_and_fetch(&g_trunk_binlog_compress_in_progress, 1) != 1) @@ -563,6 +574,7 @@ static int storage_trunk_compress() } g_trunk_last_compress_time = g_current_time; + last_write_version = current_write_version; } while (0); __sync_sub_and_fetch(&g_trunk_binlog_compress_in_progress, 1); @@ -609,7 +621,8 @@ static int storage_trunk_save() return trunk_unlink_all_mark_files(); //because the binlog file be compressed } - return result; + return (result == EAGAIN || result == EALREADY || + result == EINPROGRESS) ? 0 : result; } int trunk_binlog_compress_func(void *args) @@ -818,7 +831,7 @@ static int storage_trunk_restore(const int64_t restore_offset) memset(&record, 0, sizeof(record)); memset(&reader, 0, sizeof(reader)); reader.binlog_offset = restore_offset; - if ((result=trunk_reader_init(NULL, &reader)) != 0) + if ((result=trunk_reader_init(NULL, &reader, false)) != 0) { return result; } diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index 65909a0..b94a2bf 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -988,7 +988,7 @@ static char *trunk_get_mark_filename_by_id(const char *storage_id, } int trunk_reader_init(const FDFSStorageBrief *pStorage, - TrunkBinLogReader *pReader) + TrunkBinLogReader *pReader, const bool reset_binlog_offset) { char full_filename[MAX_PATH_SIZE]; IniContext iniContext; @@ -1116,7 +1116,13 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, } } - if ((result=trunk_open_readable_binlog(pReader, \ + if (reset_binlog_offset && pReader->binlog_offset > 0) + { + pReader->binlog_offset = 0; + trunk_write_to_mark_file(pReader); + } + + if ((result=trunk_open_readable_binlog(pReader, get_binlog_readable_filename, pReader)) != 0) { return result; @@ -1178,7 +1184,7 @@ static int trunk_binlog_preread(TrunkBinLogReader *pReader) int bytes_read; int saved_trunk_binlog_write_version; - if (pReader->binlog_buff.version == trunk_binlog_write_version && \ + if (pReader->binlog_buff.version == trunk_binlog_write_version && pReader->binlog_buff.length == 0) { return ENOENT; @@ -1550,12 +1556,12 @@ static void *trunk_sync_thread_entrance(void* arg) break; } - if ((result=trunk_reader_init(pStorage, &reader)) != 0) + if ((result=trunk_reader_init(pStorage, &reader, + thread_data->reset_binlog_offset)) != 0) { - logCrit("file: "__FILE__", line: %d, " \ - "trunk_reader_init fail, errno=%d, " \ - "program exit!", \ - __LINE__, result); + logCrit("file: "__FILE__", line: %d, " + "trunk_reader_init fail, errno=%d, " + "program exit!", __LINE__, result); g_continue_flag = false; break; } @@ -1733,6 +1739,7 @@ TrunkSyncThreadInfo *trunk_sync_alloc_thread_data() { TrunkSyncThreadInfo **thread_info; TrunkSyncThreadInfo **info_end; + TrunkSyncThreadInfo **old_thread_data; TrunkSyncThreadInfo **new_thread_data; TrunkSyncThreadInfo **new_data_start; int alloc_count; @@ -1802,12 +1809,13 @@ TrunkSyncThreadInfo *trunk_sync_alloc_thread_data() memset(*thread_info, 0, sizeof(TrunkSyncThreadInfo)); } - if (sync_thread_info_array.thread_data != NULL) - { - free(sync_thread_info_array.thread_data); - } + old_thread_data = sync_thread_info_array.thread_data; sync_thread_info_array.thread_data = new_thread_data; sync_thread_info_array.alloc_count = alloc_count; + if (old_thread_data != NULL) + { + free(old_thread_data); + } return *new_data_start; } @@ -1947,3 +1955,7 @@ int trunk_unlink_all_mark_files() return 0; } +int trunk_binlog_get_write_version() +{ + return trunk_binlog_write_version; +} diff --git a/storage/trunk_mgr/trunk_sync.h b/storage/trunk_mgr/trunk_sync.h index e94c471..1bb7309 100644 --- a/storage/trunk_mgr/trunk_sync.h +++ b/storage/trunk_mgr/trunk_sync.h @@ -74,7 +74,7 @@ int trunk_open_readable_binlog(TrunkBinLogReader *pReader, \ get_filename_func filename_func, const void *pArg); int trunk_reader_init(const FDFSStorageBrief *pStorage, - TrunkBinLogReader *pReader); + TrunkBinLogReader *pReader, const bool reset_binlog_offset); void trunk_reader_destroy(TrunkBinLogReader *pReader); //trunk binlog compress @@ -83,6 +83,7 @@ int trunk_binlog_compress_commit(); int trunk_binlog_compress_rollback(); int trunk_sync_notify_thread_reset_offset(); +int trunk_binlog_get_write_version(); #ifdef __cplusplus } From 2c5955c1fe3344abe9ccf8b1ab10d9d010f717b9 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 18 Dec 2019 21:16:34 +0800 Subject: [PATCH 70/95] trunk binlog compression support transaction --- HISTORY | 3 +- storage/storage_func.c | 59 ++- storage/storage_service.c | 2 +- storage/tracker_client_thread.c | 2 +- storage/trunk_mgr/trunk_mem.c | 210 ++++++-- storage/trunk_mgr/trunk_mem.h | 20 +- storage/trunk_mgr/trunk_sync.c | 861 +++++++++++++++++++++++--------- storage/trunk_mgr/trunk_sync.h | 15 +- 8 files changed, 867 insertions(+), 305 deletions(-) diff --git a/HISTORY b/HISTORY index 4919bc3..5bf5b05 100644 --- a/HISTORY +++ b/HISTORY @@ -1,11 +1,12 @@ -Version 6.05 2019-12-15 +Version 6.05 2019-12-18 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version * trunk server support compress the trunk binlog periodically, config items in tracker.conf: trunk_compress_binlog_interval and trunk_compress_binlog_time_base + * trunk binlog compression support transaction Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST diff --git a/storage/storage_func.c b/storage/storage_func.c index 44acd99..fae7b1e 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -73,8 +73,8 @@ typedef struct #define INIT_ITEM_LAST_HTTP_PORT "last_http_port" #define INIT_ITEM_CURRENT_TRUNK_FILE_ID "current_trunk_file_id" #define INIT_ITEM_TRUNK_LAST_COMPRESS_TIME "trunk_last_compress_time" -#define INIT_ITEM_TRUNK_BINLOG_COMPRESS_IN_PROGRESS \ - "trunk_binlog_compress_in_progress" +#define INIT_ITEM_TRUNK_BINLOG_COMPRESS_STAGE \ + "trunk_binlog_compress_stage" #define INIT_ITEM_STORE_PATH_MARK_PREFIX "store_path_mark" #define STAT_ITEM_TOTAL_UPLOAD "total_upload_count" @@ -652,28 +652,28 @@ int storage_write_to_sync_ini_file() fdfs_multi_ips_to_string(&g_tracker_client_ip, ip_str, sizeof(ip_str)); - len = sprintf(buff, "%s=%d\n" - "%s=%d\n" - "%s=%s\n" - "%s=%d\n" - "%s=%s\n" - "%s=%d\n" - "%s=%d\n" - "%s=%d\n" - "%s=%d\n" - "%s=%d\n", - INIT_ITEM_STORAGE_JOIN_TIME, g_storage_join_time, - INIT_ITEM_SYNC_OLD_DONE, g_sync_old_done, - INIT_ITEM_SYNC_SRC_SERVER, g_sync_src_id, - INIT_ITEM_SYNC_UNTIL_TIMESTAMP, g_sync_until_timestamp, - INIT_ITEM_LAST_IP_ADDRESS, ip_str, - INIT_ITEM_LAST_SERVER_PORT, g_last_server_port, - INIT_ITEM_LAST_HTTP_PORT, g_last_http_port, - INIT_ITEM_CURRENT_TRUNK_FILE_ID, g_current_trunk_file_id, - INIT_ITEM_TRUNK_LAST_COMPRESS_TIME, (int)g_trunk_last_compress_time, - INIT_ITEM_TRUNK_BINLOG_COMPRESS_IN_PROGRESS, - g_trunk_binlog_compress_in_progress - ); + len = sprintf(buff, "%s=%d\n" + "%s=%d\n" + "%s=%s\n" + "%s=%d\n" + "%s=%s\n" + "%s=%d\n" + "%s=%d\n" + "%s=%d\n" + "%s=%d\n" + "%s=%d\n", + INIT_ITEM_STORAGE_JOIN_TIME, g_storage_join_time, + INIT_ITEM_SYNC_OLD_DONE, g_sync_old_done, + INIT_ITEM_SYNC_SRC_SERVER, g_sync_src_id, + INIT_ITEM_SYNC_UNTIL_TIMESTAMP, g_sync_until_timestamp, + INIT_ITEM_LAST_IP_ADDRESS, ip_str, + INIT_ITEM_LAST_SERVER_PORT, g_last_server_port, + INIT_ITEM_LAST_HTTP_PORT, g_last_http_port, + INIT_ITEM_CURRENT_TRUNK_FILE_ID, g_current_trunk_file_id, + INIT_ITEM_TRUNK_LAST_COMPRESS_TIME, + (int)g_trunk_last_compress_time, + INIT_ITEM_TRUNK_BINLOG_COMPRESS_STAGE, + g_trunk_binlog_compress_stage); if (g_check_store_path_mark) { @@ -1070,9 +1070,9 @@ static int storage_check_and_make_data_dirs() INIT_ITEM_CURRENT_TRUNK_FILE_ID, &iniContext, 0); g_trunk_last_compress_time = iniGetIntValue(NULL, INIT_ITEM_TRUNK_LAST_COMPRESS_TIME , &iniContext, 0); - g_trunk_binlog_compress_in_progress = iniGetIntValue(NULL, - INIT_ITEM_TRUNK_BINLOG_COMPRESS_IN_PROGRESS, - &iniContext, 0); + g_trunk_binlog_compress_stage = iniGetIntValue(NULL, + INIT_ITEM_TRUNK_BINLOG_COMPRESS_STAGE, + &iniContext, STORAGE_TRUNK_COMPRESS_STAGE_NONE); if ((result=storage_load_store_path_marks(&iniContext)) != 0) { @@ -2302,6 +2302,11 @@ int storage_func_init(const char *filename, \ return result; } + if ((result=storage_trunk_binlog_compress_check_recovery()) != 0) + { + return result; + } + if ((result=init_pthread_lock(&sync_stat_file_lock)) != 0) { return result; diff --git a/storage/storage_service.c b/storage/storage_service.c index a94be37..6f2f45a 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -4064,7 +4064,7 @@ static int storage_server_trunk_delete_binlog_marks(struct fast_task_info *pTask return result; } - return trunk_unlink_all_mark_files(); + return trunk_unlink_all_mark_files(false); } /** diff --git a/storage/tracker_client_thread.c b/storage/tracker_client_thread.c index cd48015..68abe42 100644 --- a/storage/tracker_client_thread.c +++ b/storage/tracker_client_thread.c @@ -1262,7 +1262,7 @@ static void do_unset_trunk_server_myself(ConnectionInfo *pTrackerServer) trunk_waiting_sync_thread_exit(); - storage_trunk_destroy_ex(true); + storage_trunk_destroy_ex(true, true); if (g_trunk_create_file_advance && g_trunk_create_file_interval > 0) { diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 23481ae..1dff485 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -62,13 +62,14 @@ bool g_if_trunker_self = false; bool g_trunk_create_file_advance = false; bool g_trunk_init_check_occupying = false; bool g_trunk_init_reload_from_binlog = false; -volatile int g_trunk_binlog_compress_in_progress = 0; -volatile int g_trunk_data_save_in_progress = 0; -static byte trunk_init_flag = STORAGE_TRUNK_INIT_FLAG_NONE; +int g_trunk_binlog_compress_stage = STORAGE_TRUNK_COMPRESS_STAGE_NONE; int64_t g_trunk_total_free_space = 0; int64_t g_trunk_create_file_space_threshold = 0; time_t g_trunk_last_compress_time = 0; +static byte trunk_init_flag = STORAGE_TRUNK_INIT_FLAG_NONE; +static volatile int trunk_binlog_compress_in_progress = 0; +static volatile int trunk_data_save_in_progress = 0; static pthread_mutex_t trunk_file_lock; static pthread_mutex_t trunk_mem_lock; static struct fast_mblock_man free_blocks_man; @@ -272,7 +273,8 @@ int storage_trunk_init() return 0; } -int storage_trunk_destroy_ex(const bool bNeedSleep) +int storage_trunk_destroy_ex(const bool bNeedSleep, + const bool bSaveData) { int result; int i; @@ -292,7 +294,14 @@ int storage_trunk_destroy_ex(const bool bNeedSleep) logDebug("file: "__FILE__", line: %d, " \ "storage trunk destroy", __LINE__); - result = storage_trunk_save(); + if (bSaveData) + { + result = storage_trunk_save(); + } + else + { + result = 0; + } for (i=0; i g_trunk_compress_binlog_min_interval)) { - if (__sync_add_and_fetch(&g_trunk_binlog_compress_in_progress, 0) == 0) + if (__sync_add_and_fetch(&trunk_binlog_compress_in_progress, 0) == 0) { return storage_trunk_do_save(); } @@ -610,15 +743,16 @@ static int storage_trunk_save() { logWarning("file: "__FILE__", line: %d, " "trunk binlog compress already in progress, " - "g_trunk_binlog_compress_in_progress=%d", - __LINE__, g_trunk_binlog_compress_in_progress); + "trunk_binlog_compress_in_progress=%d", + __LINE__, trunk_binlog_compress_in_progress); return 0; } } if ((result=storage_trunk_compress()) == 0) { - return trunk_unlink_all_mark_files(); //because the binlog file be compressed + g_trunk_binlog_compress_stage = STORAGE_TRUNK_COMPRESS_STAGE_FINISHED; + return storage_write_to_sync_ini_file(); } return (result == EAGAIN || result == EALREADY || @@ -634,19 +768,20 @@ int trunk_binlog_compress_func(void *args) return 0; } - result = storage_trunk_compress(); - if (result != 0) + if ((result=storage_trunk_compress()) != 0) { return result; } if (!g_if_trunker_self) { - return 0; + g_trunk_binlog_compress_stage = STORAGE_TRUNK_COMPRESS_STAGE_FINISHED; + return storage_write_to_sync_ini_file(); } trunk_sync_notify_thread_reset_offset(); - return 0; + g_trunk_binlog_compress_stage = STORAGE_TRUNK_COMPRESS_STAGE_FINISHED; + return storage_write_to_sync_ini_file(); } static bool storage_trunk_is_space_occupied(const FDFSTrunkFullInfo *pTrunkInfo) @@ -980,10 +1115,13 @@ static int storage_trunk_restore(const int64_t restore_offset) trunk_mark_filename_by_reader(&reader, trunk_mark_filename); if (unlink(trunk_mark_filename) != 0) { - logError("file: "__FILE__", line: %d, " \ - "unlink file %s fail, " \ - "errno: %d, error info: %s", __LINE__, \ - trunk_mark_filename, errno, STRERROR(errno)); + if (errno != ENOENT) + { + logError("file: "__FILE__", line: %d, " + "unlink file %s fail, " + "errno: %d, error info: %s", __LINE__, + trunk_mark_filename, errno, STRERROR(errno)); + } } if (result != 0) @@ -1039,10 +1177,10 @@ int storage_delete_trunk_data_file() result = errno != 0 ? errno : ENOENT; if (result != ENOENT) { - logError("file: "__FILE__", line: %d, " \ - "unlink trunk data file: %s fail, " \ - "errno: %d, error info: %s", \ - __LINE__, trunk_data_filename, \ + logError("file: "__FILE__", line: %d, " + "unlink trunk data file: %s fail, " + "errno: %d, error info: %s", + __LINE__, trunk_data_filename, result, STRERROR(result)); } diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index 7cd1f85..b7162b5 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -22,6 +22,17 @@ #include "trunk_shared.h" #include "fdfs_shared_func.h" +#define STORAGE_TRUNK_COMPRESS_STAGE_NONE 0 +#define STORAGE_TRUNK_COMPRESS_STAGE_COMPRESS_BEGIN 1 +#define STORAGE_TRUNK_COMPRESS_STAGE_APPLY_DONE 2 +#define STORAGE_TRUNK_COMPRESS_STAGE_SAVE_DONE 3 +#define STORAGE_TRUNK_COMPRESS_STAGE_COMMIT_MERGING 4 +#define STORAGE_TRUNK_COMPRESS_STAGE_COMMIT_MERGE_DONE 5 +#define STORAGE_TRUNK_COMPRESS_STAGE_COMPRESS_SUCCESS 6 +#define STORAGE_TRUNK_COMPRESS_STAGE_ROLLBACK_MERGING 7 +#define STORAGE_TRUNK_COMPRESS_STAGE_ROLLBACK_MERGE_DONE 8 +#define STORAGE_TRUNK_COMPRESS_STAGE_FINISHED 9 + #ifdef __cplusplus extern "C" { #endif @@ -44,7 +55,7 @@ extern bool g_if_use_trunk_file; //if use trunk file extern bool g_trunk_create_file_advance; extern bool g_trunk_init_check_occupying; extern bool g_trunk_init_reload_from_binlog; -extern volatile int g_trunk_binlog_compress_in_progress; +extern int g_trunk_binlog_compress_stage; extern bool g_if_trunker_self; //if am i trunk server extern int64_t g_trunk_create_file_space_threshold; extern int64_t g_trunk_total_free_space; //trunk total free space in bytes @@ -63,9 +74,10 @@ typedef struct { } FDFSTrunkSlot; int storage_trunk_init(); -int storage_trunk_destroy_ex(const bool bNeedSleep); +int storage_trunk_destroy_ex(const bool bNeedSleep, + const bool bSaveData); -#define storage_trunk_destroy() storage_trunk_destroy_ex(false) +#define storage_trunk_destroy() storage_trunk_destroy_ex(false, true) int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult); int trunk_alloc_confirm(const FDFSTrunkFullInfo *pTrunkInfo, const int status); @@ -92,6 +104,8 @@ int trunk_create_trunk_file_advance(void *args); int trunk_binlog_compress_func(void *args); +int storage_trunk_binlog_compress_check_recovery(); + int storage_delete_trunk_data_file(); char *storage_trunk_get_data_filename(char *full_filename); diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index b94a2bf..a0ffacb 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -38,10 +39,11 @@ #include "storage_sync_func.h" #include "trunk_sync.h" -#define TRUNK_SYNC_BINLOG_FILENAME "binlog" +#define TRUNK_SYNC_BINLOG_FILENAME "binlog" #define TRUNK_SYNC_BINLOG_ROLLBACK_EXT ".rollback" -#define TRUNK_SYNC_MARK_FILE_EXT ".mark" -#define TRUNK_DIR_NAME "trunk" +#define TRUNK_SYNC_MARK_FILE_EXT_STR ".mark" +#define TRUNK_SYNC_MARK_FILE_EXT_LEN (sizeof(TRUNK_SYNC_MARK_FILE_EXT_STR) - 1) +#define TRUNK_DIR_NAME "trunk" #define MARK_ITEM_BINLOG_FILE_OFFSET "binlog_offset" static int trunk_binlog_fd = -1; @@ -85,7 +87,7 @@ char *get_trunk_binlog_filename(char *full_filename) return full_filename; } -static char *get_trunk_rollback_filename(char *full_filename) +static char *get_trunk_binlog_rollback_filename(char *full_filename) { get_trunk_binlog_filename(full_filename); if (strlen(full_filename) + sizeof(TRUNK_SYNC_BINLOG_ROLLBACK_EXT) > @@ -97,6 +99,38 @@ static char *get_trunk_rollback_filename(char *full_filename) return full_filename; } +static char *get_trunk_data_rollback_filename(char *full_filename) +{ + storage_trunk_get_data_filename(full_filename); + if (strlen(full_filename) + sizeof(TRUNK_SYNC_BINLOG_ROLLBACK_EXT) > + MAX_PATH_SIZE) + { + return NULL; + } + strcat(full_filename, TRUNK_SYNC_BINLOG_ROLLBACK_EXT); + return full_filename; +} + +char *get_trunk_binlog_tmp_filename_ex(const char *binlog_filename, + char *tmp_filename) +{ + const char *true_binlog_filename; + char filename[MAX_PATH_SIZE]; + + if (binlog_filename == NULL) + { + get_trunk_binlog_filename(filename); + true_binlog_filename = filename; + } + else + { + true_binlog_filename = binlog_filename; + } + + sprintf(tmp_filename, "%s.tmp", true_binlog_filename); + return tmp_filename; +} + static int trunk_binlog_open_writer(const char *binlog_filename) { trunk_binlog_fd = open(binlog_filename, O_WRONLY | O_CREAT | @@ -264,12 +298,15 @@ int kill_trunk_sync_threads() int trunk_sync_notify_thread_reset_offset() { int result; + int i; + int count; + bool done; TrunkSyncThreadInfo **thread_info; TrunkSyncThreadInfo **info_end; if (sync_thread_info_array.thread_data == NULL) { - return 0; + return EINVAL; } if ((result=pthread_mutex_lock(&trunk_sync_thread_lock)) != 0) @@ -280,6 +317,7 @@ int trunk_sync_notify_thread_reset_offset() __LINE__, result, STRERROR(result)); } + count = 0; info_end = sync_thread_info_array.thread_data + sync_thread_info_array.alloc_count; for (thread_info=sync_thread_info_array.thread_data; @@ -288,6 +326,7 @@ int trunk_sync_notify_thread_reset_offset() if ((*thread_info)->running) { (*thread_info)->reset_binlog_offset = true; + count++; } } @@ -299,7 +338,59 @@ int trunk_sync_notify_thread_reset_offset() __LINE__, result, STRERROR(result)); } - return result; + logInfo("file: "__FILE__", line: %d, " + "notify %d trunk sync threads to reset offset.", + __LINE__, count); + + done = false; + for (i=0; i<300 && g_continue_flag; i++) + { + info_end = sync_thread_info_array.thread_data + + sync_thread_info_array.alloc_count; + for (thread_info=sync_thread_info_array.thread_data; + thread_inforunning && (*thread_info)->reset_binlog_offset) + { + break; + } + } + + if (thread_info == info_end) + { + done = true; + break; + } + + sleep(1); + } + + if (done) + { + logInfo("file: "__FILE__", line: %d, " + "trunk sync threads reset binlog offset done.", + __LINE__); + return 0; + } + else + { + count = 0; + info_end = sync_thread_info_array.thread_data + + sync_thread_info_array.alloc_count; + for (thread_info=sync_thread_info_array.thread_data; + thread_inforunning && (*thread_info)->reset_binlog_offset) + { + count++; + } + } + + logError("file: "__FILE__", line: %d, " + "%d trunk sync threads reset binlog offset timeout.", + __LINE__, count); + return EBUSY; + } } int trunk_binlog_sync_func(void *args) @@ -339,80 +430,120 @@ int trunk_binlog_truncate() return 0; } -int trunk_binlog_compress_apply() +static int trunk_binlog_delete_rollback_file(const char *filename, + const bool silence) { int result; - char binlog_filename[MAX_PATH_SIZE]; - char rollback_filename[MAX_PATH_SIZE]; + if (access(filename, F_OK) == 0) + { + if (!silence) + { + logWarning("file: "__FILE__", line: %d, " + "rollback file %s exist, delete it!", + __LINE__, filename); + } + if (unlink(filename) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result != ENOENT) + { + logError("file: "__FILE__", line: %d, " + "unlink file %s fail, errno: %d, error info: %s", + __LINE__, filename, result, STRERROR(result)); + return result; + } + } + } + else + { + result = errno != 0 ? errno : EPERM; + if (result != ENOENT) + { + logError("file: "__FILE__", line: %d, " + "access file %s fail, errno: %d, error info: %s", + __LINE__, filename, result, STRERROR(result)); + return result; + } + } - get_trunk_binlog_filename(binlog_filename); - if (get_trunk_rollback_filename(rollback_filename) == NULL) + return 0; +} + +int trunk_binlog_compress_delete_binlog_rollback_file(const bool silence) +{ + char binlog_rollback_filename[MAX_PATH_SIZE]; + + if (get_trunk_binlog_rollback_filename(binlog_rollback_filename) == NULL) { - logError("file: "__FILE__", line: %d, " \ - "filename: %s is too long", - __LINE__, binlog_filename); + logError("file: "__FILE__", line: %d, " + "binlog rollback filename is too long", __LINE__); return ENAMETOOLONG; } - if (trunk_binlog_fd < 0) - { - if (access(binlog_filename, F_OK) == 0) - { - if (rename(binlog_filename, rollback_filename) != 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "rename %s to %s fail, " \ - "errno: %d, error info: %s", - __LINE__, binlog_filename, - rollback_filename, result, - STRERROR(result)); - return result; - } - } - else if (errno != ENOENT) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "call access %s fail, " \ - "errno: %d, error info: %s", - __LINE__, binlog_filename, - result, STRERROR(result)); - return result; - } + return trunk_binlog_delete_rollback_file(binlog_rollback_filename, silence); +} - return 0; - } +int trunk_binlog_compress_delete_rollback_files(const bool silence) +{ + int result; + char data_rollback_filename[MAX_PATH_SIZE]; - pthread_mutex_lock(&trunk_sync_thread_lock); - - do + if ((result=trunk_binlog_compress_delete_binlog_rollback_file( + silence)) != 0) { - if ((result=trunk_binlog_close_writer(false)) != 0) - { - break; - } + return result; + } - if (rename(binlog_filename, rollback_filename) != 0) + if (get_trunk_data_rollback_filename(data_rollback_filename) == NULL) + { + logError("file: "__FILE__", line: %d, " + "data rollback filename is too long", __LINE__); + return ENAMETOOLONG; + } + + if ((result=trunk_binlog_delete_rollback_file(data_rollback_filename, + silence)) != 0) + { + return result; + } + + return 0; +} + +static int trunk_binlog_rename_file(const char *src_filename, + const char *dest_filename, const int log_ignore_errno) +{ + int result; + if (access(src_filename, F_OK) == 0) + { + if (rename(src_filename, dest_filename) != 0) { result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "rename %s to %s fail, " \ + logError("file: "__FILE__", line: %d, " + "rename %s to %s fail, " "errno: %d, error info: %s", - __LINE__, binlog_filename, rollback_filename, - result, STRERROR(result)); - break; + __LINE__, src_filename, + dest_filename, result, + STRERROR(result)); + return result; } - - if ((result=trunk_binlog_open_writer(binlog_filename)) != 0) + } + else + { + result = errno != 0 ? errno : EIO; + if (result - log_ignore_errno != 0) { - rename(rollback_filename, binlog_filename); //rollback - break; + logError("file: "__FILE__", line: %d, " + "call access %s fail, " + "errno: %d, error info: %s", + __LINE__, src_filename, + result, STRERROR(result)); } - } while (0); - pthread_mutex_unlock(&trunk_sync_thread_lock); - return result; + return result; + } + + return 0; } static int trunk_binlog_open_read(const char *filename, @@ -445,7 +576,7 @@ static int trunk_binlog_open_read(const char *filename, return fd; } -static int trunk_binlog_merge_file(int old_fd) +static int trunk_binlog_merge_file(int old_fd, const int stage) { int result; int tmp_fd; @@ -454,19 +585,19 @@ static int trunk_binlog_merge_file(int old_fd) char tmp_filename[MAX_PATH_SIZE]; char buff[64 * 1024]; - get_trunk_binlog_filename(binlog_filename); - sprintf(tmp_filename, "%s.tmp", binlog_filename); + get_trunk_binlog_filename(binlog_filename); + get_trunk_binlog_tmp_filename_ex(binlog_filename, tmp_filename); tmp_fd = open(tmp_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (tmp_fd < 0) { result = errno != 0 ? errno : EACCES; - logError("file: "__FILE__", line: %d, " \ - "open file \"%s\" fail, " \ - "errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "open file \"%s\" fail, " + "errno: %d, error info: %s", __LINE__, tmp_filename, result, STRERROR(result)); return result; } - + while ((bytes=fc_safe_read(old_fd, buff, sizeof(buff))) > 0) { if (fc_safe_write(tmp_fd, buff, bytes) != bytes) @@ -523,6 +654,9 @@ static int trunk_binlog_merge_file(int old_fd) } close(tmp_fd); + g_trunk_binlog_compress_stage = stage; + storage_write_to_sync_ini_file(); + if (rename(tmp_filename, binlog_filename) != 0) { result = errno != 0 ? errno : EPERM; @@ -537,6 +671,269 @@ static int trunk_binlog_merge_file(int old_fd) return 0; } +static int trunk_compress_rollback_data_file() +{ + int result; + char data_filename[MAX_PATH_SIZE]; + char data_rollback_filename[MAX_PATH_SIZE]; + struct stat fs; + + storage_trunk_get_data_filename(data_filename); + get_trunk_data_rollback_filename(data_rollback_filename); + + if (stat(data_rollback_filename, &fs) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result == ENOENT) + { + return 0; + } + + logError("file: "__FILE__", line: %d, " + "stat file %s fail, errno: %d, error info: %s", + __LINE__, data_rollback_filename, + result, STRERROR(result)); + return result; + } + + if (unlink(data_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result != ENOENT) + { + logError("file: "__FILE__", line: %d, " + "unlink %s fail, errno: %d, error info: %s", + __LINE__, data_filename, result, STRERROR(result)); + return result; + } + } + + if (fs.st_size == 0) + { + unlink(data_rollback_filename); //delete zero file directly + return 0; + } + + if (rename(data_rollback_filename, data_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result == ENOENT) + { + return 0; + } + + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", + __LINE__, data_rollback_filename, + data_filename, result, STRERROR(result)); + return result; + } + + return 0; +} + +static int trunk_compress_rollback_binlog_file(const char *binlog_filename) +{ + int result; + int rollback_fd; + char binlog_rollback_filename[MAX_PATH_SIZE]; + struct stat fs; + + get_trunk_binlog_rollback_filename(binlog_rollback_filename); + if (stat(binlog_rollback_filename, &fs) != 0) + { + result = errno != 0 ? errno : ENOENT; + if (result == ENOENT) + { + return 0; + } + logError("file: "__FILE__", line: %d, " + "stat file %s fail, errno: %d, error info: %s", + __LINE__, binlog_rollback_filename, + result, STRERROR(result)); + return result; + } + + if (fs.st_size == 0) + { + unlink(binlog_rollback_filename); //delete zero file directly + return 0; + } + + if (access(binlog_filename, F_OK) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result == ENOENT) + { + if (rename(binlog_rollback_filename, binlog_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result != ENOENT) + { + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", + __LINE__, binlog_rollback_filename, + binlog_filename, errno, STRERROR(errno)); + return result; + } + } + + return 0; + } + else + { + logError("file: "__FILE__", line: %d, " + "access file %s fail, errno: %d, error info: %s", + __LINE__, binlog_filename, errno, STRERROR(errno)); + return result; + } + } + + if ((rollback_fd=trunk_binlog_open_read(binlog_rollback_filename, + false)) < 0) + { + result = errno != 0 ? errno : EPERM; + if (result == ENOENT) + { + return 0; + } + + return result; + } + + result = trunk_binlog_merge_file(rollback_fd, + STORAGE_TRUNK_COMPRESS_STAGE_ROLLBACK_MERGING); + close(rollback_fd); + + g_trunk_binlog_compress_stage = + STORAGE_TRUNK_COMPRESS_STAGE_ROLLBACK_MERGE_DONE; + storage_write_to_sync_ini_file(); + + if (unlink(binlog_rollback_filename) != 0) + { + logWarning("file: "__FILE__", line: %d, " + "unlink %s fail, errno: %d, error info: %s", + __LINE__, binlog_rollback_filename, + errno, STRERROR(errno)); + } + + return result; +} + +int trunk_binlog_compress_delete_temp_files_after_commit() +{ + int result; + char data_filename[MAX_PATH_SIZE]; + + storage_trunk_get_data_filename(data_filename); + if (unlink(data_filename) != 0) + { + result = errno != 0 ? errno : ENOENT; + logError("file: "__FILE__", line: %d, " + "unlink %s fail, errno: %d, error info: %s", + __LINE__, data_filename, + result, STRERROR(result)); + if (result != ENOENT) + { + return result; + } + } + + return trunk_binlog_compress_delete_rollback_files(true); +} + +int trunk_binlog_compress_apply() +{ + int result; + int open_res; + bool need_open_binlog; + char binlog_filename[MAX_PATH_SIZE]; + char data_filename[MAX_PATH_SIZE]; + char binlog_rollback_filename[MAX_PATH_SIZE]; + char data_rollback_filename[MAX_PATH_SIZE]; + + get_trunk_binlog_filename(binlog_filename); + if (get_trunk_binlog_rollback_filename(binlog_rollback_filename) == NULL) + { + logError("file: "__FILE__", line: %d, " + "filename: %s is too long", + __LINE__, binlog_filename); + return ENAMETOOLONG; + } + + storage_trunk_get_data_filename(data_filename); + if (get_trunk_data_rollback_filename(data_rollback_filename) == NULL) + { + logError("file: "__FILE__", line: %d, " + "data rollback filename is too long", __LINE__); + return ENAMETOOLONG; + } + + if (access(binlog_filename, F_OK) != 0) + { + result = errno != 0 ? errno : EPERM; + logError("file: "__FILE__", line: %d, " + "access file: %s is fail, " + "errno: %d, error info: %s", + __LINE__, binlog_filename, + result, STRERROR(result)); + return result; + } + + need_open_binlog = trunk_binlog_fd >= 0; + + pthread_mutex_lock(&trunk_sync_thread_lock); + if (need_open_binlog) + { + trunk_binlog_close_writer(false); + } + + do + { + result = trunk_binlog_rename_file(data_filename, + data_rollback_filename, ENOENT); + if (result != 0) + { + if (result == ENOENT) + { + result = writeToFile(data_rollback_filename, "", 0); + } + + if (result != 0) + { + break; + } + } + + if ((result=trunk_binlog_rename_file(binlog_filename, + binlog_rollback_filename, 0)) != 0) + { + trunk_compress_rollback_data_file(); + break; + } + } while (0); + + if (need_open_binlog) + { + if ((open_res=trunk_binlog_open_writer(binlog_filename)) != 0) + { + trunk_binlog_rename_file(binlog_rollback_filename, + binlog_filename, 0); //rollback + trunk_compress_rollback_data_file(); + + if (result == 0) + { + result = open_res; + } + } + } + + pthread_mutex_unlock(&trunk_sync_thread_lock); + return result; +} + int trunk_binlog_compress_commit() { int result; @@ -544,7 +941,6 @@ int trunk_binlog_compress_commit() bool need_open_binlog; char binlog_filename[MAX_PATH_SIZE]; char data_filename[MAX_PATH_SIZE]; - char rollback_filename[MAX_PATH_SIZE]; need_open_binlog = trunk_binlog_fd >= 0; get_trunk_binlog_filename(binlog_filename); @@ -563,35 +959,26 @@ int trunk_binlog_compress_commit() do { - result = trunk_binlog_merge_file(data_fd); + result = trunk_binlog_merge_file(data_fd, + STORAGE_TRUNK_COMPRESS_STAGE_COMMIT_MERGING); close(data_fd); if (result != 0) { break; } - if (unlink(data_filename) != 0) + + g_trunk_binlog_compress_stage = + STORAGE_TRUNK_COMPRESS_STAGE_COMMIT_MERGE_DONE; + storage_write_to_sync_ini_file(); + + if ((result=trunk_binlog_compress_delete_temp_files_after_commit()) != 0) { - result = errno != 0 ? errno : EPERM; - logError("file: "__FILE__", line: %d, " - "unlink %s fail, errno: %d, error info: %s", - __LINE__, data_filename, - result, STRERROR(result)); break; } - get_trunk_rollback_filename(rollback_filename); - if (access(rollback_filename, F_OK) == 0) - { - if (unlink(rollback_filename) != 0) - { - result = errno != 0 ? errno : EPERM; - logWarning("file: "__FILE__", line: %d, " - "unlink %s fail, errno: %d, error info: %s", - __LINE__, rollback_filename, - result, STRERROR(result)); - break; - } - } + g_trunk_binlog_compress_stage = + STORAGE_TRUNK_COMPRESS_STAGE_COMPRESS_SUCCESS; + storage_write_to_sync_ini_file(); if (need_open_binlog) { @@ -604,89 +991,34 @@ int trunk_binlog_compress_commit() return result; } -int trunk_binlog_compress_rollback() +static int do_compress_rollback() { int result; - int rollback_fd; + bool need_open_binlog; char binlog_filename[MAX_PATH_SIZE]; - char rollback_filename[MAX_PATH_SIZE]; - struct stat fs; + need_open_binlog = trunk_binlog_fd >= 0; get_trunk_binlog_filename(binlog_filename); - get_trunk_rollback_filename(rollback_filename); - if (trunk_binlog_fd < 0) - { - if (access(rollback_filename, F_OK) == 0) - { - if (rename(rollback_filename, binlog_filename) != 0) - { - result = errno != 0 ? errno : EPERM; - logError("file: "__FILE__", line: %d, "\ - "rename %s to %s fail, " \ - "errno: %d, error info: %s", - __LINE__, rollback_filename, - binlog_filename, result, - STRERROR(result)); - return result; - } - } - - return 0; - } - - if (stat(rollback_filename, &fs) != 0) - { - result = errno != 0 ? errno : ENOENT; - if (result == ENOENT) - { - return 0; - } - logError("file: "__FILE__", line: %d, " - "stat file %s fail, errno: %d, error info: %s", - __LINE__, rollback_filename, - result, STRERROR(result)); - return result; - } - - if (fs.st_size == 0) - { - unlink(rollback_filename); //delete zero file directly - return 0; - } pthread_mutex_lock(&trunk_sync_thread_lock); + if (need_open_binlog) + { + trunk_binlog_close_writer(false); + } + do { - if ((result=trunk_binlog_close_writer(false)) != 0) + if ((result=trunk_compress_rollback_binlog_file(binlog_filename)) != 0) { break; } - if ((rollback_fd=trunk_binlog_open_read(rollback_filename, - false)) < 0) + if ((result=trunk_compress_rollback_data_file()) != 0) { - result = errno != 0 ? errno : ENOENT; break; } - result = trunk_binlog_merge_file(rollback_fd); - close(rollback_fd); - if (result == 0) - { - if (unlink(rollback_filename) != 0) - { - result = errno != 0 ? errno : EPERM; - logWarning("file: "__FILE__", line: %d, " \ - "unlink %s fail, " \ - "errno: %d, error info: %s", - __LINE__, rollback_filename, - result, STRERROR(result)); - break; - } - - result = trunk_binlog_open_writer(binlog_filename); - } - else + if (need_open_binlog) { result = trunk_binlog_open_writer(binlog_filename); } @@ -696,6 +1028,20 @@ int trunk_binlog_compress_rollback() return result; } +int trunk_binlog_compress_rollback() +{ + int result; + + if ((result=do_compress_rollback()) == 0) + { + g_trunk_binlog_compress_stage = + STORAGE_TRUNK_COMPRESS_STAGE_FINISHED; + storage_write_to_sync_ini_file(); + } + + return result; +} + static int trunk_binlog_fsync_ex(const bool bNeedLock, \ const char *buff, int *length) { @@ -943,13 +1289,13 @@ static char *trunk_get_mark_filename_by_id_and_port(const char *storage_id, \ { snprintf(full_filename, filename_size, \ "%s/data/"TRUNK_DIR_NAME"/%s%s", g_fdfs_base_path, \ - storage_id, TRUNK_SYNC_MARK_FILE_EXT); + storage_id, TRUNK_SYNC_MARK_FILE_EXT_STR); } else { snprintf(full_filename, filename_size, \ "%s/data/"TRUNK_DIR_NAME"/%s_%d%s", g_fdfs_base_path, \ - storage_id, port, TRUNK_SYNC_MARK_FILE_EXT); + storage_id, port, TRUNK_SYNC_MARK_FILE_EXT_STR); } return full_filename; @@ -960,7 +1306,7 @@ static char *trunk_get_mark_filename_by_ip_and_port(const char *ip_addr, \ { snprintf(full_filename, filename_size, \ "%s/data/"TRUNK_DIR_NAME"/%s_%d%s", g_fdfs_base_path, \ - ip_addr, port, TRUNK_SYNC_MARK_FILE_EXT); + ip_addr, port, TRUNK_SYNC_MARK_FILE_EXT_STR); return full_filename; } @@ -990,7 +1336,6 @@ static char *trunk_get_mark_filename_by_id(const char *storage_id, int trunk_reader_init(const FDFSStorageBrief *pStorage, TrunkBinLogReader *pReader, const bool reset_binlog_offset) { - char full_filename[MAX_PATH_SIZE]; IniContext iniContext; int result; int64_t saved_binlog_offset; @@ -999,7 +1344,6 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, saved_binlog_offset = pReader->binlog_offset; memset(pReader, 0, sizeof(TrunkBinLogReader)); - pReader->mark_fd = -1; pReader->binlog_fd = -1; pReader->binlog_buff.buffer = (char *)malloc( \ @@ -1023,7 +1367,7 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, { strcpy(pReader->storage_id, pStorage->id); } - trunk_mark_filename_by_reader(pReader, full_filename); + trunk_mark_filename_by_reader(pReader, pReader->mark_filename); if (pStorage == NULL) { @@ -1032,22 +1376,23 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, } else { - bFileExist = fileExists(full_filename); + bFileExist = fileExists(pReader->mark_filename); if (!bFileExist && (g_use_storage_id && pStorage != NULL)) { char old_mark_filename[MAX_PATH_SIZE]; - trunk_get_mark_filename_by_ip_and_port( \ - pStorage->ip_addr, g_server_port, \ + trunk_get_mark_filename_by_ip_and_port( + pStorage->ip_addr, g_server_port, old_mark_filename, sizeof(old_mark_filename)); if (fileExists(old_mark_filename)) { - if (rename(old_mark_filename, full_filename)!=0) + if (rename(old_mark_filename, + pReader->mark_filename) != 0) { - logError("file: "__FILE__", line: %d, "\ - "rename file %s to %s fail" \ - ", errno: %d, error info: %s", \ - __LINE__, old_mark_filename, \ - full_filename, errno, \ + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", + __LINE__, old_mark_filename, + pReader->mark_filename, errno, STRERROR(errno)); return errno != 0 ? errno : EACCES; } @@ -1059,35 +1404,36 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, if (bFileExist) { memset(&iniContext, 0, sizeof(IniContext)); - if ((result=iniLoadFromFile(full_filename, &iniContext)) \ - != 0) + if ((result=iniLoadFromFile(pReader->mark_filename, + &iniContext)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "load from mark file \"%s\" fail, " \ - "error code: %d", \ - __LINE__, full_filename, result); + logError("file: "__FILE__", line: %d, " + "load from mark file \"%s\" fail, " + "error code: %d", __LINE__, + pReader->mark_filename, result); return result; } if (iniContext.global.count < 1) { iniFreeContext(&iniContext); - logError("file: "__FILE__", line: %d, " \ - "in mark file \"%s\", item count: %d < 7", \ - __LINE__, full_filename, iniContext.global.count); + logError("file: "__FILE__", line: %d, " + "in mark file \"%s\", item count: %d < 1", + __LINE__, pReader->mark_filename, + iniContext.global.count); return ENOENT; } - pReader->binlog_offset = iniGetInt64Value(NULL, \ - MARK_ITEM_BINLOG_FILE_OFFSET, \ + pReader->binlog_offset = iniGetInt64Value(NULL, + MARK_ITEM_BINLOG_FILE_OFFSET, &iniContext, -1); if (pReader->binlog_offset < 0) { iniFreeContext(&iniContext); - logError("file: "__FILE__", line: %d, " \ - "in mark file \"%s\", binlog_offset: "\ - "%"PRId64" < 0", \ - __LINE__, full_filename, \ + logError("file: "__FILE__", line: %d, " + "in mark file \"%s\", binlog_offset: " + "%"PRId64" < 0", __LINE__, + pReader->mark_filename, pReader->binlog_offset); return EINVAL; } @@ -1096,18 +1442,6 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, } pReader->last_binlog_offset = pReader->binlog_offset; - - pReader->mark_fd = open(full_filename, O_WRONLY | O_CREAT, 0644); - if (pReader->mark_fd < 0) - { - logError("file: "__FILE__", line: %d, " \ - "open mark file \"%s\" fail, " \ - "error no: %d, error info: %s", \ - __LINE__, full_filename, \ - errno, STRERROR(errno)); - return errno != 0 ? errno : ENOENT; - } - if (!bFileExist && pStorage != NULL) { if ((result=trunk_write_to_mark_file(pReader)) != 0) @@ -1139,12 +1473,6 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, void trunk_reader_destroy(TrunkBinLogReader *pReader) { - if (pReader->mark_fd >= 0) - { - close(pReader->mark_fd); - pReader->mark_fd = -1; - } - if (pReader->binlog_fd >= 0) { close(pReader->binlog_fd); @@ -1170,11 +1498,11 @@ static int trunk_write_to_mark_file(TrunkBinLogReader *pReader) "%s=%"PRId64"\n", MARK_ITEM_BINLOG_FILE_OFFSET, pReader->binlog_offset); - if ((result=storage_write_to_fd(pReader->mark_fd, - trunk_mark_filename_by_reader, pReader, buff, len)) == 0) - { + if ((result=safeWriteToFile(pReader->mark_filename, buff, len)) == 0) + { + STORAGE_CHOWN(pReader->mark_filename, geteuid(), getegid()) pReader->last_binlog_offset = pReader->binlog_offset; - } + } return result; } @@ -1518,7 +1846,6 @@ static void *trunk_sync_thread_entrance(void* arg) memset(local_ip_addr, 0, sizeof(local_ip_addr)); memset(&reader, 0, sizeof(reader)); - reader.mark_fd = -1; reader.binlog_fd = -1; current_time = g_current_time; @@ -1624,13 +1951,13 @@ static void *trunk_sync_thread_entrance(void* arg) read_result = trunk_binlog_preread(&reader); if (read_result == ENOENT) { - if (reader.last_binlog_offset != \ + if (reader.last_binlog_offset != reader.binlog_offset) { if (trunk_write_to_mark_file(&reader)!=0) { - logCrit("file: "__FILE__", line: %d, " \ - "trunk_write_to_mark_file fail, " \ + logCrit("file: "__FILE__", line: %d, " + "trunk_write_to_mark_file fail, " "program exit!", __LINE__); g_continue_flag = false; break; @@ -1905,7 +2232,7 @@ void trunk_waiting_sync_thread_exit() } count = 0; - while (g_trunk_sync_thread_count > 0 && count < 20) + while (g_trunk_sync_thread_count > 0 && count < 60) { usleep(50000); count++; @@ -1927,32 +2254,98 @@ void trunk_waiting_sync_thread_exit() } } -int trunk_unlink_all_mark_files() +int trunk_unlink_all_mark_files(const bool force_delete) { - FDFSStorageServer *pStorageServer; - FDFSStorageServer *pServerEnd; + char file_path[MAX_PATH_SIZE]; + char old_filename[MAX_PATH_SIZE]; + char new_filename[MAX_PATH_SIZE]; + DIR *dir; + struct dirent *ent; int result; + int name_len; + time_t t; + struct tm tm; - pServerEnd = g_storage_servers + g_storage_count; - for (pStorageServer=g_storage_servers; pStorageServerserver))) - { - continue; - } + t = g_current_time; + localtime_r(&t, &tm); - if ((result=trunk_unlink_mark_file( - pStorageServer->server.id)) != 0) - { - if (result != ENOENT) - { - return result; - } - } - } + snprintf(file_path, sizeof(file_path), + "%s/data/%s", g_fdfs_base_path, TRUNK_DIR_NAME); - return 0; + if ((dir=opendir(file_path)) == NULL) + { + result = errno != 0 ? errno : EPERM; + logError("file: "__FILE__", line: %d, " + "call opendir %s fail, errno: %d, error info: %s", + __LINE__, file_path, result, STRERROR(result)); + return result; + } + + result = 0; + while ((ent=readdir(dir)) != NULL) + { + name_len = strlen(ent->d_name); + if (name_len <= TRUNK_SYNC_MARK_FILE_EXT_LEN) + { + continue; + } + if (memcmp(ent->d_name + (name_len - + TRUNK_SYNC_MARK_FILE_EXT_LEN), + TRUNK_SYNC_MARK_FILE_EXT_STR, + TRUNK_SYNC_MARK_FILE_EXT_LEN) != 0) + { + continue; + } + + snprintf(old_filename, sizeof(old_filename), "%s/%s", + file_path, ent->d_name); + if (force_delete) + { + if (unlink(old_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result == ENOENT) + { + result = 0; + } + else + { + logError("file: "__FILE__", line: %d, " + "unlink %s fail, errno: %d, error info: %s", + __LINE__, old_filename, + result, STRERROR(result)); + break; + } + } + } + else + { + snprintf(new_filename, sizeof(new_filename), + "%s.%04d%02d%02d%02d%02d%02d", old_filename, + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + if (rename(old_filename, new_filename) != 0) + { + result = errno != 0 ? errno : EPERM; + if (result == ENOENT) + { + result = 0; + } + else + { + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", + __LINE__, old_filename, new_filename, + result, STRERROR(result)); + break; + } + } + } + } + + closedir(dir); + return result; } int trunk_binlog_get_write_version() diff --git a/storage/trunk_mgr/trunk_sync.h b/storage/trunk_mgr/trunk_sync.h index 1bb7309..c853e28 100644 --- a/storage/trunk_mgr/trunk_sync.h +++ b/storage/trunk_mgr/trunk_sync.h @@ -28,8 +28,8 @@ extern "C" { typedef struct { char storage_id[FDFS_STORAGE_ID_MAX_SIZE]; + char mark_filename[MAX_PATH_SIZE]; BinLogBuffer binlog_buff; - int mark_fd; int binlog_fd; int64_t binlog_offset; int64_t last_binlog_offset; //for write to mark file @@ -65,7 +65,7 @@ void trunk_waiting_sync_thread_exit(); char *get_trunk_binlog_filename(char *full_filename); char *trunk_mark_filename_by_reader(const void *pArg, char *full_filename); -int trunk_unlink_all_mark_files(); +int trunk_unlink_all_mark_files(const bool force_delete); int trunk_unlink_mark_file(const char *storage_id); int trunk_rename_mark_file(const char *old_ip_addr, const int old_port, \ const char *new_ip_addr, const int new_port); @@ -78,6 +78,9 @@ int trunk_reader_init(const FDFSStorageBrief *pStorage, void trunk_reader_destroy(TrunkBinLogReader *pReader); //trunk binlog compress +int trunk_binlog_compress_delete_binlog_rollback_file(const bool silence); +int trunk_binlog_compress_delete_rollback_files(const bool silence); +int trunk_binlog_compress_delete_temp_files_after_commit(); int trunk_binlog_compress_apply(); int trunk_binlog_compress_commit(); int trunk_binlog_compress_rollback(); @@ -85,6 +88,14 @@ int trunk_binlog_compress_rollback(); int trunk_sync_notify_thread_reset_offset(); int trunk_binlog_get_write_version(); +char *get_trunk_binlog_tmp_filename_ex(const char *binlog_filename, + char *tmp_filename); + +static inline char *get_trunk_binlog_tmp_filename(char *tmp_filename) +{ + return get_trunk_binlog_tmp_filename_ex(NULL, tmp_filename); +} + #ifdef __cplusplus } #endif From 4a6f89c6924b83ee23651862b01936c320ad243e Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 19 Dec 2019 18:38:01 +0800 Subject: [PATCH 71/95] support backup binlog file when truncate trunk binlog --- HISTORY | 6 +- conf/tracker.conf | 5 + storage/storage_param_getter.c | 126 ++++++++--------- storage/trunk_mgr/trunk_mem.c | 1 + storage/trunk_mgr/trunk_mem.h | 1 + storage/trunk_mgr/trunk_sync.c | 243 ++++++++++++++++++++++++++++++--- tracker/tracker_func.c | 169 ++++++++++++----------- tracker/tracker_global.c | 1 + tracker/tracker_global.h | 1 + tracker/tracker_service.c | 87 ++++++------ 10 files changed, 431 insertions(+), 209 deletions(-) diff --git a/HISTORY b/HISTORY index 5bf5b05..b67e019 100644 --- a/HISTORY +++ b/HISTORY @@ -1,12 +1,14 @@ -Version 6.05 2019-12-18 +Version 6.05 2019-12-19 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version * trunk server support compress the trunk binlog periodically, - config items in tracker.conf: trunk_compress_binlog_interval + the config items in tracker.conf: trunk_compress_binlog_interval and trunk_compress_binlog_time_base * trunk binlog compression support transaction + * support backup binlog file when truncate trunk binlog, + the config item in tracker.conf: trunk_binlog_max_backups Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST diff --git a/conf/tracker.conf b/conf/tracker.conf index adccc65..c328270 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -217,6 +217,11 @@ trunk_compress_binlog_interval = 86400 # since V6.05 trunk_compress_binlog_time_base = 03:00 +# max backups for the trunk binlog file +# default value is 0 (never backup) +# since V6.05 +trunk_binlog_max_backups = 7 + # if use storage server ID instead of IP address # if you want to use dual IPs for storage server, you MUST set # this parameter to true, and configure the dual IPs in the file diff --git a/storage/storage_param_getter.c b/storage/storage_param_getter.c index e439116..73f526d 100644 --- a/storage/storage_param_getter.c +++ b/storage/storage_param_getter.c @@ -74,30 +74,30 @@ int storage_get_params_from_tracker() char reserved_space_str[32]; char *pIdType; - if ((result=fdfs_get_ini_context_from_tracker(&g_tracker_group, \ - &iniContext, (bool * volatile)&g_continue_flag, \ + if ((result=fdfs_get_ini_context_from_tracker(&g_tracker_group, + &iniContext, (bool * volatile)&g_continue_flag, g_client_bind_addr, g_bind_addr)) != 0) { return result; } - g_storage_ip_changed_auto_adjust = iniGetBoolValue(NULL, \ - "storage_ip_changed_auto_adjust", \ + g_storage_ip_changed_auto_adjust = iniGetBoolValue(NULL, + "storage_ip_changed_auto_adjust", &iniContext, false); - g_store_path_mode = iniGetIntValue(NULL, "store_path", &iniContext, \ + g_store_path_mode = iniGetIntValue(NULL, "store_path", &iniContext, FDFS_STORE_PATH_ROUND_ROBIN); - if ((result=fdfs_parse_storage_reserved_space(&iniContext, \ + if ((result=fdfs_parse_storage_reserved_space(&iniContext, &g_storage_reserved_space)) != 0) { iniFreeContext(&iniContext); return result; } - if (g_storage_reserved_space.flag == \ + if (g_storage_reserved_space.flag == TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB) { - g_avg_storage_reserved_mb = g_storage_reserved_space.rs.mb \ + g_avg_storage_reserved_mb = g_storage_reserved_space.rs.mb / g_fdfs_store_paths.count; } else @@ -105,31 +105,31 @@ int storage_get_params_from_tracker() g_avg_storage_reserved_mb = 0; } - g_use_storage_id = iniGetBoolValue(NULL, "use_storage_id", \ + g_use_storage_id = iniGetBoolValue(NULL, "use_storage_id", &iniContext, false); - use_trunk_file = iniGetBoolValue(NULL, "use_trunk_file", \ + use_trunk_file = iniGetBoolValue(NULL, "use_trunk_file", &iniContext, false); - g_slot_min_size = iniGetIntValue(NULL, "slot_min_size", \ + g_slot_min_size = iniGetIntValue(NULL, "slot_min_size", &iniContext, 256); - g_trunk_file_size = iniGetIntValue(NULL, "trunk_file_size", \ + g_trunk_file_size = iniGetIntValue(NULL, "trunk_file_size", &iniContext, 64 * 1024 * 1024); - g_slot_max_size = iniGetIntValue(NULL, "slot_max_size", \ + g_slot_max_size = iniGetIntValue(NULL, "slot_max_size", &iniContext, g_trunk_file_size / 2); - g_trunk_create_file_advance = iniGetBoolValue(NULL, \ + g_trunk_create_file_advance = iniGetBoolValue(NULL, "trunk_create_file_advance", &iniContext, false); - if ((result=get_time_item_from_conf(&iniContext, \ - "trunk_create_file_time_base", \ + if ((result=get_time_item_from_conf(&iniContext, + "trunk_create_file_time_base", &g_trunk_create_file_time_base, 2, 0)) != 0) { iniFreeContext(&iniContext); return result; } - g_trunk_create_file_interval = iniGetIntValue(NULL, \ - "trunk_create_file_interval", &iniContext, \ + g_trunk_create_file_interval = iniGetIntValue(NULL, + "trunk_create_file_interval", &iniContext, 86400); - g_trunk_create_file_space_threshold = iniGetInt64Value(NULL, \ - "trunk_create_file_space_threshold", \ + g_trunk_create_file_space_threshold = iniGetInt64Value(NULL, + "trunk_create_file_space_threshold", &iniContext, 0); g_trunk_init_check_occupying = iniGetBoolValue(NULL, @@ -147,7 +147,9 @@ int storage_get_params_from_tracker() return result; } - g_store_slave_file_use_link = iniGetBoolValue(NULL, \ + g_trunk_binlog_max_backups = iniGetIntValue(NULL, + "trunk_binlog_max_backups", &iniContext, 0); + g_store_slave_file_use_link = iniGetBoolValue(NULL, "store_slave_file_use_link", &iniContext, false); pIdType = iniGetStrValue(NULL, "id_type_in_filename", &iniContext); @@ -178,48 +180,50 @@ int storage_get_params_from_tracker() } g_if_use_trunk_file = use_trunk_file; - logInfo("file: "__FILE__", line: %d, " \ - "use_storage_id=%d, " \ - "id_type_in_filename=%s, " \ - "storage_ip_changed_auto_adjust=%d, " \ - "store_path=%d, " \ - "reserved_storage_space=%s, " \ - "use_trunk_file=%d, " \ - "slot_min_size=%d, " \ - "slot_max_size=%d MB, " \ - "trunk_file_size=%d MB, " \ - "trunk_create_file_advance=%d, " \ - "trunk_create_file_time_base=%02d:%02d, " \ - "trunk_create_file_interval=%d, " \ - "trunk_create_file_space_threshold=%d GB, " \ - "trunk_init_check_occupying=%d, " \ - "trunk_init_reload_from_binlog=%d, " \ - "trunk_compress_binlog_min_interval=%d, " \ - "trunk_compress_binlog_interval=%d, " \ - "trunk_compress_binlog_time_base=%02d:%02d, " \ - "store_slave_file_use_link=%d", \ - __LINE__, g_use_storage_id, \ - g_id_type_in_filename == FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", \ - g_storage_ip_changed_auto_adjust, \ - g_store_path_mode, fdfs_storage_reserved_space_to_string( \ - &g_storage_reserved_space, reserved_space_str), \ - g_if_use_trunk_file, g_slot_min_size, \ - g_slot_max_size / FDFS_ONE_MB, \ - g_trunk_file_size / FDFS_ONE_MB, \ - g_trunk_create_file_advance, \ - g_trunk_create_file_time_base.hour, \ - g_trunk_create_file_time_base.minute, \ - g_trunk_create_file_interval, \ - (int)(g_trunk_create_file_space_threshold / \ - (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, \ - g_trunk_init_reload_from_binlog, \ - g_trunk_compress_binlog_min_interval, \ - g_trunk_compress_binlog_interval, \ - g_trunk_compress_binlog_time_base.hour, \ - g_trunk_compress_binlog_time_base.minute, \ + logInfo("file: "__FILE__", line: %d, " + "use_storage_id=%d, " + "id_type_in_filename=%s, " + "storage_ip_changed_auto_adjust=%d, " + "store_path=%d, " + "reserved_storage_space=%s, " + "use_trunk_file=%d, " + "slot_min_size=%d, " + "slot_max_size=%d MB, " + "trunk_file_size=%d MB, " + "trunk_create_file_advance=%d, " + "trunk_create_file_time_base=%02d:%02d, " + "trunk_create_file_interval=%d, " + "trunk_create_file_space_threshold=%d GB, " + "trunk_init_check_occupying=%d, " + "trunk_init_reload_from_binlog=%d, " + "trunk_compress_binlog_min_interval=%d, " + "trunk_compress_binlog_interval=%d, " + "trunk_compress_binlog_time_base=%02d:%02d, " + "trunk_binlog_max_backups=%d, " + "store_slave_file_use_link=%d", + __LINE__, g_use_storage_id, + g_id_type_in_filename == FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", + g_storage_ip_changed_auto_adjust, + g_store_path_mode, fdfs_storage_reserved_space_to_string( + &g_storage_reserved_space, reserved_space_str), + g_if_use_trunk_file, g_slot_min_size, + g_slot_max_size / FDFS_ONE_MB, + g_trunk_file_size / FDFS_ONE_MB, + g_trunk_create_file_advance, + g_trunk_create_file_time_base.hour, + g_trunk_create_file_time_base.minute, + g_trunk_create_file_interval, + (int)(g_trunk_create_file_space_threshold / + (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, + g_trunk_init_reload_from_binlog, + g_trunk_compress_binlog_min_interval, + g_trunk_compress_binlog_interval, + g_trunk_compress_binlog_time_base.hour, + g_trunk_compress_binlog_time_base.minute, + g_trunk_binlog_max_backups, g_store_slave_file_use_link); - if (g_use_storage_id && *g_sync_src_id != '\0' && \ + if (g_use_storage_id && *g_sync_src_id != '\0' && !fdfs_is_server_id_valid(g_sync_src_id)) { if ((result=storage_convert_src_server_id()) == 0) diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 1dff485..4443090 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -56,6 +56,7 @@ TimeInfo g_trunk_compress_binlog_time_base = {0, 0}; int g_trunk_create_file_interval = 86400; int g_trunk_compress_binlog_min_interval = 0; int g_trunk_compress_binlog_interval = 0; +int g_trunk_binlog_max_backups = 0; TrackerServerInfo g_trunk_server = {0, 0}; bool g_if_use_trunk_file = false; bool g_if_trunker_self = false; diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index b7162b5..f8b2d75 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -50,6 +50,7 @@ extern TimeInfo g_trunk_compress_binlog_time_base; extern int g_trunk_create_file_interval; extern int g_trunk_compress_binlog_min_interval; extern int g_trunk_compress_binlog_interval; +extern int g_trunk_binlog_max_backups; extern TrackerServerInfo g_trunk_server; //the trunk server extern bool g_if_use_trunk_file; //if use trunk file extern bool g_trunk_create_file_advance; diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index a0ffacb..54ac1fb 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -39,7 +39,8 @@ #include "storage_sync_func.h" #include "trunk_sync.h" -#define TRUNK_SYNC_BINLOG_FILENAME "binlog" +#define TRUNK_SYNC_BINLOG_FILENAME_STR "binlog" +#define TRUNK_SYNC_BINLOG_FILENAME_LEN (sizeof(TRUNK_SYNC_BINLOG_FILENAME_STR) - 1) #define TRUNK_SYNC_BINLOG_ROLLBACK_EXT ".rollback" #define TRUNK_SYNC_MARK_FILE_EXT_STR ".mark" #define TRUNK_SYNC_MARK_FILE_EXT_LEN (sizeof(TRUNK_SYNC_MARK_FILE_EXT_STR) - 1) @@ -82,7 +83,7 @@ static int trunk_binlog_preread(TrunkBinLogReader *pReader); char *get_trunk_binlog_filename(char *full_filename) { snprintf(full_filename, MAX_PATH_SIZE, \ - "%s/data/"TRUNK_DIR_NAME"/"TRUNK_SYNC_BINLOG_FILENAME, \ + "%s/data/"TRUNK_DIR_NAME"/"TRUNK_SYNC_BINLOG_FILENAME_STR, \ g_fdfs_base_path); return full_filename; } @@ -405,29 +406,227 @@ int trunk_binlog_sync_func(void *args) } } +#define BACKUP_FILENAME_LEN (TRUNK_SYNC_BINLOG_FILENAME_LEN + 15) + +typedef struct +{ + char filename[BACKUP_FILENAME_LEN + 1]; +} TrunkBinlogBackupFileInfo; + +typedef struct +{ + TrunkBinlogBackupFileInfo *files; + int count; + int alloc; +} TrunkBinlogBackupFileArray; + +static int trunk_binlog_check_alloc_filename_array( + TrunkBinlogBackupFileArray *file_array) +{ + int bytes; + TrunkBinlogBackupFileInfo *files; + int alloc; + + if (file_array->count < file_array->alloc) + { + return 0; + } + + if (file_array->alloc == 0) + { + alloc = g_trunk_binlog_max_backups + 1; + } + else + { + alloc = file_array->alloc * 2; + } + + bytes = sizeof(TrunkBinlogBackupFileInfo) * alloc; + files = (TrunkBinlogBackupFileInfo *)malloc(bytes); + if (files == NULL) + { + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail", __LINE__, bytes); + return ENOMEM; + } + + if (file_array->count > 0) + { + memcpy(files, file_array->files, sizeof(TrunkBinlogBackupFileInfo) * + file_array->count); + } + + if (file_array->files != NULL) + { + free(file_array->files); + } + + file_array->files = files; + file_array->alloc = alloc; + return 0; +} + +static int trunk_binlog_compare_filename(const void *p1, const void *p2) +{ + return strcmp(((TrunkBinlogBackupFileInfo *)p1)->filename, + ((TrunkBinlogBackupFileInfo *)p2)->filename); +} + +static int trunk_binlog_delete_overflow_backups() +{ +#define BACKUP_FILENAME_PREFIX_STR TRUNK_SYNC_BINLOG_FILENAME_STR"." +#define BACKUP_FILENAME_PREFIX_LEN (sizeof(BACKUP_FILENAME_PREFIX_STR) - 1) + + int result; + int i; + int over_count; + char file_path[MAX_PATH_SIZE]; + char full_filename[MAX_PATH_SIZE]; + DIR *dir; + struct dirent *ent; + TrunkBinlogBackupFileArray file_array; + + snprintf(file_path, sizeof(file_path), + "%s/data/%s", g_fdfs_base_path, TRUNK_DIR_NAME); + if ((dir=opendir(file_path)) == NULL) + { + result = errno != 0 ? errno : EPERM; + logError("file: "__FILE__", line: %d, " + "call opendir %s fail, errno: %d, error info: %s", + __LINE__, file_path, result, STRERROR(result)); + return result; + } + + result = 0; + file_array.files = NULL; + file_array.count = 0; + file_array.alloc = 0; + while ((ent=readdir(dir)) != NULL) + { + if (strlen(ent->d_name) == BACKUP_FILENAME_LEN && + memcmp(ent->d_name, BACKUP_FILENAME_PREFIX_STR, + BACKUP_FILENAME_PREFIX_LEN) == 0) + { + if ((result=trunk_binlog_check_alloc_filename_array( + &file_array)) != 0) + { + break; + } + + strcpy(file_array.files[file_array.count]. + filename, ent->d_name); + file_array.count++; + break; + } + } + + closedir(dir); + + over_count = file_array.count - g_trunk_binlog_max_backups; + if (result != 0 || over_count <= 0) + { + if (file_array.files != NULL) + { + free(file_array.files); + } + return result; + } + + qsort(file_array.files, file_array.count, + sizeof(TrunkBinlogBackupFileInfo), + trunk_binlog_compare_filename); + for (i=0; i 0) - { - if ((result=trunk_binlog_fsync(true)) != 0) - { - return result; - } - } + result = 0; + pthread_mutex_lock(&trunk_sync_thread_lock); + do + { + if (g_trunk_binlog_max_backups > 0) + { + result = trunk_binlog_backup_and_truncate(); + } + else + { + if (trunk_binlog_write_cache_len > 0) + { + if ((result=trunk_binlog_fsync(false)) != 0) + { + break; + } + } + if (ftruncate(trunk_binlog_fd, 0) != 0) + { + result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " + "call ftruncate fail, " + "errno: %d, error info: %s", + __LINE__, result, STRERROR(result)); + break; + } + } + } while (0); - if (ftruncate(trunk_binlog_fd, 0) != 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "call ftruncate fail, " \ - "errno: %d, error info: %s", \ - __LINE__, result, STRERROR(result)); - return result; - } - - return 0; + pthread_mutex_unlock(&trunk_sync_thread_lock); + return result; } static int trunk_binlog_delete_rollback_file(const char *filename, @@ -1206,7 +1405,7 @@ int trunk_binlog_write_buffer(const char *buff, const int length) return write_ret; } -static char *get_binlog_readable_filename(const void *pArg, \ +static char *get_binlog_readable_filename(const void *pArg, char *full_filename) { static char buff[MAX_PATH_SIZE]; @@ -1217,7 +1416,7 @@ static char *get_binlog_readable_filename(const void *pArg, \ } snprintf(full_filename, MAX_PATH_SIZE, - "%s/data/"TRUNK_DIR_NAME"/"TRUNK_SYNC_BINLOG_FILENAME, \ + "%s/data/"TRUNK_DIR_NAME"/"TRUNK_SYNC_BINLOG_FILENAME_STR, g_fdfs_base_path); return full_filename; } diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index 79917c8..b88d3dc 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -576,6 +576,9 @@ int tracker_load_from_conf_file(const char *filename, \ return result; } + g_trunk_binlog_max_backups = iniGetIntValue(NULL, + "trunk_binlog_max_backups", &iniContext, 0); + g_trunk_init_check_occupying = iniGetBoolValue(NULL, \ "trunk_init_check_occupying", &iniContext, false); @@ -728,89 +731,91 @@ int tracker_load_from_conf_file(const char *filename, \ int_to_comma_str(g_min_buff_size, sz_min_buff_size); int_to_comma_str(g_max_buff_size, sz_max_buff_size); - logInfo("FastDFS v%d.%02d, base_path=%s, " \ - "run_by_group=%s, run_by_user=%s, " \ - "connect_timeout=%ds, " \ - "network_timeout=%ds, " \ - "port=%d, bind_addr=%s, " \ - "max_connections=%d, " \ - "accept_threads=%d, " \ - "work_threads=%d, " \ - "min_buff_size=%s, " \ - "max_buff_size=%s, " \ - "store_lookup=%d, store_group=%s, " \ - "store_server=%d, store_path=%d, " \ - "reserved_storage_space=%s, " \ - "download_server=%d, " \ - "allow_ip_count=%d, sync_log_buff_interval=%ds, " \ - "check_active_interval=%ds, " \ - "thread_stack_size=%d KB, " \ - "storage_ip_changed_auto_adjust=%d, " \ - "storage_sync_file_max_delay=%ds, " \ - "storage_sync_file_max_time=%ds, " \ - "use_trunk_file=%d, " \ - "slot_min_size=%d, " \ - "slot_max_size=%d MB, " \ - "trunk_file_size=%d MB, " \ - "trunk_create_file_advance=%d, " \ - "trunk_create_file_time_base=%02d:%02d, " \ - "trunk_create_file_interval=%d, " \ - "trunk_create_file_space_threshold=%d GB, " \ - "trunk_init_check_occupying=%d, " \ - "trunk_init_reload_from_binlog=%d, " \ - "trunk_compress_binlog_min_interval=%d, " \ - "trunk_compress_binlog_interval=%d, " \ - "trunk_compress_binlog_time_base=%02d:%02d, " \ - "use_storage_id=%d, " \ - "id_type_in_filename=%s, " \ - "storage_id/ip_count=%d / %d, " \ - "rotate_error_log=%d, " \ - "error_log_rotate_time=%02d:%02d, " \ - "compress_old_error_log=%d, " \ - "compress_error_log_days_before=%d, " \ - "rotate_error_log_size=%"PRId64", " \ - "log_file_keep_days=%d, " \ - "store_slave_file_use_link=%d, " \ - "use_connection_pool=%d, " \ - "g_connection_pool_max_idle_time=%ds", \ - g_fdfs_version.major, g_fdfs_version.minor, \ - g_fdfs_base_path, g_run_by_group, g_run_by_user, \ - g_fdfs_connect_timeout, \ - g_fdfs_network_timeout, g_server_port, bind_addr, \ - g_max_connections, g_accept_threads, g_work_threads, \ - sz_min_buff_size, sz_max_buff_size, \ - g_groups.store_lookup, g_groups.store_group, \ - g_groups.store_server, g_groups.store_path, \ - fdfs_storage_reserved_space_to_string( \ - &g_storage_reserved_space, reserved_space_str), \ - g_groups.download_server, \ - g_allow_ip_count, g_sync_log_buff_interval, \ - g_check_active_interval, g_thread_stack_size / 1024, \ - g_storage_ip_changed_auto_adjust, \ - g_storage_sync_file_max_delay, \ - g_storage_sync_file_max_time, \ - g_if_use_trunk_file, g_slot_min_size, \ - g_slot_max_size / FDFS_ONE_MB, \ - g_trunk_file_size / FDFS_ONE_MB, \ - g_trunk_create_file_advance, \ - g_trunk_create_file_time_base.hour, \ - g_trunk_create_file_time_base.minute, \ - g_trunk_create_file_interval, \ - (int)(g_trunk_create_file_space_threshold / \ - (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, \ - g_trunk_init_reload_from_binlog, \ - g_trunk_compress_binlog_min_interval, \ - g_trunk_compress_binlog_interval, \ - g_trunk_compress_binlog_time_base.hour, \ - g_trunk_compress_binlog_time_base.minute, \ - g_use_storage_id, g_id_type_in_filename == \ - FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", \ - g_storage_ids_by_id.count, g_storage_ids_by_ip.count, \ - g_rotate_error_log, g_error_log_rotate_time.hour, \ - g_error_log_rotate_time.minute, g_compress_old_error_log, \ - g_compress_error_log_days_before, \ + logInfo("FastDFS v%d.%02d, base_path=%s, " + "run_by_group=%s, run_by_user=%s, " + "connect_timeout=%ds, " + "network_timeout=%ds, " + "port=%d, bind_addr=%s, " + "max_connections=%d, " + "accept_threads=%d, " + "work_threads=%d, " + "min_buff_size=%s, " + "max_buff_size=%s, " + "store_lookup=%d, store_group=%s, " + "store_server=%d, store_path=%d, " + "reserved_storage_space=%s, " + "download_server=%d, " + "allow_ip_count=%d, sync_log_buff_interval=%ds, " + "check_active_interval=%ds, " + "thread_stack_size=%d KB, " + "storage_ip_changed_auto_adjust=%d, " + "storage_sync_file_max_delay=%ds, " + "storage_sync_file_max_time=%ds, " + "use_trunk_file=%d, " + "slot_min_size=%d, " + "slot_max_size=%d MB, " + "trunk_file_size=%d MB, " + "trunk_create_file_advance=%d, " + "trunk_create_file_time_base=%02d:%02d, " + "trunk_create_file_interval=%d, " + "trunk_create_file_space_threshold=%d GB, " + "trunk_init_check_occupying=%d, " + "trunk_init_reload_from_binlog=%d, " + "trunk_compress_binlog_min_interval=%d, " + "trunk_compress_binlog_interval=%d, " + "trunk_compress_binlog_time_base=%02d:%02d, " + "trunk_binlog_max_backups=%d, " + "use_storage_id=%d, " + "id_type_in_filename=%s, " + "storage_id/ip_count=%d / %d, " + "rotate_error_log=%d, " + "error_log_rotate_time=%02d:%02d, " + "compress_old_error_log=%d, " + "compress_error_log_days_before=%d, " + "rotate_error_log_size=%"PRId64", " + "log_file_keep_days=%d, " + "store_slave_file_use_link=%d, " + "use_connection_pool=%d, " + "g_connection_pool_max_idle_time=%ds", + g_fdfs_version.major, g_fdfs_version.minor, + g_fdfs_base_path, g_run_by_group, g_run_by_user, + g_fdfs_connect_timeout, + g_fdfs_network_timeout, g_server_port, bind_addr, + g_max_connections, g_accept_threads, g_work_threads, + sz_min_buff_size, sz_max_buff_size, + g_groups.store_lookup, g_groups.store_group, + g_groups.store_server, g_groups.store_path, + fdfs_storage_reserved_space_to_string( + &g_storage_reserved_space, reserved_space_str), + g_groups.download_server, + g_allow_ip_count, g_sync_log_buff_interval, + g_check_active_interval, g_thread_stack_size / 1024, + g_storage_ip_changed_auto_adjust, + g_storage_sync_file_max_delay, + g_storage_sync_file_max_time, + g_if_use_trunk_file, g_slot_min_size, + g_slot_max_size / FDFS_ONE_MB, + g_trunk_file_size / FDFS_ONE_MB, + g_trunk_create_file_advance, + g_trunk_create_file_time_base.hour, + g_trunk_create_file_time_base.minute, + g_trunk_create_file_interval, + (int)(g_trunk_create_file_space_threshold / + (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, + g_trunk_init_reload_from_binlog, + g_trunk_compress_binlog_min_interval, + g_trunk_compress_binlog_interval, + g_trunk_compress_binlog_time_base.hour, + g_trunk_compress_binlog_time_base.minute, + g_trunk_binlog_max_backups, + g_use_storage_id, g_id_type_in_filename == + FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", + g_storage_ids_by_id.count, g_storage_ids_by_ip.count, + g_rotate_error_log, g_error_log_rotate_time.hour, + g_error_log_rotate_time.minute, g_compress_old_error_log, + g_compress_error_log_days_before, g_log_context.rotate_size, g_log_file_keep_days, - g_store_slave_file_use_link, \ + g_store_slave_file_use_link, g_use_connection_pool, g_connection_pool_max_idle_time); #ifdef WITH_HTTPD diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index de47160..9dadae9 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -59,6 +59,7 @@ TimeInfo g_trunk_compress_binlog_time_base = {0, 0}; int g_trunk_create_file_interval = 86400; int g_trunk_compress_binlog_interval = 0; int g_trunk_compress_binlog_min_interval = 0; +int g_trunk_binlog_max_backups = 0; int64_t g_trunk_create_file_space_threshold = 0; time_t g_up_time = 0; diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index 1bec35c..05ce99d 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -83,6 +83,7 @@ extern TimeInfo g_trunk_compress_binlog_time_base; extern int g_trunk_create_file_interval; extern int g_trunk_compress_binlog_interval; extern int g_trunk_compress_binlog_min_interval; +extern int g_trunk_binlog_max_backups; extern int64_t g_trunk_create_file_space_threshold; extern time_t g_up_time; diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index f75d0b4..3e9d03b 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -661,6 +661,7 @@ static int tracker_deal_get_trunk_fid(struct fast_task_info *pTask) static int tracker_deal_parameter_req(struct fast_task_info *pTask) { char reserved_space_str[32]; + int body_len; if (pTask->length - sizeof(TrackerHeader) != 0) { @@ -676,49 +677,51 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) return EINVAL; } - pTask->length = sizeof(TrackerHeader) + \ - sprintf(pTask->data + sizeof(TrackerHeader), \ - "use_storage_id=%d\n" \ - "id_type_in_filename=%s\n" \ - "storage_ip_changed_auto_adjust=%d\n" \ - "storage_sync_file_max_delay=%d\n" \ - "store_path=%d\n" \ - "reserved_storage_space=%s\n" \ - "use_trunk_file=%d\n" \ - "slot_min_size=%d\n" \ - "slot_max_size=%d\n" \ - "trunk_file_size=%d\n" \ - "trunk_create_file_advance=%d\n" \ - "trunk_create_file_time_base=%02d:%02d\n" \ - "trunk_create_file_interval=%d\n" \ - "trunk_create_file_space_threshold=%"PRId64"\n" \ - "trunk_init_check_occupying=%d\n" \ - "trunk_init_reload_from_binlog=%d\n" \ - "trunk_compress_binlog_min_interval=%d\n" \ - "trunk_compress_binlog_interval=%d\n" \ - "trunk_compress_binlog_time_base=%02d:%02d\n" \ - "store_slave_file_use_link=%d\n", \ - g_use_storage_id, g_id_type_in_filename == \ - FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", \ - g_storage_ip_changed_auto_adjust, \ - g_storage_sync_file_max_delay, g_groups.store_path, \ - fdfs_storage_reserved_space_to_string( \ - &g_storage_reserved_space, reserved_space_str), \ - g_if_use_trunk_file, \ - g_slot_min_size, g_slot_max_size, \ - g_trunk_file_size, g_trunk_create_file_advance, \ - g_trunk_create_file_time_base.hour, \ - g_trunk_create_file_time_base.minute, \ - g_trunk_create_file_interval, \ - g_trunk_create_file_space_threshold, \ - g_trunk_init_check_occupying, \ - g_trunk_init_reload_from_binlog, \ - g_trunk_compress_binlog_min_interval, \ - g_trunk_compress_binlog_interval, \ - g_trunk_compress_binlog_time_base.hour, \ - g_trunk_compress_binlog_time_base.minute, \ - g_store_slave_file_use_link); + body_len = sprintf(pTask->data + sizeof(TrackerHeader), + "use_storage_id=%d\n" + "id_type_in_filename=%s\n" + "storage_ip_changed_auto_adjust=%d\n" + "storage_sync_file_max_delay=%d\n" + "store_path=%d\n" + "reserved_storage_space=%s\n" + "use_trunk_file=%d\n" + "slot_min_size=%d\n" + "slot_max_size=%d\n" + "trunk_file_size=%d\n" + "trunk_create_file_advance=%d\n" + "trunk_create_file_time_base=%02d:%02d\n" + "trunk_create_file_interval=%d\n" + "trunk_create_file_space_threshold=%"PRId64"\n" + "trunk_init_check_occupying=%d\n" + "trunk_init_reload_from_binlog=%d\n" + "trunk_compress_binlog_min_interval=%d\n" + "trunk_compress_binlog_interval=%d\n" + "trunk_compress_binlog_time_base=%02d:%02d\n" + "trunk_binlog_max_backups=%d\n" + "store_slave_file_use_link=%d\n", + g_use_storage_id, g_id_type_in_filename == + FDFS_ID_TYPE_SERVER_ID ? "id" : "ip", + g_storage_ip_changed_auto_adjust, + g_storage_sync_file_max_delay, g_groups.store_path, + fdfs_storage_reserved_space_to_string( + &g_storage_reserved_space, reserved_space_str), + g_if_use_trunk_file, + g_slot_min_size, g_slot_max_size, + g_trunk_file_size, g_trunk_create_file_advance, + g_trunk_create_file_time_base.hour, + g_trunk_create_file_time_base.minute, + g_trunk_create_file_interval, + g_trunk_create_file_space_threshold, + g_trunk_init_check_occupying, + g_trunk_init_reload_from_binlog, + g_trunk_compress_binlog_min_interval, + g_trunk_compress_binlog_interval, + g_trunk_compress_binlog_time_base.hour, + g_trunk_compress_binlog_time_base.minute, + g_trunk_binlog_max_backups, + g_store_slave_file_use_link); + pTask->length = sizeof(TrackerHeader) + body_len; return 0; } From 13ba0963a357627f4bcaccf99fd0a611338a9830 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 20 Dec 2019 09:07:09 +0800 Subject: [PATCH 72/95] trunk_binlog_truncate delete trunk data file --- HISTORY | 2 +- storage/storage_service.c | 4 +- storage/trunk_mgr/trunk_mem.c | 28 +--------- storage/trunk_mgr/trunk_mem.h | 2 - storage/trunk_mgr/trunk_sync.c | 93 ++++++++++++++++++---------------- storage/trunk_mgr/trunk_sync.h | 4 +- 6 files changed, 56 insertions(+), 77 deletions(-) diff --git a/HISTORY b/HISTORY index b67e019..91e2317 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.05 2019-12-19 +Version 6.05 2019-12-20 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version diff --git a/storage/storage_service.c b/storage/storage_service.c index 6f2f45a..2613266 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -4059,12 +4059,12 @@ static int storage_server_trunk_delete_binlog_marks(struct fast_task_info *pTask } result = storage_delete_trunk_data_file(); - if (!(result == 0 || result == ENOENT)) + if (result != 0) { return result; } - return trunk_unlink_all_mark_files(false); + return trunk_unlink_all_mark_files(); } /** diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 4443090..1e8de2e 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -591,7 +591,7 @@ int storage_trunk_binlog_compress_check_recovery() } case STORAGE_TRUNK_COMPRESS_STAGE_COMPRESS_SUCCESS: /* unlink all mark files because the binlog file be compressed */ - result = trunk_unlink_all_mark_files(true); + result = trunk_unlink_all_mark_files(); if (result == 0) { g_trunk_binlog_compress_stage = @@ -693,7 +693,7 @@ static int storage_trunk_compress() last_write_version = current_write_version; /* unlink all mark files because the binlog file be compressed */ - result = trunk_unlink_all_mark_files(true); + result = trunk_unlink_all_mark_files(); } while (0); __sync_sub_and_fetch(&trunk_binlog_compress_in_progress, 1); @@ -1164,30 +1164,6 @@ static int storage_trunk_restore(const int64_t restore_offset) return result; } -int storage_delete_trunk_data_file() -{ - char trunk_data_filename[MAX_PATH_SIZE]; - int result; - - storage_trunk_get_data_filename(trunk_data_filename); - if (unlink(trunk_data_filename) == 0) - { - return 0; - } - - result = errno != 0 ? errno : ENOENT; - if (result != ENOENT) - { - logError("file: "__FILE__", line: %d, " - "unlink trunk data file: %s fail, " - "errno: %d, error info: %s", - __LINE__, trunk_data_filename, - result, STRERROR(result)); - } - - return result; -} - static int storage_trunk_load() { #define TRUNK_DATA_NEW_FIELD_COUNT 8 // >= v5.01 diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index f8b2d75..293a603 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -107,8 +107,6 @@ int trunk_binlog_compress_func(void *args); int storage_trunk_binlog_compress_check_recovery(); -int storage_delete_trunk_data_file(); - char *storage_trunk_get_data_filename(char *full_filename); #define storage_check_reserved_space(pGroup) \ diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index 54ac1fb..a82a111 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -387,7 +387,7 @@ int trunk_sync_notify_thread_reset_offset() } } - logError("file: "__FILE__", line: %d, " + logWarning("file: "__FILE__", line: %d, " "%d trunk sync threads reset binlog offset timeout.", __LINE__, count); return EBUSY; @@ -516,13 +516,12 @@ static int trunk_binlog_delete_overflow_backups() strcpy(file_array.files[file_array.count]. filename, ent->d_name); file_array.count++; - break; } } closedir(dir); - over_count = file_array.count - g_trunk_binlog_max_backups; + over_count = (file_array.count - g_trunk_binlog_max_backups) + 1; if (result != 0 || over_count <= 0) { if (file_array.files != NULL) @@ -539,7 +538,6 @@ static int trunk_binlog_delete_overflow_backups() { sprintf(full_filename, "%s/%s", file_path, file_array.files[i].filename); - logInfo("unlink old file: %s", full_filename); unlink(full_filename); } @@ -592,6 +590,34 @@ static int trunk_binlog_backup_and_truncate() return (result == 0) ? open_res : result; } +int storage_delete_trunk_data_file() +{ + char trunk_data_filename[MAX_PATH_SIZE]; + int result; + + storage_trunk_get_data_filename(trunk_data_filename); + if (unlink(trunk_data_filename) == 0) + { + return 0; + } + + result = errno != 0 ? errno : ENOENT; + if (result == ENOENT) + { + result = 0; + } + else + { + logError("file: "__FILE__", line: %d, " + "unlink trunk data file: %s fail, " + "errno: %d, error info: %s", + __LINE__, trunk_data_filename, + result, STRERROR(result)); + } + + return result; +} + int trunk_binlog_truncate() { int result; @@ -626,6 +652,11 @@ int trunk_binlog_truncate() } while (0); pthread_mutex_unlock(&trunk_sync_thread_lock); + if (result == 0) + { + result = storage_delete_trunk_data_file(); + } + return result; } @@ -2453,11 +2484,10 @@ void trunk_waiting_sync_thread_exit() } } -int trunk_unlink_all_mark_files(const bool force_delete) +int trunk_unlink_all_mark_files() { char file_path[MAX_PATH_SIZE]; - char old_filename[MAX_PATH_SIZE]; - char new_filename[MAX_PATH_SIZE]; + char full_filename[MAX_PATH_SIZE]; DIR *dir; struct dirent *ent; int result; @@ -2496,49 +2526,22 @@ int trunk_unlink_all_mark_files(const bool force_delete) continue; } - snprintf(old_filename, sizeof(old_filename), "%s/%s", + snprintf(full_filename, sizeof(full_filename), "%s/%s", file_path, ent->d_name); - if (force_delete) + if (unlink(full_filename) != 0) { - if (unlink(old_filename) != 0) + result = errno != 0 ? errno : EPERM; + if (result == ENOENT) { - result = errno != 0 ? errno : EPERM; - if (result == ENOENT) - { - result = 0; - } - else - { - logError("file: "__FILE__", line: %d, " - "unlink %s fail, errno: %d, error info: %s", - __LINE__, old_filename, - result, STRERROR(result)); - break; - } + result = 0; } - } - else - { - snprintf(new_filename, sizeof(new_filename), - "%s.%04d%02d%02d%02d%02d%02d", old_filename, - tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec); - if (rename(old_filename, new_filename) != 0) + else { - result = errno != 0 ? errno : EPERM; - if (result == ENOENT) - { - result = 0; - } - else - { - logError("file: "__FILE__", line: %d, " - "rename file %s to %s fail, " - "errno: %d, error info: %s", - __LINE__, old_filename, new_filename, - result, STRERROR(result)); - break; - } + logError("file: "__FILE__", line: %d, " + "unlink %s fail, errno: %d, error info: %s", + __LINE__, full_filename, + result, STRERROR(result)); + break; } } } diff --git a/storage/trunk_mgr/trunk_sync.h b/storage/trunk_mgr/trunk_sync.h index c853e28..4292c8f 100644 --- a/storage/trunk_mgr/trunk_sync.h +++ b/storage/trunk_mgr/trunk_sync.h @@ -65,7 +65,7 @@ void trunk_waiting_sync_thread_exit(); char *get_trunk_binlog_filename(char *full_filename); char *trunk_mark_filename_by_reader(const void *pArg, char *full_filename); -int trunk_unlink_all_mark_files(const bool force_delete); +int trunk_unlink_all_mark_files(); int trunk_unlink_mark_file(const char *storage_id); int trunk_rename_mark_file(const char *old_ip_addr, const int old_port, \ const char *new_ip_addr, const int new_port); @@ -88,6 +88,8 @@ int trunk_binlog_compress_rollback(); int trunk_sync_notify_thread_reset_offset(); int trunk_binlog_get_write_version(); +int storage_delete_trunk_data_file(); + char *get_trunk_binlog_tmp_filename_ex(const char *binlog_filename, char *tmp_filename); From f55d8fafc8b8076f174896dbfb1beefc3a2c1317 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 20 Dec 2019 12:02:48 +0800 Subject: [PATCH 73/95] support alignment size for trunk space allocation --- HISTORY | 1 + conf/tracker.conf | 9 ++++- storage/storage_param_getter.c | 14 +++++-- storage/trunk_mgr/trunk_mem.c | 47 ++++++++++++++++----- storage/trunk_mgr/trunk_mem.h | 1 + tracker/tracker_func.c | 74 ++++++++++++++++++++-------------- tracker/tracker_global.c | 1 + tracker/tracker_global.h | 1 + tracker/tracker_service.c | 2 + 9 files changed, 106 insertions(+), 44 deletions(-) diff --git a/HISTORY b/HISTORY index 91e2317..c8b6c57 100644 --- a/HISTORY +++ b/HISTORY @@ -9,6 +9,7 @@ Version 6.05 2019-12-20 * trunk binlog compression support transaction * support backup binlog file when truncate trunk binlog, the config item in tracker.conf: trunk_binlog_max_backups + * support alignment size for trunk space allocation Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST diff --git a/conf/tracker.conf b/conf/tracker.conf index c328270..b4e511b 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -152,7 +152,14 @@ slot_min_size = 256 # store the upload file to trunk file when it's size <= this value # default value is 16MB # since V3.00 -slot_max_size = 4MB +slot_max_size = 1MB + +# the alignment size to allocate the trunk space +# default value is 0 (never align) +# since V6.05 +# NOTE: the larger the alignment size, the less likely of disk +# fragmentation, but the more space is wasted. +trunk_alloc_alignment_size = 512 # the trunk file size, should >= 4MB # default value is 64MB diff --git a/storage/storage_param_getter.c b/storage/storage_param_getter.c index 73f526d..a01185b 100644 --- a/storage/storage_param_getter.c +++ b/storage/storage_param_getter.c @@ -114,7 +114,13 @@ int storage_get_params_from_tracker() g_trunk_file_size = iniGetIntValue(NULL, "trunk_file_size", &iniContext, 64 * 1024 * 1024); g_slot_max_size = iniGetIntValue(NULL, "slot_max_size", - &iniContext, g_trunk_file_size / 2); + &iniContext, g_trunk_file_size / 4); + g_trunk_alloc_alignment_size = iniGetIntValue(NULL, + "trunk_alloc_alignment_size", &iniContext, 0); + if (g_slot_min_size < g_trunk_alloc_alignment_size) + { + g_slot_min_size = g_trunk_alloc_alignment_size; + } g_trunk_create_file_advance = iniGetBoolValue(NULL, "trunk_create_file_advance", &iniContext, false); @@ -188,7 +194,8 @@ int storage_get_params_from_tracker() "reserved_storage_space=%s, " "use_trunk_file=%d, " "slot_min_size=%d, " - "slot_max_size=%d MB, " + "slot_max_size=%d KB, " + "trunk_alloc_alignment_size=%d, " "trunk_file_size=%d MB, " "trunk_create_file_advance=%d, " "trunk_create_file_time_base=%02d:%02d, " @@ -207,7 +214,8 @@ int storage_get_params_from_tracker() g_store_path_mode, fdfs_storage_reserved_space_to_string( &g_storage_reserved_space, reserved_space_str), g_if_use_trunk_file, g_slot_min_size, - g_slot_max_size / FDFS_ONE_MB, + g_slot_max_size / 1024, + g_trunk_alloc_alignment_size, g_trunk_file_size / FDFS_ONE_MB, g_trunk_create_file_advance, g_trunk_create_file_time_base.hour, diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 1e8de2e..b752d43 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -42,9 +42,10 @@ #define STORAGE_TRUNK_INIT_FLAG_DESTROYING 1 #define STORAGE_TRUNK_INIT_FLAG_DONE 2 -int g_slot_min_size; -int g_trunk_file_size; -int g_slot_max_size; +int g_slot_min_size = 0; +int g_slot_max_size = 0; +int g_trunk_alloc_alignment_size = 0; +int g_trunk_file_size = 0; int g_store_path_mode = FDFS_STORE_PATH_ROUND_ROBIN; FDFSStorageReservedSpace g_storage_reserved_space = { TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB}; @@ -1748,10 +1749,33 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) FDFSTrunkNode *pPreviousNode; FDFSTrunkNode *pTrunkNode; int result; + int aligned_size; + int remain; STORAGE_TRUNK_CHECK_STATUS(); - target_slot.size = (size > g_slot_min_size) ? size : g_slot_min_size; + if (size <= g_slot_min_size) + { + aligned_size = g_slot_min_size; + } + else if (g_trunk_alloc_alignment_size == 0) + { + aligned_size = size; + } + else + { + remain = size % g_trunk_alloc_alignment_size; + if (remain == 0) + { + aligned_size = size; + } + else + { + aligned_size = size + (g_trunk_alloc_alignment_size - remain); + } + } + + target_slot.size = aligned_size; target_slot.head = NULL; pPreviousNode = NULL; @@ -1759,7 +1783,7 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) pthread_mutex_lock(&trunk_mem_lock); while (1) { - pSlot = (FDFSTrunkSlot *)avl_tree_find_ge(tree_info_by_sizes \ + pSlot = (FDFSTrunkSlot *)avl_tree_find_ge(tree_info_by_sizes + pResult->path.store_path_index, &target_slot); if (pSlot == NULL) { @@ -1768,7 +1792,7 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) pPreviousNode = NULL; pTrunkNode = pSlot->head; - while (pTrunkNode != NULL && \ + while (pTrunkNode != NULL && pTrunkNode->trunk.status == FDFS_TRUNK_STATUS_HOLD) { pPreviousNode = pTrunkNode; @@ -1790,7 +1814,7 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) pSlot->head = pTrunkNode->next; if (pSlot->head == NULL) { - trunk_delete_size_tree_entry(pResult->path. \ + trunk_delete_size_tree_entry(pResult->path. store_path_index, pSlot); } } @@ -1803,7 +1827,7 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) } else { - pTrunkNode = trunk_create_trunk_file(pResult->path. \ + pTrunkNode = trunk_create_trunk_file(pResult->path. store_path_index, &result); if (pTrunkNode == NULL) { @@ -1813,7 +1837,7 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) } pthread_mutex_unlock(&trunk_mem_lock); - result = trunk_split(pTrunkNode, size); + result = trunk_split(pTrunkNode, aligned_size); if (result != 0) { return result; @@ -1823,10 +1847,13 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) result = trunk_add_free_block(pTrunkNode, true); if (result == 0) { - memcpy(pResult, &(pTrunkNode->trunk), \ + memcpy(pResult, &(pTrunkNode->trunk), sizeof(FDFSTrunkFullInfo)); } + logInfo("alloc size: %d, aligned_size: %d, alloced trunk size: %d", + size, aligned_size, pResult->file.size); + return result; } diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index 293a603..e7f55db 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -39,6 +39,7 @@ extern "C" { extern int g_slot_min_size; //slot min size, such as 256 bytes extern int g_slot_max_size; //slot max size +extern int g_trunk_alloc_alignment_size; //the alignment size for trunk alloc extern int g_trunk_file_size; //the trunk file size, such as 64MB extern int g_store_path_mode; //store which path mode, fetch from tracker extern FDFSStorageReservedSpace g_storage_reserved_space; //fetch from tracker diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index b88d3dc..640ede2 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -504,19 +504,19 @@ int tracker_load_from_conf_file(const char *filename, \ g_trunk_file_size = (int)trunk_file_size; if (g_trunk_file_size < 4 * 1024 * 1024) { - logWarning("file: "__FILE__", line: %d, " \ - "item \"trunk_file_size\" %d is too small, " \ + logWarning("file: "__FILE__", line: %d, " + "item \"trunk_file_size\" %d is too small, " "change to 4MB", __LINE__, g_trunk_file_size); g_trunk_file_size = 4 * 1024 * 1024; } - pSlotMaxSize = iniGetStrValue(NULL, \ + pSlotMaxSize = iniGetStrValue(NULL, "slot_max_size", &iniContext); if (pSlotMaxSize == NULL) { - slot_max_size = g_trunk_file_size / 2; + slot_max_size = g_trunk_file_size / 8; } - else if ((result=parse_bytes(pSlotMaxSize, 1, \ + else if ((result=parse_bytes(pSlotMaxSize, 1, &slot_max_size)) != 0) { return result; @@ -524,26 +524,26 @@ int tracker_load_from_conf_file(const char *filename, \ g_slot_max_size = (int)slot_max_size; if (g_slot_max_size <= g_slot_min_size) { - logError("file: "__FILE__", line: %d, " \ - "item \"slot_max_size\" %d is invalid, " \ - "which <= slot_min_size: %d", \ + logError("file: "__FILE__", line: %d, " + "item \"slot_max_size\" %d is invalid, " + "which <= slot_min_size: %d", __LINE__, g_slot_max_size, g_slot_min_size); result = EINVAL; break; } if (g_slot_max_size > g_trunk_file_size / 2) { - logWarning("file: "__FILE__", line: %d, " \ - "item \"slot_max_size\": %d is too large, " \ - "change to %d", __LINE__, g_slot_max_size, \ + logWarning("file: "__FILE__", line: %d, " + "item \"slot_max_size\": %d is too large, " + "change to %d", __LINE__, g_slot_max_size, g_trunk_file_size / 2); g_slot_max_size = g_trunk_file_size / 2; } - g_trunk_create_file_advance = iniGetBoolValue(NULL, \ + g_trunk_create_file_advance = iniGetBoolValue(NULL, "trunk_create_file_advance", &iniContext, false); - if ((result=get_time_item_from_conf(&iniContext, \ - "trunk_create_file_time_base", \ + if ((result=get_time_item_from_conf(&iniContext, + "trunk_create_file_time_base", &g_trunk_create_file_time_base, 2, 0)) != 0) { return result; @@ -579,13 +579,25 @@ int tracker_load_from_conf_file(const char *filename, \ g_trunk_binlog_max_backups = iniGetIntValue(NULL, "trunk_binlog_max_backups", &iniContext, 0); - g_trunk_init_check_occupying = iniGetBoolValue(NULL, \ + g_trunk_alloc_alignment_size = iniGetIntValue(NULL, + "trunk_alloc_alignment_size", &iniContext, 0); + if (g_slot_min_size < g_trunk_alloc_alignment_size) + { + logWarning("file: "__FILE__", line: %d, " + "item \"slot_min_size\": %d < " + "\"trunk_alloc_alignment_size\": %d, " + "change to %d", __LINE__, g_slot_min_size, + g_trunk_alloc_alignment_size); + g_slot_min_size = g_trunk_alloc_alignment_size; + } + + g_trunk_init_check_occupying = iniGetBoolValue(NULL, "trunk_init_check_occupying", &iniContext, false); - g_trunk_init_reload_from_binlog = iniGetBoolValue(NULL, \ + g_trunk_init_reload_from_binlog = iniGetBoolValue(NULL, "trunk_init_reload_from_binlog", &iniContext, false); - if ((result=tracker_load_storage_id_info( \ + if ((result=tracker_load_storage_id_info( filename, &iniContext)) != 0) { return result; @@ -604,40 +616,40 @@ int tracker_load_from_conf_file(const char *filename, \ log_set_compress_log_days_before(g_compress_error_log_days_before); } - if ((result=get_time_item_from_conf(&iniContext, \ - "error_log_rotate_time", &g_error_log_rotate_time, \ + if ((result=get_time_item_from_conf(&iniContext, + "error_log_rotate_time", &g_error_log_rotate_time, 0, 0)) != 0) { break; } - pRotateErrorLogSize = iniGetStrValue(NULL, \ + pRotateErrorLogSize = iniGetStrValue(NULL, "rotate_error_log_size", &iniContext); if (pRotateErrorLogSize == NULL) { rotate_error_log_size = 0; } - else if ((result=parse_bytes(pRotateErrorLogSize, 1, \ + else if ((result=parse_bytes(pRotateErrorLogSize, 1, &rotate_error_log_size)) != 0) { break; } - if (rotate_error_log_size > 0 && \ + if (rotate_error_log_size > 0 && rotate_error_log_size < FDFS_ONE_MB) { - logWarning("file: "__FILE__", line: %d, " \ - "item \"rotate_error_log_size\": " \ - "%"PRId64" is too small, " \ - "change to 1 MB", __LINE__, \ + logWarning("file: "__FILE__", line: %d, " + "item \"rotate_error_log_size\": " + "%"PRId64" is too small, " + "change to 1 MB", __LINE__, rotate_error_log_size); rotate_error_log_size = FDFS_ONE_MB; } fdfs_set_log_rotate_size(&g_log_context, rotate_error_log_size); - g_log_file_keep_days = iniGetIntValue(NULL, \ + g_log_file_keep_days = iniGetIntValue(NULL, "log_file_keep_days", &iniContext, 0); - g_store_slave_file_use_link = iniGetBoolValue(NULL, \ + g_store_slave_file_use_link = iniGetBoolValue(NULL, "store_slave_file_use_link", &iniContext, false); if ((result=fdfs_connection_pool_init(filename, &iniContext)) != 0) @@ -753,7 +765,8 @@ int tracker_load_from_conf_file(const char *filename, \ "storage_sync_file_max_time=%ds, " "use_trunk_file=%d, " "slot_min_size=%d, " - "slot_max_size=%d MB, " + "slot_max_size=%d KB, " + "trunk_alloc_alignment_size=%d, " "trunk_file_size=%d MB, " "trunk_create_file_advance=%d, " "trunk_create_file_time_base=%02d:%02d, " @@ -794,7 +807,8 @@ int tracker_load_from_conf_file(const char *filename, \ g_storage_sync_file_max_delay, g_storage_sync_file_max_time, g_if_use_trunk_file, g_slot_min_size, - g_slot_max_size / FDFS_ONE_MB, + g_slot_max_size / 1024, + g_trunk_alloc_alignment_size, g_trunk_file_size / FDFS_ONE_MB, g_trunk_create_file_advance, g_trunk_create_file_time_base.hour, diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index 9dadae9..96c3689 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -60,6 +60,7 @@ int g_trunk_create_file_interval = 86400; int g_trunk_compress_binlog_interval = 0; int g_trunk_compress_binlog_min_interval = 0; int g_trunk_binlog_max_backups = 0; +int g_trunk_alloc_alignment_size = 0; int64_t g_trunk_create_file_space_threshold = 0; time_t g_up_time = 0; diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index 05ce99d..812bdef 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -84,6 +84,7 @@ extern int g_trunk_create_file_interval; extern int g_trunk_compress_binlog_interval; extern int g_trunk_compress_binlog_min_interval; extern int g_trunk_binlog_max_backups; +extern int g_trunk_alloc_alignment_size; extern int64_t g_trunk_create_file_space_threshold; extern time_t g_up_time; diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 3e9d03b..793ec61 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -687,6 +687,7 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) "use_trunk_file=%d\n" "slot_min_size=%d\n" "slot_max_size=%d\n" + "trunk_alloc_alignment_size=%d\n" "trunk_file_size=%d\n" "trunk_create_file_advance=%d\n" "trunk_create_file_time_base=%02d:%02d\n" @@ -707,6 +708,7 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) &g_storage_reserved_space, reserved_space_str), g_if_use_trunk_file, g_slot_min_size, g_slot_max_size, + g_trunk_alloc_alignment_size, g_trunk_file_size, g_trunk_create_file_advance, g_trunk_create_file_time_base.hour, g_trunk_create_file_time_base.minute, From 8d2a04e435c09373efc0be3139f09330ebdba0f2 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Fri, 20 Dec 2019 14:46:42 +0800 Subject: [PATCH 74/95] remove debug log --- storage/trunk_mgr/trunk_mem.c | 95 +++++++++++++++++----------------- storage/trunk_mgr/trunk_sync.c | 30 +++++------ tracker/tracker_func.c | 1 + 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index b752d43..22bb90f 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -83,13 +83,13 @@ static int trunk_create_next_file(FDFSTrunkFullInfo *pTrunkInfo); static int trunk_add_free_block(FDFSTrunkNode *pNode, const bool bWriteBinLog); static int trunk_restore_node(const FDFSTrunkFullInfo *pTrunkInfo); -static int trunk_delete_space(const FDFSTrunkFullInfo *pTrunkInfo, \ +static int trunk_delete_space(const FDFSTrunkFullInfo *pTrunkInfo, const bool bWriteBinLog); static int storage_trunk_save(); static int storage_trunk_load(); -static int trunk_mem_binlog_write(const int timestamp, const char op_type, \ +static int trunk_mem_binlog_write(const int timestamp, const char op_type, const FDFSTrunkFullInfo *pTrunk) { pthread_mutex_lock(&trunk_file_lock); @@ -328,6 +328,8 @@ static int64_t storage_trunk_get_binlog_size() char full_filename[MAX_PATH_SIZE]; struct stat stat_buf; + trunk_binlog_sync_func(NULL); + get_trunk_binlog_filename(full_filename); if (stat(full_filename, &stat_buf) != 0) { @@ -336,10 +338,10 @@ static int64_t storage_trunk_get_binlog_size() return 0; } - logError("file: "__FILE__", line: %d, " \ - "stat file %s fail, " \ - "errno: %d, error info: %s", \ - __LINE__, full_filename, \ + logError("file: "__FILE__", line: %d, " + "stat file %s fail, " + "errno: %d, error info: %s", + __LINE__, full_filename, errno, STRERROR(errno)); return -1; } @@ -367,28 +369,28 @@ static int tree_walk_callback(void *data, void *args) while (pCurrent != NULL) { pTrunkInfo = &pCurrent->trunk; - len = sprintf(pCallbackArgs->pCurrent, \ - "%d %c %d %d %d %d %d %d\n", \ - (int)g_current_time, TRUNK_OP_TYPE_ADD_SPACE, \ - pTrunkInfo->path.store_path_index, \ - pTrunkInfo->path.sub_path_high, \ - pTrunkInfo->path.sub_path_low, \ - pTrunkInfo->file.id, \ - pTrunkInfo->file.offset, \ + len = sprintf(pCallbackArgs->pCurrent, + "%d %c %d %d %d %d %d %d\n", + (int)g_current_time, TRUNK_OP_TYPE_ADD_SPACE, + pTrunkInfo->path.store_path_index, + pTrunkInfo->path.sub_path_high, + pTrunkInfo->path.sub_path_low, + pTrunkInfo->file.id, + pTrunkInfo->file.offset, pTrunkInfo->file.size); pCallbackArgs->pCurrent += len; - if (pCallbackArgs->pCurrent - pCallbackArgs->buff > \ + if (pCallbackArgs->pCurrent - pCallbackArgs->buff > sizeof(pCallbackArgs->buff) - 128) { - if (fc_safe_write(pCallbackArgs->fd, pCallbackArgs->buff, \ - pCallbackArgs->pCurrent - pCallbackArgs->buff) \ + if (fc_safe_write(pCallbackArgs->fd, pCallbackArgs->buff, + pCallbackArgs->pCurrent - pCallbackArgs->buff) != pCallbackArgs->pCurrent - pCallbackArgs->buff) { result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, "\ - "write to file %s fail, " \ - "errno: %d, error info: %s", __LINE__, \ - pCallbackArgs->temp_trunk_filename, \ + logError("file: "__FILE__", line: %d, " + "write to file %s fail, " + "errno: %d, error info: %s", __LINE__, + pCallbackArgs->temp_trunk_filename, result, STRERROR(result)); return result; } @@ -420,22 +422,22 @@ static int do_save_trunk_data() memset(&callback_args, 0, sizeof(callback_args)); callback_args.pCurrent = callback_args.buff; - sprintf(callback_args.temp_trunk_filename, "%s/data/.%s.tmp", \ + sprintf(callback_args.temp_trunk_filename, "%s/data/.%s.tmp", g_fdfs_base_path, STORAGE_TRUNK_DATA_FILENAME); - callback_args.fd = open(callback_args.temp_trunk_filename, \ + callback_args.fd = open(callback_args.temp_trunk_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (callback_args.fd < 0) { result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "open file %s fail, " \ - "errno: %d, error info: %s", \ - __LINE__, callback_args.temp_trunk_filename, \ + logError("file: "__FILE__", line: %d, " + "open file %s fail, " + "errno: %d, error info: %s", + __LINE__, callback_args.temp_trunk_filename, result, STRERROR(result)); return result; } - len = sprintf(callback_args.pCurrent, "%"PRId64"\n", \ + len = sprintf(callback_args.pCurrent, "%"PRId64"\n", trunk_binlog_size); callback_args.pCurrent += len; @@ -443,7 +445,7 @@ static int do_save_trunk_data() pthread_mutex_lock(&trunk_mem_lock); for (i=0; ifile.size); - return result; } diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index a82a111..fdd9139 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -1272,7 +1272,7 @@ int trunk_binlog_compress_rollback() return result; } -static int trunk_binlog_fsync_ex(const bool bNeedLock, \ +static int trunk_binlog_fsync_ex(const bool bNeedLock, const char *buff, int *length) { int result; @@ -1281,9 +1281,9 @@ static int trunk_binlog_fsync_ex(const bool bNeedLock, \ if (bNeedLock && (result=pthread_mutex_lock(&trunk_sync_thread_lock)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_lock fail, " \ - "errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_lock fail, " + "errno: %d, error info: %s", __LINE__, result, STRERROR(result)); } @@ -1294,19 +1294,19 @@ static int trunk_binlog_fsync_ex(const bool bNeedLock, \ else if (fc_safe_write(trunk_binlog_fd, buff, *length) != *length) { write_ret = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "write to binlog file \"%s\" fail, fd=%d, " \ - "errno: %d, error info: %s", \ - __LINE__, get_trunk_binlog_filename(full_filename), \ + logError("file: "__FILE__", line: %d, " + "write to binlog file \"%s\" fail, fd=%d, " + "errno: %d, error info: %s", + __LINE__, get_trunk_binlog_filename(full_filename), trunk_binlog_fd, errno, STRERROR(errno)); } else if (fsync(trunk_binlog_fd) != 0) { write_ret = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "sync to binlog file \"%s\" fail, " \ - "errno: %d, error info: %s", \ - __LINE__, get_trunk_binlog_filename(full_filename), \ + logError("file: "__FILE__", line: %d, " + "sync to binlog file \"%s\" fail, " + "errno: %d, error info: %s", + __LINE__, get_trunk_binlog_filename(full_filename), errno, STRERROR(errno)); } else @@ -1322,9 +1322,9 @@ static int trunk_binlog_fsync_ex(const bool bNeedLock, \ if (bNeedLock && (result=pthread_mutex_unlock(&trunk_sync_thread_lock)) != 0) { - logError("file: "__FILE__", line: %d, " \ - "call pthread_mutex_unlock fail, " \ - "errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "call pthread_mutex_unlock fail, " + "errno: %d, error info: %s", __LINE__, result, STRERROR(result)); } diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index 640ede2..fb7d660 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -587,6 +587,7 @@ int tracker_load_from_conf_file(const char *filename, \ "item \"slot_min_size\": %d < " "\"trunk_alloc_alignment_size\": %d, " "change to %d", __LINE__, g_slot_min_size, + g_trunk_alloc_alignment_size, g_trunk_alloc_alignment_size); g_slot_min_size = g_trunk_alloc_alignment_size; } From 513894c5a20aa5e9aa01889e1e4d967e3b57bbf3 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sat, 21 Dec 2019 21:00:09 +0800 Subject: [PATCH 75/95] support merge free trunk spaces --- HISTORY | 3 +- conf/tracker.conf | 5 + storage/storage_param_getter.c | 4 + storage/trunk_mgr/trunk_mem.c | 311 +++++++++++++++++++++++++++++---- storage/trunk_mgr/trunk_mem.h | 1 + tracker/tracker_func.c | 5 + tracker/tracker_global.c | 1 + tracker/tracker_global.h | 1 + tracker/tracker_service.c | 2 + 9 files changed, 299 insertions(+), 34 deletions(-) diff --git a/HISTORY b/HISTORY index c8b6c57..b611584 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.05 2019-12-20 +Version 6.05 2019-12-21 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version @@ -10,6 +10,7 @@ Version 6.05 2019-12-20 * support backup binlog file when truncate trunk binlog, the config item in tracker.conf: trunk_binlog_max_backups * support alignment size for trunk space allocation + * support merge free trunk spaces Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST diff --git a/conf/tracker.conf b/conf/tracker.conf index b4e511b..26e677e 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -161,6 +161,11 @@ slot_max_size = 1MB # fragmentation, but the more space is wasted. trunk_alloc_alignment_size = 512 +# if merge contiguous free spaces of trunk file +# default value is false +# since V6.05 +trunk_free_space_merge = true + # the trunk file size, should >= 4MB # default value is 64MB # since V3.00 diff --git a/storage/storage_param_getter.c b/storage/storage_param_getter.c index a01185b..62ebd3c 100644 --- a/storage/storage_param_getter.c +++ b/storage/storage_param_getter.c @@ -142,6 +142,8 @@ int storage_get_params_from_tracker() "trunk_init_check_occupying", &iniContext, false); g_trunk_init_reload_from_binlog = iniGetBoolValue(NULL, "trunk_init_reload_from_binlog", &iniContext, false); + g_trunk_free_space_merge = iniGetBoolValue(NULL, + "trunk_free_space_merge", &iniContext, false); g_trunk_compress_binlog_min_interval = iniGetIntValue(NULL, "trunk_compress_binlog_min_interval", &iniContext, 0); g_trunk_compress_binlog_interval = iniGetIntValue(NULL, @@ -203,6 +205,7 @@ int storage_get_params_from_tracker() "trunk_create_file_space_threshold=%d GB, " "trunk_init_check_occupying=%d, " "trunk_init_reload_from_binlog=%d, " + "trunk_free_space_merge=%d, " "trunk_compress_binlog_min_interval=%d, " "trunk_compress_binlog_interval=%d, " "trunk_compress_binlog_time_base=%02d:%02d, " @@ -224,6 +227,7 @@ int storage_get_params_from_tracker() (int)(g_trunk_create_file_space_threshold / (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, g_trunk_init_reload_from_binlog, + g_trunk_free_space_merge, g_trunk_compress_binlog_min_interval, g_trunk_compress_binlog_interval, g_trunk_compress_binlog_time_base.hour, diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 22bb90f..ed16591 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -64,6 +64,7 @@ bool g_if_trunker_self = false; bool g_trunk_create_file_advance = false; bool g_trunk_init_check_occupying = false; bool g_trunk_init_reload_from_binlog = false; +bool g_trunk_free_space_merge = false; int g_trunk_binlog_compress_stage = STORAGE_TRUNK_COMPRESS_STAGE_NONE; int64_t g_trunk_total_free_space = 0; int64_t g_trunk_create_file_space_threshold = 0; @@ -349,54 +350,141 @@ static int64_t storage_trunk_get_binlog_size() return stat_buf.st_size; } +struct trunk_info_array { + FDFSTrunkFullInfo **trunks; + int count; + int alloc; +}; + struct walk_callback_args { int fd; char buff[16 * 1024]; char temp_trunk_filename[MAX_PATH_SIZE]; char *pCurrent; + + struct trunk_info_array trunk_array; //for space combine }; -static int tree_walk_callback(void *data, void *args) +static int trunk_alloc_trunk_array( + struct trunk_info_array *trunk_array) +{ + int bytes; + FDFSTrunkFullInfo **trunks; + int alloc; + + if (trunk_array->alloc == 0) + { + alloc = 64 * 1024; + } + else + { + alloc = trunk_array->alloc * 2; + } + + bytes = sizeof(FDFSTrunkFullInfo *) * alloc; + trunks = (FDFSTrunkFullInfo **)malloc(bytes); + if (trunks == NULL) + { + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail", __LINE__, bytes); + return ENOMEM; + } + + if (trunk_array->count > 0) + { + memcpy(trunks, trunk_array->trunks, + sizeof(FDFSTrunkFullInfo *) * + trunk_array->count); + } + + if (trunk_array->trunks != NULL) + { + free(trunk_array->trunks); + } + + trunk_array->trunks = trunks; + trunk_array->alloc = alloc; + return 0; +} + +static int save_one_trunk(struct walk_callback_args *pCallbackArgs, + FDFSTrunkFullInfo *pTrunkInfo) +{ + int len; + int result; + + len = sprintf(pCallbackArgs->pCurrent, + "%d %c %d %d %d %d %d %d\n", + (int)g_current_time, TRUNK_OP_TYPE_ADD_SPACE, + pTrunkInfo->path.store_path_index, + pTrunkInfo->path.sub_path_high, + pTrunkInfo->path.sub_path_low, + pTrunkInfo->file.id, + pTrunkInfo->file.offset, + pTrunkInfo->file.size); + pCallbackArgs->pCurrent += len; + if (pCallbackArgs->pCurrent - pCallbackArgs->buff > + sizeof(pCallbackArgs->buff) - 128) + { + if (fc_safe_write(pCallbackArgs->fd, pCallbackArgs->buff, + pCallbackArgs->pCurrent - pCallbackArgs->buff) + != pCallbackArgs->pCurrent - pCallbackArgs->buff) + { + result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " + "write to file %s fail, " + "errno: %d, error info: %s", __LINE__, + pCallbackArgs->temp_trunk_filename, + result, STRERROR(result)); + return result; + } + + pCallbackArgs->pCurrent = pCallbackArgs->buff; + } + + return 0; +} + +static int tree_walk_callback_to_file(void *data, void *args) { struct walk_callback_args *pCallbackArgs; - FDFSTrunkFullInfo *pTrunkInfo; FDFSTrunkNode *pCurrent; - int len; int result; pCallbackArgs = (struct walk_callback_args *)args; pCurrent = ((FDFSTrunkSlot *)data)->head; while (pCurrent != NULL) { - pTrunkInfo = &pCurrent->trunk; - len = sprintf(pCallbackArgs->pCurrent, - "%d %c %d %d %d %d %d %d\n", - (int)g_current_time, TRUNK_OP_TYPE_ADD_SPACE, - pTrunkInfo->path.store_path_index, - pTrunkInfo->path.sub_path_high, - pTrunkInfo->path.sub_path_low, - pTrunkInfo->file.id, - pTrunkInfo->file.offset, - pTrunkInfo->file.size); - pCallbackArgs->pCurrent += len; - if (pCallbackArgs->pCurrent - pCallbackArgs->buff > - sizeof(pCallbackArgs->buff) - 128) - { - if (fc_safe_write(pCallbackArgs->fd, pCallbackArgs->buff, - pCallbackArgs->pCurrent - pCallbackArgs->buff) - != pCallbackArgs->pCurrent - pCallbackArgs->buff) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " - "write to file %s fail, " - "errno: %d, error info: %s", __LINE__, - pCallbackArgs->temp_trunk_filename, - result, STRERROR(result)); - return result; - } + if ((result=save_one_trunk(pCallbackArgs, &pCurrent->trunk)) != 0) + { + return result; + } + pCurrent = pCurrent->next; + } - pCallbackArgs->pCurrent = pCallbackArgs->buff; - } + return 0; +} + +static int tree_walk_callback_to_list(void *data, void *args) +{ + struct walk_callback_args *pCallbackArgs; + FDFSTrunkNode *pCurrent; + int result; + + pCallbackArgs = (struct walk_callback_args *)args; + pCurrent = ((FDFSTrunkSlot *)data)->head; + while (pCurrent != NULL) + { + if (pCallbackArgs->trunk_array.count >= pCallbackArgs->trunk_array.alloc) + { + if ((result=trunk_alloc_trunk_array( + &pCallbackArgs->trunk_array)) != 0) + { + return result; + } + } + pCallbackArgs->trunk_array.trunks[pCallbackArgs->trunk_array. + count++] = &pCurrent->trunk; pCurrent = pCurrent->next; } @@ -404,6 +492,145 @@ static int tree_walk_callback(void *data, void *args) return 0; } +static int trunk_compare_id_offset(const void *p1, const void *p2) +{ + FDFSTrunkFullInfo *pTrunkInfo1; + FDFSTrunkFullInfo *pTrunkInfo2; + int result; + + pTrunkInfo1 = *((FDFSTrunkFullInfo **)p1); + pTrunkInfo2 = *((FDFSTrunkFullInfo **)p2); + + result = memcmp(&(pTrunkInfo1->path), &(pTrunkInfo2->path), + sizeof(FDFSTrunkPathInfo)); + if (result != 0) + { + return result; + } + + result = pTrunkInfo1->file.id - pTrunkInfo2->file.id; + if (result != 0) + { + return result; + } + + return pTrunkInfo1->file.offset - pTrunkInfo2->file.offset; +} + +static int trunk_compare_path_and_id(const FDFSTrunkFullInfo *pTrunkInfo1, + const FDFSTrunkFullInfo *pTrunkInfo2) +{ + int result; + + result = memcmp(&(pTrunkInfo1->path), &(pTrunkInfo2->path), + sizeof(FDFSTrunkPathInfo)); + if (result != 0) + { + return result; + } + + return pTrunkInfo1->file.id - pTrunkInfo2->file.id; +} + +static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) +{ + FDFSTrunkFullInfo **ppTrunkInfo; + FDFSTrunkFullInfo **ppEnd; + FDFSTrunkFullInfo **previous; + FDFSTrunkFullInfo **ppMergeFirst; + FDFSTrunkFullInfo trunk_info; + FDFSTrunkFullInfo *pTrunkInfo; + int merge_count; + int merged_trunk_count; + int64_t total_size; + int64_t merged_size; + int result; + + if (pCallbackArgs->trunk_array.count == 0) + { + return 0; + } + + qsort(pCallbackArgs->trunk_array.trunks, pCallbackArgs->trunk_array. + count, sizeof(FDFSTrunkFullInfo *), trunk_compare_id_offset); + + merge_count = 0; + merged_trunk_count = 0; + merged_size = 0; + ppEnd = pCallbackArgs->trunk_array.trunks + + pCallbackArgs->trunk_array.count; + ppTrunkInfo = pCallbackArgs->trunk_array.trunks; + ppMergeFirst = ppTrunkInfo; + total_size = (*ppTrunkInfo)->file.size; + while (++ppTrunkInfo < ppEnd) + { + total_size += (*ppTrunkInfo)->file.size; + previous = ppTrunkInfo - 1; + if (trunk_compare_path_and_id(*previous, *ppTrunkInfo) == 0 && + (*previous)->file.offset + (*previous)->file.size == + (*ppTrunkInfo)->file.offset) + { + continue; + } + + if (ppTrunkInfo - ppMergeFirst == 1) + { + pTrunkInfo = *ppMergeFirst; + } + else + { + pTrunkInfo = &trunk_info; + memcpy(pTrunkInfo, *ppMergeFirst, sizeof(FDFSTrunkFullInfo)); + pTrunkInfo->file.size = (*ppTrunkInfo)->file.offset - + (*ppMergeFirst)->file.offset; + + merge_count++; + merged_size += pTrunkInfo->file.size; + merged_trunk_count += ppTrunkInfo - ppMergeFirst; + } + + if ((result=save_one_trunk(pCallbackArgs, pTrunkInfo)) != 0) + { + return result; + } + + ppMergeFirst = ppTrunkInfo; + ppTrunkInfo++; + } + + if (ppEnd - ppMergeFirst == 1) + { + pTrunkInfo = *ppMergeFirst; + } + else + { + FDFSTrunkFullInfo **ppLast; + + pTrunkInfo = &trunk_info; + ppLast = ppEnd - 1; + memcpy(pTrunkInfo, *ppMergeFirst, sizeof(FDFSTrunkFullInfo)); + pTrunkInfo->file.size = (*ppLast)->file.offset - + (*ppMergeFirst)->file.offset + (*ppLast)->file.size; + + merge_count++; + merged_size += pTrunkInfo->file.size; + merged_trunk_count += ppEnd - ppMergeFirst; + } + + if ((result=save_one_trunk(pCallbackArgs, pTrunkInfo)) != 0) + { + return result; + } + + logInfo("file: "__FILE__", line: %d, " + "trunk count: %d, total_size: %"PRId64", merge count: %d, " + "merged trunk count: %d, merged size: %"PRId64, __LINE__, + pCallbackArgs->trunk_array.count, total_size, + merge_count, merged_trunk_count, merged_size); + + return 0; +} + static int do_save_trunk_data() { int64_t trunk_binlog_size; @@ -445,14 +672,32 @@ static int do_save_trunk_data() pthread_mutex_lock(&trunk_mem_lock); for (i=0; i 0 && result == 0) { diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index e7f55db..48409a0 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -57,6 +57,7 @@ extern bool g_if_use_trunk_file; //if use trunk file extern bool g_trunk_create_file_advance; extern bool g_trunk_init_check_occupying; extern bool g_trunk_init_reload_from_binlog; +extern bool g_trunk_free_space_merge; extern int g_trunk_binlog_compress_stage; extern bool g_if_trunker_self; //if am i trunk server extern int64_t g_trunk_create_file_space_threshold; diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index fb7d660..00d9824 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -598,6 +598,9 @@ int tracker_load_from_conf_file(const char *filename, \ g_trunk_init_reload_from_binlog = iniGetBoolValue(NULL, "trunk_init_reload_from_binlog", &iniContext, false); + g_trunk_free_space_merge = iniGetBoolValue(NULL, + "trunk_free_space_merge", &iniContext, false); + if ((result=tracker_load_storage_id_info( filename, &iniContext)) != 0) { @@ -775,6 +778,7 @@ int tracker_load_from_conf_file(const char *filename, \ "trunk_create_file_space_threshold=%d GB, " "trunk_init_check_occupying=%d, " "trunk_init_reload_from_binlog=%d, " + "trunk_free_space_merge=%d, " "trunk_compress_binlog_min_interval=%d, " "trunk_compress_binlog_interval=%d, " "trunk_compress_binlog_time_base=%02d:%02d, " @@ -818,6 +822,7 @@ int tracker_load_from_conf_file(const char *filename, \ (int)(g_trunk_create_file_space_threshold / (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, g_trunk_init_reload_from_binlog, + g_trunk_free_space_merge, g_trunk_compress_binlog_min_interval, g_trunk_compress_binlog_interval, g_trunk_compress_binlog_time_base.hour, diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index 96c3689..bd19da4 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -51,6 +51,7 @@ bool g_if_use_trunk_file = false; //if use trunk file bool g_trunk_create_file_advance = false; bool g_trunk_init_check_occupying = false; bool g_trunk_init_reload_from_binlog = false; +bool g_trunk_free_space_merge = false; int g_slot_min_size = 256; //slot min size, such as 256 bytes int g_slot_max_size = 16 * 1024 * 1024; //slot max size, such as 16MB int g_trunk_file_size = 64 * 1024 * 1024; //the trunk file size, such as 64MB diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index 812bdef..cfd489f 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -75,6 +75,7 @@ extern bool g_if_use_trunk_file; //if use trunk file extern bool g_trunk_create_file_advance; extern bool g_trunk_init_check_occupying; extern bool g_trunk_init_reload_from_binlog; +extern bool g_trunk_free_space_merge; extern int g_slot_min_size; //slot min size, such as 256 bytes extern int g_slot_max_size; //slot max size, such as 16MB extern int g_trunk_file_size; //the trunk file size, such as 64MB diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 793ec61..f98d8fb 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -695,6 +695,7 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) "trunk_create_file_space_threshold=%"PRId64"\n" "trunk_init_check_occupying=%d\n" "trunk_init_reload_from_binlog=%d\n" + "trunk_free_space_merge=%d\n" "trunk_compress_binlog_min_interval=%d\n" "trunk_compress_binlog_interval=%d\n" "trunk_compress_binlog_time_base=%02d:%02d\n" @@ -716,6 +717,7 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) g_trunk_create_file_space_threshold, g_trunk_init_check_occupying, g_trunk_init_reload_from_binlog, + g_trunk_free_space_merge, g_trunk_compress_binlog_min_interval, g_trunk_compress_binlog_interval, g_trunk_compress_binlog_time_base.hour, From 1e56afb08d074bd56c36683e027b9fbf22d3218a Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 22 Dec 2019 17:09:37 +0800 Subject: [PATCH 76/95] remove trunk_file_lock and use atomic add/sub --- HISTORY | 2 +- storage/trunk_mgr/trunk_mem.c | 305 ++++++++++++++++++---------------- storage/trunk_mgr/trunk_mem.h | 4 +- 3 files changed, 166 insertions(+), 145 deletions(-) diff --git a/HISTORY b/HISTORY index b611584..9a19868 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.05 2019-12-21 +Version 6.05 2019-12-22 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index ed16591..68b40c7 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -51,7 +51,7 @@ FDFSStorageReservedSpace g_storage_reserved_space = { TRACKER_STORAGE_RESERVED_SPACE_FLAG_MB}; int g_avg_storage_reserved_mb = FDFS_DEF_STORAGE_RESERVED_MB; int g_store_path_index = 0; -int g_current_trunk_file_id = 0; +volatile int g_current_trunk_file_id = 0; TimeInfo g_trunk_create_file_time_base = {0, 0}; TimeInfo g_trunk_compress_binlog_time_base = {0, 0}; int g_trunk_create_file_interval = 86400; @@ -66,14 +66,13 @@ bool g_trunk_init_check_occupying = false; bool g_trunk_init_reload_from_binlog = false; bool g_trunk_free_space_merge = false; int g_trunk_binlog_compress_stage = STORAGE_TRUNK_COMPRESS_STAGE_NONE; -int64_t g_trunk_total_free_space = 0; +volatile int64_t g_trunk_total_free_space = 0; int64_t g_trunk_create_file_space_threshold = 0; time_t g_trunk_last_compress_time = 0; static byte trunk_init_flag = STORAGE_TRUNK_INIT_FLAG_NONE; static volatile int trunk_binlog_compress_in_progress = 0; static volatile int trunk_data_save_in_progress = 0; -static pthread_mutex_t trunk_file_lock; static pthread_mutex_t trunk_mem_lock; static struct fast_mblock_man free_blocks_man; static struct fast_mblock_man tree_nodes_man; @@ -84,8 +83,11 @@ static int trunk_create_next_file(FDFSTrunkFullInfo *pTrunkInfo); static int trunk_add_free_block(FDFSTrunkNode *pNode, const bool bWriteBinLog); static int trunk_restore_node(const FDFSTrunkFullInfo *pTrunkInfo); -static int trunk_delete_space(const FDFSTrunkFullInfo *pTrunkInfo, - const bool bWriteBinLog); + +static int trunk_delete_space_ex(const FDFSTrunkFullInfo *pTrunkInfo, + const bool bNeedLock, const bool bWriteBinLog); +#define trunk_delete_space(pTrunkInfo, bWriteBinLog) \ + trunk_delete_space_ex(pTrunkInfo, true, bWriteBinLog) static int storage_trunk_save(); static int storage_trunk_load(); @@ -93,16 +95,14 @@ static int storage_trunk_load(); static int trunk_mem_binlog_write(const int timestamp, const char op_type, const FDFSTrunkFullInfo *pTrunk) { - pthread_mutex_lock(&trunk_file_lock); if (op_type == TRUNK_OP_TYPE_ADD_SPACE) { - g_trunk_total_free_space += pTrunk->file.size; + __sync_add_and_fetch(&g_trunk_total_free_space, pTrunk->file.size); } else if (op_type == TRUNK_OP_TYPE_DEL_SPACE) { - g_trunk_total_free_space -= pTrunk->file.size; + __sync_sub_and_fetch(&g_trunk_total_free_space, pTrunk->file.size); } - pthread_mutex_unlock(&trunk_file_lock); return trunk_binlog_write(timestamp, op_type, pTrunk); } @@ -166,6 +166,7 @@ int storage_trunk_init() int result; int i; int count; + char comma_str[32]; if (!g_if_trunker_self) { @@ -185,15 +186,6 @@ int storage_trunk_init() "storage trunk init ...", __LINE__); memset(&g_trunk_server, 0, sizeof(g_trunk_server)); - if ((result=init_pthread_lock(&trunk_file_lock)) != 0) - { - logError("file: "__FILE__", line: %d, " \ - "init_pthread_lock fail, " \ - "errno: %d, error info: %s", \ - __LINE__, result, STRERROR(result)); - return result; - } - if ((result=init_pthread_lock(&trunk_mem_lock)) != 0) { logError("file: "__FILE__", line: %d, " \ @@ -256,13 +248,13 @@ int storage_trunk_init() count += avl_tree_count(tree_info_by_sizes + i); } - logInfo("file: "__FILE__", line: %d, " \ - "tree by space size node count: %d, tree by trunk file id " \ - "node count: %d, free block count: %d, " \ - "trunk_total_free_space: %"PRId64, __LINE__, \ - count, trunk_free_block_tree_node_count(), \ - trunk_free_block_total_count(), \ - g_trunk_total_free_space); + logInfo("file: "__FILE__", line: %d, " + "tree by space size node count: %d, tree by trunk file id " + "node count: %d, free block count: %d, " + "trunk_total_free_space: %s", __LINE__, + count, trunk_free_block_tree_node_count(), + trunk_free_block_total_count(), + long_to_comma_str(g_trunk_total_free_space, comma_str)); /* { @@ -317,7 +309,6 @@ int storage_trunk_destroy_ex(const bool bNeedSleep, fast_mblock_destroy(&free_blocks_man); fast_mblock_destroy(&tree_nodes_man); - pthread_mutex_destroy(&trunk_file_lock); pthread_mutex_destroy(&trunk_mem_lock); trunk_init_flag = STORAGE_TRUNK_INIT_FLAG_NONE; @@ -363,6 +354,10 @@ struct walk_callback_args { char *pCurrent; struct trunk_info_array trunk_array; //for space combine + struct { + int trunk_count; + int64_t total_size; + } stats; }; static int trunk_alloc_trunk_array( @@ -459,6 +454,8 @@ static int tree_walk_callback_to_file(void *data, void *args) { return result; } + pCallbackArgs->stats.trunk_count++; + pCallbackArgs->stats.total_size += pCurrent->trunk.file.size; pCurrent = pCurrent->next; } @@ -483,9 +480,12 @@ static int tree_walk_callback_to_list(void *data, void *args) return result; } } + pCallbackArgs->trunk_array.trunks[pCallbackArgs->trunk_array. count++] = &pCurrent->trunk; + pCallbackArgs->stats.trunk_count++; + pCallbackArgs->stats.total_size += pCurrent->trunk.file.size; pCurrent = pCurrent->next; } @@ -532,18 +532,45 @@ static int trunk_compare_path_and_id(const FDFSTrunkFullInfo *pTrunkInfo1, return pTrunkInfo1->file.id - pTrunkInfo2->file.id; } +typedef struct trunk_merge_stat +{ + int merge_count; + int merged_trunk_count; + int64_t merged_size; +} TrunkMergeStat; + +static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, + FDFSTrunkFullInfo **ppLast, TrunkMergeStat *stat) +{ + FDFSTrunkFullInfo **ppTrunkInfo; + int append_size; + + (*ppMergeFirst)->file.size = (*ppLast)->file.offset - + (*ppMergeFirst)->file.offset + (*ppLast)->file.size; + + stat->merge_count++; + stat->merged_size += (*ppMergeFirst)->file.size; + stat->merged_trunk_count += ppLast - ppMergeFirst + 1; + + append_size = 0; + for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) + { + append_size += (*ppTrunkInfo)->file.size; + trunk_delete_space_ex(*ppTrunkInfo, false, false); + } + + __sync_add_and_fetch(&g_trunk_total_free_space, + append_size); +} + static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) { FDFSTrunkFullInfo **ppTrunkInfo; FDFSTrunkFullInfo **ppEnd; FDFSTrunkFullInfo **previous; FDFSTrunkFullInfo **ppMergeFirst; - FDFSTrunkFullInfo trunk_info; - FDFSTrunkFullInfo *pTrunkInfo; - int merge_count; - int merged_trunk_count; - int64_t total_size; - int64_t merged_size; + TrunkMergeStat merge_stat; + char comma_buff[32]; int result; if (pCallbackArgs->trunk_array.count == 0) @@ -554,17 +581,17 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) qsort(pCallbackArgs->trunk_array.trunks, pCallbackArgs->trunk_array. count, sizeof(FDFSTrunkFullInfo *), trunk_compare_id_offset); - merge_count = 0; - merged_trunk_count = 0; - merged_size = 0; + merge_stat.merge_count = 0; + merge_stat.merged_trunk_count = 0; + merge_stat.merged_size = 0; + previous = NULL; + ppEnd = pCallbackArgs->trunk_array.trunks + pCallbackArgs->trunk_array.count; ppTrunkInfo = pCallbackArgs->trunk_array.trunks; ppMergeFirst = ppTrunkInfo; - total_size = (*ppTrunkInfo)->file.size; while (++ppTrunkInfo < ppEnd) { - total_size += (*ppTrunkInfo)->file.size; previous = ppTrunkInfo - 1; if (trunk_compare_path_and_id(*previous, *ppTrunkInfo) == 0 && (*previous)->file.offset + (*previous)->file.size == @@ -573,23 +600,12 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) continue; } - if (ppTrunkInfo - ppMergeFirst == 1) + if (ppTrunkInfo - ppMergeFirst > 1) { - pTrunkInfo = *ppMergeFirst; - } - else - { - pTrunkInfo = &trunk_info; - memcpy(pTrunkInfo, *ppMergeFirst, sizeof(FDFSTrunkFullInfo)); - pTrunkInfo->file.size = (*ppTrunkInfo)->file.offset - - (*ppMergeFirst)->file.offset; - - merge_count++; - merged_size += pTrunkInfo->file.size; - merged_trunk_count += ppTrunkInfo - ppMergeFirst; + trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); } - if ((result=save_one_trunk(pCallbackArgs, pTrunkInfo)) != 0) + if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) { return result; } @@ -598,35 +614,22 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) ppTrunkInfo++; } - if (ppEnd - ppMergeFirst == 1) + if (ppEnd - ppMergeFirst > 1) { - pTrunkInfo = *ppMergeFirst; - } - else - { - FDFSTrunkFullInfo **ppLast; - - pTrunkInfo = &trunk_info; - ppLast = ppEnd - 1; - memcpy(pTrunkInfo, *ppMergeFirst, sizeof(FDFSTrunkFullInfo)); - pTrunkInfo->file.size = (*ppLast)->file.offset - - (*ppMergeFirst)->file.offset + (*ppLast)->file.size; - - merge_count++; - merged_size += pTrunkInfo->file.size; - merged_trunk_count += ppEnd - ppMergeFirst; + trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); } - if ((result=save_one_trunk(pCallbackArgs, pTrunkInfo)) != 0) + if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) { return result; } logInfo("file: "__FILE__", line: %d, " - "trunk count: %d, total_size: %"PRId64", merge count: %d, " - "merged trunk count: %d, merged size: %"PRId64, __LINE__, - pCallbackArgs->trunk_array.count, total_size, - merge_count, merged_trunk_count, merged_size); + "merge free trunk spaces, merge count: %d, " + "merged trunk count: %d, merged size: %s", + __LINE__, merge_stat.merge_count, + merge_stat.merged_trunk_count, + long_to_comma_str(merge_stat.merged_size, comma_buff)); return 0; } @@ -635,6 +638,7 @@ static int do_save_trunk_data() { int64_t trunk_binlog_size; char trunk_data_filename[MAX_PATH_SIZE]; + char comma_buff[32]; struct walk_callback_args callback_args; int len; int result; @@ -689,6 +693,10 @@ static int do_save_trunk_data() } } + logInfo("file: "__FILE__", line: %d, " + "free trunk stats, count: %d, size: %s", + __LINE__, callback_args.stats.trunk_count, + long_to_comma_str(callback_args.stats.total_size,comma_buff)); if (g_trunk_free_space_merge) { result = trunk_save_merged_spaces(&callback_args); @@ -1337,7 +1345,7 @@ static int storage_trunk_restore(const int64_t restore_offset) line_count, buff); } } - else if ((result=trunk_delete_space( \ + else if ((result=trunk_delete_space( &record.trunk, false)) != 0) { if (result == ENOENT) @@ -1380,19 +1388,19 @@ static int storage_trunk_restore(const int64_t restore_offset) avl_tree_destroy(&tree_info_by_offset); } - logError("file: "__FILE__", line: %d, " \ - "trunk load fail, errno: %d, error info: %s", \ + logError("file: "__FILE__", line: %d, " + "trunk load fail, errno: %d, error info: %s", __LINE__, result, STRERROR(result)); return result; } if (trunk_init_reload_from_binlog) { - logInfo("file: "__FILE__", line: %d, " \ - "free tree node count: %d", \ + logInfo("file: "__FILE__", line: %d, " + "free tree node count: %d", __LINE__, avl_tree_count(&tree_info_by_offset)); - result = avl_tree_walk(&tree_info_by_offset, \ + result = avl_tree_walk(&tree_info_by_offset, storage_trunk_add_free_blocks_callback, NULL); tree_info_by_offset.free_data_func = NULL; @@ -1401,10 +1409,10 @@ static int storage_trunk_restore(const int64_t restore_offset) if (result == 0) { - logDebug("file: "__FILE__", line: %d, " \ - "trunk metadata recovery done. start offset: " \ - "%"PRId64", recovery file size: " \ - "%"PRId64, __LINE__, \ + logDebug("file: "__FILE__", line: %d, " + "trunk metadata recovery done. start offset: " + "%"PRId64", recovery file size: " + "%"PRId64, __LINE__, restore_offset, trunk_binlog_size - restore_offset); return storage_trunk_save(); } @@ -1717,14 +1725,13 @@ static int trunk_add_free_block(FDFSTrunkNode *pNode, const bool bWriteBinLog) if (bWriteBinLog) { - result = trunk_mem_binlog_write(g_current_time, \ + result = trunk_mem_binlog_write(g_current_time, TRUNK_OP_TYPE_ADD_SPACE, &(pNode->trunk)); } else { - pthread_mutex_lock(&trunk_file_lock); - g_trunk_total_free_space += pNode->trunk.file.size; - pthread_mutex_unlock(&trunk_file_lock); + __sync_add_and_fetch(&g_trunk_total_free_space, + pNode->trunk.file.size); result = 0; } @@ -1758,8 +1765,8 @@ static void trunk_delete_size_tree_entry(const int store_path_index, \ } } -static int trunk_delete_space(const FDFSTrunkFullInfo *pTrunkInfo, \ - const bool bWriteBinLog) +static int trunk_delete_space_ex(const FDFSTrunkFullInfo *pTrunkInfo, + const bool bNeedLock, const bool bWriteBinLog) { int result; FDFSTrunkSlot target_slot; @@ -1770,64 +1777,79 @@ static int trunk_delete_space(const FDFSTrunkFullInfo *pTrunkInfo, \ target_slot.size = pTrunkInfo->file.size; target_slot.head = NULL; + result = 0; - pthread_mutex_lock(&trunk_mem_lock); - pSlot = (FDFSTrunkSlot *)avl_tree_find(tree_info_by_sizes + \ - pTrunkInfo->path.store_path_index, &target_slot); - if (pSlot == NULL) - { - pthread_mutex_unlock(&trunk_mem_lock); - logError("file: "__FILE__", line: %d, " \ - "can't find trunk entry: %s", __LINE__, \ - trunk_info_dump(pTrunkInfo, buff, sizeof(buff))); - return ENOENT; - } + if (bNeedLock) + { + pthread_mutex_lock(&trunk_mem_lock); + } + do + { + pSlot = (FDFSTrunkSlot *)avl_tree_find(tree_info_by_sizes + + pTrunkInfo->path.store_path_index, &target_slot); + if (pSlot == NULL) + { + logError("file: "__FILE__", line: %d, " + "can't find trunk entry: %s", __LINE__, + trunk_info_dump(pTrunkInfo, buff, sizeof(buff))); + result = ENOENT; + break; + } - pPrevious = NULL; - pCurrent = pSlot->head; - while (pCurrent != NULL && memcmp(&(pCurrent->trunk), pTrunkInfo, \ - sizeof(FDFSTrunkFullInfo)) != 0) - { - pPrevious = pCurrent; - pCurrent = pCurrent->next; - } + pPrevious = NULL; + pCurrent = pSlot->head; + while (pCurrent != NULL && memcmp(&(pCurrent->trunk), + pTrunkInfo, sizeof(FDFSTrunkFullInfo)) != 0) + { + pPrevious = pCurrent; + pCurrent = pCurrent->next; + } - if (pCurrent == NULL) - { - pthread_mutex_unlock(&trunk_mem_lock); - logError("file: "__FILE__", line: %d, " \ - "can't find trunk entry: %s", __LINE__, \ - trunk_info_dump(pTrunkInfo, buff, sizeof(buff))); - return ENOENT; - } + if (pCurrent == NULL) + { + logError("file: "__FILE__", line: %d, " + "can't find trunk entry: %s", __LINE__, + trunk_info_dump(pTrunkInfo, buff, sizeof(buff))); + result = ENOENT; + break; + } - if (pPrevious == NULL) - { - pSlot->head = pCurrent->next; - if (pSlot->head == NULL) - { - trunk_delete_size_tree_entry(pTrunkInfo->path. \ - store_path_index, pSlot); - } - } - else - { - pPrevious->next = pCurrent->next; - } + if (pPrevious == NULL) + { + pSlot->head = pCurrent->next; + if (pSlot->head == NULL) + { + trunk_delete_size_tree_entry(pTrunkInfo->path. + store_path_index, pSlot); + } + } + else + { + pPrevious->next = pCurrent->next; + } - trunk_free_block_delete(&(pCurrent->trunk)); - pthread_mutex_unlock(&trunk_mem_lock); + trunk_free_block_delete(&(pCurrent->trunk)); + } while (0); + + if (bNeedLock) + { + pthread_mutex_unlock(&trunk_mem_lock); + } + + if (result != 0) + { + return result; + } if (bWriteBinLog) { - result = trunk_mem_binlog_write(g_current_time, \ + result = trunk_mem_binlog_write(g_current_time, TRUNK_OP_TYPE_DEL_SPACE, &(pCurrent->trunk)); } else { - pthread_mutex_lock(&trunk_file_lock); - g_trunk_total_free_space -= pCurrent->trunk.file.size; - pthread_mutex_unlock(&trunk_file_lock); + __sync_sub_and_fetch(&g_trunk_total_free_space, + pCurrent->trunk.file.size); result = 0; } @@ -2150,10 +2172,9 @@ static int trunk_create_next_file(FDFSTrunkFullInfo *pTrunkInfo) while (1) { - pthread_mutex_lock(&trunk_file_lock); - pTrunkInfo->file.id = ++g_current_trunk_file_id; + pTrunkInfo->file.id = __sync_add_and_fetch( + &g_current_trunk_file_id, 1); result = storage_write_to_sync_ini_file(); - pthread_mutex_unlock(&trunk_file_lock); if (result != 0) { return result; @@ -2434,23 +2455,23 @@ int trunk_create_trunk_file_advance(void *args) if (!g_trunk_create_file_advance) { - logError("file: "__FILE__", line: %d, " \ + logError("file: "__FILE__", line: %d, " "do not need create trunk file advancely!", __LINE__); return EINVAL; } if (!g_if_trunker_self) { - logError("file: "__FILE__", line: %d, " \ + logError("file: "__FILE__", line: %d, " "I am not trunk server!", __LINE__); return ENOENT; } - alloc_space = g_trunk_create_file_space_threshold - \ - g_trunk_total_free_space; + alloc_space = g_trunk_create_file_space_threshold - + __sync_add_and_fetch(&g_trunk_total_free_space, 0); if (alloc_space <= 0) { - logDebug("file: "__FILE__", line: %d, " \ + logDebug("file: "__FILE__", line: %d, " "do not need create trunk file!", __LINE__); return 0; } diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index 48409a0..fe65922 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -45,7 +45,7 @@ extern int g_store_path_mode; //store which path mode, fetch from tracker extern FDFSStorageReservedSpace g_storage_reserved_space; //fetch from tracker extern int g_avg_storage_reserved_mb; //calc by above var: g_storage_reserved_mb extern int g_store_path_index; //store to which path -extern int g_current_trunk_file_id; //current trunk file id +extern volatile int g_current_trunk_file_id; //current trunk file id extern TimeInfo g_trunk_create_file_time_base; extern TimeInfo g_trunk_compress_binlog_time_base; extern int g_trunk_create_file_interval; @@ -61,7 +61,7 @@ extern bool g_trunk_free_space_merge; extern int g_trunk_binlog_compress_stage; extern bool g_if_trunker_self; //if am i trunk server extern int64_t g_trunk_create_file_space_threshold; -extern int64_t g_trunk_total_free_space; //trunk total free space in bytes +extern volatile int64_t g_trunk_total_free_space; //trunk total free space in bytes extern time_t g_trunk_last_compress_time; typedef struct tagFDFSTrunkNode { From 2ab095bafdb4559bdc32db3c9cb5fa09b21fd65a Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Sun, 22 Dec 2019 22:21:31 +0800 Subject: [PATCH 77/95] bugfixed: ++ppTrunkInfo again --- storage/trunk_mgr/trunk_mem.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 68b40c7..54977bb 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -456,6 +456,7 @@ static int tree_walk_callback_to_file(void *data, void *args) } pCallbackArgs->stats.trunk_count++; pCallbackArgs->stats.total_size += pCurrent->trunk.file.size; + pCurrent = pCurrent->next; } @@ -486,6 +487,7 @@ static int tree_walk_callback_to_list(void *data, void *args) pCallbackArgs->stats.trunk_count++; pCallbackArgs->stats.total_size += pCurrent->trunk.file.size; + pCurrent = pCurrent->next; } @@ -549,8 +551,8 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, (*ppMergeFirst)->file.offset + (*ppLast)->file.size; stat->merge_count++; + stat->merged_trunk_count += (ppLast - ppMergeFirst) + 1; stat->merged_size += (*ppMergeFirst)->file.size; - stat->merged_trunk_count += ppLast - ppMergeFirst + 1; append_size = 0; for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) @@ -604,21 +606,18 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) { trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); } - if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) { return result; } ppMergeFirst = ppTrunkInfo; - ppTrunkInfo++; } if (ppEnd - ppMergeFirst > 1) { trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); } - if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) { return result; From 49d51e949b9b70d9c1d2fdde37bd9e2e595a0984 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 23 Dec 2019 08:13:40 +0800 Subject: [PATCH 78/95] fix previous value in trunk_save_merged_spaces --- HISTORY | 2 +- common/fdfs_global.c | 2 +- storage/trunk_mgr/trunk_mem.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/HISTORY b/HISTORY index 9a19868..c77a6de 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.05 2019-12-22 +Version 6.05 2019-12-23 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version diff --git a/common/fdfs_global.c b/common/fdfs_global.c index 87d8c22..375ab2e 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -23,7 +23,7 @@ int g_fdfs_connect_timeout = DEFAULT_CONNECT_TIMEOUT; int g_fdfs_network_timeout = DEFAULT_NETWORK_TIMEOUT; char g_fdfs_base_path[MAX_PATH_SIZE] = {'/', 't', 'm', 'p', '\0'}; -Version g_fdfs_version = {6, 4}; +Version g_fdfs_version = {6, 5}; bool g_use_connection_pool = false; ConnectionPool g_connection_pool; int g_connection_pool_max_idle_time = 3600; diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 54977bb..2e49d8f 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -591,14 +591,14 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) ppEnd = pCallbackArgs->trunk_array.trunks + pCallbackArgs->trunk_array.count; ppTrunkInfo = pCallbackArgs->trunk_array.trunks; - ppMergeFirst = ppTrunkInfo; + ppMergeFirst = previous = ppTrunkInfo; while (++ppTrunkInfo < ppEnd) { - previous = ppTrunkInfo - 1; if (trunk_compare_path_and_id(*previous, *ppTrunkInfo) == 0 && (*previous)->file.offset + (*previous)->file.size == (*ppTrunkInfo)->file.offset) { + previous = ppTrunkInfo; continue; } @@ -611,7 +611,7 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) return result; } - ppMergeFirst = ppTrunkInfo; + ppMergeFirst = previous = ppTrunkInfo; } if (ppEnd - ppMergeFirst > 1) From b7447e59034f018057dff1d73cb706b9c91f8eca Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 23 Dec 2019 16:11:18 +0800 Subject: [PATCH 79/95] support delete unused trunk files --- HISTORY | 8 ++++ conf/tracker.conf | 7 ++- storage/storage_param_getter.c | 4 ++ storage/trunk_mgr/trunk_mem.c | 81 +++++++++++++++++++++++++++++----- storage/trunk_mgr/trunk_mem.h | 1 + tracker/tracker_func.c | 5 +++ tracker/tracker_global.c | 1 + tracker/tracker_global.h | 1 + tracker/tracker_service.c | 14 +++--- 9 files changed, 105 insertions(+), 17 deletions(-) diff --git a/HISTORY b/HISTORY index c77a6de..3807ec7 100644 --- a/HISTORY +++ b/HISTORY @@ -6,11 +6,19 @@ Version 6.05 2019-12-23 * trunk server support compress the trunk binlog periodically, the config items in tracker.conf: trunk_compress_binlog_interval and trunk_compress_binlog_time_base + * trunk binlog compression support transaction * support backup binlog file when truncate trunk binlog, the config item in tracker.conf: trunk_binlog_max_backups + * support alignment size for trunk space allocation + the config item in tracker.conf: trunk_alloc_alignment_size + * support merge free trunk spaces + the config item in tracker.conf: trunk_free_space_merge + + * support delete unused trunk files + the config item in tracker.conf: delete_unused_trunk_files Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST diff --git a/conf/tracker.conf b/conf/tracker.conf index 26e677e..5ffdfe8 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -146,7 +146,7 @@ use_trunk_file = false # the min slot size, should <= 4KB # default value is 256 bytes # since V3.00 -slot_min_size = 256 +slot_min_size = 512 # the max slot size, should > slot_min_size # store the upload file to trunk file when it's size <= this value @@ -166,6 +166,11 @@ trunk_alloc_alignment_size = 512 # since V6.05 trunk_free_space_merge = true +# if delete / reclaim the unused trunk files +# default value is false +# since V6.05 +delete_unused_trunk_files = true + # the trunk file size, should >= 4MB # default value is 64MB # since V3.00 diff --git a/storage/storage_param_getter.c b/storage/storage_param_getter.c index 62ebd3c..ed05270 100644 --- a/storage/storage_param_getter.c +++ b/storage/storage_param_getter.c @@ -144,6 +144,8 @@ int storage_get_params_from_tracker() "trunk_init_reload_from_binlog", &iniContext, false); g_trunk_free_space_merge = iniGetBoolValue(NULL, "trunk_free_space_merge", &iniContext, false); + g_delete_unused_trunk_files = iniGetBoolValue(NULL, + "delete_unused_trunk_files", &iniContext, false); g_trunk_compress_binlog_min_interval = iniGetIntValue(NULL, "trunk_compress_binlog_min_interval", &iniContext, 0); g_trunk_compress_binlog_interval = iniGetIntValue(NULL, @@ -206,6 +208,7 @@ int storage_get_params_from_tracker() "trunk_init_check_occupying=%d, " "trunk_init_reload_from_binlog=%d, " "trunk_free_space_merge=%d, " + "delete_unused_trunk_files=%d, " "trunk_compress_binlog_min_interval=%d, " "trunk_compress_binlog_interval=%d, " "trunk_compress_binlog_time_base=%02d:%02d, " @@ -228,6 +231,7 @@ int storage_get_params_from_tracker() (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, g_trunk_init_reload_from_binlog, g_trunk_free_space_merge, + g_delete_unused_trunk_files, g_trunk_compress_binlog_min_interval, g_trunk_compress_binlog_interval, g_trunk_compress_binlog_time_base.hour, diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 2e49d8f..76ca2cf 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -65,6 +65,7 @@ bool g_trunk_create_file_advance = false; bool g_trunk_init_check_occupying = false; bool g_trunk_init_reload_from_binlog = false; bool g_trunk_free_space_merge = false; +bool g_delete_unused_trunk_files = false; int g_trunk_binlog_compress_stage = STORAGE_TRUNK_COMPRESS_STAGE_NONE; volatile int64_t g_trunk_total_free_space = 0; int64_t g_trunk_create_file_space_threshold = 0; @@ -542,17 +543,20 @@ typedef struct trunk_merge_stat } TrunkMergeStat; static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, - FDFSTrunkFullInfo **ppLast, TrunkMergeStat *stat) + FDFSTrunkFullInfo **ppLast, TrunkMergeStat *merge_stat, + bool *bDeleted) { FDFSTrunkFullInfo **ppTrunkInfo; int append_size; + char full_filename[MAX_PATH_SIZE]; + struct stat file_stat; (*ppMergeFirst)->file.size = (*ppLast)->file.offset - (*ppMergeFirst)->file.offset + (*ppLast)->file.size; - stat->merge_count++; - stat->merged_trunk_count += (ppLast - ppMergeFirst) + 1; - stat->merged_size += (*ppMergeFirst)->file.size; + merge_stat->merge_count++; + merge_stat->merged_trunk_count += (ppLast - ppMergeFirst) + 1; + merge_stat->merged_size += (*ppMergeFirst)->file.size; append_size = 0; for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) @@ -563,6 +567,52 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, __sync_add_and_fetch(&g_trunk_total_free_space, append_size); + do + { + if (!g_delete_unused_trunk_files) + { + break; + } + + if (!((*ppMergeFirst)->file.offset == 0 && + (*ppMergeFirst)->file.size >= g_trunk_file_size)) + { + break; + } + + trunk_get_full_filename(*ppMergeFirst, full_filename, + sizeof(full_filename)); + if (stat(full_filename, &file_stat) != 0) + { + logError("file: "__FILE__", line: %d, " + "stat trunk file %s fail, " + "errno: %d, error info: %s", __LINE__, + full_filename, errno, STRERROR(errno)); + break; + } + if ((*ppMergeFirst)->file.size != file_stat.st_size) + { + break; + } + + if (unlink(full_filename) != 0) + { + if (errno != ENOENT) + { + logError("file: "__FILE__", line: %d, " + "unlink trunk file %s fail, " + "errno: %d, error info: %s", __LINE__, + full_filename, errno, STRERROR(errno)); + break; + } + } + + logInfo("file: "__FILE__", line: %d, " + "delete unused trunk file: %s", + __LINE__, full_filename); + trunk_delete_space_ex(*ppMergeFirst, false, false); + *bDeleted = true; + } while (0); } static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) @@ -574,6 +624,7 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) TrunkMergeStat merge_stat; char comma_buff[32]; int result; + bool bDeleted; if (pCallbackArgs->trunk_array.count == 0) { @@ -602,25 +653,35 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) continue; } + bDeleted = false; if (ppTrunkInfo - ppMergeFirst > 1) { - trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); + trunk_merge_spaces(ppMergeFirst, previous, + &merge_stat, &bDeleted); } - if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) + if (!bDeleted) { - return result; + if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) + { + return result; + } } ppMergeFirst = previous = ppTrunkInfo; } + bDeleted = false; if (ppEnd - ppMergeFirst > 1) { - trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); + trunk_merge_spaces(ppMergeFirst, previous, + &merge_stat, &bDeleted); } - if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) + if (!bDeleted) { - return result; + if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) + { + return result; + } } logInfo("file: "__FILE__", line: %d, " diff --git a/storage/trunk_mgr/trunk_mem.h b/storage/trunk_mgr/trunk_mem.h index fe65922..bed9650 100644 --- a/storage/trunk_mgr/trunk_mem.h +++ b/storage/trunk_mgr/trunk_mem.h @@ -58,6 +58,7 @@ extern bool g_trunk_create_file_advance; extern bool g_trunk_init_check_occupying; extern bool g_trunk_init_reload_from_binlog; extern bool g_trunk_free_space_merge; +extern bool g_delete_unused_trunk_files; extern int g_trunk_binlog_compress_stage; extern bool g_if_trunker_self; //if am i trunk server extern int64_t g_trunk_create_file_space_threshold; diff --git a/tracker/tracker_func.c b/tracker/tracker_func.c index 00d9824..be08a4c 100644 --- a/tracker/tracker_func.c +++ b/tracker/tracker_func.c @@ -601,6 +601,9 @@ int tracker_load_from_conf_file(const char *filename, \ g_trunk_free_space_merge = iniGetBoolValue(NULL, "trunk_free_space_merge", &iniContext, false); + g_delete_unused_trunk_files = iniGetBoolValue(NULL, + "delete_unused_trunk_files", &iniContext, false); + if ((result=tracker_load_storage_id_info( filename, &iniContext)) != 0) { @@ -779,6 +782,7 @@ int tracker_load_from_conf_file(const char *filename, \ "trunk_init_check_occupying=%d, " "trunk_init_reload_from_binlog=%d, " "trunk_free_space_merge=%d, " + "delete_unused_trunk_files=%d, " "trunk_compress_binlog_min_interval=%d, " "trunk_compress_binlog_interval=%d, " "trunk_compress_binlog_time_base=%02d:%02d, " @@ -823,6 +827,7 @@ int tracker_load_from_conf_file(const char *filename, \ (FDFS_ONE_MB * 1024)), g_trunk_init_check_occupying, g_trunk_init_reload_from_binlog, g_trunk_free_space_merge, + g_delete_unused_trunk_files, g_trunk_compress_binlog_min_interval, g_trunk_compress_binlog_interval, g_trunk_compress_binlog_time_base.hour, diff --git a/tracker/tracker_global.c b/tracker/tracker_global.c index bd19da4..a02d530 100644 --- a/tracker/tracker_global.c +++ b/tracker/tracker_global.c @@ -52,6 +52,7 @@ bool g_trunk_create_file_advance = false; bool g_trunk_init_check_occupying = false; bool g_trunk_init_reload_from_binlog = false; bool g_trunk_free_space_merge = false; +bool g_delete_unused_trunk_files = false; int g_slot_min_size = 256; //slot min size, such as 256 bytes int g_slot_max_size = 16 * 1024 * 1024; //slot max size, such as 16MB int g_trunk_file_size = 64 * 1024 * 1024; //the trunk file size, such as 64MB diff --git a/tracker/tracker_global.h b/tracker/tracker_global.h index cfd489f..fc0580f 100644 --- a/tracker/tracker_global.h +++ b/tracker/tracker_global.h @@ -76,6 +76,7 @@ extern bool g_trunk_create_file_advance; extern bool g_trunk_init_check_occupying; extern bool g_trunk_init_reload_from_binlog; extern bool g_trunk_free_space_merge; +extern bool g_delete_unused_trunk_files; extern int g_slot_min_size; //slot min size, such as 256 bytes extern int g_slot_max_size; //slot max size, such as 16MB extern int g_trunk_file_size; //the trunk file size, such as 64MB diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index f98d8fb..063df82 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -665,12 +665,12 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) if (pTask->length - sizeof(TrackerHeader) != 0) { - logError("file: "__FILE__", line: %d, " \ - "cmd=%d, client ip: %s, package size " \ - PKG_LEN_PRINTF_FORMAT" is not correct, " \ - "expect length = %d", __LINE__, \ - TRACKER_PROTO_CMD_STORAGE_PARAMETER_REQ, \ - pTask->client_ip, pTask->length - \ + logError("file: "__FILE__", line: %d, " + "cmd=%d, client ip: %s, package size " + PKG_LEN_PRINTF_FORMAT" is not correct, " + "expect length = %d", __LINE__, + TRACKER_PROTO_CMD_STORAGE_PARAMETER_REQ, + pTask->client_ip, pTask->length - (int)sizeof(TrackerHeader), 0); pTask->length = sizeof(TrackerHeader); @@ -696,6 +696,7 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) "trunk_init_check_occupying=%d\n" "trunk_init_reload_from_binlog=%d\n" "trunk_free_space_merge=%d\n" + "delete_unused_trunk_files=%d\n" "trunk_compress_binlog_min_interval=%d\n" "trunk_compress_binlog_interval=%d\n" "trunk_compress_binlog_time_base=%02d:%02d\n" @@ -718,6 +719,7 @@ static int tracker_deal_parameter_req(struct fast_task_info *pTask) g_trunk_init_check_occupying, g_trunk_init_reload_from_binlog, g_trunk_free_space_merge, + g_delete_unused_trunk_files, g_trunk_compress_binlog_min_interval, g_trunk_compress_binlog_interval, g_trunk_compress_binlog_time_base.hour, From 71856858ebcee40614d37848f2f53971467eb8f7 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 23 Dec 2019 19:05:30 +0800 Subject: [PATCH 80/95] bugfixed: delete first merged trunk node --- HISTORY | 3 + storage/trunk_mgr/trunk_mem.c | 279 +++++++++++++++++++--------------- 2 files changed, 156 insertions(+), 126 deletions(-) diff --git a/HISTORY b/HISTORY index 3807ec7..ac1cfdb 100644 --- a/HISTORY +++ b/HISTORY @@ -3,11 +3,13 @@ Version 6.05 2019-12-23 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version + * trunk server support compress the trunk binlog periodically, the config items in tracker.conf: trunk_compress_binlog_interval and trunk_compress_binlog_time_base * trunk binlog compression support transaction + * support backup binlog file when truncate trunk binlog, the config item in tracker.conf: trunk_binlog_max_backups @@ -20,6 +22,7 @@ Version 6.05 2019-12-23 * support delete unused trunk files the config item in tracker.conf: delete_unused_trunk_files + Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST * use get_gzip_command_filename from libfastcommon v1.42 diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 76ca2cf..f831437 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -81,7 +81,11 @@ static struct fast_mblock_man tree_nodes_man; static AVLTreeInfo *tree_info_by_sizes = NULL; //for block alloc static int trunk_create_next_file(FDFSTrunkFullInfo *pTrunkInfo); -static int trunk_add_free_block(FDFSTrunkNode *pNode, const bool bWriteBinLog); +static int trunk_add_free_block_ex(FDFSTrunkNode *pNode, + const bool bNeedLock, const bool bWriteBinLog); + +#define trunk_add_free_block(pNode, bWriteBinLog) \ + trunk_add_free_block_ex(pNode, true, bWriteBinLog) static int trunk_restore_node(const FDFSTrunkFullInfo *pTrunkInfo); @@ -90,6 +94,10 @@ static int trunk_delete_space_ex(const FDFSTrunkFullInfo *pTrunkInfo, #define trunk_delete_space(pTrunkInfo, bWriteBinLog) \ trunk_delete_space_ex(pTrunkInfo, true, bWriteBinLog) +static FDFSTrunkFullInfo *free_space_by_trunk(const FDFSTrunkFullInfo + *pTrunkInfo, const bool bNeedLock, const bool bWriteBinLog, + int *result); + static int storage_trunk_save(); static int storage_trunk_load(); @@ -543,30 +551,25 @@ typedef struct trunk_merge_stat } TrunkMergeStat; static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, - FDFSTrunkFullInfo **ppLast, TrunkMergeStat *merge_stat, - bool *bDeleted) + FDFSTrunkFullInfo **ppLast, TrunkMergeStat *merge_stat) { FDFSTrunkFullInfo **ppTrunkInfo; - int append_size; + int merged_size; char full_filename[MAX_PATH_SIZE]; struct stat file_stat; - (*ppMergeFirst)->file.size = (*ppLast)->file.offset - - (*ppMergeFirst)->file.offset + (*ppLast)->file.size; + merged_size = (*ppLast)->file.offset - (*ppMergeFirst)->file.offset + + (*ppLast)->file.size; merge_stat->merge_count++; merge_stat->merged_trunk_count += (ppLast - ppMergeFirst) + 1; - merge_stat->merged_size += (*ppMergeFirst)->file.size; + merge_stat->merged_size += merged_size; - append_size = 0; for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) { - append_size += (*ppTrunkInfo)->file.size; trunk_delete_space_ex(*ppTrunkInfo, false, false); } - __sync_add_and_fetch(&g_trunk_total_free_space, - append_size); do { if (!g_delete_unused_trunk_files) @@ -575,7 +578,7 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, } if (!((*ppMergeFirst)->file.offset == 0 && - (*ppMergeFirst)->file.size >= g_trunk_file_size)) + merged_size >= g_trunk_file_size)) { break; } @@ -590,7 +593,7 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, full_filename, errno, STRERROR(errno)); break; } - if ((*ppMergeFirst)->file.size != file_stat.st_size) + if (merged_size != file_stat.st_size) { break; } @@ -611,8 +614,21 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, "delete unused trunk file: %s", __LINE__, full_filename); trunk_delete_space_ex(*ppMergeFirst, false, false); - *bDeleted = true; + *ppMergeFirst = NULL; } while (0); + + if (*ppMergeFirst != NULL) + { + FDFSTrunkFullInfo trunkInfo; + int result; + + trunkInfo = **ppMergeFirst; + trunkInfo.file.size = merged_size; + + trunk_delete_space_ex(*ppMergeFirst, false, false); + *ppMergeFirst = free_space_by_trunk(&trunkInfo, + false, false, &result); + } } static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) @@ -624,7 +640,6 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) TrunkMergeStat merge_stat; char comma_buff[32]; int result; - bool bDeleted; if (pCallbackArgs->trunk_array.count == 0) { @@ -653,13 +668,11 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) continue; } - bDeleted = false; if (ppTrunkInfo - ppMergeFirst > 1) { - trunk_merge_spaces(ppMergeFirst, previous, - &merge_stat, &bDeleted); + trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); } - if (!bDeleted) + if (*ppMergeFirst != NULL) { if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) { @@ -670,13 +683,11 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) ppMergeFirst = previous = ppTrunkInfo; } - bDeleted = false; if (ppEnd - ppMergeFirst > 1) { - trunk_merge_spaces(ppMergeFirst, previous, - &merge_stat, &bDeleted); + trunk_merge_spaces(ppMergeFirst, previous, &merge_stat); } - if (!bDeleted) + if (*ppMergeFirst != NULL) { if ((result=save_one_trunk(pCallbackArgs, *ppMergeFirst)) != 0) { @@ -1672,48 +1683,23 @@ static int storage_trunk_load() return storage_trunk_restore(restore_offset); } -int trunk_free_space(const FDFSTrunkFullInfo *pTrunkInfo, \ - const bool bWriteBinLog) +static FDFSTrunkFullInfo *free_space_by_trunk(const FDFSTrunkFullInfo + *pTrunkInfo, const bool bNeedLock, const bool bWriteBinLog, + int *result) { - int result; struct fast_mblock_node *pMblockNode; FDFSTrunkNode *pTrunkNode; - if (!g_if_trunker_self) - { - logError("file: "__FILE__", line: %d, " \ - "I am not trunk server!", __LINE__); - return EINVAL; - } - - if (trunk_init_flag != STORAGE_TRUNK_INIT_FLAG_DONE) - { - if (bWriteBinLog) - { - logError("file: "__FILE__", line: %d, " \ - "I am not inited!", __LINE__); - return EINVAL; - } - } - - if (pTrunkInfo->file.size < g_slot_min_size) - { - logDebug("file: "__FILE__", line: %d, " \ - "space: %d is too small, do not need recycle!", \ - __LINE__, pTrunkInfo->file.size); - return 0; - } - pMblockNode = fast_mblock_alloc(&free_blocks_man); if (pMblockNode == NULL) { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "malloc %d bytes fail, " \ - "errno: %d, error info: %s", \ - __LINE__, (int)sizeof(FDFSTrunkNode), \ - result, STRERROR(result)); - return result; + *result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail, " + "errno: %d, error info: %s", + __LINE__, (int)sizeof(FDFSTrunkNode), + *result, STRERROR(*result)); + return NULL; } pTrunkNode = (FDFSTrunkNode *)pMblockNode->data; @@ -1722,89 +1708,130 @@ int trunk_free_space(const FDFSTrunkFullInfo *pTrunkInfo, \ pTrunkNode->pMblockNode = pMblockNode; pTrunkNode->trunk.status = FDFS_TRUNK_STATUS_FREE; pTrunkNode->next = NULL; - return trunk_add_free_block(pTrunkNode, bWriteBinLog); + *result = trunk_add_free_block_ex(pTrunkNode, bNeedLock, bWriteBinLog); + return &pTrunkNode->trunk; } -static int trunk_add_free_block(FDFSTrunkNode *pNode, const bool bWriteBinLog) +int trunk_free_space(const FDFSTrunkFullInfo *pTrunkInfo, + const bool bWriteBinLog) +{ + int result; + if (!g_if_trunker_self) + { + logError("file: "__FILE__", line: %d, " + "I am not trunk server!", __LINE__); + return EINVAL; + } + + if (trunk_init_flag != STORAGE_TRUNK_INIT_FLAG_DONE) + { + if (bWriteBinLog) + { + logError("file: "__FILE__", line: %d, " + "I am not inited!", __LINE__); + return EINVAL; + } + } + + if (pTrunkInfo->file.size < g_slot_min_size) + { + logDebug("file: "__FILE__", line: %d, " + "space: %d is too small, do not need reclaim!", + __LINE__, pTrunkInfo->file.size); + return 0; + } + + free_space_by_trunk(pTrunkInfo, true, bWriteBinLog, &result); + return result; +} + +static int trunk_add_free_block_ex(FDFSTrunkNode *pNode, + const bool bNeedLock, const bool bWriteBinLog) { int result; struct fast_mblock_node *pMblockNode; FDFSTrunkSlot target_slot; FDFSTrunkSlot *chain; - pthread_mutex_lock(&trunk_mem_lock); + if (bNeedLock) + { + pthread_mutex_lock(&trunk_mem_lock); + } - if ((result=trunk_free_block_check_duplicate(&(pNode->trunk))) != 0) - { - pthread_mutex_unlock(&trunk_mem_lock); - return result; - } + do + { + if ((result=trunk_free_block_check_duplicate(&(pNode->trunk))) != 0) + { + break; + } - target_slot.size = pNode->trunk.file.size; - target_slot.head = NULL; - chain = (FDFSTrunkSlot *)avl_tree_find(tree_info_by_sizes + \ - pNode->trunk.path.store_path_index, &target_slot); - if (chain == NULL) - { - pMblockNode = fast_mblock_alloc(&tree_nodes_man); - if (pMblockNode == NULL) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "malloc %d bytes fail, " \ - "errno: %d, error info: %s", \ - __LINE__, (int)sizeof(FDFSTrunkSlot), \ - result, STRERROR(result)); - pthread_mutex_unlock(&trunk_mem_lock); - return result; - } + target_slot.size = pNode->trunk.file.size; + target_slot.head = NULL; + chain = (FDFSTrunkSlot *)avl_tree_find(tree_info_by_sizes + + pNode->trunk.path.store_path_index, &target_slot); + if (chain == NULL) + { + pMblockNode = fast_mblock_alloc(&tree_nodes_man); + if (pMblockNode == NULL) + { + result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail, " + "errno: %d, error info: %s", + __LINE__, (int)sizeof(FDFSTrunkSlot), + result, STRERROR(result)); + break; + } - chain = (FDFSTrunkSlot *)pMblockNode->data; - chain->pMblockNode = pMblockNode; - chain->size = pNode->trunk.file.size; - pNode->next = NULL; - chain->head = pNode; + chain = (FDFSTrunkSlot *)pMblockNode->data; + chain->pMblockNode = pMblockNode; + chain->size = pNode->trunk.file.size; + pNode->next = NULL; + chain->head = pNode; - if (avl_tree_insert(tree_info_by_sizes + pNode->trunk. \ - path.store_path_index, chain) != 1) - { - result = errno != 0 ? errno : ENOMEM; - logError("file: "__FILE__", line: %d, " \ - "avl_tree_insert fail, " \ - "errno: %d, error info: %s", \ - __LINE__, result, STRERROR(result)); - pthread_mutex_unlock(&trunk_mem_lock); - return result; - } - } - else - { - pNode->next = chain->head; - chain->head = pNode; - } + if (avl_tree_insert(tree_info_by_sizes + pNode->trunk. + path.store_path_index, chain) != 1) + { + result = errno != 0 ? errno : ENOMEM; + logError("file: "__FILE__", line: %d, " + "avl_tree_insert fail, " + "errno: %d, error info: %s", + __LINE__, result, STRERROR(result)); + break; + } + } + else + { + pNode->next = chain->head; + chain->head = pNode; + } - if (bWriteBinLog) - { - result = trunk_mem_binlog_write(g_current_time, - TRUNK_OP_TYPE_ADD_SPACE, &(pNode->trunk)); - } - else - { - __sync_add_and_fetch(&g_trunk_total_free_space, - pNode->trunk.file.size); - result = 0; - } + if (bWriteBinLog) + { + result = trunk_mem_binlog_write(g_current_time, + TRUNK_OP_TYPE_ADD_SPACE, &(pNode->trunk)); + } + else + { + __sync_add_and_fetch(&g_trunk_total_free_space, + pNode->trunk.file.size); + result = 0; + } - if (result == 0) - { - result = trunk_free_block_insert(&(pNode->trunk)); - } - else - { - trunk_free_block_insert(&(pNode->trunk)); - } + if (result == 0) + { + result = trunk_free_block_insert(&(pNode->trunk)); + } + else + { + trunk_free_block_insert(&(pNode->trunk)); + } + } while (0); - pthread_mutex_unlock(&trunk_mem_lock); + if (bNeedLock) + { + pthread_mutex_unlock(&trunk_mem_lock); + } return result; } From ef31a3115294b7108d668702d9978a42bdf83aa7 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 24 Dec 2019 21:15:46 +0800 Subject: [PATCH 81/95] trunk file id printf format change from %d to %u --- HISTORY | 2 +- INSTALL | 2 +- fastdfs.spec | 2 +- storage/storage_func.c | 4 ++-- storage/trunk_mgr/trunk_mem.c | 32 +++++++++++++++++++++++--------- storage/trunk_mgr/trunk_shared.c | 15 +++++++++------ storage/trunk_mgr/trunk_shared.h | 2 +- storage/trunk_mgr/trunk_sync.c | 9 ++++++++- storage/trunk_mgr/trunk_sync.h | 1 + 9 files changed, 47 insertions(+), 22 deletions(-) diff --git a/HISTORY b/HISTORY index ac1cfdb..f7e5842 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.05 2019-12-23 +Version 6.05 2019-12-24 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version diff --git a/INSTALL b/INSTALL index 840735c..2d730da 100644 --- a/INSTALL +++ b/INSTALL @@ -21,7 +21,7 @@ Chinese language: http://www.fastken.com/ # command lines as: git clone https://github.com/happyfish100/fastdfs.git - cd fastdfs; git checkout V6.04 + cd fastdfs; git checkout V6.05 ./make.sh clean && ./make.sh && ./make.sh install diff --git a/fastdfs.spec b/fastdfs.spec index 1943e16..16dd64b 100644 --- a/fastdfs.spec +++ b/fastdfs.spec @@ -3,7 +3,7 @@ %define FDFSClient libfdfsclient %define FDFSClientDevel libfdfsclient-devel %define FDFSTool fastdfs-tool -%define FDFSVersion 6.0.4 +%define FDFSVersion 6.0.5 %define CommitVersion %(echo $COMMIT_VERSION) Name: %{FastDFS} diff --git a/storage/storage_func.c b/storage/storage_func.c index fae7b1e..2593cdc 100644 --- a/storage/storage_func.c +++ b/storage/storage_func.c @@ -659,7 +659,7 @@ int storage_write_to_sync_ini_file() "%s=%s\n" "%s=%d\n" "%s=%d\n" - "%s=%d\n" + "%s=%u\n" "%s=%d\n" "%s=%d\n", INIT_ITEM_STORAGE_JOIN_TIME, g_storage_join_time, @@ -1107,7 +1107,7 @@ static int storage_check_and_make_data_dirs() "g_last_storage_ip = %s, " "g_last_server_port = %d, " "g_last_http_port = %d, " - "g_current_trunk_file_id = %d, " + "g_current_trunk_file_id = %u, " "g_trunk_last_compress_time = %d", g_sync_old_done, g_sync_src_id, g_sync_until_timestamp, g_last_storage_ip, g_last_server_port, g_last_http_port, diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index f831437..3b239a3 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -130,7 +130,7 @@ static int storage_trunk_node_compare_offset(void *p1, void *p2) pTrunkInfo1 = &(((FDFSTrunkNode *)p1)->trunk); pTrunkInfo2 = &(((FDFSTrunkNode *)p2)->trunk); - result = memcmp(&(pTrunkInfo1->path), &(pTrunkInfo2->path), \ + result = memcmp(&(pTrunkInfo1->path), &(pTrunkInfo2->path), sizeof(FDFSTrunkPathInfo)); if (result != 0) { @@ -329,8 +329,6 @@ static int64_t storage_trunk_get_binlog_size() char full_filename[MAX_PATH_SIZE]; struct stat stat_buf; - trunk_binlog_sync_func(NULL); - get_trunk_binlog_filename(full_filename); if (stat(full_filename, &stat_buf) != 0) { @@ -418,7 +416,7 @@ static int save_one_trunk(struct walk_callback_args *pCallbackArgs, int result; len = sprintf(pCallbackArgs->pCurrent, - "%d %c %d %d %d %d %d %d\n", + "%d %c %d %d %d %u %d %d\n", (int)g_current_time, TRUNK_OP_TYPE_ADD_SPACE, pTrunkInfo->path.store_path_index, pTrunkInfo->path.sub_path_high, @@ -547,7 +545,9 @@ typedef struct trunk_merge_stat { int merge_count; int merged_trunk_count; + int deleted_file_count; int64_t merged_size; + int64_t deleted_file_size; } TrunkMergeStat; static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, @@ -613,6 +613,9 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, logInfo("file: "__FILE__", line: %d, " "delete unused trunk file: %s", __LINE__, full_filename); + + merge_stat->deleted_file_count++; + merge_stat->deleted_file_size += merged_size; trunk_delete_space_ex(*ppMergeFirst, false, false); *ppMergeFirst = NULL; } while (0); @@ -638,7 +641,8 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) FDFSTrunkFullInfo **previous; FDFSTrunkFullInfo **ppMergeFirst; TrunkMergeStat merge_stat; - char comma_buff[32]; + char merged_comma_buff[32]; + char deleted_comma_buff[32]; int result; if (pCallbackArgs->trunk_array.count == 0) @@ -652,6 +656,8 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) merge_stat.merge_count = 0; merge_stat.merged_trunk_count = 0; merge_stat.merged_size = 0; + merge_stat.deleted_file_count = 0; + merge_stat.deleted_file_size = 0; previous = NULL; ppEnd = pCallbackArgs->trunk_array.trunks + @@ -697,10 +703,13 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) logInfo("file: "__FILE__", line: %d, " "merge free trunk spaces, merge count: %d, " - "merged trunk count: %d, merged size: %s", + "merged trunk count: %d, merged size: %s, " + "deleted file count: %d, deleted file size: %s", __LINE__, merge_stat.merge_count, merge_stat.merged_trunk_count, - long_to_comma_str(merge_stat.merged_size, comma_buff)); + long_to_comma_str(merge_stat.merged_size, merged_comma_buff), + merge_stat.deleted_file_count, long_to_comma_str( + merge_stat.deleted_file_size, deleted_comma_buff)); return 0; } @@ -715,10 +724,14 @@ static int do_save_trunk_data() int result; int i; + pthread_mutex_lock(&trunk_mem_lock); + trunk_binlog_flush(false); trunk_binlog_size = storage_trunk_get_binlog_size(); if (trunk_binlog_size < 0) { - return errno != 0 ? errno : EPERM; + result = errno != 0 ? errno : EIO; + pthread_mutex_unlock(&trunk_mem_lock); + return result; } memset(&callback_args, 0, sizeof(callback_args)); @@ -736,6 +749,8 @@ static int do_save_trunk_data() "errno: %d, error info: %s", __LINE__, callback_args.temp_trunk_filename, result, STRERROR(result)); + + pthread_mutex_unlock(&trunk_mem_lock); return result; } @@ -744,7 +759,6 @@ static int do_save_trunk_data() callback_args.pCurrent += len; result = 0; - pthread_mutex_lock(&trunk_mem_lock); for (i=0; ipath.store_path_index, \ pTrunkInfo->path.sub_path_high, \ pTrunkInfo->path.sub_path_low, \ @@ -382,7 +382,7 @@ void trunk_file_info_encode(const FDFSTrunkFileInfo *pTrunkFile, char *str) int2buff(pTrunkFile->id, buff); int2buff(pTrunkFile->offset, buff + sizeof(int)); int2buff(pTrunkFile->size, buff + sizeof(int) * 2); - base64_encode_ex(&g_fdfs_base64_context, buff, sizeof(buff), \ + base64_encode_ex(&g_fdfs_base64_context, buff, sizeof(buff), str, &len, false); } @@ -391,7 +391,7 @@ void trunk_file_info_decode(const char *str, FDFSTrunkFileInfo *pTrunkFile) char buff[FDFS_TRUNK_FILE_INFO_LEN]; int len; - base64_decode_auto(&g_fdfs_base64_context, str, FDFS_TRUNK_FILE_INFO_LEN, \ + base64_decode_auto(&g_fdfs_base64_context, str, FDFS_TRUNK_FILE_INFO_LEN, buff, &len); pTrunkFile->id = buff2int(buff); @@ -659,10 +659,13 @@ int trunk_file_do_lstat_func_ex(const FDFSStorePaths *pStorePaths, \ } else { - close(fd); - logError("file: "__FILE__", line: %d, " \ - "Invalid file type: %d", __LINE__, \ + /* + logError("file: "__FILE__", line: %d, " + "Invalid file type: %d", __LINE__, pTrunkHeader->file_type); + */ + + close(fd); return ENOENT; } diff --git a/storage/trunk_mgr/trunk_shared.h b/storage/trunk_mgr/trunk_shared.h index 85f11e2..d2e69e8 100644 --- a/storage/trunk_mgr/trunk_shared.h +++ b/storage/trunk_mgr/trunk_shared.h @@ -46,7 +46,7 @@ #define IS_TRUNK_FILE_BY_ID(trunkInfo) (trunkInfo.file.id > 0) #define TRUNK_GET_FILENAME(file_id, filename) \ - sprintf(filename, "%06d", file_id) + sprintf(filename, "%06u", file_id) typedef struct { diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index fdd9139..018a9c5 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -1331,6 +1331,13 @@ static int trunk_binlog_fsync_ex(const bool bNeedLock, return write_ret; } +int trunk_binlog_flush(const bool bNeedLock) +{ + return trunk_binlog_fsync_ex(bNeedLock, + trunk_binlog_write_cache_buff, + (&trunk_binlog_write_cache_len)); +} + int trunk_binlog_write(const int timestamp, const char op_type, \ const FDFSTrunkFullInfo *pTrunk) { @@ -1347,7 +1354,7 @@ int trunk_binlog_write(const int timestamp, const char op_type, \ trunk_binlog_write_cache_len += sprintf(trunk_binlog_write_cache_buff + \ trunk_binlog_write_cache_len, \ - "%d %c %d %d %d %d %d %d\n", \ + "%d %c %d %d %d %u %d %d\n", \ timestamp, op_type, \ pTrunk->path.store_path_index, \ pTrunk->path.sub_path_high, \ diff --git a/storage/trunk_mgr/trunk_sync.h b/storage/trunk_mgr/trunk_sync.h index 4292c8f..958d7bd 100644 --- a/storage/trunk_mgr/trunk_sync.h +++ b/storage/trunk_mgr/trunk_sync.h @@ -61,6 +61,7 @@ int trunk_sync_thread_start_all(); int trunk_sync_thread_start(const FDFSStorageBrief *pStorage); int kill_trunk_sync_threads(); int trunk_binlog_sync_func(void *args); +int trunk_binlog_flush(const bool bNeedLock); //wrapper for trunk_binlog_fsync void trunk_waiting_sync_thread_exit(); char *get_trunk_binlog_filename(char *full_filename); From aefb4611aa28cb88242f0e19170e89d708c10dd8 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 24 Dec 2019 21:44:50 +0800 Subject: [PATCH 82/95] refine logging delete unused trunk files --- storage/trunk_mgr/trunk_mem.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 3b239a3..c048ed7 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -643,6 +643,7 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) TrunkMergeStat merge_stat; char merged_comma_buff[32]; char deleted_comma_buff[32]; + char delete_file_prompt[256]; int result; if (pCallbackArgs->trunk_array.count == 0) @@ -701,15 +702,25 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) } } + if (g_delete_unused_trunk_files) + { + sprintf(delete_file_prompt, ", deleted file count: %d, " + "deleted file size: %s", merge_stat.deleted_file_count, + long_to_comma_str(merge_stat.deleted_file_size, + deleted_comma_buff)); + } + else + { + *delete_file_prompt = '\0'; + } + logInfo("file: "__FILE__", line: %d, " "merge free trunk spaces, merge count: %d, " - "merged trunk count: %d, merged size: %s, " - "deleted file count: %d, deleted file size: %s", + "merged trunk count: %d, merged size: %s%s", __LINE__, merge_stat.merge_count, merge_stat.merged_trunk_count, long_to_comma_str(merge_stat.merged_size, merged_comma_buff), - merge_stat.deleted_file_count, long_to_comma_str( - merge_stat.deleted_file_size, deleted_comma_buff)); + delete_file_prompt); return 0; } From e6ec41ba049d414964233495c3ef06a93f6eaaf9 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 24 Dec 2019 22:12:40 +0800 Subject: [PATCH 83/95] static variable expect_header --- storage/storage_dio.c | 17 ++++++++--------- storage/trunk_mgr/trunk_mem.c | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/storage/storage_dio.c b/storage/storage_dio.c index 0b9ccd5..56a6c59 100644 --- a/storage/storage_dio.c +++ b/storage/storage_dio.c @@ -814,35 +814,34 @@ int dio_check_trunk_file_ex(int fd, const char *filename, const int64_t offset) { int result; char old_header[FDFS_TRUNK_FILE_HEADER_SIZE]; - char expect_header[FDFS_TRUNK_FILE_HEADER_SIZE]; + static char expect_header[FDFS_TRUNK_FILE_HEADER_SIZE] = {'\0'}; if (fc_safe_read(fd, old_header, FDFS_TRUNK_FILE_HEADER_SIZE) != FDFS_TRUNK_FILE_HEADER_SIZE) { result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "read trunk header of file: %s fail, " \ - "errno: %d, error info: %s", \ - __LINE__, filename, \ + logError("file: "__FILE__", line: %d, " + "read trunk header of file: %s fail, " + "errno: %d, error info: %s", + __LINE__, filename, result, STRERROR(result)); return result; } - memset(expect_header, 0, sizeof(expect_header)); - if (memcmp(old_header, expect_header, \ + if (memcmp(old_header, expect_header, FDFS_TRUNK_FILE_HEADER_SIZE) != 0) { FDFSTrunkHeader srcOldTrunkHeader; FDFSTrunkHeader newOldTrunkHeader; trunk_unpack_header(old_header, &srcOldTrunkHeader); - memcpy(&newOldTrunkHeader, &srcOldTrunkHeader, \ + memcpy(&newOldTrunkHeader, &srcOldTrunkHeader, sizeof(FDFSTrunkHeader)); newOldTrunkHeader.alloc_size = 0; newOldTrunkHeader.file_size = 0; newOldTrunkHeader.file_type = 0; trunk_pack_header(&newOldTrunkHeader, old_header); - if (memcmp(old_header, expect_header, \ + if (memcmp(old_header, expect_header, FDFS_TRUNK_FILE_HEADER_SIZE) != 0) { char buff[256]; diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index c048ed7..135869c 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -2540,7 +2540,7 @@ int trunk_file_delete(const char *trunk_filename, \ remain_bytes = pTrunkInfo->file.size - FDFS_TRUNK_FILE_HEADER_SIZE; while (remain_bytes > 0) { - write_bytes = remain_bytes > sizeof(buff) ? \ + write_bytes = remain_bytes > sizeof(buff) ? sizeof(buff) : remain_bytes; if (fc_safe_write(fd, buff, write_bytes) != write_bytes) { From a885fd23ccd87f33c1d3123e767a77154502be6d Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 25 Dec 2019 17:37:51 +0800 Subject: [PATCH 84/95] set all space to ascii 0 when delete trunk file --- storage/fdfs_storaged.c | 6 +- storage/storage_dio.c | 2 +- storage/trunk_mgr/trunk_mem.c | 264 +++++++++++++------------------ storage/trunk_mgr/trunk_shared.c | 15 +- storage/trunk_mgr/trunk_shared.h | 3 +- storage/trunk_mgr/trunk_sync.c | 22 +-- 6 files changed, 134 insertions(+), 178 deletions(-) diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index 376b4b1..f721aa8 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -103,7 +103,11 @@ int main(int argc, char *argv[]) g_up_time = g_current_time; log_init2(); - trunk_shared_init(); + if ((result=trunk_shared_init()) != 0) + { + log_destroy(); + return result; + } conf_filename = argv[1]; if (!fileExists(conf_filename)) diff --git a/storage/storage_dio.c b/storage/storage_dio.c index 56a6c59..dc7c9ad 100644 --- a/storage/storage_dio.c +++ b/storage/storage_dio.c @@ -222,7 +222,7 @@ int dio_delete_trunk_file(struct fast_task_info *pTask) pFileContext = &(((StorageClientInfo *)pTask->arg)->file_context); - if ((result=trunk_file_delete(pFileContext->filename, \ + if ((result=trunk_file_delete(pFileContext->filename, &(pFileContext->extra_info.upload.trunk_info))) != 0) { pFileContext->log_callback(pTask, result); diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 135869c..b347e69 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -26,6 +26,7 @@ #include "fastcommon/pthread_func.h" #include "fastcommon/sched_thread.h" #include "fastcommon/avl_tree.h" +#include "fastcommon/buffered_file_writer.h" #include "tracker_types.h" #include "tracker_proto.h" #include "storage_global.h" @@ -148,7 +149,7 @@ static int storage_trunk_node_compare_offset(void *p1, void *p2) char *storage_trunk_get_data_filename(char *full_filename) { - snprintf(full_filename, MAX_PATH_SIZE, "%s/data/%s", \ + snprintf(full_filename, MAX_PATH_SIZE, "%s/data/%s", g_fdfs_base_path, STORAGE_TRUNK_DATA_FILENAME); return full_filename; } @@ -355,10 +356,7 @@ struct trunk_info_array { }; struct walk_callback_args { - int fd; - char buff[16 * 1024]; - char temp_trunk_filename[MAX_PATH_SIZE]; - char *pCurrent; + BufferedFileWriter data_writer; struct trunk_info_array trunk_array; //for space combine struct { @@ -367,8 +365,7 @@ struct walk_callback_args { } stats; }; -static int trunk_alloc_trunk_array( - struct trunk_info_array *trunk_array) +static int trunk_alloc_trunk_array(struct trunk_info_array *trunk_array) { int bytes; FDFSTrunkFullInfo **trunks; @@ -376,7 +373,7 @@ static int trunk_alloc_trunk_array( if (trunk_array->alloc == 0) { - alloc = 64 * 1024; + alloc = 16 * 1024; } else { @@ -409,13 +406,26 @@ static int trunk_alloc_trunk_array( return 0; } -static int save_one_trunk(struct walk_callback_args *pCallbackArgs, +static inline int trunk_merge_add_to_array(struct trunk_info_array + *trunk_array, FDFSTrunkFullInfo *pTrunkInfo) +{ + int result; + if (trunk_array->count >= trunk_array->alloc) + { + if ((result=trunk_alloc_trunk_array(trunk_array)) != 0) + { + return result; + } + } + + trunk_array->trunks[trunk_array->count++] = pTrunkInfo; + return 0; +} + +static inline int save_one_trunk(struct walk_callback_args *pCallbackArgs, FDFSTrunkFullInfo *pTrunkInfo) { - int len; - int result; - - len = sprintf(pCallbackArgs->pCurrent, + return buffered_file_writer_append(&pCallbackArgs->data_writer, "%d %c %d %d %d %u %d %d\n", (int)g_current_time, TRUNK_OP_TYPE_ADD_SPACE, pTrunkInfo->path.store_path_index, @@ -424,27 +434,6 @@ static int save_one_trunk(struct walk_callback_args *pCallbackArgs, pTrunkInfo->file.id, pTrunkInfo->file.offset, pTrunkInfo->file.size); - pCallbackArgs->pCurrent += len; - if (pCallbackArgs->pCurrent - pCallbackArgs->buff > - sizeof(pCallbackArgs->buff) - 128) - { - if (fc_safe_write(pCallbackArgs->fd, pCallbackArgs->buff, - pCallbackArgs->pCurrent - pCallbackArgs->buff) - != pCallbackArgs->pCurrent - pCallbackArgs->buff) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " - "write to file %s fail, " - "errno: %d, error info: %s", __LINE__, - pCallbackArgs->temp_trunk_filename, - result, STRERROR(result)); - return result; - } - - pCallbackArgs->pCurrent = pCallbackArgs->buff; - } - - return 0; } static int tree_walk_callback_to_file(void *data, void *args) @@ -480,18 +469,12 @@ static int tree_walk_callback_to_list(void *data, void *args) pCurrent = ((FDFSTrunkSlot *)data)->head; while (pCurrent != NULL) { - if (pCallbackArgs->trunk_array.count >= pCallbackArgs->trunk_array.alloc) + if ((result=trunk_merge_add_to_array(&pCallbackArgs->trunk_array, + &pCurrent->trunk)) != 0) { - if ((result=trunk_alloc_trunk_array( - &pCallbackArgs->trunk_array)) != 0) - { - return result; - } + return result; } - pCallbackArgs->trunk_array.trunks[pCallbackArgs->trunk_array. - count++] = &pCurrent->trunk; - pCallbackArgs->stats.trunk_count++; pCallbackArgs->stats.total_size += pCurrent->trunk.file.size; @@ -550,11 +533,12 @@ typedef struct trunk_merge_stat int64_t deleted_file_size; } TrunkMergeStat; -static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, +static int trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, FDFSTrunkFullInfo **ppLast, TrunkMergeStat *merge_stat) { - FDFSTrunkFullInfo **ppTrunkInfo; + int result; int merged_size; + FDFSTrunkFullInfo **ppTrunkInfo; char full_filename[MAX_PATH_SIZE]; struct stat file_stat; @@ -565,11 +549,6 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, merge_stat->merged_trunk_count += (ppLast - ppMergeFirst) + 1; merge_stat->merged_size += merged_size; - for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) - { - trunk_delete_space_ex(*ppTrunkInfo, false, false); - } - do { if (!g_delete_unused_trunk_files) @@ -620,10 +599,10 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, *ppMergeFirst = NULL; } while (0); + result = 0; if (*ppMergeFirst != NULL) { FDFSTrunkFullInfo trunkInfo; - int result; trunkInfo = **ppMergeFirst; trunkInfo.file.size = merged_size; @@ -632,6 +611,13 @@ static void trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, *ppMergeFirst = free_space_by_trunk(&trunkInfo, false, false, &result); } + + for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) + { + trunk_delete_space_ex(*ppTrunkInfo, false, false); + } + + return result; } static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) @@ -725,16 +711,59 @@ static int trunk_save_merged_spaces(struct walk_callback_args *pCallbackArgs) return 0; } +static int trunk_open_file_writers(struct walk_callback_args *pCallbackArgs) +{ + int result; + char temp_trunk_filename[MAX_PATH_SIZE]; + + memset(pCallbackArgs, 0, sizeof(*pCallbackArgs)); + + snprintf(temp_trunk_filename, MAX_PATH_SIZE, "%s/data/.%s.tmp", + g_fdfs_base_path, STORAGE_TRUNK_DATA_FILENAME); + if ((result=buffered_file_writer_open(&pCallbackArgs->data_writer, + temp_trunk_filename)) != 0) + { + return result; + } + + return 0; +} + +static int trunk_rename_writers_filename(struct walk_callback_args *pCallbackArgs) +{ + char trunk_data_filename[MAX_PATH_SIZE]; + int result; + + storage_trunk_get_data_filename(trunk_data_filename); + if (rename(pCallbackArgs->data_writer.filename, + trunk_data_filename) != 0) + { + result = errno != 0 ? errno : EIO; + logError("file: "__FILE__", line: %d, " + "rename file %s to %s fail, " + "errno: %d, error info: %s", __LINE__, + pCallbackArgs->data_writer.filename, + trunk_data_filename, result, STRERROR(result)); + return result; + } + + return 0; +} + static int do_save_trunk_data() { int64_t trunk_binlog_size; - char trunk_data_filename[MAX_PATH_SIZE]; char comma_buff[32]; struct walk_callback_args callback_args; - int len; int result; + int close_res; int i; + if ((result=trunk_open_file_writers(&callback_args)) != 0) + { + return result; + } + pthread_mutex_lock(&trunk_mem_lock); trunk_binlog_flush(false); trunk_binlog_size = storage_trunk_get_binlog_size(); @@ -745,29 +774,13 @@ static int do_save_trunk_data() return result; } - memset(&callback_args, 0, sizeof(callback_args)); - callback_args.pCurrent = callback_args.buff; - - sprintf(callback_args.temp_trunk_filename, "%s/data/.%s.tmp", - g_fdfs_base_path, STORAGE_TRUNK_DATA_FILENAME); - callback_args.fd = open(callback_args.temp_trunk_filename, - O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (callback_args.fd < 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " - "open file %s fail, " - "errno: %d, error info: %s", - __LINE__, callback_args.temp_trunk_filename, - result, STRERROR(result)); - + if ((result=buffered_file_writer_append(&callback_args.data_writer, + "%"PRId64"\n", trunk_binlog_size)) != 0) + { pthread_mutex_unlock(&trunk_mem_lock); + buffered_file_writer_close(&callback_args.data_writer); return result; - } - - len = sprintf(callback_args.pCurrent, "%"PRId64"\n", - trunk_binlog_size); - callback_args.pCurrent += len; + } result = 0; for (i=0; i 0 && result == 0) - { - if (fc_safe_write(callback_args.fd, callback_args.buff, len) != len) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " - "write to file %s fail, " - "errno: %d, error info: %s", - __LINE__, callback_args.temp_trunk_filename, - result, STRERROR(result)); - } - } + close_res = buffered_file_writer_close(&callback_args.data_writer); - if (result == 0 && fsync(callback_args.fd) != 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " - "fsync file %s fail, " - "errno: %d, error info: %s", - __LINE__, callback_args.temp_trunk_filename, - result, STRERROR(result)); - } - - if (close(callback_args.fd) != 0) - { - if (result == 0) - { - result = errno != 0 ? errno : EIO; - } - logError("file: "__FILE__", line: %d, " - "close file %s fail, " - "errno: %d, error info: %s", - __LINE__, callback_args.temp_trunk_filename, - errno, STRERROR(errno)); - } + if (result == 0) + { + if (close_res != 0) + { + result = close_res; + } + else + { + result = trunk_rename_writers_filename(&callback_args); + } + } pthread_mutex_unlock(&trunk_mem_lock); - if (result != 0) - { - return result; - } + if (callback_args.trunk_array.trunks != NULL) + { + free(callback_args.trunk_array.trunks); + } - storage_trunk_get_data_filename(trunk_data_filename); - if (rename(callback_args.temp_trunk_filename, trunk_data_filename) != 0) - { - result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " - "rename file %s to %s fail, " - "errno: %d, error info: %s", __LINE__, - callback_args.temp_trunk_filename, trunk_data_filename, - result, STRERROR(result)); - } - - return result; + return result; } static int storage_trunk_do_save() @@ -2174,7 +2149,7 @@ int trunk_alloc_space(const int size, FDFSTrunkFullInfo *pResult) pPreviousNode = NULL; pTrunkNode = pSlot->head; while (pTrunkNode != NULL && - pTrunkNode->trunk.status == FDFS_TRUNK_STATUS_HOLD) + pTrunkNode->trunk.status != FDFS_TRUNK_STATUS_FREE) { pPreviousNode = pTrunkNode; pTrunkNode = pTrunkNode->next; @@ -2498,16 +2473,13 @@ bool trunk_check_size(const int64_t file_size) return file_size <= g_slot_max_size; } -int trunk_file_delete(const char *trunk_filename, \ +int trunk_file_delete(const char *trunk_filename, const FDFSTrunkFullInfo *pTrunkInfo) { - char pack_buff[FDFS_TRUNK_FILE_HEADER_SIZE]; - char buff[64 * 1024]; int fd; int write_bytes; int result; int remain_bytes; - FDFSTrunkHeader trunkHeader; fd = open(trunk_filename, O_WRONLY); if (fd < 0) @@ -2522,27 +2494,13 @@ int trunk_file_delete(const char *trunk_filename, \ return result; } - memset(&trunkHeader, 0, sizeof(trunkHeader)); - trunkHeader.alloc_size = pTrunkInfo->file.size; - trunkHeader.file_type = FDFS_TRUNK_FILE_TYPE_NONE; - trunk_pack_header(&trunkHeader, pack_buff); - - write_bytes = fc_safe_write(fd, pack_buff, FDFS_TRUNK_FILE_HEADER_SIZE); - if (write_bytes != FDFS_TRUNK_FILE_HEADER_SIZE) - { - result = errno != 0 ? errno : EIO; - close(fd); - return result; - } - - memset(buff, 0, sizeof(buff)); result = 0; - remain_bytes = pTrunkInfo->file.size - FDFS_TRUNK_FILE_HEADER_SIZE; + remain_bytes = pTrunkInfo->file.size; while (remain_bytes > 0) { - write_bytes = remain_bytes > sizeof(buff) ? - sizeof(buff) : remain_bytes; - if (fc_safe_write(fd, buff, write_bytes) != write_bytes) + write_bytes = remain_bytes > g_zero_buffer.length ? + g_zero_buffer.length : remain_bytes; + if (fc_safe_write(fd, g_zero_buffer.buff, write_bytes) != write_bytes) { result = errno != 0 ? errno : EIO; break; diff --git a/storage/trunk_mgr/trunk_shared.c b/storage/trunk_mgr/trunk_shared.c index 4ee2d6a..174c5c3 100644 --- a/storage/trunk_mgr/trunk_shared.c +++ b/storage/trunk_mgr/trunk_shared.c @@ -26,10 +26,23 @@ FDFSStorePaths g_fdfs_store_paths = {0, NULL}; struct base64_context g_fdfs_base64_context; +BufferInfo g_zero_buffer = {NULL, 0, 0}; -void trunk_shared_init() +int trunk_shared_init() { base64_init_ex(&g_fdfs_base64_context, 0, '-', '_', '.'); + g_zero_buffer.alloc_size = g_zero_buffer.length = 256 * 1024; + g_zero_buffer.buff = (char *)malloc(g_zero_buffer.alloc_size); + if (g_zero_buffer.buff == NULL) + { + logError("file: "__FILE__", line: %d, " + "malloc %d bytes fail", __LINE__, + g_zero_buffer.alloc_size); + return ENOMEM; + } + + memset(g_zero_buffer.buff, 0, g_zero_buffer.length); + return 0; } FDFSStorePathInfo *storage_load_paths_from_conf_file_ex( diff --git a/storage/trunk_mgr/trunk_shared.h b/storage/trunk_mgr/trunk_shared.h index d2e69e8..c845c15 100644 --- a/storage/trunk_mgr/trunk_shared.h +++ b/storage/trunk_mgr/trunk_shared.h @@ -68,6 +68,7 @@ extern "C" { extern FDFSStorePaths g_fdfs_store_paths; //file store paths extern struct base64_context g_fdfs_base64_context; //base64 context +extern BufferInfo g_zero_buffer; //zero buffer for reset typedef int (*stat_func)(const char *filename, struct stat *buf); @@ -102,7 +103,7 @@ FDFSStorePathInfo *storage_load_paths_from_conf_file_ex( IniContext *pItemContext, const char *szSectionName, const bool bUseBasePath, int *path_count, int *err_no); int storage_load_paths_from_conf_file(IniContext *pItemContext); -void trunk_shared_init(); +int trunk_shared_init(); int storage_split_filename(const char *logic_filename, \ int *filename_len, char *true_filename, char **ppStorePath); diff --git a/storage/trunk_mgr/trunk_sync.c b/storage/trunk_mgr/trunk_sync.c index 018a9c5..751d43c 100644 --- a/storage/trunk_mgr/trunk_sync.c +++ b/storage/trunk_mgr/trunk_sync.c @@ -593,29 +593,9 @@ static int trunk_binlog_backup_and_truncate() int storage_delete_trunk_data_file() { char trunk_data_filename[MAX_PATH_SIZE]; - int result; storage_trunk_get_data_filename(trunk_data_filename); - if (unlink(trunk_data_filename) == 0) - { - return 0; - } - - result = errno != 0 ? errno : ENOENT; - if (result == ENOENT) - { - result = 0; - } - else - { - logError("file: "__FILE__", line: %d, " - "unlink trunk data file: %s fail, " - "errno: %d, error info: %s", - __LINE__, trunk_data_filename, - result, STRERROR(result)); - } - - return result; + return fc_delete_file_ex(trunk_data_filename, "trunk data"); } int trunk_binlog_truncate() From 8c5a6b6f004ed373542f4ff34e3b75a85a1a5ae2 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 25 Dec 2019 19:26:58 +0800 Subject: [PATCH 85/95] fdfs_monitor.c: do NOT call getHostnameByIp --- HISTORY | 6 +++++- INSTALL | 2 +- client/fdfs_monitor.c | 3 ++- conf/tracker.conf | 6 +++--- storage/trunk_mgr/trunk_mem.c | 10 +++++----- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/HISTORY b/HISTORY index f7e5842..c1a305d 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,5 @@ -Version 6.05 2019-12-24 +Version 6.05 2019-12-25 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters to show the server version @@ -22,6 +22,10 @@ Version 6.05 2019-12-24 * support delete unused trunk files the config item in tracker.conf: delete_unused_trunk_files + * fdfs_monitor.c: do NOT call getHostnameByIp + + NOTE: you MUST upgrade libfastcommon to V1.43 or later + Version 6.04 2019-12-05 * storage_report_ip_changed ignore result EEXIST diff --git a/INSTALL b/INSTALL index 2d730da..3bf83df 100644 --- a/INSTALL +++ b/INSTALL @@ -11,7 +11,7 @@ Chinese language: http://www.fastken.com/ # command lines as: git clone https://github.com/happyfish100/libfastcommon.git - cd libfastcommon; git checkout V1.0.42 + cd libfastcommon; git checkout V1.0.43 ./make.sh clean && ./make.sh && ./make.sh install diff --git a/client/fdfs_monitor.c b/client/fdfs_monitor.c index d648cab..5adae26 100644 --- a/client/fdfs_monitor.c +++ b/client/fdfs_monitor.c @@ -389,7 +389,8 @@ static int list_storages(FDFSGroupStat *pGroupStat) } } - getHostnameByIp(pStorage->ip_addr, szHostname, sizeof(szHostname)); + //getHostnameByIp(pStorage->ip_addr, szHostname, sizeof(szHostname)); + *szHostname = '\0'; if (*szHostname != '\0') { sprintf(szHostnamePrompt, " (%s)", szHostname); diff --git a/conf/tracker.conf b/conf/tracker.conf index 5ffdfe8..6bf3f4b 100644 --- a/conf/tracker.conf +++ b/conf/tracker.conf @@ -146,7 +146,7 @@ use_trunk_file = false # the min slot size, should <= 4KB # default value is 256 bytes # since V3.00 -slot_min_size = 512 +slot_min_size = 256 # the max slot size, should > slot_min_size # store the upload file to trunk file when it's size <= this value @@ -159,7 +159,7 @@ slot_max_size = 1MB # since V6.05 # NOTE: the larger the alignment size, the less likely of disk # fragmentation, but the more space is wasted. -trunk_alloc_alignment_size = 512 +trunk_alloc_alignment_size = 256 # if merge contiguous free spaces of trunk file # default value is false @@ -169,7 +169,7 @@ trunk_free_space_merge = true # if delete / reclaim the unused trunk files # default value is false # since V6.05 -delete_unused_trunk_files = true +delete_unused_trunk_files = false # the trunk file size, should >= 4MB # default value is 64MB diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index b347e69..a5ff2ff 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -549,6 +549,11 @@ static int trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, merge_stat->merged_trunk_count += (ppLast - ppMergeFirst) + 1; merge_stat->merged_size += merged_size; + for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) + { + trunk_delete_space_ex(*ppTrunkInfo, false, false); + } + do { if (!g_delete_unused_trunk_files) @@ -612,11 +617,6 @@ static int trunk_merge_spaces(FDFSTrunkFullInfo **ppMergeFirst, false, false, &result); } - for (ppTrunkInfo=ppMergeFirst + 1; ppTrunkInfo<=ppLast; ppTrunkInfo++) - { - trunk_delete_space_ex(*ppTrunkInfo, false, false); - } - return result; } From 4be26a52f906f3de2e2ca65fdd7c728a848932a6 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 26 Dec 2019 07:28:40 +0800 Subject: [PATCH 86/95] fdfs_monitor code refine --- client/fdfs_monitor.c | 313 +++++++++++++++++++++--------------------- 1 file changed, 158 insertions(+), 155 deletions(-) diff --git a/client/fdfs_monitor.c b/client/fdfs_monitor.c index 5adae26..f97b484 100644 --- a/client/fdfs_monitor.c +++ b/client/fdfs_monitor.c @@ -277,32 +277,35 @@ static int list_storages(FDFSGroupStat *pGroupStat) char szSyncedDelaySeconds[128]; char szHostname[128]; char szHostnamePrompt[128+8]; + char szDiskTotalSpace[32]; + char szDiskFreeSpace[32]; + char szTrunkSpace[32]; int k; int max_last_source_update; - printf( "group name = %s\n" \ - "disk total space = %"PRId64" MB\n" \ - "disk free space = %"PRId64" MB\n" \ - "trunk free space = %"PRId64" MB\n" \ - "storage server count = %d\n" \ - "active server count = %d\n" \ - "storage server port = %d\n" \ - "storage HTTP port = %d\n" \ - "store path count = %d\n" \ - "subdir count per path = %d\n" \ - "current write server index = %d\n" \ - "current trunk file id = %d\n\n", \ - pGroupStat->group_name, \ - pGroupStat->total_mb, \ - pGroupStat->free_mb, \ - pGroupStat->trunk_free_mb, \ - pGroupStat->count, \ - pGroupStat->active_count, \ - pGroupStat->storage_port, \ - pGroupStat->storage_http_port, \ - pGroupStat->store_path_count, \ - pGroupStat->subdir_count_per_path, \ - pGroupStat->current_write_server, \ + printf( "group name = %s\n" + "disk total space = %s MB\n" + "disk free space = %s MB\n" + "trunk free space = %s MB\n" + "storage server count = %d\n" + "active server count = %d\n" + "storage server port = %d\n" + "storage HTTP port = %d\n" + "store path count = %d\n" + "subdir count per path = %d\n" + "current write server index = %d\n" + "current trunk file id = %d\n\n", + pGroupStat->group_name, + long_to_comma_str(pGroupStat->total_mb, szDiskTotalSpace), + long_to_comma_str(pGroupStat->free_mb, szDiskFreeSpace), + long_to_comma_str(pGroupStat->trunk_free_mb, szTrunkSpace), + pGroupStat->count, + pGroupStat->active_count, + pGroupStat->storage_port, + pGroupStat->storage_http_port, + pGroupStat->store_path_count, + pGroupStat->subdir_count_per_path, + pGroupStat->current_write_server, pGroupStat->current_trunk_file_id ); @@ -411,138 +414,138 @@ static int list_storages(FDFSGroupStat *pGroupStat) *szUpTime = '\0'; } - printf( "\tStorage %d:\n" \ - "\t\tid = %s\n" \ - "\t\tip_addr = %s%s %s\n" \ - "\t\thttp domain = %s\n" \ - "\t\tversion = %s\n" \ - "\t\tjoin time = %s\n" \ - "\t\tup time = %s\n" \ - "\t\ttotal storage = %d MB\n" \ - "\t\tfree storage = %d MB\n" \ - "\t\tupload priority = %d\n" \ - "\t\tstore_path_count = %d\n" \ - "\t\tsubdir_count_per_path = %d\n" \ - "\t\tstorage_port = %d\n" \ - "\t\tstorage_http_port = %d\n" \ - "\t\tcurrent_write_path = %d\n" \ - "\t\tsource storage id = %s\n" \ - "\t\tif_trunk_server = %d\n" \ - "\t\tconnection.alloc_count = %d\n" \ - "\t\tconnection.current_count = %d\n" \ - "\t\tconnection.max_count = %d\n" \ - "\t\ttotal_upload_count = %"PRId64"\n" \ - "\t\tsuccess_upload_count = %"PRId64"\n" \ - "\t\ttotal_append_count = %"PRId64"\n" \ - "\t\tsuccess_append_count = %"PRId64"\n" \ - "\t\ttotal_modify_count = %"PRId64"\n" \ - "\t\tsuccess_modify_count = %"PRId64"\n" \ - "\t\ttotal_truncate_count = %"PRId64"\n" \ - "\t\tsuccess_truncate_count = %"PRId64"\n" \ - "\t\ttotal_set_meta_count = %"PRId64"\n" \ - "\t\tsuccess_set_meta_count = %"PRId64"\n" \ - "\t\ttotal_delete_count = %"PRId64"\n" \ - "\t\tsuccess_delete_count = %"PRId64"\n" \ - "\t\ttotal_download_count = %"PRId64"\n" \ - "\t\tsuccess_download_count = %"PRId64"\n" \ - "\t\ttotal_get_meta_count = %"PRId64"\n" \ - "\t\tsuccess_get_meta_count = %"PRId64"\n" \ - "\t\ttotal_create_link_count = %"PRId64"\n" \ - "\t\tsuccess_create_link_count = %"PRId64"\n"\ - "\t\ttotal_delete_link_count = %"PRId64"\n" \ - "\t\tsuccess_delete_link_count = %"PRId64"\n" \ - "\t\ttotal_upload_bytes = %"PRId64"\n" \ - "\t\tsuccess_upload_bytes = %"PRId64"\n" \ - "\t\ttotal_append_bytes = %"PRId64"\n" \ - "\t\tsuccess_append_bytes = %"PRId64"\n" \ - "\t\ttotal_modify_bytes = %"PRId64"\n" \ - "\t\tsuccess_modify_bytes = %"PRId64"\n" \ - "\t\tstotal_download_bytes = %"PRId64"\n" \ - "\t\tsuccess_download_bytes = %"PRId64"\n" \ - "\t\ttotal_sync_in_bytes = %"PRId64"\n" \ - "\t\tsuccess_sync_in_bytes = %"PRId64"\n" \ - "\t\ttotal_sync_out_bytes = %"PRId64"\n" \ - "\t\tsuccess_sync_out_bytes = %"PRId64"\n" \ - "\t\ttotal_file_open_count = %"PRId64"\n" \ - "\t\tsuccess_file_open_count = %"PRId64"\n" \ - "\t\ttotal_file_read_count = %"PRId64"\n" \ - "\t\tsuccess_file_read_count = %"PRId64"\n" \ - "\t\ttotal_file_write_count = %"PRId64"\n" \ - "\t\tsuccess_file_write_count = %"PRId64"\n" \ - "\t\tlast_heart_beat_time = %s\n" \ - "\t\tlast_source_update = %s\n" \ - "\t\tlast_sync_update = %s\n" \ - "\t\tlast_synced_timestamp = %s %s\n", \ - ++k, pStorage->id, pStorage->ip_addr, \ - szHostnamePrompt, get_storage_status_caption( \ - pStorage->status), pStorage->domain_name, \ - pStorage->version, \ - formatDatetime(pStorage->join_time, \ - "%Y-%m-%d %H:%M:%S", \ - szJoinTime, sizeof(szJoinTime)), \ - szUpTime, pStorage->total_mb, \ - pStorage->free_mb, \ - pStorage->upload_priority, \ - pStorage->store_path_count, \ - pStorage->subdir_count_per_path, \ - pStorage->storage_port, \ - pStorage->storage_http_port, \ - pStorage->current_write_path, \ - pStorage->src_id, \ - pStorage->if_trunk_server, \ - pStorageStat->connection.alloc_count, \ - pStorageStat->connection.current_count, \ - pStorageStat->connection.max_count, \ - pStorageStat->total_upload_count, \ - pStorageStat->success_upload_count, \ - pStorageStat->total_append_count, \ - pStorageStat->success_append_count, \ - pStorageStat->total_modify_count, \ - pStorageStat->success_modify_count, \ - pStorageStat->total_truncate_count, \ - pStorageStat->success_truncate_count, \ - pStorageStat->total_set_meta_count, \ - pStorageStat->success_set_meta_count, \ - pStorageStat->total_delete_count, \ - pStorageStat->success_delete_count, \ - pStorageStat->total_download_count, \ - pStorageStat->success_download_count, \ - pStorageStat->total_get_meta_count, \ - pStorageStat->success_get_meta_count, \ - pStorageStat->total_create_link_count, \ - pStorageStat->success_create_link_count, \ - pStorageStat->total_delete_link_count, \ - pStorageStat->success_delete_link_count, \ - pStorageStat->total_upload_bytes, \ - pStorageStat->success_upload_bytes, \ - pStorageStat->total_append_bytes, \ - pStorageStat->success_append_bytes, \ - pStorageStat->total_modify_bytes, \ - pStorageStat->success_modify_bytes, \ - pStorageStat->total_download_bytes, \ - pStorageStat->success_download_bytes, \ - pStorageStat->total_sync_in_bytes, \ - pStorageStat->success_sync_in_bytes, \ - pStorageStat->total_sync_out_bytes, \ - pStorageStat->success_sync_out_bytes, \ - pStorageStat->total_file_open_count, \ - pStorageStat->success_file_open_count, \ - pStorageStat->total_file_read_count, \ - pStorageStat->success_file_read_count, \ - pStorageStat->total_file_write_count, \ - pStorageStat->success_file_write_count, \ - formatDatetime(pStorageStat->last_heart_beat_time, \ - "%Y-%m-%d %H:%M:%S", \ - szLastHeartBeatTime, sizeof(szLastHeartBeatTime)), \ - formatDatetime(pStorageStat->last_source_update, \ - "%Y-%m-%d %H:%M:%S", \ - szSrcUpdTime, sizeof(szSrcUpdTime)), \ - formatDatetime(pStorageStat->last_sync_update, \ - "%Y-%m-%d %H:%M:%S", \ - szSyncUpdTime, sizeof(szSyncUpdTime)), \ - formatDatetime(pStorageStat->last_synced_timestamp, \ - "%Y-%m-%d %H:%M:%S", \ - szSyncedTimestamp, sizeof(szSyncedTimestamp)),\ + printf( "\tStorage %d:\n" + "\t\tid = %s\n" + "\t\tip_addr = %s%s %s\n" + "\t\thttp domain = %s\n" + "\t\tversion = %s\n" + "\t\tjoin time = %s\n" + "\t\tup time = %s\n" + "\t\ttotal storage = %s MB\n" + "\t\tfree storage = %s MB\n" + "\t\tupload priority = %d\n" + "\t\tstore_path_count = %d\n" + "\t\tsubdir_count_per_path = %d\n" + "\t\tstorage_port = %d\n" + "\t\tstorage_http_port = %d\n" + "\t\tcurrent_write_path = %d\n" + "\t\tsource storage id = %s\n" + "\t\tif_trunk_server = %d\n" + "\t\tconnection.alloc_count = %d\n" + "\t\tconnection.current_count = %d\n" + "\t\tconnection.max_count = %d\n" + "\t\ttotal_upload_count = %"PRId64"\n" + "\t\tsuccess_upload_count = %"PRId64"\n" + "\t\ttotal_append_count = %"PRId64"\n" + "\t\tsuccess_append_count = %"PRId64"\n" + "\t\ttotal_modify_count = %"PRId64"\n" + "\t\tsuccess_modify_count = %"PRId64"\n" + "\t\ttotal_truncate_count = %"PRId64"\n" + "\t\tsuccess_truncate_count = %"PRId64"\n" + "\t\ttotal_set_meta_count = %"PRId64"\n" + "\t\tsuccess_set_meta_count = %"PRId64"\n" + "\t\ttotal_delete_count = %"PRId64"\n" + "\t\tsuccess_delete_count = %"PRId64"\n" + "\t\ttotal_download_count = %"PRId64"\n" + "\t\tsuccess_download_count = %"PRId64"\n" + "\t\ttotal_get_meta_count = %"PRId64"\n" + "\t\tsuccess_get_meta_count = %"PRId64"\n" + "\t\ttotal_create_link_count = %"PRId64"\n" + "\t\tsuccess_create_link_count = %"PRId64"\n" + "\t\ttotal_delete_link_count = %"PRId64"\n" + "\t\tsuccess_delete_link_count = %"PRId64"\n" + "\t\ttotal_upload_bytes = %"PRId64"\n" + "\t\tsuccess_upload_bytes = %"PRId64"\n" + "\t\ttotal_append_bytes = %"PRId64"\n" + "\t\tsuccess_append_bytes = %"PRId64"\n" + "\t\ttotal_modify_bytes = %"PRId64"\n" + "\t\tsuccess_modify_bytes = %"PRId64"\n" + "\t\tstotal_download_bytes = %"PRId64"\n" + "\t\tsuccess_download_bytes = %"PRId64"\n" + "\t\ttotal_sync_in_bytes = %"PRId64"\n" + "\t\tsuccess_sync_in_bytes = %"PRId64"\n" + "\t\ttotal_sync_out_bytes = %"PRId64"\n" + "\t\tsuccess_sync_out_bytes = %"PRId64"\n" + "\t\ttotal_file_open_count = %"PRId64"\n" + "\t\tsuccess_file_open_count = %"PRId64"\n" + "\t\ttotal_file_read_count = %"PRId64"\n" + "\t\tsuccess_file_read_count = %"PRId64"\n" + "\t\ttotal_file_write_count = %"PRId64"\n" + "\t\tsuccess_file_write_count = %"PRId64"\n" + "\t\tlast_heart_beat_time = %s\n" + "\t\tlast_source_update = %s\n" + "\t\tlast_sync_update = %s\n" + "\t\tlast_synced_timestamp = %s %s\n", + ++k, pStorage->id, pStorage->ip_addr, + szHostnamePrompt, get_storage_status_caption( + pStorage->status), pStorage->domain_name, + pStorage->version, + formatDatetime(pStorage->join_time, + "%Y-%m-%d %H:%M:%S", + szJoinTime, sizeof(szJoinTime)), szUpTime, + long_to_comma_str(pStorage->total_mb, szDiskTotalSpace), + long_to_comma_str(pStorage->free_mb, szDiskFreeSpace), + pStorage->upload_priority, + pStorage->store_path_count, + pStorage->subdir_count_per_path, + pStorage->storage_port, + pStorage->storage_http_port, + pStorage->current_write_path, + pStorage->src_id, + pStorage->if_trunk_server, + pStorageStat->connection.alloc_count, + pStorageStat->connection.current_count, + pStorageStat->connection.max_count, + pStorageStat->total_upload_count, + pStorageStat->success_upload_count, + pStorageStat->total_append_count, + pStorageStat->success_append_count, + pStorageStat->total_modify_count, + pStorageStat->success_modify_count, + pStorageStat->total_truncate_count, + pStorageStat->success_truncate_count, + pStorageStat->total_set_meta_count, + pStorageStat->success_set_meta_count, + pStorageStat->total_delete_count, + pStorageStat->success_delete_count, + pStorageStat->total_download_count, + pStorageStat->success_download_count, + pStorageStat->total_get_meta_count, + pStorageStat->success_get_meta_count, + pStorageStat->total_create_link_count, + pStorageStat->success_create_link_count, + pStorageStat->total_delete_link_count, + pStorageStat->success_delete_link_count, + pStorageStat->total_upload_bytes, + pStorageStat->success_upload_bytes, + pStorageStat->total_append_bytes, + pStorageStat->success_append_bytes, + pStorageStat->total_modify_bytes, + pStorageStat->success_modify_bytes, + pStorageStat->total_download_bytes, + pStorageStat->success_download_bytes, + pStorageStat->total_sync_in_bytes, + pStorageStat->success_sync_in_bytes, + pStorageStat->total_sync_out_bytes, + pStorageStat->success_sync_out_bytes, + pStorageStat->total_file_open_count, + pStorageStat->success_file_open_count, + pStorageStat->total_file_read_count, + pStorageStat->success_file_read_count, + pStorageStat->total_file_write_count, + pStorageStat->success_file_write_count, + formatDatetime(pStorageStat->last_heart_beat_time, + "%Y-%m-%d %H:%M:%S", + szLastHeartBeatTime, sizeof(szLastHeartBeatTime)), + formatDatetime(pStorageStat->last_source_update, + "%Y-%m-%d %H:%M:%S", + szSrcUpdTime, sizeof(szSrcUpdTime)), + formatDatetime(pStorageStat->last_sync_update, + "%Y-%m-%d %H:%M:%S", + szSyncUpdTime, sizeof(szSyncUpdTime)), + formatDatetime(pStorageStat->last_synced_timestamp, + "%Y-%m-%d %H:%M:%S", + szSyncedTimestamp, sizeof(szSyncedTimestamp)), szSyncedDelaySeconds); } From a277a082812d6478d514c67d13401462dac66bc2 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 26 Dec 2019 09:22:19 +0800 Subject: [PATCH 87/95] add conditions to call storage_trunk_save --- fastdfs.spec | 4 +-- storage/trunk_mgr/trunk_mem.c | 68 ++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/fastdfs.spec b/fastdfs.spec index 16dd64b..76b6238 100644 --- a/fastdfs.spec +++ b/fastdfs.spec @@ -18,14 +18,14 @@ Source: http://perso.orange.fr/sebastien.godard/%{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: %__cp %__mv %__chmod %__grep %__mkdir %__install %__id -BuildRequires: libfastcommon-devel >= 1.0.42 +BuildRequires: libfastcommon-devel >= 1.0.43 %description This package provides tracker & storage of fastdfs commit version: %{CommitVersion} %package -n %{FDFSServer} -Requires: libfastcommon >= 1.0.42 +Requires: libfastcommon >= 1.0.43 Summary: fastdfs tracker & storage %package -n %{FDFSTool} diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index a5ff2ff..3a1ccda 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -286,7 +286,7 @@ int storage_trunk_destroy_ex(const bool bNeedSleep, if (trunk_init_flag != STORAGE_TRUNK_INIT_FLAG_DONE) { - logWarning("file: "__FILE__", line: %d, " \ + logWarning("file: "__FILE__", line: %d, " "trunk not inited!", __LINE__); return 0; } @@ -297,11 +297,19 @@ int storage_trunk_destroy_ex(const bool bNeedSleep, sleep(1); } - logDebug("file: "__FILE__", line: %d, " \ + logDebug("file: "__FILE__", line: %d, " "storage trunk destroy", __LINE__); if (bSaveData) { - result = storage_trunk_save(); + if (g_current_time - g_up_time >= 3600 && + g_trunk_compress_binlog_interval == 0) + { + result = storage_trunk_save(); + } + else + { + result = 0; + } } else { @@ -1062,7 +1070,7 @@ static int storage_trunk_save() if (!(g_trunk_compress_binlog_min_interval > 0 && g_current_time - g_trunk_last_compress_time > g_trunk_compress_binlog_min_interval)) - { + { if (__sync_add_and_fetch(&trunk_binlog_compress_in_progress, 0) == 0) { return storage_trunk_do_save(); @@ -1075,7 +1083,7 @@ static int storage_trunk_save() __LINE__, trunk_binlog_compress_in_progress); return 0; } - } + } if ((result=storage_trunk_compress()) == 0) { @@ -1262,9 +1270,9 @@ static int storage_trunk_restore(const int64_t restore_offset) if (restore_offset > trunk_binlog_size) { - logWarning("file: "__FILE__", line: %d, " \ - "restore_offset: %"PRId64 \ - " > trunk_binlog_size: %"PRId64, \ + logWarning("file: "__FILE__", line: %d, " + "restore_offset: %"PRId64 + " > trunk_binlog_size: %"PRId64, __LINE__, restore_offset, trunk_binlog_size); return storage_trunk_save(); } @@ -1485,7 +1493,11 @@ static int storage_trunk_restore(const int64_t restore_offset) "%"PRId64", recovery file size: " "%"PRId64, __LINE__, restore_offset, trunk_binlog_size - restore_offset); - return storage_trunk_save(); + + if (g_trunk_compress_binlog_interval == 0) + { + return storage_trunk_save(); + } } return result; @@ -1495,7 +1507,7 @@ static int storage_trunk_load() { #define TRUNK_DATA_NEW_FIELD_COUNT 8 // >= v5.01 #define TRUNK_DATA_OLD_FIELD_COUNT 6 // < V5.01 -#define TRUNK_LINE_MAX_LENGHT 64 +#define TRUNK_LINE_MAX_LENGTH 64 int64_t restore_offset; char trunk_data_filename[MAX_PATH_SIZE]; @@ -1589,24 +1601,24 @@ static int storage_trunk_load() } len = strlen(pLineStart); - if (len > TRUNK_LINE_MAX_LENGHT) + if (len > TRUNK_LINE_MAX_LENGTH) { - logError("file: "__FILE__", line: %d, " \ - "file %s, line length: %d too long", \ + logError("file: "__FILE__", line: %d, " + "file %s, line length: %d too long", __LINE__, trunk_data_filename, len); close(fd); return EINVAL; } memcpy(buff, pLineStart, len); - if ((bytes=fc_safe_read(fd, buff + len, sizeof(buff) \ + if ((bytes=fc_safe_read(fd, buff + len, sizeof(buff) - len - 1)) < 0) { result = errno != 0 ? errno : EIO; - logError("file: "__FILE__", line: %d, " \ - "read from file %s fail, " \ - "errno: %d, error info: %s", \ - __LINE__, trunk_data_filename, \ + logError("file: "__FILE__", line: %d, " + "read from file %s fail, " + "errno: %d, error info: %s", + __LINE__, trunk_data_filename, result, STRERROR(result)); close(fd); return result; @@ -1615,9 +1627,9 @@ static int storage_trunk_load() if (bytes == 0) { result = ENOENT; - logError("file: "__FILE__", line: %d, " \ - "file: %s, end of file, expect " \ - "end line", __LINE__, \ + logError("file: "__FILE__", line: %d, " + "file: %s, end of file, expect " + "end line", __LINE__, trunk_data_filename); close(fd); return result; @@ -1633,11 +1645,11 @@ static int storage_trunk_load() *pLineEnd = '\0'; col_count = splitEx(pLineStart, ' ', cols, TRUNK_DATA_NEW_FIELD_COUNT); - if (col_count != TRUNK_DATA_NEW_FIELD_COUNT && \ + if (col_count != TRUNK_DATA_NEW_FIELD_COUNT && col_count != TRUNK_DATA_OLD_FIELD_COUNT) { - logError("file: "__FILE__", line: %d, " \ - "file %s, line: %d is invalid", \ + logError("file: "__FILE__", line: %d, " + "file %s, line: %d is invalid", __LINE__, trunk_data_filename, line_count); close(fd); return EINVAL; @@ -1670,14 +1682,14 @@ static int storage_trunk_load() if (*pLineStart != '\0') { - logError("file: "__FILE__", line: %d, " \ - "file %s does not end correctly", \ + logError("file: "__FILE__", line: %d, " + "file %s does not end correctly", __LINE__, trunk_data_filename); return EINVAL; } - logDebug("file: "__FILE__", line: %d, " \ - "file %s, line count: %d", \ + logDebug("file: "__FILE__", line: %d, " + "file %s, line count: %d", __LINE__, trunk_data_filename, line_count); return storage_trunk_restore(restore_offset); From 10906677a41e1f58d9a8011d84b3355f82e4a220 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 26 Dec 2019 10:38:28 +0800 Subject: [PATCH 88/95] change init alloc size --- storage/trunk_mgr/trunk_mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/trunk_mgr/trunk_mem.c b/storage/trunk_mgr/trunk_mem.c index 3a1ccda..f54bff1 100644 --- a/storage/trunk_mgr/trunk_mem.c +++ b/storage/trunk_mgr/trunk_mem.c @@ -381,7 +381,7 @@ static int trunk_alloc_trunk_array(struct trunk_info_array *trunk_array) if (trunk_array->alloc == 0) { - alloc = 16 * 1024; + alloc = 64 * 1024; } else { From 9442384755121b8dfca9cbad1a07bfc6d7abedab Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 26 Dec 2019 11:01:58 +0800 Subject: [PATCH 89/95] log more info when send timeout --- storage/storage_nio.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/storage/storage_nio.c b/storage/storage_nio.c index b4b04d6..0b672f0 100644 --- a/storage/storage_nio.c +++ b/storage/storage_nio.c @@ -444,8 +444,10 @@ static void client_sock_write(int sock, short event, void *arg) if (event & IOEVENT_TIMEOUT) { - logError("file: "__FILE__", line: %d, " \ - "send timeout", __LINE__); + logError("file: "__FILE__", line: %d, " + "client ip: %s, send timeout, offset: %d, " + "remain bytes: %d", __LINE__, pTask->client_ip, + pTask->offset, pTask->length - pTask->offset); task_finish_clean_up(pTask); return; @@ -453,7 +455,7 @@ static void client_sock_write(int sock, short event, void *arg) if (event & IOEVENT_ERROR) { - logDebug("file: "__FILE__", line: %d, " \ + logDebug("file: "__FILE__", line: %d, " "client ip: %s, recv error event: %d, " "close connection", __LINE__, pTask->client_ip, event); From a9e593e03bdb20f39f42918ca64eb57cfdec1ee5 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Thu, 26 Dec 2019 21:55:22 +0800 Subject: [PATCH 90/95] bugfixed: fdfs_storaged can't quit normally --- HISTORY | 3 +++ storage/fdfs_storaged.c | 2 +- storage/storage_sync.c | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/HISTORY b/HISTORY index c1a305d..014d94b 100644 --- a/HISTORY +++ b/HISTORY @@ -1,4 +1,7 @@ +Version 6.06 2019-12-26 + * bugfixed: fdfs_storaged can't quit normally + Version 6.05 2019-12-25 * fdfs_trackerd and fdfs_storaged print the server version in usage. you can execute fdfs_trackerd or fdfs_storaged without parameters diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index f721aa8..d55b7cf 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -325,7 +325,6 @@ int main(int argc, char *argv[]) tracker_report_destroy(); storage_service_destroy(); storage_sync_destroy(); - storage_func_destroy(); if (g_if_use_trunk_file) { @@ -333,6 +332,7 @@ int main(int argc, char *argv[]) storage_trunk_destroy(); } + storage_func_destroy(); delete_pid_file(pidFilename); logInfo("exit normally.\n"); log_destroy(); diff --git a/storage/storage_sync.c b/storage/storage_sync.c index 7e578d0..6aaf6e0 100644 --- a/storage/storage_sync.c +++ b/storage/storage_sync.c @@ -2997,7 +2997,10 @@ static void* storage_sync_thread_entrance(void* arg) continue; } - if ((result=storage_reader_init(pStorage, pReader)) != 0) + storage_reader_remove_from_list(pReader); + result = storage_reader_init(pStorage, pReader); + storage_reader_add_to_list(pReader); + if (result != 0) { logCrit("file: "__FILE__", line: %d, " \ "storage_reader_init fail, errno=%d, " \ From 28f9c419a3c45e8c02eb6a1512a8b82a9500c0bd Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 30 Dec 2019 17:51:24 +0800 Subject: [PATCH 91/95] memset return ip address to ascii 0 for Java SDK --- HISTORY | 3 ++- tracker/tracker_service.c | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/HISTORY b/HISTORY index 014d94b..8b92a9e 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,7 @@ -Version 6.06 2019-12-26 +Version 6.06 2019-12-30 * bugfixed: fdfs_storaged can't quit normally + * bugfixed: init/memset return ip address to ascii 0 for Java SDK Version 6.05 2019-12-25 * fdfs_trackerd and fdfs_storaged print the server version in usage. diff --git a/tracker/tracker_service.c b/tracker/tracker_service.c index 063df82..372bedd 100644 --- a/tracker/tracker_service.c +++ b/tracker/tracker_service.c @@ -2399,7 +2399,7 @@ static int tracker_deal_server_list_group_storages(struct fast_task_info *pTask) pStorageId = NULL; } - memset(pTask->data + sizeof(TrackerHeader), 0, \ + memset(pTask->data + sizeof(TrackerHeader), 0, pTask->size - sizeof(TrackerHeader)); pDest = pStart = (TrackerStorageStat *)(pTask->data + \ sizeof(TrackerHeader)); @@ -2617,11 +2617,12 @@ static int tracker_deal_service_query_fetch_update( \ } - pTask->length = sizeof(TrackerHeader) + \ - TRACKER_QUERY_STORAGE_FETCH_BODY_LEN + \ + pTask->length = sizeof(TrackerHeader) + + TRACKER_QUERY_STORAGE_FETCH_BODY_LEN + (server_count - 1) * (IP_ADDRESS_SIZE - 1); p = pTask->data + sizeof(TrackerHeader); + memset(p, 0, pTask->length - sizeof(TrackerHeader)); memcpy(p, pGroup->group_name, FDFS_GROUP_NAME_MAX_LEN); p += FDFS_GROUP_NAME_MAX_LEN; strcpy(p, fdfs_get_ipaddr_by_peer_ip( @@ -3009,6 +3010,8 @@ static int tracker_deal_service_query_storage( \ return ENOENT; } + memset(p, 0, active_count * (IP_ADDRESS_SIZE + + FDFS_PROTO_PKG_LEN_SIZE)); ppEnd = pStoreGroup->active_servers + active_count; for (ppServer=pStoreGroup->active_servers; ppServerip_addrs, pTask->client_ip)); p += IP_ADDRESS_SIZE - 1; From fde110996b477183b49eb5da55e3775a99719f65 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 31 Dec 2019 07:36:03 +0800 Subject: [PATCH 92/95] upgrade version to v6.06 --- INSTALL | 2 +- common/fdfs_global.c | 2 +- fastdfs.spec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/INSTALL b/INSTALL index 3bf83df..3ebb522 100644 --- a/INSTALL +++ b/INSTALL @@ -21,7 +21,7 @@ Chinese language: http://www.fastken.com/ # command lines as: git clone https://github.com/happyfish100/fastdfs.git - cd fastdfs; git checkout V6.05 + cd fastdfs; git checkout V6.06 ./make.sh clean && ./make.sh && ./make.sh install diff --git a/common/fdfs_global.c b/common/fdfs_global.c index 375ab2e..b741077 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -23,7 +23,7 @@ int g_fdfs_connect_timeout = DEFAULT_CONNECT_TIMEOUT; int g_fdfs_network_timeout = DEFAULT_NETWORK_TIMEOUT; char g_fdfs_base_path[MAX_PATH_SIZE] = {'/', 't', 'm', 'p', '\0'}; -Version g_fdfs_version = {6, 5}; +Version g_fdfs_version = {6, 6}; bool g_use_connection_pool = false; ConnectionPool g_connection_pool; int g_connection_pool_max_idle_time = 3600; diff --git a/fastdfs.spec b/fastdfs.spec index 76b6238..f53b5de 100644 --- a/fastdfs.spec +++ b/fastdfs.spec @@ -3,7 +3,7 @@ %define FDFSClient libfdfsclient %define FDFSClientDevel libfdfsclient-devel %define FDFSTool fastdfs-tool -%define FDFSVersion 6.0.5 +%define FDFSVersion 6.0.6 %define CommitVersion %(echo $COMMIT_VERSION) Name: %{FastDFS} From 4aff731fd50f27689a3ba443785464a24fb22c63 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Mon, 31 Aug 2020 10:48:54 +0800 Subject: [PATCH 93/95] fix action fetch in argv --- client/storage_client.c | 6 ------ storage/fdfs_storaged.c | 4 +++- tracker/fdfs_trackerd.c | 4 +++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/client/storage_client.c b/client/storage_client.c index 18d528e..2e7adb4 100644 --- a/client/storage_client.c +++ b/client/storage_client.c @@ -794,22 +794,16 @@ int storage_do_upload_file1(ConnectionInfo *pTrackerServer, \ STORAGE_PROTO_CMD_UPLOAD_FILE and STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE: 1 byte: store path index -8 bytes: meta data bytes 8 bytes: file size FDFS_FILE_EXT_NAME_MAX_LEN bytes: file ext name -meta data bytes: each meta data seperated by \x01, - name and value seperated by \x02 file size bytes: file content STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE: 8 bytes: master filename length -8 bytes: meta data bytes 8 bytes: file size FDFS_FILE_PREFIX_MAX_LEN bytes : filename prefix FDFS_FILE_EXT_NAME_MAX_LEN bytes: file ext name, do not include dot (.) master filename bytes: master filename -meta data bytes: each meta data seperated by \x01, - name and value seperated by \x02 file size bytes: file content **/ int storage_do_upload_file(ConnectionInfo *pTrackerServer, \ diff --git a/storage/fdfs_storaged.c b/storage/fdfs_storaged.c index d55b7cf..4cfc046 100644 --- a/storage/fdfs_storaged.c +++ b/storage/fdfs_storaged.c @@ -86,6 +86,7 @@ static void usage(const char *program) int main(int argc, char *argv[]) { char *conf_filename; + char *action; int result; int sock; int wait_count; @@ -132,7 +133,8 @@ int main(int argc, char *argv[]) } snprintf(pidFilename, sizeof(pidFilename), "%s/data/fdfs_storaged.pid", g_fdfs_base_path); - if ((result=process_action(pidFilename, argv[2], &stop)) != 0) + action = argc >= 3 ? argv[2] : "start"; + if ((result=process_action(pidFilename, action, &stop)) != 0) { if (result == EINVAL) { diff --git a/tracker/fdfs_trackerd.c b/tracker/fdfs_trackerd.c index da914e7..c340432 100644 --- a/tracker/fdfs_trackerd.c +++ b/tracker/fdfs_trackerd.c @@ -79,6 +79,7 @@ static void usage(const char *program) int main(int argc, char *argv[]) { char *conf_filename; + char *action; int result; int wait_count; int sock; @@ -119,7 +120,8 @@ int main(int argc, char *argv[]) snprintf(pidFilename, sizeof(pidFilename), "%s/data/fdfs_trackerd.pid", g_fdfs_base_path); - if ((result=process_action(pidFilename, argv[2], &stop)) != 0) + action = argc >= 3 ? argv[2] : "start"; + if ((result=process_action(pidFilename, action, &stop)) != 0) { if (result == EINVAL) { From b5534f9c8f1f0b0437ae45dcac08a61473626396 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Tue, 8 Sep 2020 16:36:29 +0800 Subject: [PATCH 94/95] use libfastcommon V1.44 --- HISTORY | 4 ++++ common/fdfs_global.c | 2 +- storage/storage_dio.c | 3 ++- storage/storage_nio.c | 18 ++++++------------ storage/storage_nio.h | 2 -- storage/storage_service.c | 2 +- storage/storage_sync_func.c | 4 ++-- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/HISTORY b/HISTORY index 8b92a9e..3c82fb9 100644 --- a/HISTORY +++ b/HISTORY @@ -1,4 +1,8 @@ +Version 6.07 2020-09-08 + * use libfastcommon V1.44 + NOTE: you MUST upgrade libfastcommon to V1.44 or later + Version 6.06 2019-12-30 * bugfixed: fdfs_storaged can't quit normally * bugfixed: init/memset return ip address to ascii 0 for Java SDK diff --git a/common/fdfs_global.c b/common/fdfs_global.c index b741077..8d8f4bd 100644 --- a/common/fdfs_global.c +++ b/common/fdfs_global.c @@ -23,7 +23,7 @@ int g_fdfs_connect_timeout = DEFAULT_CONNECT_TIMEOUT; int g_fdfs_network_timeout = DEFAULT_NETWORK_TIMEOUT; char g_fdfs_base_path[MAX_PATH_SIZE] = {'/', 't', 'm', 'p', '\0'}; -Version g_fdfs_version = {6, 6}; +Version g_fdfs_version = {6, 7}; bool g_use_connection_pool = false; ConnectionPool g_connection_pool; int g_connection_pool_max_idle_time = 3600; diff --git a/storage/storage_dio.c b/storage/storage_dio.c index dc7c9ad..0d456cd 100644 --- a/storage/storage_dio.c +++ b/storage/storage_dio.c @@ -24,6 +24,7 @@ #include "fastcommon/pthread_func.h" #include "fastcommon/logger.h" #include "fastcommon/sockopt.h" +#include "fastcommon/ioevent_loop.h" #include "storage_dio.h" #include "storage_nio.h" #include "storage_service.h" @@ -156,7 +157,7 @@ int storage_dio_queue_push(struct fast_task_info *pTask) pClientInfo->stage |= FDFS_STORAGE_STAGE_DIO_THREAD; if ((result=blocked_queue_push(&(pContext->queue), pTask)) != 0) { - add_to_deleted_list(pTask); + iovent_add_to_deleted_list(pTask); return result; } diff --git a/storage/storage_nio.c b/storage/storage_nio.c index 0b672f0..3bf05b0 100644 --- a/storage/storage_nio.c +++ b/storage/storage_nio.c @@ -38,13 +38,6 @@ static void client_sock_read(int sock, short event, void *arg); static void client_sock_write(int sock, short event, void *arg); static int storage_nio_init(struct fast_task_info *pTask); -void add_to_deleted_list(struct fast_task_info *pTask) -{ - ((StorageClientInfo *)pTask->arg)->canceled = true; - pTask->next = pTask->thread_data->deleted_list; - pTask->thread_data->deleted_list = pTask; -} - void task_finish_clean_up(struct fast_task_info *pTask) { StorageClientInfo *pClientInfo; @@ -66,6 +59,7 @@ void task_finish_clean_up(struct fast_task_info *pTask) pTask->event.timer.expires = 0; } + pTask->canceled = false; memset(pTask->arg, 0, sizeof(StorageClientInfo)); free_queue_push(pTask); @@ -87,7 +81,7 @@ static int set_recv_event(struct fast_task_info *pTask) pTask->event.fd, IOEVENT_READ, pTask) != 0) { result = errno != 0 ? errno : ENOENT; - add_to_deleted_list(pTask); + iovent_add_to_deleted_list(pTask); logError("file: "__FILE__", line: %d, "\ "ioevent_modify fail, " \ @@ -112,7 +106,7 @@ static int set_send_event(struct fast_task_info *pTask) pTask->event.fd, IOEVENT_WRITE, pTask) != 0) { result = errno != 0 ? errno : ENOENT; - add_to_deleted_list(pTask); + iovent_add_to_deleted_list(pTask); logError("file: "__FILE__", line: %d, "\ "ioevent_modify fail, " \ @@ -210,7 +204,7 @@ void storage_recv_notify_read(int sock, short event, void *arg) if (result != 0) { - add_to_deleted_list(pTask); + iovent_add_to_deleted_list(pTask); } } } @@ -248,7 +242,7 @@ static void client_sock_read(int sock, short event, void *arg) pTask = (struct fast_task_info *)arg; pClientInfo = (StorageClientInfo *)pTask->arg; - if (pClientInfo->canceled) + if (pTask->canceled) { return; } @@ -437,7 +431,7 @@ static void client_sock_write(int sock, short event, void *arg) pTask = (struct fast_task_info *)arg; pClientInfo = (StorageClientInfo *)pTask->arg; - if (pClientInfo->canceled) + if (pTask->canceled) { return; } diff --git a/storage/storage_nio.h b/storage/storage_nio.h index bb79ecb..3ace156 100644 --- a/storage/storage_nio.h +++ b/storage/storage_nio.h @@ -121,7 +121,6 @@ typedef struct typedef struct { int nio_thread_index; //nio thread index - bool canceled; char stage; //nio stage, send or recv char storage_server_id[FDFS_STORAGE_ID_MAX_SIZE]; @@ -152,7 +151,6 @@ void storage_recv_notify_read(int sock, short event, void *arg); int storage_send_add_event(struct fast_task_info *pTask); void task_finish_clean_up(struct fast_task_info *pTask); -void add_to_deleted_list(struct fast_task_info *pTask); #ifdef __cplusplus } diff --git a/storage/storage_service.c b/storage/storage_service.c index 2613266..a7fbda6 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -8434,7 +8434,7 @@ int storage_deal_task(struct fast_task_info *pTask) result = storage_server_fetch_one_path_binlog(pTask); break; case FDFS_PROTO_CMD_QUIT: - add_to_deleted_list(pTask); + iovent_add_to_deleted_list(pTask); return 0; case FDFS_PROTO_CMD_ACTIVE_TEST: result = storage_deal_active_test(pTask); diff --git a/storage/storage_sync_func.c b/storage/storage_sync_func.c index 34cb47a..40fba44 100644 --- a/storage/storage_sync_func.c +++ b/storage/storage_sync_func.c @@ -84,8 +84,8 @@ void storage_sync_connect_storage_server_ex(const FDFSStorageBrief *pStorage, { strcpy(conn->ip_addr, ip_addrs.ips[i].address); conn->sock = socketCreateExAuto(conn->ip_addr, - g_fdfs_connect_timeout, O_NONBLOCK, - g_client_bind_addr ? g_bind_addr : NULL, &result); + O_NONBLOCK, g_client_bind_addr ? + g_bind_addr : NULL, &result); if (conn->sock < 0) { logCrit("file: "__FILE__", line: %d, " From 55b2eeafc1214bb6196be4ebcacc15079f3a7dc8 Mon Sep 17 00:00:00 2001 From: YuQing <384681@qq.com> Date: Wed, 30 Sep 2020 19:41:09 +0800 Subject: [PATCH 95/95] correct spell iovent to ioevent follows libfastcommon --- HISTORY | 3 ++- storage/storage_dio.c | 2 +- storage/storage_nio.c | 6 +++--- storage/storage_service.c | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/HISTORY b/HISTORY index 3c82fb9..a51c7d4 100644 --- a/HISTORY +++ b/HISTORY @@ -1,7 +1,8 @@ -Version 6.07 2020-09-08 +Version 6.07 2020-09-30 * use libfastcommon V1.44 NOTE: you MUST upgrade libfastcommon to V1.44 or later + * correct spell iovent to ioevent follows libfastcommon Version 6.06 2019-12-30 * bugfixed: fdfs_storaged can't quit normally diff --git a/storage/storage_dio.c b/storage/storage_dio.c index 0d456cd..cca3e78 100644 --- a/storage/storage_dio.c +++ b/storage/storage_dio.c @@ -157,7 +157,7 @@ int storage_dio_queue_push(struct fast_task_info *pTask) pClientInfo->stage |= FDFS_STORAGE_STAGE_DIO_THREAD; if ((result=blocked_queue_push(&(pContext->queue), pTask)) != 0) { - iovent_add_to_deleted_list(pTask); + ioevent_add_to_deleted_list(pTask); return result; } diff --git a/storage/storage_nio.c b/storage/storage_nio.c index 3bf05b0..df199cc 100644 --- a/storage/storage_nio.c +++ b/storage/storage_nio.c @@ -81,7 +81,7 @@ static int set_recv_event(struct fast_task_info *pTask) pTask->event.fd, IOEVENT_READ, pTask) != 0) { result = errno != 0 ? errno : ENOENT; - iovent_add_to_deleted_list(pTask); + ioevent_add_to_deleted_list(pTask); logError("file: "__FILE__", line: %d, "\ "ioevent_modify fail, " \ @@ -106,7 +106,7 @@ static int set_send_event(struct fast_task_info *pTask) pTask->event.fd, IOEVENT_WRITE, pTask) != 0) { result = errno != 0 ? errno : ENOENT; - iovent_add_to_deleted_list(pTask); + ioevent_add_to_deleted_list(pTask); logError("file: "__FILE__", line: %d, "\ "ioevent_modify fail, " \ @@ -204,7 +204,7 @@ void storage_recv_notify_read(int sock, short event, void *arg) if (result != 0) { - iovent_add_to_deleted_list(pTask); + ioevent_add_to_deleted_list(pTask); } } } diff --git a/storage/storage_service.c b/storage/storage_service.c index a7fbda6..50e50f3 100644 --- a/storage/storage_service.c +++ b/storage/storage_service.c @@ -8434,7 +8434,7 @@ int storage_deal_task(struct fast_task_info *pTask) result = storage_server_fetch_one_path_binlog(pTask); break; case FDFS_PROTO_CMD_QUIT: - iovent_add_to_deleted_list(pTask); + ioevent_add_to_deleted_list(pTask); return 0; case FDFS_PROTO_CMD_ACTIVE_TEST: result = storage_deal_active_test(pTask);