ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【kv存储】全量持久化方案设计

【kv存储】全量持久化方案设计 一、背景内存数据库的持久化KV 存储是一种内存数据库那么如何在最小的性能损失下将数据引擎中的数据从内存中保存到磁盘上就是这类数据库必须直面的一个问题。相关阅读这篇博客介绍了数据变更日志的持久化AOF博客内容本博客包含了 vemory 项目全量持久化部分的设计思路、难点剖析以及部分的 C 源码展示我们今天的主题是快照式的全量数据持久化二、全量持久化上面有谈到全量持久化的本质是数据引擎中数据快照触发保存的时候需要全量遍历各个数据引擎中的键值对然后我们将键值对编码成适合保存的格式之后进行落盘。2.1. 同步保存和异步保存在 KV 存储的架构设计中全量快照的实现通常分为两种经典模式**同步阻塞的SAVE**与 **异步非阻塞的BGSAVE**。以下是这两种模式的完整执行流程对比a. 同步SAVE简单的阻塞快照当执行SAVE命令时打开FILE* fp- 将数据按照存储格式编码到fp-fflush-fsync在 SAVE 期间服务器会进入完全阻塞状态拒绝任何客户端请求。intkvs_rdb_save(void){chartmp_path[KVS_CONFIG_PATH_LEN8];FILE*fp;// ...fpfopen(tmp_path,wb);if(!fp)return-1;if(rdb_encode_all_engines(rdb_file_sink,fp)!0){fclose(fp);unlink(tmp_path);return-1;}if(fflush(fp)!0||fsync(fileno(fp))!0){fclose(fp);unlink(tmp_path);return-1;}fclose(fp);if(rename(tmp_path,RDB_FILE)!0){unlink(tmp_path);return-1;}// 至此保存成功return0;}同步阻塞。当内存数据量很大时遍历和fwrite会耗费大量时间导致服务直接卡死QPS 归零。b. 子进程BGSAVE工业级快照方案为了解决主线程阻塞问题我参考实现了工业界普遍采用的BGSAVE利用操作系统内核的COWCopy-on-Write写时复制机制将上面同步阻塞的逻辑丢给子进程。主线程fork完之后立刻回归事件循环继续响应处理客户端命令。intkvs_rdb_bgsave(void){//...pidfork();if(pid0){// 返回 -1创建失败return-1;}elseif(pid0){// 返回 0子进程写 RDBrdb_child_close_inherited_fds();//这是一个坑子进程会继承epoll fdif(kvs_rdb_save()!0)_exit(1);_exit(0);}else{// 返回子进程 PID父进程登记并立即返回g_rdb_child_pidpid;return0;2.2. 难点剖析在全量持久化的设计和实现中主要的难点来源是fork子进程写时复制COW机制当父进程调fork创建子进程的时候子进程获取了一份父进程页表的拷贝但是在写入数据发生之前父子进程各自页表均指向同一块物理内存。所谓的写时复制子进程读取数据并不触发复制。而父进程在完成了fork之后将快照的读取和刷盘这些任务完全抛给了子进程可以继续响应客户端——如果客户端发送了SET这类修改指令父进程这时候才复制了物理内存而这个机制的好处就在于子进程读的那一份是fork刚刚完成后的物理内存完全不受到后来修改的影响。
返回列表