不会产奶的COW(Copy-On-Write)

作者: 此时彼地 | 来源:发表于2016-12-07 23:12 被阅读952次

    COW基本定义

    写入时复制(Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

    COW的处理过程中需要维持一个为读请求使用的“指针”,并在新数据写入完成后更新这个指针以提升读写并发能力。因此,COW也间接提供了数据更新过程中的原子性。在保证数据的完整性同时还保证了一定的读写效率,下面让我们针对一些采用COW的场景看看处理流程上的差异。

    使用场景

    在文件系统中的应用

    数据的备份或者复制至少处于硬件(硬盘)和操作系统两个层级。硬件备份一般使用RAID0-10的方案,保护硬盘中的数据安全;操作系统中则使用Snapshot(快照)技术来维持数据的安全和有效。人们一直采用数据复制、备份、恢复等技术来保护重要的数据信 息,定期对数据进行备份或复制。由于数据备份过程会影响应用性能,并 且非常耗时,因此数据备份通常被安排在系统负载较轻时进行(如夜 间)。另外,为了节省存储空间,通常结合全量和增量备份技术。为了解决性能和持续运行的问题引入了Snapshot,Snapshot的实现又主要分为split mirror、changed block和current三大类,具体实现方式中会有部分依赖COW技术。

    COW快照写操作基本流程:
    1. 读取原始数据
    2. 把原始数据复制到一块新的未被使用的快照空间
    3. 把新数据写入到原始位置
    
    写时复制快照

    基本的处理流程比较简单,由于只复制了被更新部分数据,整个流程处理十分快速。在写操作的同时所有的读请求都是通过逻辑访问所有原始数据,包括被拷贝到新空间的部分,而不会读到正在写入的数据。

    尽管COW简单高效,但是也存在一些问题,比如:
    1. 数据复制过程带来了更多得io消耗(实际应用中可能更倾向于使用Redirect-on-write);
    2. 快照只保存了被修改部分的数据,并没有完整的数据备份;
    3. 如果新写入的数据超过旧数据占用的空间也会导致一系列复杂处理,最终导致快照失效;
    4. 只能应对单一的数据处理,多重复杂结构数据的修改需要特殊处理
    

    除了COW,常见的快照技术还有Log-structuredfilearchitecture(日志文件架构)、Copyonwritewithbackgroundcopy(克隆快照)、Continuous data protection(持续数据保护)Split Mirror(镜像分离)Pointer Remapping(指针重映射)等等,特性对比参见下表

    镜像分离 指针重映射 写时复制 日志文件 克隆快照 持续数据保护
    快照是否依赖源数据卷 NO 镜像包含完整的数据副本 YES 未变化的数据从源数据卷访问 YES 未变化数据从源数据卷访问 YES 未变化数据从源数据卷访问 ONLY 仅在后台拷贝未被完成时 YES 除了包含源数 据副本实现外
    空间效率 NO 要求源数据卷相同容量存储空间 YES 大多数据情况下要求变化数据存储空间 YES 大多数据情况下要求变化数据存储空间 YES 要求变化数据存储空间 NO 要求源数据卷相同容量存储空间 YES 存储空间需求取决于保存变化数据的数据和频率
    源数据卷系统CPU和I/O负载 LOW/HIGH 镜像分离后低,分离前数据同步高 HIGH/NONE软件型快照高,硬件型快照无 HIGH/NONE软件型快照高,硬件型快照无 HIGH对写操作进行日志时高 LOW一般由存储子硬件执行 具体实现相关
    源数据卷写负载 NONE写负载发生在分离前 NONE写直接重定向至新块 HIGH首次写产生额外写负载 HIGH写操作必须进行日志 HIGH拷贝完成前的首次写产生写负载 HIGH每次写操作导致相应的写操作
    逻辑数据错误保护机制 YES数据必须从镜像卷拷贝,变化没有记录,速度较慢 YES数据变化可以回滚或者同步至源数据源 YES数据变化可以回滚或者同步至源数据源 YES数据变化可以回滚 YES可以反向建立快照,由于仅复制变化数据块,速度较快 YES数据变化可以同步至原始数据副本
    源数据卷物理介质故障保护机制 YES镜像卷是完整副本 NONE有效源数据卷必须存在 NONE有效源数据卷必须存在 NONE 有效源数据卷必须存在 YES后台复制完成后完全保护 具体实现相关

    软件和web系统中也经常出现COW的使用场景,比如数据备份和并发环境下的读写性能力提升。

    软件和web系统中的应用

    linux中创建轻量级的子进程

    我们都知道,进程是操作系统中比较昂贵的资源,它具有自己的数据和程序。

    传统方式下,fork()函数在创建子进程时直接把所有资源复制给子进程,即:正文段块,数据段块,堆块,栈块。这种实现方式简单,但是效率低下,而且复制的资源可能对子进程毫无用处。linux为了降低创建子进程的成本,改进fork()实现方式使用COW技术创建子进程。当父进程创建子进程时,内核只为子进程创建虚拟空间,父子两个进程使用的是相同的物理空间。只有父子进程发生更改时才会为子进程分配独立的物理空间

    fork()函数

    上图展示从P1进程创建子进程P2时的物理空间使用状况。通过COW技术,fork()延迟了数据拷贝,根据子进程的实际操作最终可能完全避免数据复制,如:子进程创建后运行一个与当前数据无关的可执行文件。

    redis中的COW

    某些情况下,我们希望保存redis中的数据,那么只用一个主线程处理请求的redis是怎么在处理请求的同时进行数据持久化的呢?答案当然是依赖COW,具体来说就是依赖系统的fork()函数的COW实现。

    redis有两种数据持久化策略:RDB快照和AOF日志。

    RDB快照复制某一时刻redis内存中的所有数据保存在文件中,在数据备份程序被触发后redis会调用fork(),这样在轻松获取一份该时刻数据副本的同时,还可以允许主进程继续接受其它请求,读请求不会增加负担,只有写请求到达时才需要真正复制一份数据。RDB快照的一大缺点是可能丢失两次备份时刻之间产生的数据。

    AOF日志弥补了RDB快照的不足,将每个收到的写命令都写入日志文件保证数据不会丢失。日志文件会随时间不停增长,为了解决过大的日志文件,redis提供了bgrewriteaof命令对日志进行压缩。bgrewriteaof命令也会调用fork()函数,利用子进程中的数据状态转化成redis命令并保存到新的aof日志中,之后同步父进程中缓存的新写命令,最后用新日志文件替换旧日志文件

    可见,两种数据持久化策略都使用了COW。

    jdk中的COW集合

    java的基础api中也提供了基于COW的数据集合,但是与前面进行数据备份和降低数据复制开销的目的不同,java中的COW集合更偏向于提供并发能力。

    CopyOnWriteArrayListCopyOnWriteArraySet都是使用在读多写少且数据总量不大的场景下,在保证多线程写的原子性的同时又避免了读的冲突和竞争,使用迭代器的时候也绝对不会抛出ConcurrentModificationException。

    在集合内部,利用ReentrantLock同步多个写操作,锁竞争胜利进入同步代码段的程序经过已下几步完成数据修改:

    1.复制原始数据
    2.修改原始数据
    3.将修改后的数据赋给集合数据引用
    4.退出同步锁定区域
    

    在写方法执行前和执行中的读操作都是直接取得旧数据的引用,由于迭代器的修改方法被禁用,所以迭代遍历数据的程序总是在使用一份不会改变的数据引用。因为写操作最后修改数据引用的操作是原子的,所以读操作不会错误的得到“部分修改”的数据。

    linux内核中有一种叫做RCU(Read-Copy Update)数据共享策略跟COW十分相似,唯一不同在于RCU的新旧数据替换同过特定的回调(callback)机制被动执行,而不是由写操作进程自助完成,有兴趣的同学可以自己研究一下。

    总结

    copy-on-write是个古老,容易理解且比较高效的策略,可以在数据备份或者读多写少的场景下选择使用。同时它也存在一些先天缺点,数据复制过程中需要双倍的存储空间,如果涉及到IO读取那还会成倍消耗IO资源,使用过程中需要注意控制copy的范围。

    有兴趣的同学可以自行阅读相关技术,扩展学习一下优化策略。如:Redirect-On-Write、Copy-On-First-Write、Allocate-On-Flush(also called delayed allocation)、RCU。

    相关文章

      网友评论

      本文标题:不会产奶的COW(Copy-On-Write)

      本文链接:https://www.haomeiwen.com/subject/klejmttx.html