flink datastream api

原文鏈接:http://jianshu.com/p/fc4fb559d9f4

Flink DataStream API詳解

12019.03.13 19:26:51字數 4158閱讀 2044

Flink API介紹

Flink提供了三層API,每層在簡潔性和表達性之間進行了不同的權衡。

flink-api

ProcessFunction是Flink提供的最具表現力的功能接口,它提供了對時間和狀態的細粒度控制,能夠任意修改狀態。所以ProcessFunction能夠爲許多有事件驅動的應用程序實現複雜的事件處理邏輯。
DataStream API爲許多通用的流處理操作提供原語,比如window。DataStream API適用於Java和Scala,它基於函數實現,比如map()、reduce()等。我們也可以自己擴展接口自定義函數。
SQL & Table API 這兩個都是關係API,是批處理和流處理統一的API。Table API和SQL利用Apache Calcite進行解析、驗證和查詢優化。它們可以與DataStream和DataSet API無縫集成,並支持用戶定義標量、聚合和表值函數。關係API(relational api)目標在於簡化數據分析、數據流水線(data pipelining)和ETL。

我們一般主要使用DataStream進行數據處理,下面介紹的API也是DataStream相關的API。

DataStream API

DataStream是Flink編寫流處理作業的API。我們前面說過一個完整的Flink處理程序應該包含三部分:數據源(Source)、轉換操作(Transformation)、結果接收(Sink)。下面我們從這三部分來看DataStream API。

數據源(Source)

Flink應用程序從數據源獲取要處理的數據,DataStream通過StreamExecutionEnvironment.addResource(SourceFunction) 來添加數據源。爲了方便使用,Flink預提幾類預定義的數據源,比如讀取文件的Source、通過Sockt讀取的Source、從內存中獲取的Source等。

基於集合的預定義Source

基於集合的數據源一般是指從內存集合中直接讀取要處理的數據,StreamExecutionEnvironment提供了4類預定義方法。

fromCollection

fromCollection是從給定的集合中創建DataStream,StreamExecutionEnvironment提供了4種重載方法:

  • fromCollection(Collection<T> data):通過給定的集合創建DataStream。返回數據類型爲集合元素類型。
  • fromCollection(Collection<T> data,TypeInformation<T> typeInfo):通過給定的非空集合創建DataStream。返回數據類型爲typeInfo。
  • fromCollection(Iterator<T> data,Class<T> type):通過給定的迭代器創建DataStream。返回數據類型爲type。
  • fromCollection(Iterator<T> data,TypeInformation<T> typeInfo):通過給定的迭代器創建DataStream。返回數據類型爲typeInfo。

fromParallelCollection

fromParallelCollection和fromCollection類似,但是是並行的從迭代器中創建DataStream。

  • fromParallelCollection(SplittableIterator<T> data,Class<T> type)
  • fromParallelCollection(SplittableIterator<T>,TypeInfomation typeInfo)

和Iterable中Spliterator類似,這是JDK1.8新增的特性,並行讀取集合元素。

fromElements

fromElements從給定的對象序列中創建DataStream,StreamExecutionEnvironment提供了2種重載方法:

  • fromElements(T... data):從給定對象序列中創建DataStream,返回的數據類型爲該對象類型自身。
  • fromElements(Class<T> type,T... data):從給定對象序列中創建DataStream,返回的數據類型type。

generateSequence

generateSequence(long from,long to)從給定間隔的數字序列中創建DataStream,比如from爲1,to爲10,則會生成1~10的序列。

基於Socket的預定義Source

我們還可以通過Socket來讀取數據,通過Sockt創建的DataStream能夠從Socket中無限接收字符串,字符編碼採用系統默認字符集。當Socket關閉時,Source停止讀取。Socket提供了5個重載方法,但是有兩個方法已經標記廢棄。

  • socketTextStream(String hostname,int port):指定Socket主機和端口,默認數據分隔符爲換行符(\n)。
  • socketTextStream(String hostname,int port,String delimiter):指定Socket主機和端口,數據分隔符爲delimiter。
  • socketTextStream(String hostname,int port,String delimiter,long maxRetry):該重載方法能夠當與Socket斷開時進行重連,重連次數由maxRetry決定,時間間隔爲1秒。如果爲0則表示立即終止不重連,如果爲負數則表示一直重試。

基於文件的預定義Source

基於文件創建DataStream主要有兩種方式:readTextFile和readFile。(readFileStream已廢棄)。readTextFile就是簡單讀取文件,而readFile的使用方式比較靈活。

readTextFile

readTextFile提供了兩個重載方法:

  • readTextFile(String filePath):逐行讀取指定文件來創建DataStream,使用系統默認字符編碼讀取。
  • readTextFile(String filePath,String charsetName):逐行讀取文件來創建DataStream,使用charsetName編碼讀取。

readFile

readFile通過指定的FileInputFormat來讀取用戶指定路徑的文件。對於指定路徑文件,我們可以使用不同的處理模式來處理,FileProcessingMode.PROCESS_ONCE模式只會處理文件數據一次,而FileProcessingMode.PROCESS_CONTINUOUSLY會監控數據源文件是否有新數據,如果有新數據則會繼續處理。

readFile(FileInputFormat<T> inputFormat,String filePath,FileProcessingMode watchType,long interval,TypeInformation typrInfo) 
參數 說明 實例
inputFormat 創建DataStream指定的輸入格式
filePath 讀取的文件路徑,爲URI格式。既可以讀取普通文件,可以讀取HDFS文件 file:///some/local/file 或hdfs://host:port/file/path
watchType 文件數據處理方式 FileProcessingMode.PROCESS_ONCE或FileProcessingMode.PROCESS_CONTINUOUSLY
interval 在週期性監控Source的模式下(PROCESS_CONTINUOUSLY),指定每次掃描的時間間隔 10
typeInformation 返回數據流的類型  

readFile提供了幾個便於使用的重載方法,但它們最終都是調用上面這個方法的。

  • readFile(FileInputFormat<T> inputFormat,String filePath):處理方式默認使用FileProcessingMode.PROCESS_ONCE。
  • readFile(FileInputFormat<T> inputFormat,String filePath,FileProcessingMode watchType,long interval):返回類型默認爲inputFormat類型。

需要注意:在使用FileProcessingMode.PROCESS_CONTINUOUSLY時,當修改讀取文件時,Flink會將文件整體內容重新處理,也就是打破了"exactly-once"。

自定義Source

除了預定義的Source外,我們還可以通過實現SourceFunction來自定義Source,然後通過StreamExecutionEnvironment.addSource(sourceFunction)添加進來。比如讀取Kafka數據的Source:

addSource(new FlinkKafkaConsumer08<>);

我們可以實現以下三個接口來自定義Source:

  • SourceFunction:創建非並行數據源。
  • ParallelSourceFunction:創建並行數據源。
  • RichParallelSourceFunction:創建並行數據源。

數據轉換(Transformation)

數據處理的核心就是對數據進行各種轉化操作,在Flink上就是通過轉換將一個或多個DataStream轉換成新的DataStream。
爲了更好的理解transformation函數,下面給出匿名類的方式來實現各個函數。
所有轉換函數都是依賴以下基礎:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<String> dataStream = env.readTextFile("/opt/yjz/flink/flink-1.7.2/README.txt");

基礎轉換操作

Map

接受一個元素,輸出一個元素。MapFunction<T,V>中T代表輸入數據類型(map方法的參數類型),V代表操作結果輸出類型(map方法返回數據類型)。

dataStream.map(new MapFunction<String, String>() {
    @Override
    public String map(String line) throws Exception {
        return line.toUpperCase();
    }
});

flatMap

輸入一個元素,輸出0個、1個或多個元素。FlatMapFunction<T,V>中T代表輸入元素數據類型(flatMap方法的第一個參數類型),V代表輸出集合中元素類型(flatMap中的Collector類型參數)

dataStream.flatMap(new FlatMapFunction<String, String>() {
    @Override
    public void flatMap(String line, Collector<String> collector) throws Exception {
        for(String word : line.split(" "))
        collector.collect(word);
    }
});

轉換前後數據類型:DataStream->DataStream。

filter

過濾指定元素數據,如果返回true則該元素繼續向下傳遞,如果爲false則將該元素過濾掉。FilterFunction<T>中T代表輸入元素的數據類型。

dataStream.filter(new FilterFunction<String>() {
    @Override
    public boolean filter(String line) throws Exception {
        if(line.contains("flink"))
            return true;
        else
            return false;
    }
});

轉換前後數據類型:DataStream->DataStream。

keyBy

邏輯上將數據流元素進行分區,具有相同key的記錄將被劃分到同一分區。指定Key的方式有多種,這個我們在之前說過了。返回類型KeyedStream<T,KEY>中T代表KeyedStream中元素數據類型,KEY代表虛擬KEY的數據類型。

KeyedStream<String,Tuple> keyedStream = dataStream.keyBy(0);

以下情況的元素不能作爲key使用:

  1. POJO類型,但沒有重寫hashCode(),而是依賴Object.hashCode()。
  2. 該元素是數組類型。

keyBy內部使用散列來實現的。
轉換前後數據類型:DataStream->KeyedStream。

reduce

對指定的“虛擬”key相同的記錄進行滾動合併,也就是當前元素與最後一次的reduce結果進行reduce操作。ReduceFunction<T>中的T代表KeyStream中元素的數據類型。

keyedStream.reduce(new ReduceFunction<String>() {
    @Override
    public String reduce(String value1, String value2) throws Exception {
        return value1 + value2;
    }
});

轉換前後數據類型:KeyedStream->DataStream。

Fold(Deprecated)

Fold功能和Reduce類似,但是Fold提供了初始值,從初始值開始滾動對相同的key記錄進行滾動合併。FoldFunction<T,V>中的T爲KeyStream中元素數據類型,V爲初始值類型和fold方法返回值類型。

keyedStream.fold(0, new FoldFunction<String, Integer>() {
    @Override
    public Integer fold(Integer accumulator, String value) throws Exception {
        return accumulator + value.split(" ").length;
    }
});

該方法已經標記爲廢棄!

轉換前後數據類型:KeyedStream->DataStream。

Aggregations

滾動聚合具有相同key的數據流元素,我們可以指定需要聚合的字段(field)。DataStream<T>中的T爲聚合之後的結果。

//對KeyedStream中元素的第一個Filed求和
DataStream<String> dataStream1 = keyedStream.sum(0);
//對KeyedStream中元素的“count”字段求和
keyedStream.sum("count");
//獲取keyedStream中第一個字段的最小值
keyedStream.min(0);
//獲取keyedStream中count字段的最小值的元素
keyedStream.minBy("count");
keyedStream.max("count");
keyedStream.maxBy(0);

min和minBy的區別是:min返回指定字段的最小值,而minBy返回最小值所在的元素。

轉換前後數據類型:KeyedStream->DataStream。

window

對已經分區的KeyedStream上定義窗口,Window會根據某些規則(比如在最後5s到達的數據)對虛擬“key”相同的記錄進行分組。WindowedStream<T, K, W extends Window>中的T爲KeyedStream中元素數據類型,K爲指定Key的數據類型,W爲我們所使用的窗口類型

WindowedStream<String,Tuple,TimeWindow> windowedStream = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));

轉換前後的數據類型:KeyedStream->WindowedStream

關於Window之後會拿出來專門一篇文章來說。

windowAll

我們也可以在常規DataStream上使用窗口,Window根據某些條件(比如最後5s到達的數據)對所有流事件進行分組。AllWindowedStream<T,W extends Window>中的T爲DataStream中元素的數據類型,W爲我們所使用的窗口類型。

AllWindowedStream<String,TimeWindow> allWindowedStream = dataStream.windowAll(TumblingEventTimeWindows.of(Time.seconds(5)));

注意:該方法在許多時候並不是並行執行的,所有記錄都會收集到一個task中

轉換前後的數據類型:DataStream->AllWindowedStream

Window Apply

將整個窗口應用在指定函數上,可以對WindowedStream和AllWindowedStream使用。WindowFunction<IN, OUT, KEY, W extends Window>中的IN爲輸入元素類型,OUT爲輸出類型元素,KEY爲指定的key類型,W爲所使用的窗口類型。

windowedStream.apply(new WindowFunction<String, String, Tuple, TimeWindow>() {
    @Override
    public void apply(Tuple tuple, TimeWindow window, Iterable<String> input, Collector<String> out) throws Exception {
        int sumCount = 0;
        for(String line : input){
            sumCount += line.split(" ").length;
        }
        out.collect(String.valueOf(sumCount));
    }
});

AllWindowedStream使用與WindowedStream類似。

轉換前後的數據類型:WindowedStream->DataStream或AllWindowedStream->DataStream

Window Reduce/Fold/Aggregation/

對於WindowedStream數據流我們同樣也可以應用Reduce、Fold、Aggregation函數。

windowedStream.reduce(new ReduceFunction<String>() {
    @Override
    public String reduce(String value1, String value2) throws Exception {
        return value1 + value2;
    }
});
windowedStream.fold(0, new FoldFunction<String, Integer>() {
    @Override
    public Integer fold(Integer accumulator, String value) throws Exception {
        return accumulator + value.split(" ").length;
    }
});
windowedStream.sum(0);
windowedStream.max("count");

轉換前後的數據類型:WindowedStream->DataStream

Union

聯合(Union)兩個或多個DataStream,所有DataStream中的元素都會組合成一個新的DataStream。如果聯合自身,則每個元素出現兩次在新的DataStream。

dataStream.union(dataStream1);

轉換前後的數據類型:DataStream*->DataStream

Window Join

在指定key的公共窗口上連接兩個數據流。JoinFunction<IN1,IN2,OUT>中的IN1爲第一個DataStream中元素數據類型,IN2爲第二個DataStream中元素數據類型,OUT爲Join結果數據類型。

dataStream
          .join(dataStream1)
          .where(new MyKeySelector()).equalTo(new MyKeySelector())
          .window(TumblingEventTimeWindows.of(Time.seconds(5)))
          .apply(new JoinFunction<String, String, String>() {
              @Override
              public String join(String first, String second) throws Exception {
                  return first + second;
              }
          });

轉換前後的數據類型:DataStream,DataStream->DataStream

Interval Join

對指定的時間間隔內使用公共key來連接兩個KeyedStream。ProcessJoinFunction<IN1,IN2,OUT>中IN1爲第一個DataStream中元素數據類型,IN2爲第二個DataStream中的元素數據類型,OUT爲結果輸出類型。

 keyedStream
            .intervalJoin(keyedStream)
            .between(Time.milliseconds(-2),Time.milliseconds(2))//間隔時間
            .lowerBoundExclusive()//並不包含下限時間
            .upperBoundExclusive()
            .process(new ProcessJoinFunction<String, String, String>() {
                @Override
                public void processElement(String left, String right, Context ctx, Collector<String> out) throws Exception {
                    //...
                }
            });

Window CoGroup

對兩個指定的key的DataStream在公共窗口上執行CoGroup,和Join功能類似,但是更加靈活。CoGroupFunction<IN1,IN2,OUT>,IN1代表第一個DataStream中元素類型,IN2代表第二個DataStream中元素類型,OUT爲結果輸出集合類型。

dataStream
          .coGroup(dataStream1)
          .where(new MyKeySelector()).equalTo(new MyKeySelector())
          .window(TumblingEventTimeWindows.of(Time.seconds(5)))
          .apply(new CoGroupFunction<String, String, String>() {
              @Override
              public void coGroup(Iterable<String> first, Iterable<String> second, Collector<String> out) throws Exception {

              }
          });

轉換前後的數據類型:DataStream,DataStream->DataStream

Connect

連接(connect)兩個流,並且保留其類型。兩個數據流之間可以共享狀態。ConnectedStreams<IN1,IN2>中IN1代表第一個數據流中的數據類型,IN2代表第二個數據流中的數據類型。

ConnectedStreams<String,String> connectedStreams = dataStream.connect(dataStream);

轉換前後的數據類型:DataStream,DataStream->ConnectedDataStreams

CoFlatMap/CoMap

可以對連接流執行類似map和flatMap操作。

connectedStreams.map(new CoMapFunction<String, String, String>() {
            @Override
            public String map1(String value) throws Exception {
                return value.toUpperCase();
            }
            @Override
            public String map2(String value) throws Exception {
                return value.toLowerCase();
            }
        });

轉換前後的數據類型:ConnectedDataStreams->DataStream

Split(Deprecated)

我們可以根據某些規則將數據流切分成兩個或多個數據流。

dataStream.split(new OutputSelector<String>() {
            @Override
            public Iterable<String> select(String value) {
                List<String> outList = new ArrayList<>();
                if(value.contains("flink"))
                    outList.add("flink");
                else 
                    outList.add("other");
                return outList;
            }
        });

該方法底層引用以被標記爲廢棄!

轉換前後的數據類型:DataStream->SplitStream

Select

我們可以對SplitStream分開的流進行選擇,可以將其轉換成一個或多個DataStream。

splitStream.select("flink");
splitStream.select("other");

Extract Timestamps(Deprecated)

從記錄中提取時間戳,以便使用事件時間語義窗口。之後會專門來看Flink的Event Time。

dataStream.assignTimestamps(new TimestampExtractor<String>() {
            @Override
            public long extractTimestamp(String element, long currentTimestamp) {
                return 0;
            }
            @Override
            public long extractWatermark(String element, long currentTimestamp) {
                return 0;
            }
            @Override
            public long getCurrentWatermark() {
                return 0;
            }
        });

該方法以被標記爲廢棄!

轉換前後的數據類型:DataStream->DataStream

Iterate

可以使用iterate方法來獲取IterativeStream。

IterativeStream<String> iterativeStream = dataStream.iterate();

轉換前後的數據類型:DataStream->IterativeStream

Project

對元組類型的DataStream可以使用Project選取子元組。

DataStream<Tuple2<String,Integer>> dataStream2 = dataStream.project(0,2);

轉換前後的數據類型:DataStream->DataStream

自定義分區(partitionCustom)

使用用戶自定義的分區函數對指定key進行分區,partitionCustom只支持單分區。

dataStream.partitionCustom(new Partitioner<String>() {
            
            @Override
            public int partition(String key, int numPartitions) {
                return key.hashCode() % numPartitions;
            }
        },1);

轉換前後的數據類型:DataStream->DataStream

隨機分區(shuffle)

均勻隨機將元素進行分區。

dataStream.shuffle();

轉換前後的數據類型:DataStream->DataStream

rebalance

以輪詢的方式爲每個分區均衡分配元素,對於優化數據傾斜該方法非常有效。

dataStream.rebalance();

轉換前後的數據類型:DataStream->DataStream

broadcast

使用broadcast可以向每個分區廣播元素。

dataStream.broadcast();

轉換前後的數據類型:DataStream->DataStream

rescale

根據上下游task數進行分區。

rescale

dataStream.rescale();

轉換前後的數據類型:DataStream->DataStream

結果數據接收器(Data sink)

數據經過Flink處理之後,最終結果會寫到file、socket、外部系統或者直接打印出來。數據接收器定義在DataStream類下,我們通過addSink()可以來添加一個接收器。同Source,Flink也提供了一些預定義的Data Sink讓我們直接使用。

寫入文本文件

DataStream提供了兩個writeAsText重載方法,寫入格式會調用寫入對象的toString()方法。

  • writeAsText(String path):將DataStream數據寫入到指定文件。
  • writeAsText(String path,WriteMode writeMode):將DataStream數據寫入到指定文件,可以通過writeMode來指定如果文件已經存在應該採用什麼方式,可以指定OVERWRITE或NO_OVERWRITE。

寫入CSV文件

DataStream提供了三個寫入csv文件的重載方法,對於DataStream中的每個Filed,都會調用其對象的toString()方法作爲寫入格式。writeAsCsv只能用於元組(Tuple)的DataStream。

writeAsCsv(String path,WriteMode writeMode,String rowDelimiter,String fieldDelimiter)
參數 說明 實例
path 寫入文件路徑
writeMode 如果寫入文件已經存在,採用什麼方式處理 WriteMode.NO_OVERWRITE 或WriteMode.OVERWRITE
rowDelimiter 定義行分隔符
fieldDelimiter 定義列分隔符  

DataStream提供了兩個簡易重載方法:

  • writeAsCsv(String path):使用"\n"作爲行分隔符,使用","作爲列分隔符。
  • writeAsCsv(String path,WriteMode writeMode):使用"\n"作爲行分隔符,使用","作爲列分隔符。

寫入Socket

Flink提供了將DataStream作爲字節數組寫入Socket的方法,通過SerializationSchema來指定輸出格式。

writeToSocket(String hostName,int port,SerializationSchema<T> schema)

指定輸出格式

DataStream提供了自定義文件輸出的類和方法,我們能夠自定義對象到字節的轉換。

writeUsingOutputFormat(OutputFormat<T> format)

結果打印

DataStream提供了print和printToErr打印標準輸出/標準錯誤流。DataStream中的每個元素都會調用其toString()方法作爲輸出格式,我們也可以指定一個前綴字符來區分不同的輸出。

  • print():標準輸出
  • print(String sinkIdentifier):指定輸出前綴
  • printToErr():標準錯誤輸出
  • printToErr(String sinkIdentifier):指定輸出前綴

對於並行度大於1的輸出,輸出結果也將輸出任務的標識符作爲前綴。

自定義輸出器

我們一般會自定義輸出器,通過實現SinkFunction接口,然後通過DataStream.addSink(sinkFunction)來指定數據接收器。

addSink(SinkFunction<T> sinkFunction)

注意:對於DataStream中的writeXxx()方法一般都是用於測試使用,因爲他們並沒有參與chaeckpoint,所以它們只有"at-last-once"也就是至少處理一次語義。
如果想要可靠輸出,想要使用"exactly-once"語義準確將結果寫入到文件系統中,我們需要使用flink-connector-filesystem。此外,我們也可以通過addSink()自定義輸出器來使用Flink的checkpoint來完成"exactl-oncey"語義。

關注我

歡迎關注我的公衆號,會定期推送優質技術文章,讓我們一起進步、一起成長!
公衆號搜索:data_tc
或直接掃碼:🔽

 

https://www.jianshu.com/p/fc4fb559d9f4

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