讀寫文件
1 讀取文件-readFile
Q:什麼是文件數據源?
A:Apache Flink提供了一個可重置的數據源連接器,支持將文件中的數據提取成數據流。
(該文件系統是flink的一部分因此無需同Kafka那樣添加依賴包)
val lineReader = new TextInputFormat(null)
streamLocal.readFile[String](
lineReader,
// "hdfs://master:8020/file/a.txt",
"file:///D:\\tmp\\a.txt",
FileProcessingMode.PROCESS_CONTINUOUSLY,
5000L)
.map(x => {
val value = x.split(",")
(value(0), value(1).toInt)
})
- TextInputFormat:會按(由換行符指定)行來讀取文本文件。
- PROCESS_CONTINUOUSLY:會以時間間隔週期性的掃描文件,如果目標文件發生了修改,就會將修改過的文件重新讀取一份。
(注意這裏的讀取是全部讀取)
- PROCESS_ONE:不會以時間間隔掃描文件。
2 寫入到文件-StreamingFileSink
應用配置了檢查點,且讀取的數據源可以提供在故障中恢復的功能,那麼StreamFileSink就可以提供端到端的一次性保障。
2.1 在瞭解-StreamingFileSink之前你需要了解的知識點
-
進行,等待,和完成:
- 當數據寫入文件時,文件會進入“進行” (part-0-0.inprogress)狀態。當RollingPolicy決定生成文件時,原文件會關閉,並通過重命名(part-0-0.wait)進入等待狀態,在下一次檢查點完成後處於等待狀態的文件將(再次重命名part-0-0)進入完成狀態。
-
RollingPolicy:
- 使用行編碼寫入文件的時候,我們可以通過定義RollingPolicy來決定何時創建分塊文件
(什麼是分塊文件會在下面的行編碼中有講到)
,但在批量編碼中是無法選擇RollingPolicy的。批量編碼只能與檢查點結合起來使用,即當每次生成一個檢查點的時候,就會對應的生成一個新的分塊文件。
- 使用行編碼寫入文件的時候,我們可以通過定義RollingPolicy來決定何時創建分塊文件
2.1.1 結論
如果應用沒有開啓檢查點,StreamingFlinkSink將永遠不會把等待狀態的文件變成完成狀態。
2.2 行編碼
val input = streamLocal.socketTextStream("master", 9999)
val sink = StreamingFileSink.forRowFormat(
new Path("file:///D:\\tmp"),
new SimpleStringEncoder[String]("UTF-8")
).build()
input.addSink(sink)
-
自定義BucketAssigner
- 默認:以程序寫入的時間在tmp下生成每小時一個的目錄
(該目錄的形成方式是flink通過調用BucketAssigner來完成,我們可以通過修改BucketAssigner的方式來自定義。)
例如:
- 數據是2019:08:04 10:10:01秒寫入的,就會在D:\tmp下生成一個2019-08-04 - - 10的一個目錄。
- 數據是2019:08:04 11:10:01秒寫入的,就會在D:\tmp下生成一個2019-08-04 - - 11的一個目錄。
- 默認:以程序寫入的時間在tmp下生成每小時一個的目錄
-
目錄下的分塊文件
- 每一個目錄(019-08-04 - -11)中都會包含很多分塊文件(part file),如下,該分塊文件的形成方式是通過StreamingFileSink的多個並行實例來進行併發寫入
的:下面的分塊文件代表的是:編號爲0的的任務寫出的第0個文件
- 每一個目錄(019-08-04 - -11)中都會包含很多分塊文件(part file),如下,該分塊文件的形成方式是通過StreamingFileSink的多個並行實例來進行併發寫入
-
上面的分塊文件是如何創建的:RollingPolicy?
- RollingPolicy用來決定任務何時創建一個新的分塊文件。默認是現有文件大小超過128M或打開時間超過60S就會創建一個新的分塊文件。
2.2.1 行編碼自定義-BucketAssigner
我們可以通過修改BucketAssigner的方式來自定義
val sink: StreamingFileSink[String] = StreamingFileSink.forRowFormat(
new Path("file:///D:\\tmp"),
new SimpleStringEncoder[String]("UTF-8")
)
.withBucketAssigner(new BucketAssigner[String, String] { //對應的是輸入類型和BucketID(目錄名稱)類型
def getPartitions(element: String): String = {
val date_L = element.split(",")(1).toLong //將數據中的事件時間作爲目錄進行存儲
val file_path = new SimpleDateFormat("yyyyMMdd").format(new Date(date_L));
s"dt=$file_path"
}
override def getBucketId(element: String, context: BucketAssigner.Context): String = {
var partitionValue = ""
try {
partitionValue = getPartitions(element)
} catch {
case e: Exception => partitionValue = "00000000"
}
partitionValue
}
override def getSerializer: SimpleVersionedSerializer[String] = {
SimpleVersionedStringSerializer.INSTANCE;
}
}).build()
2.3 批量編碼
- 在前面的行編碼中我們使用的寫入方式是行寫入。
- 在行編碼中每條記錄都會被單獨進行編碼然後添加到分塊文件中,然而在批量編碼模式下,記錄會被攢成批,然後一次性的寫入。Apache Pqrquet會以列式的方式組織和壓縮,因此該文件格式需要以批量編碼的方式寫入。
/**
* 需要導入的依賴包
* "org.apache.flink" % "flink-parquet" % "1.7.0"
* "org.apache.parquet" % "parquet-avro" % "1.10.1"
* */
object 自定義批量寫入文件 {
case class Avro_Pojo(name: String, age: Int)
def main(args: Array[String]): Unit = {
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
val streamLocal = StreamExecutionEnvironment.createLocalEnvironment(1)
//由於檢查點爲了支持容錯會以固定間隔來進行創建,在這裏我定義成周期爲10秒
streamLocal.enableCheckpointing(5000L)
import org.apache.flink.streaming.api.scala._ //如果數據集是無限的可以引入這個包,來進行隱式轉換操作
val input = streamLocal.socketTextStream("master", 9999)
.map(x=>{
val value = x.split(",")
val end = Avro_Pojo(value(0), value(1).toInt)
end
})
val sink = StreamingFileSink.forBulkFormat(
new Path("file:///D:\\tmp"),
//媽的注意這裏是:forReflectRecord不是forSpecificRecord,小心被坑
ParquetAvroWriters.forReflectRecord(classOf[Avro_Pojo]))
.build()
input.addSink(sink).name("ceshi")
streamLocal.execute("write file")
}
}
2.3.1 批量編碼自定義-BucketAssigner
批量編碼自定義BucketAssigner,與上面的行編碼自定義是一樣的