Flink教程-flink 1.11使用sql將流式數據寫入文件系統


flink提供了一個file system connector,可以使用DDL創建一個table,然後使用sql的方法寫入數據,支持的寫入格式包括json、csv、avro、parquet、orc。

一個最簡單的DDL如下:

CREATE TABLE fs_table ( 
 user_id STRING, 
 order_amount DOUBLE, 
 dt STRING, 
 h string, 
 m string   
) PARTITIONED BY (dt,h,m) WITH ( 
   'connector'='filesystem', 
   'path'='file:///tmp/abc', 
   'format'='orc' 
 );

下面我們簡單的介紹一下相關的概念和如何使用。

滾動策略

Key Default Type
Description
sink.rolling-policy.file-size
128MB
MemorySize
分區文件的最大值,超過這個大小,將會啓動一個新文件。
sink.rolling-policy.rollover-interval 30 m Duration 分區文件滾動的最大時間間隔,超過這個時間,將會新啓動一個文件
sink.rolling-policy.check-interval 1 m Duration 一個時間間隔,定期去檢查上面那個配置指定的策略下,文件是否應該滾動生成新文件.
  • 在寫入列格式(比如parquet、orc)的時候,上述的配置和checkpoint的間隔一起來控制滾動策略,也就是說sink.rolling-policy.file-size、sink.rolling-policy.rollover-interval、checkpoint間隔,這三個選項,只要有一個條件達到了,然後就會觸發分區文件的滾動,結束上一個文件的寫入,生成新文件。
  • 對於寫入行格式的數據,比如json、csv,主要是靠sink.rolling-policy.file-size、sink.rolling-policy.rollover-interval,也就是文件的大小和時間來控制寫入數據的滾動策略.

分區提交

在往一個分區寫完了數據之後,我們希望做一些工作來通知下游。比如在分區目錄寫一個SUCCESS文件,或者是對於hive來說,去更新metastore的數據,自動刷新一下分區等等。
分區的提交主要依賴於觸發器和提交的策略:

  • 觸發器:即什麼時候觸發分區的提交,
  • 提交策略:也就是分區寫完之後我們做什麼,目前系統提供了兩種內置策略:1.往分區目錄寫一個空SUCCESS文件;2.更新元數據.

分區提交觸發器

key default type 解釋
sink.partition-commit.trigger
process-time
String
觸發器的類型,目前系統提供了兩種:process-time 和 partition-time,如果選擇了process-time,則當系統時間大於processtime的時候觸發提交,如果選擇了partition-time,則需要先從分區字段裏面抽取分區時間的開始時間,然後當水印大於這個分區時間的時候觸發分區的提交.
sink.partition-commit.delay 0 s Duration 提交分區的延遲時間
  1. process-time. 這種提交方式依賴於系統的時間,一旦遇到數據延遲等情況,會造成分區和分區的數據不一致。
  2. partition-time :這種情況需要從分區字段裏抽取出來相應的pattern,具體可參考下一個段落分區的抽取。
  3. sink.partition-commit.delay:一旦這個數值設置不爲0,則在process-time情況下,當系統時間大於分區創建時間加上delay延遲,會觸發分區提交; 如果是在partition-time 情況下,則需要水印大於分區創建時間加上delay時間,會觸發分區提交.

第一個參數process-time、partition-time,我們不用做過多的解釋,就類似於flink中的processtime和eventtime。

第二個參數sink.partition-commit.delay我們用實際案例解釋下:
比如我們配置的是分區是/yyyy-MM-dd/HH/,寫入的是ORC列格式,checkpoint配置的間隔是一分鐘,也就是默認情況下會每分鐘生成一個orc文件,最終會在每個分區(/yyyy-MM-dd/HH/)下面生成60個orc文件。

比如當前系統正在寫入/day=2020-07-06/h=10/分區的數據,那麼這個分區的創建時間是2020-07-06 10:00:00,如果這個delay配置採用的是默認值,也就是0s,這個時候當寫完了一個ORC文件,也就是2020-07-06 10:01:00分鐘的時候,就會觸發分區提交,比如更新hive的元數據,這個時候我們去查詢hive就能查到剛剛寫入的文件;如果我們想/day=2020-07-06/h=10/這個分區的60個文件都寫完了再更新分區,那麼我們可以將這個delay設置成 1h,也就是等到2020-07-06 11:00:00的時候纔會觸發分區提交,我們纔會看到/2020-07-06/10/分區下面的所有數據

分區時間的抽取

從分區值裏抽取分區時間,我們可以理解爲上面觸發器參數配置爲partition-time的時候,分區的創建時間,當水印大於這個時間+delay的時候觸發分區的提交.

Key Default Type 解釋
partition.time-extractor.kind
default
String
抽取分區的方式,目前有default和custom兩種,如果是default,需要配置partition.time-extractor.timestamp-pattern,如果是custom,需要配置自定義class
partition.time-extractor.class null String 自定義class
partition.time-extractor.timestamp-pattern null String 從分區值中抽取時間戳的模式,需要組織成yyyy-MM-dd HH:mm:ss格式,比如 對於上面我們提到的分區/yyyy-MM-dd/HH/,其中兩個分區字段對應的字段名分爲是dt和hour,那麼我們這個timestamp-pattern 可以配置成’$dt $hour:00:00’

自定義抽取分區時間的話,需要實現PartitionTimeExtractor接口:

public interface PartitionTimeExtractor extends Serializable {

	String DEFAULT = "default";
	String CUSTOM = "custom";

	/**
	 * Extract time from partition keys and values.
	 */
	LocalDateTime extract(List<String> partitionKeys, List<String> partitionValues);
    ...................
}

分區提交策略

定義了分區提交的策略,也就是寫完分區數據之後做什麼事情,目前系統提供了以下行爲:

  • metastore,只支持hive table,也就是寫完數據之後,更新hive的元數據.
  • success file: 寫完數據,往分區文件寫一個success file.
  • 自定義
key Default Type 描述
sink.partition-commit.policy.kind null string 可選:metastore,success-file,custom,這個可以寫一個或者多個,比如可以這樣,‘metastore,success-file’
sink.partition-commit.policy.class null string 如果上述選擇custom的話,這裏指定相應的class
sink.partition-commit.success-file.name null string 如果上述選擇的是success-file,這裏可以指定寫入的文件名,默認是 _SUCCESS

完整示例

定義實體類

	public static class UserInfo implements java.io.Serializable{
		private String userId;
		private Double amount;
		private Timestamp ts;

		public String getUserId(){
			return userId;
		}

		public void setUserId(String userId){
			this.userId = userId;
		}

		public Double getAmount(){
			return amount;
		}

		public void setAmount(Double amount){
			this.amount = amount;
		}

		public Timestamp getTs(){
			return ts;
		}

		public void setTs(Timestamp ts){
			this.ts = ts;
		}
	}

自定義source

	public static class MySource implements SourceFunction<UserInfo>{

		String userids[] = {
				"4760858d-2bec-483c-a535-291de04b2247", "67088699-d4f4-43f2-913c-481bff8a2dc5",
				"72f7b6a8-e1a9-49b4-9a0b-770c41e01bfb", "dfa27cb6-bd94-4bc0-a90b-f7beeb9faa8b",
				"aabbaa50-72f4-495c-b3a1-70383ee9d6a4", "3218bbb9-5874-4d37-a82d-3e35e52d1702",
				"3ebfb9602ac07779||3ebfe9612a007979", "aec20d52-c2eb-4436-b121-c29ad4097f6c",
				"e7e896cd939685d7||e7e8e6c1930689d7", "a4b1e1db-55ef-4d9d-b9d2-18393c5f59ee"
		};

		@Override
		public void run(SourceContext<UserInfo> sourceContext) throws Exception{
			while (true){
				String userid = userids[(int) (Math.random() * (userids.length - 1))];
				UserInfo userInfo = new UserInfo();
				userInfo.setUserId(userid);
				userInfo.setAmount(Math.random() * 100);
				userInfo.setTs(new Timestamp(new Date().getTime()));
				sourceContext.collect(userInfo);
				Thread.sleep(100);
			}
		}

		@Override
		public void cancel(){

		}
	}

寫入file

通過sql的ddl創建一個最簡單的基於process time的table,然後寫入數據.

在這個實例中,我們開啓了checkpoint的時間間隔是10s,所以會每隔10s寫入一個orc文件.

		StreamExecutionEnvironment bsEnv = StreamExecutionEnvironment.getExecutionEnvironment();
		bsEnv.enableCheckpointing(10000);
		StreamTableEnvironment tEnv = StreamTableEnvironment.create(bsEnv);
		DataStream<UserInfo> dataStream = bsEnv.addSource(new MySource());
		String sql = "CREATE TABLE fs_table (\n" +
		             "  user_id STRING,\n" +
		             "  order_amount DOUBLE,\n" +
		             "  dt STRING," +
		             "  h string," +
		             "  m string  \n" +
		             ") PARTITIONED BY (dt,h,m) WITH (\n" +
		             "  'connector'='filesystem',\n" +
		             "  'path'='file:///tmp/abc',\n" +
		             "  'format'='orc'\n" +
		             ")";
		tEnv.executeSql(sql);
		tEnv.createTemporaryView("users", dataStream);
		String insertSql = "insert into  fs_table SELECT userId, amount, " +
		                   " DATE_FORMAT(ts, 'yyyy-MM-dd'), DATE_FORMAT(ts, 'HH'), DATE_FORMAT(ts, 'mm') FROM users";

		tEnv.executeSql(insertSql);

完整的代碼請參考
https://github.com/zhangjun0x01/bigdata-examples/blob/master/flink/src/main/java/connectors/sql/StreamingWriteFile.java

更多精彩內容,歡迎關注我的公衆號【大數據技術與應用實戰】

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章