Flink通过org.apache.flink.core.fs.FileSystem类拥有自己的文件系统抽象。这种抽象提供了一组通用操作,并为各种类型的文件系统实现提供了最低限度的保证。
为了支持广泛的文件系统,FileSystem的可用操作集非常有限。例如,不支持追加或修改现有文件。
文件系统由文件系统方案来标识,如File://, hdfs://等。
实现
Flink直接实现文件系统,其文件系统方案如下:
文件,它表示机器的本地文件系统。
其他文件系统类型通过连接到Apache Hadoop支持的文件系统套件的实现来访问。以下是一个不完整的例子列表:
- hdfs: Hadoop Distributed File System
- s3, s3n, and s3a: Amazon S3 file system
- gcs: Google Cloud Storage
- …
如果Flink在类路径中找到Hadoop文件系统类并找到有效的Hadoop配置,那么它将透明地加载Hadoop的文件系统。默认情况下,它在类路径中查找Hadoop配置。另外,也可以通过配置条目fs.hdfs.hadoopconf指定自定义位置。
持久性保证
这些文件系统及其FsDataOutputStream实例用于持久存储数据,用于应用程序的结果以及容错和恢复。因此,很好地定义这些流的持久性语义是至关重要的。
持久性保证的定义
写入输出流的数据被认为是持久的,如果满足两个条件:
- 可见性要求:当给定绝对文件路径时,必须保证所有其他进程、机器、虚拟机、容器等能够访问该文件,以一致的方式查看数据。这个要求类似于POSIX定义的close-to-open语义,但仅限于文件本身(通过其绝对路径)。
- 耐久性要求:满足文件系统的耐久性/持久性要求。这些是特定于特定文件系统的。例如,对于硬件和操作系统的崩溃,{@link LocalFileSystem}不能提供任何持久性保证,而复制的分布式文件系统(如HDFS)通常在出现n个并发节点故障时保证持久性,其中n是复制因子。
对文件父目录的更新(以便在列出目录内容时显示该文件)不需要完成,文件流中的数据才会被认为是持久的。对于对目录内容的更新最终是一致的文件系统,这种放松非常重要。
一旦调用FSDataOutputStream.close()返回,FSDataOutputStream必须保证所写字节的数据持久性。
Examples
对于容错分布式文件系统,一旦数据被文件系统接收并确认,通常通过复制到机器的仲裁数量(持久性要求),数据就被认为是持久的。此外,绝对文件路径必须对可能访问该文件的所有其他机器可见(可见性要求)。
数据是否命中存储节点上的非易失性存储取决于特定文件系统的特定保证。
对文件父目录的元数据更新不需要达到一致状态。只要在所有节点上都可以通过文件的绝对路径访问该文件,就可以允许一些机器在列出父目录的内容时看到该文件,而另一些机器则不能。
本地文件系统必须支持POSIX close-to-open语义。由于本地文件系统没有任何容错保证,因此不存在进一步的要求。
以上特别说明,当从本地文件系统的角度考虑持久化时,数据可能仍然在操作系统缓存中。导致操作系统缓存丢失数据的崩溃对本地机器来说是致命的,并且不包括在Flink定义的本地文件系统的保证范围内。
这意味着只写入本地文件系统的计算结果、检查点和保存点不能保证从本地机器的故障中恢复,这使得本地文件系统不适合生产设置。
更新文件内容
许多文件系统要么根本不支持覆盖现有文件的内容,要么在这种情况下不支持更新内容的一致可见性。由于这个原因,Flink的FileSystem不支持追加现有文件,也不支持在输出流中查找,这样以前写入的数据就可以在同一个文件中更改。
覆盖文件
覆盖文件通常是可能的。通过删除文件并创建新文件来覆盖文件。然而,某些文件系统不能使该更改对访问该文件的所有各方同步可见。例如,Amazon S3只保证文件替换可见性的最终一致性:一些机器可能看到旧文件,一些机器可能看到新文件。
为了避免这些一致性问题,Flink中的故障/恢复机制的实现严格避免对同一个文件路径进行多次写入。
线程安全
文件系统的实现必须是线程安全的:在Flink中,同一个文件系统实例经常被多个线程共享,并且必须能够并发地创建输入/输出流和列出文件元数据。
严格来说,FSDataOutputStream和FSDataOutputStream的实现不是线程安全的。流的实例也不应该在线程之间的读或写操作之间传递,因为不能保证跨线程操作的可见性(许多操作不会创建内存围栏)。