溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Flink中CoProcessFunction如何使用

發(fā)布時(shí)間:2021-07-14 14:16:23 來源:億速云 閱讀:181 作者:Leah 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)Flink中CoProcessFunction如何使用,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

  • 本文是《Flink處理函數(shù)實(shí)戰(zhàn)》系列的第五篇,學(xué)習(xí)內(nèi)容是如何同時(shí)處理兩個(gè)數(shù)據(jù)源的數(shù)據(jù);

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

  • Flink支持的方式是擴(kuò)展CoProcessFunction來處理,為了更清楚認(rèn)識,我們把<font color="blue">KeyedProcessFunction</font>和<font color="blue">CoProcessFunction</font>的類圖擺在一起看,如下所示: Flink中CoProcessFunction如何使用

  • 從上圖可見,CoProcessFunction和KeyedProcessFunction的繼承關(guān)系一樣,另外CoProcessFunction自身也很簡單,在processElement1和processElement2中分別處理兩個(gè)上游流入的數(shù)據(jù)即可,并且也支持定時(shí)器設(shè)置;

編碼實(shí)戰(zhàn)

接下來咱們開發(fā)一個(gè)應(yīng)用來體驗(yàn)<font color="blue">CoProcessFunction</font>,功能非常簡單,描述如下:

  1. 建兩個(gè)數(shù)據(jù)源,數(shù)據(jù)分別來自本地<font color="red">9998</font>和<font color="red">9999</font>端口;

  2. 每個(gè)端口收到類似<font color="blue">aaa,123</font>這樣的數(shù)據(jù),轉(zhuǎn)成Tuple2實(shí)例,f0是<font color="blue">aaa</font>,f1是<font color="blue">123</font>;

  3. 在CoProcessFunction的實(shí)現(xiàn)類中,對每個(gè)數(shù)據(jù)源的數(shù)據(jù)都打日志,然后全部傳到下游算子;

  4. 下游操作是打印,因此<font color="red">9998</font>和<font color="red">9999</font>端口收到的所有數(shù)據(jù)都會在控制臺打印出來;

  5. 整個(gè)demo的功能如下圖所示: Flink中CoProcessFunction如何使用

  • 接下來編碼實(shí)現(xiàn)上述功能;

源碼下載

如果您不想寫代碼,整個(gè)系列的源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):

名稱鏈接備注
項(xiàng)目主頁https://github.com/zq2599/blog_demos該項(xiàng)目在GitHub上的主頁
git倉庫地址(https)https://github.com/zq2599/blog_demos.git該項(xiàng)目源碼的倉庫地址,https協(xié)議
git倉庫地址(ssh)git@github.com:zq2599/blog_demos.git該項(xiàng)目源碼的倉庫地址,ssh協(xié)議

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

Map算子

  1. 做一個(gè)map算子,用來將字符串<font color="blue">aaa,123</font>轉(zhuǎn)成Tuple2實(shí)例,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]));
    }
}

便于擴(kuò)展的抽象類

  • 開發(fā)一個(gè)抽象類,將前面圖中提到的監(jiān)聽端口、map處理、keyby處理、打印都做到這個(gè)抽象類中,但是CoProcessFunction的邏輯卻不放在這里,而是交給子類來實(shí)現(xiàn),這樣如果我們想進(jìn)一步實(shí)踐和擴(kuò)展CoProcessFunction的能力,只要在子類中專注做好CoProcessFunction相關(guān)開發(fā)即可,如下圖,紅色部分交給子類實(shí)現(xiàn),其余的都是抽象類完成的: Flink中CoProcessFunction如何使用

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

    /**
     * 返回CoProcessFunction的實(shí)例,這個(gè)方法留給子類實(shí)現(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實(shí)例,
     * 給元素加入時(shí)間戳,
     * 再按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實(shí)例,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
                // 兩個(gè)流連接
                .connect(stream2)
                // 執(zhí)行低階處理函數(shù),具體處理邏輯在子類中實(shí)現(xiàn)
                .process(getCoProcessFunctionInstance());

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

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

        // 執(zhí)行
        env.execute("ProcessFunction demo : CoProcessFunction");
    }
}
  • 關(guān)鍵點(diǎn)之一:一共有兩個(gè)數(shù)據(jù)源,每個(gè)源的處理邏輯都封裝到<font color="blue">buildStreamFromSocket</font>方法中;

  • 關(guān)鍵點(diǎn)之二:<font color="blue">stream1.connect(stream2)</font>將兩個(gè)流連接起來;

  • 關(guān)鍵點(diǎn)之三:<font color="blue">process</font>接收CoProcessFunction實(shí)例,合并后的流的處理邏輯就在這里面;

  • 關(guān)鍵點(diǎn)之四:<font color="blue">getCoProcessFunctionInstance</font>是抽象方法,返回<font color="blue">CoProcessFunction</font>實(shí)例,交給子類實(shí)現(xiàn),所以CoProcessFunction中做什么事情完全由子類決定;

  • 關(guān)鍵點(diǎn)之五:doSideOutput方法中啥也沒做,但是在主流程代碼的末尾會被調(diào)用,如果子類有側(cè)輸出(SideOutput)的需求,重寫此方法即可,此方法的入?yún)⑹翘幚磉^的數(shù)據(jù)集,可以從這里取得側(cè)輸出;

子類決定CoProcessFunction的功能

  1. 子類<font color="blue">CollectEveryOne.java</font>如下所示,邏輯很簡單,將每個(gè)源的上游數(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>> ,一共三個(gè)Tuple2,分別代表一號數(shù)據(jù)源輸入、二號數(shù)據(jù)源輸入、下游輸出的類型;

驗(yàn)證

  1. 分別開啟本機(jī)的<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電腦,直接運(yùn)行<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和processElement1方法的日志代碼已經(jīng)執(zhí)行,并且print方法作為最下游,將兩個(gè)數(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)

看完上述內(nèi)容,你們對Flink中CoProcessFunction如何使用有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI