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

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 条回复
polythene
2018-11-08 15:54:08 +08:00
Write Ahead Log?
feverzsj
2018-11-08 16:09:25 +08:00
把中间数据提交到数据库不就可以了
muntoya
2018-11-08 16:14:21 +08:00
fork 以后在子进程里安心写磁盘就行了,子进程的内存保持不变,redis 就这么做的
huhu3312
2018-11-08 16:19:01 +08:00
天池大赛啊。。。。key-value 数据库复赛
yzmm
2018-11-08 16:20:38 +08:00
hi...
iiusky
2018-11-08 16:23:31 +08:00
hi,二狗
petelin
2018-11-08 17:24:20 +08:00
推荐看下#3 的方式去写数据. 可以保证内存数据不变.另外你可以创建文件名是 data_{utcnano}.txt 的文件不就不会覆盖之前的了吗. 写失败的你就删了就得了.
ooo3o
2018-11-08 17:35:17 +08:00
先分线程各自写一个, 再跑一个慢慢归并.
firebroo
2018-11-08 18:00:13 +08:00
@huhu3312 看了下,还真的是。。不过我是实际场景遇到
firebroo
2018-11-08 18:04:28 +08:00
@muntoya 问题不在于内存变不变。。而是解决写的时候写操作被 kill -9 中断导致写文件损坏
firebroo
2018-11-08 18:05:54 +08:00
@petelin 离题。。我文章里面写明了多个文件会消耗磁盘,pass 掉
leavan
2018-11-08 18:29:15 +08:00
一般来说如果是追加到文件末尾的话,即使 kill 掉也只会导致文件末尾没写上...
feverzsj
2018-11-08 18:31:40 +08:00
你让 sqlite 之类的嵌入式数据库帮你代理读写就可以了,你想原子就原子,你想质子就质子
firebroo
2018-11-08 18:59:53 +08:00
@leavan 这里的数据具有,不能增量写,只能全量重新写入。
firebroo
2018-11-08 19:00:26 +08:00
@firebroo 具有完整性
firebroo
2018-11-08 19:07:40 +08:00
@feverzsj 数据库场景不合适,十几个 G 数据全量变化更新,不然我就不把数据放内存里面了,再次也用 redis 这种内存数据库,测试 redis 没满足性能需要
lihongjie0209
2018-11-08 19:09:53 +08:00
重新发明一个数据库
feverzsj
2018-11-08 19:14:07 +08:00
@firebroo 了解,你原来根本搞不清楚问题在哪,写入完整性本来就是依赖副本交换实现的,十几 G 根本不算大,sqlite 完全能胜任
429839446
2018-11-08 19:15:34 +08:00
可以看微信开源的 mmkv ?
firebroo
2018-11-08 19:36:27 +08:00
@feverzsj 你没测试就说可以胜任我的场景,我说的是数据有完整性,不能单一的一条条更新,必须整体更新,不是写入完整性。

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

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

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

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

© 2021 V2EX