关于java:腾讯三面进程写文件过程中进程崩溃了文件数据会丢吗

42次阅读

共计 6400 个字符,预计需要花费 16 分钟才能阅读完成。

1. Page Cache

1.1 Page Cache 是什么?

为了了解 Page Cache,咱们无妨先看一下 Linux 的文件 I/O 零碎,如下图所示:

Figure1. Linux 文件 I/O 零碎

上图中,红色局部为 Page Cache。可见 Page Cache 的实质是由 Linux 内核治理的内存区域。咱们通过 mmap 以及 buffered I/O 将文件读取到内存空间实际上都是读取到 Page Cache 中。

1.2 如何查看零碎的 Page Cache?

通过读取 /proc/meminfo 文件,可能实时获取零碎内存状况:

$ cat /proc/meminfo
...
Buffers:            1224 kB
Cached:           111472 kB
SwapCached:        36364 kB
Active:          6224232 kB
Inactive:         979432 kB
Active(anon):    6173036 kB
Inactive(anon):   927932 kB
Active(file):      51196 kB
Inactive(file):    51500 kB
...
Shmem:             10000 kB
...
SReclaimable:      43532 kB
...

依据下面的数据,你能够简略得出这样的公式(等式两边之和都是 112696 KB):

Buffers + Cached + SwapCached = Active(file) + Inactive(file) + Shmem + SwapCached

两边等式都是 Page Cache,即:

Page Cache = Buffers + Cached + SwapCached

通过浏览 1.4 以及 1.5 大节,就可能了解为什么 SwapCached 与 Buffers 也是 Page Cache 的一部分。

1.3 page 与 Page Cache

page 是内存治理调配的根本单位,Page Cache 由多个 page 形成。page 在操作系统中通常为 4KB 大小(32bits/64bits),而 Page Cache 的大小则为 4KB 的整数倍。

另一方面,并不是所有 page 都被组织为 Page Cache

Linux 零碎上供用户可拜访的内存分为两个类型[2],即:

  • File-backed pages:文件备份页也就是 Page Cache 中的 page,对应于磁盘上的若干数据块;对于这些页最大的问题是脏页回盘;
  • Anonymous pages:匿名页不对应磁盘上的任何磁盘数据块,它们是过程的运行是内存空间(例如办法栈、局部变量表等属性);

为什么 Linux 不把 Page Cache 称为 block cache,这不是更好吗?

这是因为从磁盘中加载到内存的数据不仅仅放在 Page Cache 中,还放在 buffer cache 中。例如通过 Direct I/O 技术的磁盘文件就不会进入 Page Cache 中。当然,这个问题也有 Linux 历史设计的起因,毕竟这只是一个称说,含意随着 Linux 零碎的演进也逐步不同。


上面比拟一下 File-backed pages 与 Anonymous pages 在 Swap 机制下的性能。

内存是一种珍惜资源,当内存不够用时,内存治理单元(Memory Mangament Unit)须要提供调度算法来回收相干内存空间。内存空间回收的形式通常就是 swap,即替换到长久化存储设备上。

File-backed pages(Page Cache)的内存回收代价较低。Page Cache 通常对应于一个文件上的若干程序块,因而能够通过程序 I/O 的形式落盘。另一方面,如果 Page Cache 上没有进行写操作(所谓的没有脏页),甚至不会将 Page Cache 回盘,因为数据的内容齐全能够通过再次读取磁盘文件失去。

Page Cache 的次要难点在于脏页回盘,这个内容会在第二节进行具体阐明。

Anonymous pages 的内存回收代价较高。这是因为 Anonymous pages 通常随机地写入长久化替换设施。另一方面,无论是否有写操作,为了确保数据不失落,Anonymous pages 在 swap 时必须长久化到磁盘。

1.4 Swap 与缺页中断

Swap 机制指的是当物理内存不够用,内存治理单元(Memory Mangament Unit,MMU)须要提供调度算法来回收相干内存空间,而后将清理进去的内存空间给以后内存申请方。

Swap 机制存在的实质起因是 Linux 零碎提供了虚拟内存管理机制,每一个过程认为其独占内存空间,因而所有过程的内存空间之和远远大于物理内存。所有过程的内存空间之和超过物理内存的局部就须要替换到磁盘上。

操作系统以 page 为单位治理内存,当过程发现须要拜访的数据不在内存时,操作系统可能会将数据以页的形式加载到内存中。上述过程被称为缺页中断,当操作系统产生缺页中断时,就会通过零碎调用将 page 再次读到内存中。

但主内存的空间是无限的,当主内存中不蕴含能够应用的空间时,操作系统会从抉择适合的物理内存页驱赶回磁盘,为新的内存页让出地位,抉择待驱赶页的过程在操作系统中叫做页面替换(Page Replacement),替换操作又会触发 swap 机制。

如果物理内存足够大,那么可能不须要 Swap 机制,然而 Swap 在这种状况下还是有肯定劣势:对于有产生内存透露几率的应用程序(过程),Swap 替换分区更是重要,这能够确保内存泄露不至于导致物理内存不够用,最终导致系统解体。但内存泄露会引起频繁的 swap,此时十分影响操作系统的性能。

Linux 通过一个 swappiness 参数来管制 Swap 机制[2]:这个参数值可为 0-100,控制系统 swap 的优先级:

  • 高数值:较高频率的 swap,过程不沉闷时被动将其转换出物理内存。
  • 低数值:较低频率的 swap,这能够确保交互式不因为内存空间频繁地替换到磁盘而进步响应提早。

最初,为什么 Buffers 也是 Page Cache 的一部分?

这是因为当匿名页(Inactive(anon) 以及 Active(anon))先被替换(swap out)到磁盘上后,而后再加载回(swap in)内存中,因为读入到内存后原来的 Swap File 还在,所以 SwapCached 也能够认为是 File-backed page,即属于 Page Cache。这个过程如 Figure 2 所示。

Figure2. 匿名页的被替换后也是 Page Cache

1.5 Page Cache 与 buffer cache

执行 free 命令,留神到会有两列名为 buffers 和 cached,也有一行名为“-/+ buffers/cache”。

~ free -m
             total       used       free     shared    buffers     cached
Mem:        128956      96440      32515          0       5368      39900
-/+ buffers/cache:      51172      77784
Swap:        16002          0      16001

其中,cached 列示意以后的页缓存(Page Cache)占用量,buffers 列示意以后的块缓存(buffer cache)占用量。用一句话来解释:Page Cache 用于缓存文件的页数据,buffer cache 用于缓存块设施(如磁盘)的块数据。页是逻辑上的概念,因而 Page Cache 是与文件系统同级的;块是物理上的概念,因而 buffer cache 是与块设施驱动程序同级的。

其中,cached 列示意以后的页缓存(Page Cache)占用量,buffers 列示意以后的块缓存(buffer cache)占用量。用一句话来解释:Page Cache 用于缓存文件的页数据,buffer cache 用于缓存块设施(如磁盘)的块数据。页是逻辑上的概念,因而 Page Cache 是与文件系统同级的;块是物理上的概念,因而 buffer cache 是与块设施驱动程序同级的。

Page Cache 与 buffer cache 的独特目标都是减速数据 I/O:写数据时首先写到缓存,将写入的页标记为 dirty,而后向内部存储 flush,也就是缓存写机制中的 write-back(另一种是 write-through,Linux 默认状况下不采纳);读数据时首先读取缓存,如果未命中,再去内部存储读取,并且将读取来的数据也退出缓存。操作系统总是踊跃地将所有闲暇内存都用作 Page Cache 和 buffer cache,当内存不够用时也会用 LRU 等算法淘汰缓存页。

在 Linux 2.4 版本的内核之前,Page Cache 与 buffer cache 是齐全拆散的。然而,块设施大多是磁盘,磁盘上的数据又大多通过文件系统来组织,这种设计导致很多数据被缓存了两次,节约内存。所以在 2.4 版本内核之后,两块缓存近似交融在了一起:如果一个文件的页加载到了 Page Cache,那么同时 buffer cache 只须要保护块指向页的指针就能够了。只有那些没有文件示意的块,或者绕过了文件系统间接操作(如 dd 命令)的块,才会真正放到 buffer cache 里。因而,咱们当初提起 Page Cache,基本上都同时指 Page Cache 和 buffer cache 两者,本文之后也不再辨别,间接统称为 Page Cache。

下图近似地示出 32-bit Linux 零碎中可能的一种 Page Cache 构造,其中 block size 大小为 1KB,page size 大小为 4KB。

Page Cache 中的每个文件都是一棵基数树(radix tree,实质上是多叉搜寻树),树的每个节点都是一个页。依据文件内的偏移量就能够疾速定位到所在的页,如下图所示。对于基数树的原理能够参见英文维基,这里就不细说了。

1.6 Page Cache 与预读

操作系统为基于 Page Cache 的读缓存机制提供预读机制(PAGE_READAHEAD),一个例子是:

  • 用户线程仅仅申请读取磁盘上文件 A 的 offset 为 0-3KB 范畴内的数据,因为磁盘的根本读写单位为 block(4KB),于是操作系统至多会读 0-4KB 的内容,这恰好能够在一个 page 中装下。
  • 然而操作系统出于局部性原理 [3] 会抉择将磁盘块 offset [4KB,8KB)、[8KB,12KB) 以及 [12KB,16KB) 都加载到内存,于是额定在内存中申请了 3 个 page;

下图代表了操作系统的预读机制:

Figure. 操作系统的预读机制;

上图中,应用程序利用 read 零碎调动读取 4KB 数据,实际上内核应用 readahead 机制实现了 16KB 数据的读取。

2. Page Cache 与文件长久化的一致性 & 可靠性

古代 Linux 的 Page Cache 正如其名,是对磁盘上 page(页)的内存缓存,同时能够用于读 / 写操作。任何零碎引入缓存,就会引发一致性问题:内存中的数据与磁盘中的数据不统一,例如常见后端架构中的 Redis 缓存与 MySQL 数据库就存在一致性问题。

Linux 提供多种机制来保证数据一致性,但无论是单机上的内存与磁盘一致性,还是分布式组件中节点 1 与节点 2、节点 3 的数据一致性问题,了解的要害是 trade-off:吞吐量与数据一致性保障是一对矛盾。

首先,须要咱们了解一下文件的数据。文件 = 数据 + 元数据。元数据用来形容文件的各种属性,也必须存储在磁盘上。因而,咱们说保障文件一致性其实蕴含了两个方面:数据统一 + 元数据统一。

文件的元数据包含:文件大小、创立工夫、拜访工夫、属主属组等信息。

咱们思考如下一致性问题:如果产生写操作并且对应的数据在 Page Cache 中,那么写操作就会间接作用于 Page Cache 中,此时如果数据还没刷新到磁盘,那么内存中的数据就当先于磁盘,此时对应 page 就被称为 Dirty page。

以后 Linux 下以两种形式实现文件一致性:

  1. Write Through(写穿):向用户层提供特定接口,应用程序可被动调用接口来保障文件一致性;
  2. Write back(写回):零碎中存在定期工作(表现形式为内核线程),周期性地同步文件系统中文件脏数据块,这是默认的 Linux 一致性计划;

上述两种形式最终都依赖于零碎调用,次要分为如下三种零碎调用:

办法 含意
fsync(intfd) fsync(fd):将 fd 代表的文件的脏数据和脏元数据全副刷新至磁盘中。
fdatasync(int fd) fdatasync(fd):将 fd 代表的文件的脏数据刷新至磁盘,同时对必要的元数据刷新至磁盘中,这里所说的必要的概念是指:对接下来拜访文件有关键作用的信息,如文件大小,而文件批改工夫等不属于必要信息
sync() sync():则是对系统中所有的脏的文件数据元数据刷新至磁盘中

上述三种零碎调用能够别离由用户过程与内核过程发动。上面咱们钻研一下内核线程的相干个性。

  1. 创立的针对回写工作的内核线程数由零碎中长久存储设备决定,为每个存储设备创立独自的刷新线程;
  2. 对于多线程的架构问题,Linux 内核采取了 Lighthttp 的做法,即零碎中存在一个治理线程和多个刷新线程(每个长久存储设备对应一个刷新线程)。治理线程监控设施上的脏页面状况,若设施一段时间内没有产生脏页面,就销毁设施上的刷新线程;若监测到设施上有脏页面须要回写且尚未为该设施创立刷新线程,那么创立刷新线程解决脏页面回写。而刷新线程的工作较为枯燥,只负责将设施中的脏页面回写至长久存储设备中。
  3. 刷新线程刷新设施上脏页面大抵设计如下:

    1. 每个设施保留脏文件链表,保留的是该设施上存储的脏文件的 inode 节点。所谓的回写文件脏页面即回写该 inode 链表上的某些文件的脏页面;
    2. 零碎中存在多个回写机会,第一是应用程序被动调用回写接口(fsync,fdatasync 以及 sync 等),第二治理线程周期性地唤醒设施上的回写线程进行回写,第三是某些应用程序 / 内核工作发现内存不足时要回收局部缓存页面而当时进行脏页面回写,设计一个对立的框架来治理这些回写工作十分有必要。

Write Through 与 Write back 在长久化的可靠性上有所不同:

  • Write Through 以就义零碎 I/O 吞吐量作为代价,向下层利用确保一旦写入,数据就曾经落盘,不会失落;
  • Write back 在零碎产生宕机的状况下无奈确保数据曾经落盘,因而存在数据失落的问题。不过,在程序挂了,例如被 kill -9,Page Cache 中的数据操作系统还是会确保落盘;

3. Page Cache 的优劣势

3.1 Page Cache 的劣势

1. 放慢数据拜访

如果数据可能在内存中进行缓存,那么下一次拜访就不须要通过磁盘 I/O 了,间接命中内存缓存即可。

因为内存拜访比磁盘拜访快很多,因而放慢数据拜访是 Page Cache 的一大劣势。

2. 缩小 I/O 次数,进步零碎磁盘 I/O 吞吐量

得益于 Page Cache 的缓存以及预读能力,而程序又往往合乎局部性原理,因而通过一次 I/O 将多个 page 装入 Page Cache 可能缩小磁盘 I/O 次数,进而进步零碎磁盘 I/O 吞吐量。

3.2 Page Cache 的劣势

page cache 也有其劣势,最间接的毛病是须要占用额定物理内存空间,物理内存在比拟紧俏的时候可能会导致频繁的 swap 操作,最终导致系统的磁盘 I/O 负载的回升。

Page Cache 的另一个缺点是对应用层并没有提供很好的治理 API,简直是通明治理。应用层即便想优化 Page Cache 的应用策略也很难进行。因而一些利用抉择在用户空间实现本人的 page 治理,而不应用 page cache,例如 MySQL InnoDB 存储引擎以 16KB 的页进行治理。

Page Cache 最初一个缺点是在某些利用场景下比 Direct I/O 多一次磁盘读 I/O 以及磁盘写 I/O。这一点能够参考[4]。

起源:https://spongecaptain.cool/Si…

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0