wbt_status wbt_bmtp2_on_disconn(wbt_event_t *ev) { ev->is_exit = 1; wbt_log_add("BMTP recvived disconn\n"); return WBT_OK; }
static wbt_status wbt_mq_persist_timer(wbt_timer_t *timer) { if( wbt_persist_mid_fd ) { ssize_t nwrite = wbt_write_file(wbt_persist_mid_fd, &wbt_mq_persist_count, sizeof(wbt_mq_persist_count), 0); if( nwrite < 0 ) { wbt_log_add("Error writing to the MID file: %d\n", wbt_errno); } else { if (wbt_sync_file_data(wbt_persist_mid_fd) != 0) { // 如果该操作失败,可能是磁盘故障 //} else { // wbt_log_add("msg_id %lld synced\n", wbt_mq_persist_count); } } } if( wbt_persist_aof_fd ) { // TODO 尝试使用 fdatasync if( wbt_sync_file(wbt_persist_aof_fd) != 0 ) { // 如果该操作失败,可能是磁盘故障 } } if(timer && !wbt_wating_to_exit) { /* 重新注册定时事件 */ timer->timeout += 1 * 1000; if(wbt_timer_mod(timer) != WBT_OK) { return WBT_ERROR; } } else { wbt_free(timer); } return WBT_OK; }
void wbt_exit(int exit_code) { wbt_wating_to_exit = 1; /* 卸载所有模块 */ wbt_module_exit(); wbt_log_add("Webit exit (pid: %d)\n", getpid()); wbt_log_print("\n\nWebit exit (pid: %d)\n", getpid()); exit(exit_code); }
wbt_status wbt_mq_persist_check(ssize_t nwrite, size_t len) { if( nwrite != len ) { if (nwrite == -1) { wbt_log_add("Error writing to the AOF file: %d\n", wbt_errno); } else { wbt_log_add("Short write while writing to " "the AOF file: (nwritten=%lld, " "expected=%lld)\n", (long long)nwrite, (long long)sizeof(wbt_msg_block_t)); if (wbt_truncate_file(wbt_persist_aof_fd, wbt_persist_aof_size) == -1) { wbt_log_add("Could not remove short write " "from the append-only file. The file may be corrupted. " "ftruncate: %d\n", wbt_errno); } } return WBT_ERROR; } return WBT_OK; }
void wbt_signal(int signo, siginfo_t *info, void *context) { wbt_log_add("received singal: %d\n", signo); pid_t pid; int stat; switch( signo ) { case SIGCHLD: /* 仅父进程:子进程退出,从列表中移除 * 如果同时有大量进程退出,SIGCHLD 信号可能会丢失, * 所以不能使只根据 info 中的 pid 判断 */ while((pid = waitpid(-1, &stat, WNOHANG)) > 0){ wbt_proc_remove(pid); } break; case SIGTERM: /* 父进程:停止所有子进程并退出 */ /* 子进程:停止事件循环并退出 */ wbt_wating_to_exit = 1; break; case SIGINT: /* 仅调试模式,退出 */ wbt_wating_to_exit = 1; break; case SIGPIPE: /* 对一个已经收到FIN包的socket调用write方法时, 如果发送缓冲没问题, * 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket * 已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法 * (假设在收到RST之后), 会生成SIGPIPE信号 */ break; case SIGHUP: /* 仅父进程:reload 信号 */ break; case SIGUSR1: /* 重新打开日志文件 */ break; case SIGUSR2: /* 平滑的升级二进制文件 */ wbt_wating_to_update = 1; break; } }
void wbt_worker_process() { /* 设置进程标题 */ if( !wbt_conf.run_mode ) { wbt_set_proc_title("webit: worker process"); } /* 设置需要监听的信号(后台模式) */ struct sigaction act; sigset_t set; act.sa_sigaction = wbt_signal; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; sigemptyset(&set); sigaddset(&set, SIGCHLD); sigaddset(&set, SIGTERM); sigaddset(&set, SIGPIPE); if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1) { wbt_log_add("sigprocmask() failed\n"); } sigaction(SIGINT, &act, NULL); /* 退出信号 */ sigaction(SIGTERM, &act, NULL); /* 退出信号 */ sigaction(SIGPIPE, &act, NULL); /* 忽略 */ wbt_log_add("Webit startup (pid: %d)\n", getpid()); /* 降低 worker 进程的权限 */ const char * user = wbt_conf_get("user"); if ( user != NULL && geteuid() == 0 ) { // 根据用户名查询 // TODO getpwnam 应当在更早的时候调用。以免调用 getpwnam 失败的时候,工作进程被不断重启。 struct passwd * pw = getpwnam(user); if( pw == NULL ) { wbt_log_add("user %s not exists\n", user); return; } if (setgid(pw->pw_gid) == -1) { wbt_log_add("setgid(%d) failed", pw->pw_gid); return; } if (initgroups(user, pw->pw_gid) == -1) { wbt_log_add("initgroups(%s, %d) failed", user, pw->pw_gid); return; } if (setuid(pw->pw_uid) == -1) { wbt_log_add("setuid(%d) failed", pw->pw_uid); return; } } /* 进入事件循环 */ wbt_event_dispatch(); wbt_exit(0); }
void wbt_master_process() { /* 设置需要监听的信号(后台模式) */ struct sigaction act; sigset_t set; act.sa_sigaction = wbt_signal; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; sigemptyset(&set); sigaddset(&set, SIGCHLD); sigaddset(&set, SIGTERM); sigaddset(&set, SIGPIPE); sigaddset(&set, SIGUSR2); if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) { wbt_log_add("sigprocmask() failed\n"); } sigemptyset(&set); sigaction(SIGCHLD, &act, NULL); /* 子进程退出 */ sigaction(SIGTERM, &act, NULL); /* 命令 Webit 退出 */ sigaction(SIGPIPE, &act, NULL); /* 忽略 */ sigaction(SIGUSR2, &act, NULL); /* 更新二进制文件 */ /* TODO: 自定义的 reload 信号 */ time_t prev_time; int count = 0; while(1) { /* 防止 worker process 出错导致 fork 炸弹 */ wbt_time_update(); if( cur_mtime - prev_time <= 1000 ) { if( ( count ++ ) > 9 ) { wbt_log_add("try to fork child too fast\n"); /* 触发限制后,每 5s 尝试一次 * 由于阻塞了相关信号,这会导致信号处理出现延迟 */ sleep(5); } } else { count = 0; } prev_time = cur_mtime; /* fork + execve */ /* 在 master 进程中,只有监听端口和日志文件是打开状态,其中监听描述符需要被传递给新进程 */ /* 新老进程将共用监听端口同时提提供服务 */ if( wbt_wating_to_update && fork() == 0 ) { // 子进程并且 fork 成功 int i = 0; for (i = 0; wbt_os_environ[i]; i++) { } wbt_mem_t tmp; wbt_malloc(&tmp, (i + 3) * sizeof(char *)); wbt_environ = tmp.ptr; memcpy(wbt_environ, wbt_os_environ, i * sizeof(char *)); wbt_malloc(&tmp, 32 * sizeof(char *)); wbt_sprintf(&tmp, "WBT_LISTEN_FD=%d", listen_fd); wbt_environ[i] = tmp.ptr; wbt_environ[i+1] = "SPARE=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; wbt_environ[i+2] = NULL; if (execve(wbt_argv[0], wbt_argv, wbt_environ) < 0) { wbt_log_add("execve failed: errno:%d error:%s\n", errno, strerror(errno)); } } wbt_wating_to_update = 0; if( wbt_wating_to_exit ) { break; } /* 创建子进程直至指定数量 */ wbt_proc_create(wbt_worker_process, wbt_conf.process); sigsuspend(&set); } /* 结束所有子进程 */ pid_t child; while( ( child = wbt_proc_pop() ) != 0 ) { kill( child, SIGTERM ); } wbt_exit(0); }
wbt_status wbt_mq_persist_recovery(wbt_timer_t *timer) { wbt_mq_persist_aof_lock(); wbt_msg_block_t *block; wbt_msg_t *msg; uint32_t crc32; ssize_t nread; char *data; block = wbt_malloc(sizeof(wbt_msg_block_t)); if(!block) { goto error; } int wait_time = 50; int n = 1000; while( !timer || n ) { if(wbt_is_oom()) { // 内存不足,等待一会儿再尝试恢复 wait_time = 1000; n = 0; break; } nread = wbt_read_file(wbt_persist_aof_fd, block, sizeof(wbt_msg_block_t), wbt_mq_persist_recovery_offset); if( nread == 0 ) { break; } else if( nread != sizeof(wbt_msg_block_t) ) { goto error; } else { wbt_mq_persist_recovery_offset += sizeof(wbt_msg_block_t); } if( wbt_conf.aof_crc ) { if (wbt_read_file(wbt_persist_aof_fd, &crc32, sizeof(uint32_t), wbt_mq_persist_recovery_offset) != sizeof(uint32_t)) { goto error; } else { wbt_mq_persist_recovery_offset += sizeof(uint32_t); } if( crc32 != wbt_crc32( (unsigned char *)block, sizeof(wbt_msg_block_t) ) ) { // 校验失败 goto error; } } /* 注意,这里可能出现 malloc(0) 的情况 * 目前看来,已知的 malloc 实现都会在 mallc(0) 时返回一个可用指针而不是 NULL * 但是,C 标准规定 malloc(0) 可以返回 NULL。如果发生这种情况,可以考虑故意多 * 申请一个字节的空间。 */ // data = wbt_malloc(block->data_len+1); data = wbt_malloc(block->data_len); if(!data) { goto error; } if (wbt_read_file(wbt_persist_aof_fd, data, block->data_len, wbt_mq_persist_recovery_offset) != block->data_len) { wbt_free( data ); goto error; } else { wbt_mq_persist_recovery_offset += block->data_len; } if( wbt_conf.aof_crc ) { if (wbt_read_file(wbt_persist_aof_fd, &crc32, sizeof(uint32_t), wbt_mq_persist_recovery_offset) != sizeof(uint32_t)) { wbt_free( data ); goto error; } else { wbt_mq_persist_recovery_offset += sizeof(uint32_t); } if( crc32 != wbt_crc32( (unsigned char *)data, block->data_len ) ) { // 校验失败 wbt_free( data ); goto error; } } wbt_log_debug( "msg %lld recovered\n", block->msg_id ); // 由于内存和文件中保存的结构并不一致,这里我们不得不进行内存拷贝 // TODO 修改消息相关的内存操作,尝试直接使用读入的内存 // TODO 使用 mdb 文件加速载入 // 已过期的消息应当被忽略 if( block->type == MSG_ACK ) { // 未过期但是已经收到 ACK 的负载均衡消息可能会在 fast_boot 期间被重复处理 wbt_msg_t *msg = wbt_mq_msg_get(block->consumer_id); if(msg) { wbt_mq_msg_event_expire( &msg->timer ); } wbt_free(data); } else if( block->expire > wbt_cur_mtime ) { msg = wbt_mq_msg_create(block->msg_id); if( msg == NULL ) { // * ID 冲突,忽略该条消息,并将该消息从 aof 文件中移除 // * 注意,只能通过重写 aof 文件来完成移除操作,如果在重写操作完 // 成之前程序再次重启,aof 文件中将存在一部分 msg_id 冲突的消息 // * 不使用 fast_boot 可以避免该问题 wbt_free(data); } else { wbt_memcpy(msg, block, sizeof(wbt_msg_block_t)); msg->data = data; wbt_mq_msg_delivery(msg); } } else { wbt_free(data); } n --; } wbt_log_add( "%d msg recovered\n", 1000-n ); //success: if(timer && ( n == 0 ) && !wbt_wating_to_exit) { /* 重新注册定时事件 */ timer->timeout += wait_time; if(wbt_timer_mod(timer) != WBT_OK) { goto error; } } else { wbt_free(timer); wbt_mq_persist_aof_unlock(); } wbt_free(block); return WBT_OK; error: wbt_free( block ); wbt_free( timer ); wbt_mq_persist_aof_unlock(); return WBT_ERROR; }
// * 遍历并导出内存中的所有活跃消息 // * 目前不允许对已投递消息做任何修改,所以我们可以将这个操作通过定时事件分步完成。 // * 和 fork 新进程的方式相比,这样做可以节省大量的内存。这允许我们更加频繁地执行该 // 操作。而我们需要付出的唯一代价是导出的文件中可能会存在少量刚刚过期的消息 // TODO static wbt_status wbt_mq_persist_dump(wbt_timer_t *timer) { char aof_file[256]; snprintf( aof_file, sizeof(aof_file), "%.*s/bmq.aof", wbt_conf.data.len, wbt_conf.data.str ); char mdp_file[256]; snprintf( mdp_file, sizeof(mdp_file), "%.*s/bmq.mdp", wbt_conf.data.len, wbt_conf.data.str ); // 临时文件 fd static wbt_fd_t rdp_fd = 0; // 记录当前的最大消息 ID max static wbt_mq_id processed_msg_id = 0; if( wbt_mq_persist_aof_is_lock() == 1 ) { // 如果目前正在从 aof 文件中恢复数据,则 dump 操作必须被推迟 // 这是为了保证 dump 完成的时刻,所有未过期消息都在内存中 if(timer && !wbt_wating_to_exit) { /* 重新注册定时事件 */ timer->timeout += 60 * 1000; if(wbt_timer_mod(timer) != WBT_OK) { return WBT_ERROR; } } // 发生数据恢复往往是因为旧的消息被删除了,所以推迟之后,dump 操作应当重新开始 rdp_fd = 0; return WBT_OK; } else if( 0 /* 如果当前负载过高 */) { // 如果当前负载过高,则 dump 操作必须被推迟 if(timer && !wbt_wating_to_exit) { /* 重新注册定时事件 */ timer->timeout += 3600 * 1000; if(wbt_timer_mod(timer) != WBT_OK) { return WBT_ERROR; } } // 负载过高时,会推迟很长一段时间再尝试,所以推迟之后,dump 操作应当重新开始 rdp_fd = 0; return WBT_OK; } if( !rdp_fd ) { // 创建临时文件 rdp_fd = wbt_open_tmpfile(mdp_file); if( (int)rdp_fd <= 0 ) { wbt_log_add("Could not open mdp file: %d\n", wbt_errno); // TODO data目录存在权限问题时会导致错误 return WBT_ERROR; } processed_msg_id = 0; } wbt_str_t key; wbt_variable_to_str(processed_msg_id, key); // 从小到大遍历消息,直至 max int max = 1000; wbt_rb_node_t *node; wbt_msg_t *msg; for (node = wbt_rb_get_greater(wbt_mq_msg_get_all(), &key); node; node = wbt_rb_next(node)) { msg = (wbt_msg_t *)node->value.str; if( wbt_mq_persist_append_to_file(rdp_fd, msg) == WBT_OK ) { processed_msg_id = msg->msg_id; } else { return WBT_ERROR; } if( --max <= 0 && timer ) { break; } } if( node == NULL ) { // 重写完成 // 如果刷盘策略为 AOF_FSYNC_ALWAYS,我们立刻将数据同步到磁盘上。 // 如果刷盘策略为 AOF_FSYNC_EVERYSEC,我们什么都不做,只需要等待定时任务执行 fsync(2)。 // 如果刷盘策略为 AOF_FSYNC_NO,我们什么都不做。 if( wbt_conf.aof_fsync == AOF_FSYNC_ALWAYS ) { wbt_sync_file(rdp_fd); } // 旧的 aof 文件已经无用了,不需要保证数据落盘,所以直接 close 即可 if( wbt_close_file(wbt_persist_aof_fd) < 0 ) { return WBT_ERROR; } wbt_persist_aof_fd = 0; // 用 mdp 文件覆盖 aof 文件 #ifdef WIN32 if(wbt_close_file(rdp_fd) < 0) { return WBT_ERROR; } rdp_fd = 0; if(MoveFileEx(mdp_file, aof_file, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0) { #else if( rename(mdp_file, aof_file) < 0 ) { #endif wbt_log_add("Could not rename mdp file. " "rename: %d\n", wbt_errno); // 尝试重新打开 aof 文件 wbt_persist_aof_fd = wbt_open_logfile(aof_file); if( (int)wbt_persist_aof_fd < 0 ) { wbt_persist_aof_fd = 0; wbt_log_add("Could not open AOF file: %d\n", wbt_errno); } return WBT_ERROR; } #ifdef WIN32 wbt_persist_aof_fd = wbt_open_logfile(aof_file); if( (int)wbt_persist_aof_fd <= 0 ) { wbt_persist_aof_fd = 0; wbt_log_add("Could not open AOF file: %d\n", wbt_errno); return WBT_ERROR; } #else wbt_persist_aof_fd = rdp_fd; rdp_fd = 0; #endif // 重写完成时,必然所有消息都在内存中生效,所以直接将 offset 设定为文件末尾 wbt_mq_persist_recovery_offset = wbt_get_file_size(wbt_persist_aof_fd); // 重写完成后,下一次重写将在设定的时间间隔之后运行 if(!wbt_wating_to_exit) { if(!timer) { timer = wbt_malloc(sizeof(wbt_timer_t)); timer->on_timeout = wbt_mq_persist_dump; timer->heap_idx = 0; timer->timeout = wbt_cur_mtime; } /* 重新注册定时事件 */ // TODO 从配置文件中读取 timer->timeout += 6 * 3600 * 1000; if(wbt_timer_mod(timer) != WBT_OK) { return WBT_ERROR; } } else { wbt_free(timer); } } else { if(!wbt_wating_to_exit) { /* 重新注册定时事件 */ timer->timeout += 50; if(wbt_timer_mod(timer) != WBT_OK) { return WBT_ERROR; } } else { wbt_free(timer); } } return WBT_OK; }
wbt_status wbt_mq_persist_init() { char mid_file[256]; snprintf( mid_file, sizeof(mid_file), "%.*s/bmq.mid", wbt_conf.data.len, wbt_conf.data.str ); char aof_file[256]; snprintf( aof_file, sizeof(aof_file), "%.*s/bmq.aof", wbt_conf.data.len, wbt_conf.data.str ); // 无论是否启用持久化,mid 都应当被保存,这样可以避免 msg_id 冲突(或减小其发生概率) wbt_persist_mid_fd = wbt_open_datafile(mid_file); if ((int)wbt_persist_mid_fd <= 0) { return WBT_ERROR; } // TODO 记录 wbt_persist_mid_fd 的值 // 读取 mid if (wbt_read_file(wbt_persist_mid_fd, &wbt_mq_persist_count, sizeof(wbt_mq_persist_count), 0) == sizeof(wbt_mq_persist_count)) { wbt_log_add("msg_id %lld recovered\n", wbt_mq_persist_count); wbt_mq_msg_init_snowflake(wbt_mq_persist_count); } else { wbt_mq_persist_count = 0; } if( !wbt_conf.aof ) { return WBT_OK; } wbt_persist_aof_fd = wbt_open_logfile(aof_file); if( (int)wbt_persist_aof_fd <= 0 ) { return WBT_ERROR; } wbt_persist_aof_size = wbt_get_file_size(wbt_persist_aof_fd); if( wbt_persist_aof_size < 0 ) { return WBT_ERROR; } // redis 2.4. 以后开始使用后台线程异步刷盘,我们暂时不那么做 if( wbt_conf.aof_fsync == AOF_FSYNC_EVERYSEC ) { wbt_timer_t *timer = wbt_malloc(sizeof(wbt_timer_t)); timer->on_timeout = wbt_mq_persist_timer; timer->timeout = wbt_cur_mtime + 1 * 1000; timer->heap_idx = 0; if( wbt_timer_add(timer) != WBT_OK ) { return WBT_ERROR; } } if( wbt_conf.aof_fast_boot && wbt_mq_persist_count ) { wbt_timer_t *timer = wbt_malloc(sizeof(wbt_timer_t)); timer->on_timeout = wbt_mq_persist_recovery; timer->timeout = wbt_cur_mtime; timer->heap_idx = 0; if( wbt_timer_add(timer) != WBT_OK ) { return WBT_ERROR; } // TODO 启动后立刻进行重写可能并不合适 timer = wbt_malloc(sizeof(wbt_timer_t)); timer->on_timeout = wbt_mq_persist_dump; timer->timeout = wbt_cur_mtime + 1; // +1 是为了确保先执行 recovery,再执行 dump timer->heap_idx = 0; if( wbt_timer_add(timer) != WBT_OK ) { return WBT_ERROR; } } else { wbt_mq_persist_recovery(NULL); wbt_mq_persist_dump(NULL); } return WBT_OK; }