V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
maxxfire
V2EX  ›  程序员

同时写文件和数据库,如何保证数据一致性?

  •  1
     
  •   maxxfire · 2019-06-25 21:02:29 +08:00 · 6686 次点击
    这是一个创建于 1760 天前的主题,其中的信息可能已经有所发展或是发生改变。
    有一个事务需要做 2 件事:在文件系统中存储一个文件,同时在数据库生成一条记录。
    两者没有先后顺序的要求,但是要满足要么都成功,要么都失败。
    怎么才能做到这样,满足一致性(比如在突然断电情况下,也不影响结果),或者有什么措施能够尽量使数据可靠?
    37 条回复    2019-06-27 20:52:29 +08:00
    sun1991
        1
    sun1991  
       2019-06-25 21:12:14 +08:00
    考虑数据一致性, 何不把文件存到数据库里去?
    iwong0exv2
        2
    iwong0exv2  
       2019-06-25 21:15:19 +08:00 via Android
    @sun1991 还要考虑查询啊
    ace12
        3
    ace12  
       2019-06-25 21:17:03 +08:00
    版本
    des
        4
    des  
       2019-06-25 21:19:04 +08:00 via Android
    是否有竞争、读写频率、不一致的接受程度、是否可以删除不说一下?
    corvofeng
        5
    corvofeng  
       2019-06-25 21:19:40 +08:00 via Android
    只存到数据库, 定时刷到文件里面, 类似缓存的用法不行吗?
    maxxfire
        6
    maxxfire  
    OP
       2019-06-25 21:22:21 +08:00
    @des 单线程操作,可以删除,性能要求不高
    iwong0exv2
        7
    iwong0exv2  
       2019-06-25 21:36:41 +08:00 via Android
    可参考 4 楼的思路,需要楼主自己完善下。
    另外再给个也许可行的方案:
    - 先写文件,再插入数据库。
    - 两个操作都成功才算成功;其中一个失败就回滚。
    - 读的时候以数据库表记录为准,数据库没有记录就算无效数据。
    Vegetable
        8
    Vegetable  
       2019-06-25 21:41:53 +08:00
    @iwong0exv2 +1

    你直接考虑查询,实际上查不到就意味着操作是失败的,查得到就是成功的.所以你只需要保证数据库里的记录,都有对应的文件,而不需要严格保证每个文件都对应了数据库的条目.
    只有这样你才能保证突然断电了也不会出现查询的时候出错,因为写数据库是最后一步.

    但是如果是对现有文件进行修改的话就比较麻烦咯
    linbiaye
        9
    linbiaye  
       2019-06-25 21:45:01 +08:00   ❤️ 1
    1.计算文件 md5 ; 2.记日志(比如某张表中插入一条记录包含 md5, filepath ;或者日志文件 -> md5 为文件名,内容 filepath ); 3. 写文件; 4. 写文件成功后写表; 5. 如果 2 个写都成功或者任意一个不成功则删除日志。6. 系统恢复或者重启后检查日志,如果文件 md5 值不匹配认为则删除脏数据,若匹配则跳转到第 4 步或者跳转到第 5 步。
    carlclone
        10
    carlclone  
       2019-06-25 21:53:50 +08:00
    这完全就是 mysql 里 redolog 和 binlog 的场景 , 两阶段提交
    mysql 写完了先处于 prepare 状态 , 如果文件系统写入成功了则提交 , 没成功事务就回滚了
    GavinAlison
        11
    GavinAlison  
       2019-06-25 22:12:08 +08:00
    @linbiaye +1

    1. 计算文件的 md5
    2. 记录操作日志,比如这条记录的信息,包括文件的 md5, filename, filePath, meta 信息
    3. 开始写入数据库,先记录入库的日志,写入数据库中,利用数据库的事物保证写入成功,失败记录失败日志
    4. 开始写入文件,先记录写文件日志,在写入文件,更具 md5 值,从 mongodb 中查询对应的数据,如果有删除,排除中断上传的问题,重新上传, 失败重试,记录写入重试次数,败次数超过 5 次,写入失败日志
    5. 成功之后,返回主程序,记录成功日志。
    ihciah
        12
    ihciah  
       2019-06-25 23:55:48 +08:00
    2PC
    vindurriel
        13
    vindurriel  
       2019-06-26 00:29:10 +08:00 via iPhone
    begin; insert; err=writeFile(); if err then rollback; else commit;
    wweir
        14
    wweir  
       2019-06-26 04:58:24 +08:00 via Android
    强一致是一定做不到,只能考虑最终一致。这样的话,写个事后检查就可以了
    wweir
        15
    wweir  
       2019-06-26 04:59:48 +08:00 via Android
    不得不说,不懂装懂的好多……
    jorneyr
        16
    jorneyr  
       2019-06-26 07:48:23 +08:00
    写之前保存一个开始事务的标记,都成功后再保存一个操作成功的记录,如果失败就回滚 (回滚逻辑根据你的业务逻辑来实现),和分布式事务差不多一个道理。
    Huelse
        17
    Huelse  
       2019-06-26 08:12:07 +08:00
    一般选择单线程,先文件后数据库,成功标记可选择返回或者数据库记录。也可不返回,进行事后检查确认两者
    rrfeng
        18
    rrfeng  
       2019-06-26 08:32:54 +08:00
    要看写文件是什么场景,如果是对象存储,只有添加新文件而没有更新操作,那么完全可以写文件然后写数据库,因为多写了文件并不影响一致性(多了一些没有索引的文件而已,而且如果重新上传被覆盖也没问题)

    如果要考虑删除、更新文件操作,那么就不一样了,2pc 或者其他
    leonme
        19
    leonme  
       2019-06-26 08:50:21 +08:00 via Android
    @wweir 老哥的方案是?
    opengps
        20
    opengps  
       2019-06-26 08:52:53 +08:00
    先把文件写成功在把结果存数据库会不会太慢?
    文件一般是允许多不能少,数据库则要求尽可能一一对应
    janxin
        21
    janxin  
       2019-06-26 11:38:37 +08:00
    同时写缓存和数据库,怎么保证一致性?
    jaskle
        22
    jaskle  
       2019-06-26 12:43:11 +08:00 via Android
    事务回滚,写完文件再提交,写文件用写完后移动改名的方法,瞬间完成,若是失败回滚即可。但是要注意,频繁断电无法保证,甚至提交到数据库的内容都可能会丢失,经历过。
    linyinma
        23
    linyinma  
       2019-06-26 12:46:27 +08:00
    Pos 机冲正概念应该你能用到: ( 1 )写冲正文件;( 2 )做些事情( A、B、C...);( 3 )删除冲正文件;

    ( 1 )做事情前修需要检测冲正文件,文件存在就要回滚(防止上一次掉电等原因事务未做完);
    ( 2 )事务完整就删冲正文件;
    BBCCBB
        24
    BBCCBB  
       2019-06-26 13:21:18 +08:00
    用 2pc, tcc 这种思路。
    petelin
        25
    petelin  
       2019-06-26 13:46:58 +08:00 via iPhone
    Wal 日志
    tabris17
        26
    tabris17  
       2019-06-26 13:48:09 +08:00
    预写日志,失败回滚
    Mirana
        27
    Mirana  
       2019-06-26 13:53:30 +08:00
    写数据库失败了 需要删除文件?
    Mitt
        28
    Mitt  
       2019-06-26 13:54:21 +08:00 via iPhone
    @wweir 你哪句话里看到说要强一致性了
    prasanta
        29
    prasanta  
       2019-06-26 14:03:29 +08:00 via Android
    只能保证最终一致性,看你们的不一致时间窗口容忍度
    IsaacYoung
        30
    IsaacYoung  
       2019-06-26 14:16:23 +08:00
    single source of truth
    anyuhanfei
        31
    anyuhanfei  
       2019-06-26 17:18:07 +08:00
    这难道不是类似上传图片的基本操作?在逻辑层做判断呗
    MeteorCat
        32
    MeteorCat  
       2019-06-26 17:25:39 +08:00 via Android
    文件一致性用文件的 hash 值就行了
    DonaldY
        33
    DonaldY  
       2019-06-26 17:37:50 +08:00
    话说计算文件 md5,不得把文件读取到服务器本地嘛,或者分块计算?
    DonaldY
        34
    DonaldY  
       2019-06-26 17:38:33 +08:00
    歪楼。

    话说计算文件 md5,不得把文件读取到服务器本地嘛,或者分块计算?

    请教下,大家一般怎么处理的?
    husinhu
        35
    husinhu  
       2019-06-26 20:01:47 +08:00 via iPhone
    两个操作对应两个 fsync() 所以本质上要把这两个操作一起 commit。即便是 TxF 也不能保证能一起 rollback。要是我做就简单的 db as log,加一个 column 叫 filetxid,每次写完就写一行就 append 文件以 filetxid 作为前缀写完 fsync()落物理存储。每次恢复以 db 为准,如果文件末尾损坏做 replay。
    husinhu
        36
    husinhu  
       2019-06-26 20:04:25 +08:00 via iPhone
    楼主是要存储文件不是写文件,我看错了嘿嘿,不过方法类似,db as wal log
    troywinter
        37
    troywinter  
       2019-06-27 20:52:29 +08:00
    TCC,最终一致性,2pc 对业务不透明而且实现难度大,https://queue.acm.org/detail.cfm?id=1394128 参考 BASE 模型
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2613 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 01:47 · PVG 09:47 · LAX 18:47 · JFK 21:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.