CoProcessFunction實戰(zhàn)三部曲之一:基本功能

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內(nèi)容:所有原創(chuàng)文章分類匯總及配套源碼抽兆,涉及Java识补、Docker、Kubernetes辫红、DevOPS等凭涂;

關(guān)于《CoProcessFunction實戰(zhàn)三部曲》系列

  • 《CoProcessFunction實戰(zhàn)三部曲》旨在通過三次實戰(zhàn)祝辣,由淺入深的學(xué)習(xí)和掌握Flink低階處理函數(shù)CoProcessFunction的用法;
  • 整個系列的開篇先介紹CoProcessFunction切油,然后迅速進(jìn)入實戰(zhàn)蝙斜,了解CoProcessFunction的基本功能;
  • 下一篇會結(jié)合狀態(tài)澎胡,讓雙流元素的處理彼此保持關(guān)系孕荠;
  • 終篇的實戰(zhàn)會加入定時器功能,確保同一個key的數(shù)據(jù)在雙流場景下能夠及時處理滤馍;

版本信息

  1. 開發(fā)環(huán)境操作系統(tǒng):MacBook Pro 13寸岛琼, macOS Catalina 10.15.3
  2. 開發(fā)工具:IDEA ULTIMATE 2018.3
  3. JDK:1.8.0_211
  4. Maven:3.6.0
  5. Flink:1.9.2

系列文章鏈接

  1. 基本功能
  2. 狀態(tài)處理
  3. 定時器和側(cè)輸出

關(guān)于CoProcessFunction

  • CoProcessFunction的作用是同時處理兩個數(shù)據(jù)源的數(shù)據(jù);
  • 試想在面對兩個輸入流時巢株,如果這兩個流的數(shù)據(jù)之間有業(yè)務(wù)關(guān)系槐瑞,該如何編碼實現(xiàn)呢,例如下圖中的操作阁苞,同時監(jiān)聽<font color="blue">9998</font>和<font color="blue">9999</font>端口困檩,將收到的輸出分別處理后,再由同一個sink處理(打印):


    在這里插入圖片描述
  • Flink支持的方式是擴展CoProcessFunction來處理那槽,為了更清楚認(rèn)識悼沿,我們把<font color="blue">KeyedProcessFunction</font>和<font color="blue">CoProcessFunction</font>的類圖擺在一起看,如下所示:


    在這里插入圖片描述
  • 從上圖可見骚灸,CoProcessFunction和KeyedProcessFunction的繼承關(guān)系一樣糟趾,另外CoProcessFunction自身也很簡單,在<font color="blue">processElement1</font>和<font color="blue">processElement2</font>中分別處理兩個上游流入的數(shù)據(jù)即可甚牲,并且也支持定時器設(shè)置义郑;

本篇實戰(zhàn)功能簡介

本篇咱們要開發(fā)的應(yīng)用,其功能非常簡單丈钙,描述如下:

  1. 建兩個數(shù)據(jù)源非驮,數(shù)據(jù)分別來自本地<font color="red">9998</font>和<font color="red">9999</font>端口;
  2. 每個端口收到類似<font color="blue">aaa,123</font>這樣的數(shù)據(jù)雏赦,轉(zhuǎn)成Tuple2實例劫笙,f0是<font color="blue">aaa</font>,f1是<font color="blue">123</font>星岗;
  3. 在CoProcessFunction的實現(xiàn)類中填大,對每個數(shù)據(jù)源的數(shù)據(jù)都打日志,然后全部傳到下游算子伍茄;
  4. 下游操作是打印栋盹,因此<font color="red">9998</font>和<font color="red">9999</font>端口收到的所有數(shù)據(jù)都會在控制臺打印出來;
  5. 整個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協(xié)議
git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該項目源碼的倉庫地址榨汤,ssh協(xié)議

這個git項目中有多個文件夾,本章的應(yīng)用在<font color="blue">flinkstudy</font>文件夾下怎茫,如下圖紅框所示:


在這里插入圖片描述

代碼簡介

  1. 開發(fā)一個Map算子收壕,將字符串轉(zhuǎn)成Tuple2;
  2. 再開發(fā)抽象類<font color="blue">AbstractCoProcessFunctionExecutor</font>轨蛤,功能包括:flink啟動蜜宪、監(jiān)聽端口、調(diào)用算子處理數(shù)據(jù)祥山、雙流連接圃验、將雙流處理結(jié)果打印出來;
  3. 從上面的描述可見缝呕,<font color="blue">AbstractCoProcessFunctionExecutor</font>做了很多事情澳窑,唯獨沒有實現(xiàn)雙流連接后的具體業(yè)務(wù)邏輯,這些沒有做的是留給子類來實現(xiàn)的供常,整個三部曲系列的重點都集中在AbstractCoProcessFunctionExecutor的子類上摊聋,把雙流連接后的業(yè)務(wù)邏輯做好,如下圖所示栈暇,紅色為CoProcessFunction的業(yè)務(wù)代碼麻裁,其他的都在抽象類中完成:


    在這里插入圖片描述

Map算子

  1. 做一個map算子,用來將字符串<font color="blue">aaa,123</font>轉(zhuǎn)成Tuple2實例源祈,f0是<font color="red">aaa</font>煎源,f1是<font color="red">123</font>;
  2. 算子名為<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]));
    }
}

抽象類

  • 抽象類AbstractCoProcessFunctionExecutor.java新博,源碼如下薪夕,稍后會說明幾個關(guān)鍵點:
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 串起整個邏輯的執(zhí)行類,用于體驗CoProcessFunction
 */
public abstract class AbstractCoProcessFunctionExecutor {

    /**
     * 返回CoProcessFunction的實例赫悄,這個方法留給子類實現(xiàn)
     * @return
     */
    protected abstract CoProcessFunction<
            Tuple2<String, Integer>,
            Tuple2<String, Integer>,
            Tuple2<String, Integer>> getCoProcessFunctionInstance();

    /**
     * 監(jiān)聽根據(jù)指定的端口原献,
     * 得到的數(shù)據(jù)先通過map轉(zhuǎn)為Tuple2實例,
     * 給元素加入時間戳埂淮,
     * 再按f0字段分區(qū)姑隅,
     * 將分區(qū)后的KeyedStream返回
     * @param port
     * @return
     */
    protected KeyedStream<Tuple2<String, Integer>, Tuple> buildStreamFromSocket(StreamExecutionEnvironment env, int port) {
        return env
                // 監(jiān)聽端口
                .socketTextStream("localhost", port)
                // 得到的字符串"aaa,3"轉(zhuǎn)成Tuple2實例,f0="aaa"倔撞,f1=3
                .map(new WordCountMap())
                // 將單詞作為key分區(qū)
                .keyBy(0);
    }

    /**
     * 如果子類有側(cè)輸出需要處理讲仰,請重寫此方法,會在主流程執(zhí)行完畢后被調(diào)用
     */
    protected void doSideOutput(SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream) {
    }

    /**
     * 執(zhí)行業(yè)務(wù)的方法
     * @throws Exception
     */
    public void execute() throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 并行度1
        env.setParallelism(1);

        // 監(jiān)聽9998端口的輸入
        KeyedStream<Tuple2<String, Integer>, Tuple> stream1 = buildStreamFromSocket(env, 9998);

        // 監(jiān)聽9999端口的輸入
        KeyedStream<Tuple2<String, Integer>, Tuple> stream2 = buildStreamFromSocket(env, 9999);

        SingleOutputStreamOperator<Tuple2<String, Integer>> mainDataStream = stream1
                // 兩個流連接
                .connect(stream2)
                // 執(zhí)行低階處理函數(shù)痪蝇,具體處理邏輯在子類中實現(xiàn)
                .process(getCoProcessFunctionInstance());

        // 將低階處理函數(shù)輸出的元素全部打印出來
        mainDataStream.print();

        // 側(cè)輸出相關(guān)邏輯鄙陡,子類有側(cè)輸出需求時重寫此方法
        doSideOutput(mainDataStream);

        // 執(zhí)行
        env.execute("ProcessFunction demo : CoProcessFunction");
    }
}
  • 關(guān)鍵點之一:一共有兩個數(shù)據(jù)源冕房,每個源的處理邏輯都封裝到<font color="blue">buildStreamFromSocket</font>方法中;
  • 關(guān)鍵點之二:<font color="blue">stream1.connect(stream2)</font>將兩個流連接起來趁矾;
  • 關(guān)鍵點之三:<font color="blue">process</font>接收CoProcessFunction實例耙册,合并后的流的處理邏輯就在這里面;
  • 關(guān)鍵點之四:<font color="blue">getCoProcessFunctionInstance</font>是抽象方法毫捣,返回<font color="blue">CoProcessFunction</font>實例详拙,交給子類實現(xiàn),所以CoProcessFunction中做什么事情完全由子類決定蔓同;
  • 關(guān)鍵點之五:doSideOutput方法中啥也沒做饶辙,但是在主流程代碼的末尾會被調(diào)用,如果子類有側(cè)輸出(SideOutput)的需求斑粱,重寫此方法即可弃揽,此方法的入?yún)⑹翘幚磉^的數(shù)據(jù)集,可以從這里取得側(cè)輸出珊佣;

子類蹋宦,對連接后的雙流進(jìn)行操作

  1. 本篇子類<font color="blue">CollectEveryOne.java</font>如下所示,邏輯很簡單咒锻,將每個源的上游數(shù)據(jù)直接輸出到下游算子:
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();
    }
}
  1. 上述代碼中冷冗,CoProcessFunction后面的泛型定義很長:<Tuple2<String, Integer>, Tuple2<String, Integer>, Tuple2<String, Integer>> ,一共三個Tuple2惑艇,分別代表一號數(shù)據(jù)源輸入蒿辙、二號數(shù)據(jù)源輸入、下游輸出的類型滨巴;
  2. 編碼完成思灌,運行起來試試;

驗證

  1. 分別開啟本機的<font color="blue">9998</font>和<font color="blue">9999</font>端口恭取,我這里是MacBook泰偿,執(zhí)行<font color="blue">nc -l 9998</font>和<font color="blue">nc -l 9999</font>
  2. 啟動Flink應(yīng)用,如果您和我一樣是Mac電腦蜈垮,直接運行<font color="blue">CollectEveryOne.main</font>方法即可(如果是windows電腦耗跛,我這沒試過,不過做成jar在線部署也是可以的)攒发;
  3. 在監(jiān)聽9998和9999端口的控制臺分別輸入<font color="blue">aaa,111</font>和<font color="blue">bbb,222</font>
  4. 以下是flink控制臺輸出的內(nèi)容调塌,可見processElement1和processElement2方法的日志代碼已經(jīng)執(zhí)行,并且print方法作為最下游惠猿,將兩個數(shù)據(jù)源的數(shù)據(jù)都打印出來了羔砾,符合預(yù)期:
12:45:38,774 INFO CollectEveryOne - 處理1號流的元素:(aaa,111),
(aaa,111)
12:45:43,816 INFO CollectEveryOne - 處理2號流的元素:(bbb,222)
(bbb,222)
  • 至此,咱們的第一個雙流處理低階函數(shù)就完成了,對CoProcessFunction也有了最基本的認(rèn)識姜凄,當(dāng)然CoProcessFunction的作用遠(yuǎn)不及此政溃,下一篇咱們借助狀態(tài)讓<font color="blue">processElement1</font>和<font color="blue">processElement2</font>分別對方處理過的狀態(tài),讓每個元素的處理都和另一個流關(guān)聯(lián)檀葛,不再孤立玩祟;

你不孤單腹缩,欣宸原創(chuàng)一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 數(shù)據(jù)庫+中間件系列
  6. DevOps系列

歡迎關(guān)注公眾號:程序員欣宸

微信搜索「程序員欣宸」屿聋,我是欣宸,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末藏鹊,一起剝皮案震驚了整個濱河市润讥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盘寡,老刑警劉巖楚殿,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異竿痰,居然都是意外死亡脆粥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門影涉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來变隔,“玉大人,你說我怎么就攤上這事蟹倾∠辉担” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵鲜棠,是天一觀的道長肌厨。 經(jīng)常有香客問我,道長豁陆,這世上最難降的妖魔是什么柑爸? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮盒音,結(jié)果婚禮上表鳍,老公的妹妹穿的比我還像新娘。我一直安慰自己里逆,他們只是感情好进胯,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著原押,像睡著了一般胁镐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天盯漂,我揣著相機與錄音颇玷,去河邊找鬼。 笑死就缆,一個胖子當(dāng)著我的面吹牛帖渠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播竭宰,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼空郊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了切揭?” 一聲冷哼從身側(cè)響起狞甚,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎廓旬,沒想到半個月后哼审,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡孕豹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年涩盾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片励背。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡春霍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出椅野,到底是詐尸還是另有隱情终畅,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布竟闪,位于F島的核電站离福,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炼蛤。R本人自食惡果不足惜妖爷,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望理朋。 院中可真熱鬧絮识,春花似錦省撑、人聲如沸昧互。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陵叽,卻和暖如春疏叨,著一層夾襖步出監(jiān)牢的瞬間腌逢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工逐沙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哲思,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓吩案,卻偏偏與公主長得像棚赔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子徘郭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容