Stay Hungry.Stay Foolish.
多线程环境内存数据安全持久化到磁盘

问题

最近一个项目,对计算效率时实性要求比较高,我把所有数据都放到内存里面操作计算,这个时候如果进程被意外终止(人为kill或者说oom kill都可能),数据就会全部丢失,所以需要定时的把内存数据备份到磁盘(数据可能几十M,也可能十几个G),这样进程重启就可以读取备份文件恢复数据到内存,丢失几分钟的数据还是可以接受的;还有一个需求是需要有个触发器线程,会在一定情况触发去同步内存数据到硬盘。

思考

总的来说就是一个定时线程,一个触发器线程,有几率会出现多线程写一个文件情况,多线程写文件比较好解决,首先想到的就是给文件上锁,确实可以解决多线程环境写的问题,但是无法解决在写的时候,进程被意外kill,写到一半操作被终止,数据就会被损坏,这个时候比较尴尬的就是直接在原文件基础操作的,数据完全被损坏,进程重启找不到完整的数据去恢复,于是想到每次备份的时候写到不同的文件里面,这个时候面临的问题就是如果数据很大,就会产生n个备份文件,极端情况也无法接受,毕竟磁盘也是钱阿。所以抛出的问题就是多线程环境内存数据安全持久化到磁盘。

解决方案

寻找一种原子级别的备份操作,备份成功则数据更新,备份失败保留原始数据,由于是原子操作,也不存在多线程的竞争问题

具体实现

首先将内存数据写入一个随机的tmp文件,然后使用rename函数将tmp文件更新为备份文件名字,rename的manpage

If newpath already exists it will be atomically replaced (subject to a few conditions; see ERRORS below), so that there is no point at which another process attempting to access newpath will find it missing.

重点就是只要操作系统不crash,rename操作就是原子的。

bool
concurrent_safe_backup()
{
    ofstream ofs;
    std::string tmp("tmp");

    //random filename
    static default_random_engine e(time(0));
    tmp += to_string(e());

    //open
    FILE* fp = fopen(tmp.c_str(), "w");
    if (!fp) {
        return false;
    }

    std::string contentString("dataaaaaa\n");
    fwrite(contentString.c_str(), 1, contentString.length(), fp);

    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp) == EOF) return false;
    if (fsync(fileno(fp)) == -1) return false;
    if (fclose(fp) == EOF) return false;

    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok. */
    int ret = rename(tmp.c_str(), "memory.bak");
    if (ret == -1) {
        return false;
    }
    //done
    return true;
}
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证
评论

暂无评论~~