您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關Flink中CoProcessFunction如何使用,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
本文是《Flink處理函數實戰》系列的第五篇,學習內容是如何同時處理兩個數據源的數據;
試想在面對兩個輸入流時,如果這兩個流的數據之間有業務關系,該如何編碼實現呢,例如下圖中的操作,同時監聽<font color="blue">9998</font>和<font color="blue">9999</font>端口,將收到的輸出分別處理后,再由同一個sink處理(打印):
Flink支持的方式是擴展CoProcessFunction來處理,為了更清楚認識,我們把<font color="blue">KeyedProcessFunction</font>和<font color="blue">CoProcessFunction</font>的類圖擺在一起看,如下所示:
從上圖可見,CoProcessFunction和KeyedProcessFunction的繼承關系一樣,另外CoProcessFunction自身也很簡單,在processElement1和processElement2中分別處理兩個上游流入的數據即可,并且也支持定時器設置;
接下來咱們開發一個應用來體驗<font color="blue">CoProcessFunction</font>,功能非常簡單,描述如下:
建兩個數據源,數據分別來自本地<font color="red">9998</font>和<font color="red">9999</font>端口;
每個端口收到類似<font color="blue">aaa,123</font>這樣的數據,轉成Tuple2實例,f0是<font color="blue">aaa</font>,f1是<font color="blue">123</font>;
在CoProcessFunction的實現類中,對每個數據源的數據都打日志,然后全部傳到下游算子;
下游操作是打印,因此<font color="red">9998</font>和<font color="red">9999</font>端口收到的所有數據都會在控制臺打印出來;
整個demo的功能如下圖所示:
接下來編碼實現上述功能;
如果您不想寫代碼,整個系列的源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):
名稱 | 鏈接 | 備注 |
---|---|---|
項目主頁 | https://github.com/zq2599/blog_demos | 該項目在GitHub上的主頁 |
git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該項目源碼的倉庫地址,https協議 |
git倉庫地址(ssh) | git@github.com:zq2599/blog_demos.git | 該項目源碼的倉庫地址,ssh協議 |
這個git項目中有多個文件夾,本章的應用在<font color="blue">flinkstudy</font>文件夾下,如下圖紅框所示:
做一個map算子,用來將字符串<font color="blue">aaa,123</font>轉成Tuple2實例,f0是<font color="red">aaa</font>,f1是<font color="red">123</font>;
算子名為<font color="blue">WordCountMap.java</font>:
package com.bolingcavalry.coprocessfunction; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.util.StringUtils; public class WordCountMap implements MapFunction<String, Tuple2<String, Integer>> { @Override public Tuple2<String, Integer> map(String s) throws Exception { if(StringUtils.isNullOrWhitespaceOnly(s)) { System.out.println("invalid line"); return null; } String[] array = s.split(","); if(null==array || array.length<2) { System.out.println("invalid line for array"); return null; } return new Tuple2<>(array[0], Integer.valueOf(array[1])); } }
開發一個抽象類,將前面圖中提到的監聽端口、map處理、keyby處理、打印都做到這個抽象類中,但是CoProcessFunction的邏輯卻不放在這里,而是交給子類來實現,這樣如果我們想進一步實踐和擴展CoProcessFunction的能力,只要在子類中專注做好CoProcessFunction相關開發即可,如下圖,紅色部分交給子類實現,其余的都是抽象類完成的:
抽象類AbstractCoProcessFunctionExecutor.java,源碼如下,稍后會說明幾個關鍵點:
package com.bolingcavalry.coprocessfunction; import org.apache.flink.api.java.tuple.Tuple; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.KeyedStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.co.CoProcessFunction; /** * @author will * @email zq2599@gmail.com * @date 2020-11-09 17:33 * @description 串起整個邏輯的執行類,用于體驗CoProcessFunction */ public abstract class AbstractCoProcessFunctionExecutor { /** * 返回CoProcessFunction的實例,這個方法留給子類實現 * @return */ protected abstract CoProcessFunction< Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> getCoProcessFunctionInstance(); /** * 監聽根據指定的端口, * 得到的數據先通過map轉為Tuple2實例, * 給元素加入時間戳, * 再按f0字段分區, * 將分區后的KeyedStream返回 * @param port * @return */ protected KeyedStream<Tuple2<String, Integer>, Tuple> buildStreamFromSocket(StreamExecutionEnvironment env, int port) { return env // 監聽端口 .socketTextStream("localhost", port) // 得到的字符串"aaa,3"轉成Tuple2實例,f0="aaa",f1=3 .map(new WordCountMap()) // 將單詞作為key分區 .keyBy(0); } /** * 如果子類有側輸出需要處理,請重寫此方法,會在主流程執行完畢后被調用 */ protected void doSideOutput(SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream) { } /** * 執行業務的方法 * @throws Exception */ public void execute() throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 并行度1 env.setParallelism(1); // 監聽9998端口的輸入 KeyedStream<Tuple2<String, Integer>, Tuple> stream1 = buildStreamFromSocket(env, 9998); // 監聽9999端口的輸入 KeyedStream<Tuple2<String, Integer>, Tuple> stream2 = buildStreamFromSocket(env, 9999); SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream = stream1 // 兩個流連接 .connect(stream2) // 執行低階處理函數,具體處理邏輯在子類中實現 .process(getCoProcessFunctionInstance()); // 將低階處理函數輸出的元素全部打印出來 mainDataStream.print(); // 側輸出相關邏輯,子類有側輸出需求時重寫此方法 doSideOutput(mainDataStream); // 執行 env.execute("ProcessFunction demo : CoProcessFunction"); } }
關鍵點之一:一共有兩個數據源,每個源的處理邏輯都封裝到<font color="blue">buildStreamFromSocket</font>方法中;
關鍵點之二:<font color="blue">stream1.connect(stream2)</font>將兩個流連接起來;
關鍵點之三:<font color="blue">process</font>接收CoProcessFunction實例,合并后的流的處理邏輯就在這里面;
關鍵點之四:<font color="blue">getCoProcessFunctionInstance</font>是抽象方法,返回<font color="blue">CoProcessFunction</font>實例,交給子類實現,所以CoProcessFunction中做什么事情完全由子類決定;
關鍵點之五:doSideOutput方法中啥也沒做,但是在主流程代碼的末尾會被調用,如果子類有側輸出(SideOutput)的需求,重寫此方法即可,此方法的入參是處理過的數據集,可以從這里取得側輸出;
子類<font color="blue">CollectEveryOne.java</font>如下所示,邏輯很簡單,將每個源的上游數據直接輸出到下游算子:
package com.bolingcavalry.coprocessfunction; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.functions.co.CoProcessFunction; import org.apache.flink.util.Collector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CollectEveryOne extends AbstractCoProcessFunctionExecutor { private static final Logger logger = LoggerFactory.getLogger(CollectEveryOne.class); @Override protected CoProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> getCoProcessFunctionInstance() { return new CoProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>>() { @Override public void processElement1(Tuple2<String, Integer> value, Context ctx, Collector<Tuple2<String, Integer>> out) { logger.info("處理1號流的元素:{},", value); out.collect(value); } @Override public void processElement2(Tuple2<String, Integer> value, Context ctx, Collector<Tuple2<String, Integer>> out) { logger.info("處理2號流的元素:{}", value); out.collect(value); } }; } public static void main(String[] args) throws Exception { new CollectEveryOne().execute(); } }
上述代碼中,CoProcessFunction后面的泛型定義很長:<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> ,一共三個Tuple2,分別代表一號數據源輸入、二號數據源輸入、下游輸出的類型;
分別開啟本機的<font color="blue">9998</font>和<font color="blue">9999</font>端口,我這里是MacBook,執行<font color="blue">nc -l 9998</font>和<font color="blue">nc -l 9999</font>
啟動Flink應用,如果您和我一樣是Mac電腦,直接運行<font color="blue">CollectEveryOne.main</font>方法即可(如果是windows電腦,我這沒試過,不過做成jar在線部署也是可以的);
在監聽9998和9999端口的控制臺分別輸入<font color="blue">aaa,111</font>和<font color="blue">bbb,222</font>
以下是flink控制臺輸出的內容,可見processElement1和processElement1方法的日志代碼已經執行,并且print方法作為最下游,將兩個數據源的數據都打印出來了,符合預期:
12:45:38,774 INFO CollectEveryOne - 處理1號流的元素:(aaa,111), (aaa,111) 12:45:43,816 INFO CollectEveryOne - 處理2號流的元素:(bbb,222) (bbb,222)
看完上述內容,你們對Flink中CoProcessFunction如何使用有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。