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

2018-11-08 15:33:21 +08:00
 firebroo

问题

最近一个项目,对计算效率时实性要求比较高,我把所有数据都放到内存里面操作计算,这个时候如果进程被意外终止(人为 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";

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

    //open
    ofs.open(tmp);
    if (!ofs) {
        return false;
    }

    std::string contentString = "data";
    ofs << contentString;
    ofs.close();

    int ret = rename(tmp.c_str(), "memory.bak");
    if (ret == -1) {
        return false;
    }
    //done
    return true;
}
4132 次点击
所在节点    C
44 条回复
firebroo
2018-11-09 13:52:00 +08:00
@no1xsyzy 啊哈,上磁带是啥意思?
no1xsyzy
2018-11-09 16:04:31 +08:00
@firebroo 磁带+RAID 0,单位容量价格极低,顺序读写速度堪比 SSD,缺点是基本只能顺序,随机查找 IOPS 极低,但你这个情景用磁带做持久,之后用磁带还原状态,也就顺序……要是 VPS 上跑就当我没说
firebroo
2018-11-09 16:08:54 +08:00
@no1xsyzy 擦。。感觉太复杂,不是 vps,线上环境。
pythonCoder
2018-11-12 13:38:00 +08:00
爬山心得

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/505805

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX