zoukankan      html  css  js  c++  java
  • 笔记2

    Flink time时间:

    1、eventing

    2、Ingestime

    3、processing time

    处理乱序 watemark

    1.Flink第一个入门程序

    package com.djt.flink.batch;

    import org.apache.flink.api.common.functions.FlatMapFunction;
    import org.apache.flink.api.java.DataSet;
    import org.apache.flink.api.java.ExecutionEnvironment;
    import org.apache.flink.api.java.tuple.Tuple2;
    import org.apache.flink.api.java.utils.ParameterTool;
    import org.apache.flink.util.Collector;
    import org.apache.flink.util.Preconditions;

    /**
     * Implements the "WordCount" program that computes a simple word occurrence histogram
     * over text files.
     *
     *
    <p>The input is a plain text file with lines separated by newline characters.
     *
     *
    <p>Usage: <code>WordCount --input &lt;path&gt; --output &lt;path&gt;</code><br>
     * If no parameters are provided, the program is run with default data from {@link WordCountData}.
     *
     *
    <p>This example shows how to:
     *
    <ul>
     *
    <li>write a simple Flink program.
     *
    <li>use Tuple data types.
     *
    <li>write and use user-defined functions.
     *
    </ul>
     *
     */
    public class WordCount {

       // *************************************************************************
       //     PROGRAM
       // *************************************************************************

      
    public static void main(String[] args) throws Exception {

          final ParameterTool params = ParameterTool.fromArgs(args);

          // set up the execution environment
         
    final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

          // make parameters available in the web interface
          //env.getConfig().setGlobalJobParameters(params);

          // get input data
         
    DataSet<String> text = null;
          if (params.has("input")) {
             // union all the inputs from text files
            
    text = env.readTextFile("input");
             Preconditions.checkNotNull(text, "Input DataSet should not be null.");
          } else {
             // get default test text data
            
    System.out.println("Executing WordCount example with default input data set.");
             System.out.println("Use --input to specify file input.");
             text = WordCountData.getDefaultTextLineDataSet(env);
          }

          DataSet<Tuple2<String, Integer>> counts =
                // split up the lines in pairs (2-tuples) containing: (word,1)
               
    text.flatMap(new Tokenizer())
                // group by the tuple field "0" and sum up tuple field "1"
               
    .groupBy(0)
                .sum(1);

          // emit result
         
    if (params.has("output")) {
             counts.writeAsCsv(params.get("output"), "\n", " ");
             // execute program
            
    env.execute("WordCount Example");
          } else {
             System.out.println("Printing result to stdout. Use --output to specify output path.");
             counts.print();
          }

       }

       // *************************************************************************
       //     USER FUNCTIONS
       // *************************************************************************

       /**
        * Implements the string tokenizer that splits sentences into words as a user-defined
        * FlatMapFunction. The function takes a line (String) and splits it into
        * multiple pairs in the form of "(word,1)" ({@code Tuple2
    <String, Integer>}).
        */
      
    public static final class Tokenizer implements FlatMapFunction<String, Tuple2<String, Integer>> {

          @Override
          public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
             // normalize and split the line
            
    String[] tokens = value.toLowerCase().split("\\W+");

             // emit the pairs
            
    for (String token : tokens) {
                if (token.length() > 0) {
                   out.collect(new Tuple2<>(token, 1));
                }
             }
          }
       }
    }

    2.Broadcast广播变量

    Broadcast:把元素广播给所有的分区,数据会被重复处理。

    使用方法:dataStream.broadcast()

    广播变量允许编程人员在每台机器上保持1个只读的缓存变量,而不是传送变量的副本给tasks。广播变量创建后,它可以运行在集群中的任何function上,而不需要多次传递给集群节点。另外需要记住,不应该修改广播变量,这样才能确保每个节点获取到的值都是一致的。

    总结:可以理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份。如果不使用broadcast,则在每个节点中的每个task中都需要拷贝一份dataset数据集,比较浪费内存(也就是一个节点中可能会存在多份dataset数据)。

     checkpoing:

    FLink 动态容错:

    流式计算:

    数据输出

    public static void main(string 
    )
    大数据计算框架,掌握一个,其他的举一反三
    broadcast广播变量
    slot flink做数据仓库
    
    taskmanager139duotao fang
    dataSet<两个表jloin>string写
    select ,hive
    Flink实施表数据-------》int public class
    public DataSet<string> 
    
    //获取累加器:
    int numLines = jobResult.getAccumlnt.path()
    //分布式缓存
    Distributed cache
    对广播变量  同样的条件
    //time&window&watermark
    用当前最大时间减去 周期性生成watermark

    list.add(t.f1)

    获取mysql connection

    watemark窗口值21 结束值24

    @Overabble

    用法

    1:初始化数据

    DataSet<Integer> toBroadcast = env.fromElements(1, 2, 3)

    2:广播数据

    .withBroadcastSet(toBroadcast, "broadcastSetName");

    3:获取数据

    Collection<Integer> broadcastSet = getRuntimeContext().getBroadcastVariable("broadcastSetName");

    注意:

    1:广播出去的变量存在于每个节点的内存中,所以这个数据集不能太大。因为广播出去的数据,会常驻内存,除非程序执行结束

    2:广播变量在初始化广播出去以后不支持修改,这样才能保证每个节点的数据都是一致的。

    案例:

    package com.djt.flink.BACD;

    import org.apache.flink.api.common.functions.MapFunction;

    import org.apache.flink.api.common.functions.RichMapFunction;

    import org.apache.flink.api.java.DataSet;

    import org.apache.flink.api.java.ExecutionEnvironment;

    import org.apache.flink.api.java.operators.DataSource;

    import org.apache.flink.api.java.tuple.Tuple2;

    import org.apache.flink.configuration.Configuration;

    import java.util.ArrayList;

    import java.util.HashMap;

    import java.util.List;

    public class BroadcastDemo {

        public static void main(String[] args) throws Exception{

            ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

            //1.准备需要广播的数据

            ArrayList<Tuple2< Integer,String>> broadData = new ArrayList<>();

            broadData.add(new Tuple2<>(101,"beijing"));

            broadData.add(new Tuple2<>(102,"shanghai"));

            broadData.add(new Tuple2<>(103,"hubei"));

            DataSet<Tuple2<Integer,String>> tupleData = env.fromCollection(broadData);

            //2.处理需要广播的数据,把数据集转换成map类型

            DataSet<HashMap<Integer,String>> toBroadcast = tupleData.map(new MapFunction<Tuple2<Integer,String>, HashMap<Integer,String>>() {

                @Override

                public HashMap<Integer,String> map(Tuple2<Integer,String> value) throws Exception {

                    HashMap<Integer,String> res = new HashMap<>();

                    res.put(value.f0, value.f1);

                    return res;

                }

            });

            //3.源数据

            ArrayList<Tuple2<Integer, String>> dataSource = new ArrayList<>();

            dataSource.add(new Tuple2(101,"1.1万亿"));

            dataSource.add(new Tuple2(102,"1万亿"));

            dataSource.add(new Tuple2(103,"8千亿"));

            DataSource<Tuple2<Integer,String>> data = env.fromCollection(dataSource);

            //注意:在这里需要使用到RichMapFunction获取广播变量

            DataSet<String> result = data.map(new RichMapFunction<Tuple2<Integer, String>, String>() {

                //存储广播数据

                List<HashMap<Integer, String>> broadCastMap = new ArrayList<HashMap<Integer, String>>();

                HashMap<Integer, String> allMap = new HashMap<Integer, String>();

                /**

                 * 这个方法只会执行一次

                 * 可以在这里实现一些初始化的功能

                 *

                 * 所以,就可以在open方法中获取广播变量数据

                 *

                 */

                @Override

                public void open(Configuration parameters) throws Exception {

                    super.open(parameters);

                    //5:获取广播数据

                    this.broadCastMap = getRuntimeContext().getBroadcastVariable("broadCastName");

                    for (HashMap map : broadCastMap) {

                        allMap.putAll(map);

                    }

                }

                /**

                 * 数据源于广播数据匹配

                 * @param value

                 * @return

                 * @throws Exception

                 */

                @Override

                public String map(Tuple2<Integer, String> value) throws Exception {

                    String province = allMap.get(value.f0);

                    return province+","+value.f1;

                }

            }).withBroadcastSet(toBroadcast, "broadCastName");//4:执行广播数据的操作

            result.print();

        }

    }

    3.Accumulators 累加器

    Accumulator即累加器,与Mapreduce counter的应用场景差不多,都能很好地观察task在运行期间的数据变化。可以在Flink job任务中的算子函数中操作累加器,但是只能在任务执行结束之后才能获得累加器的最终结果。Counter是一个具体的累加器(Accumulator)实现。如IntCounter, LongCounter 和 DoubleCounter

    用法:

    1.创建累加器

    IntCounter numLines = new IntCounter();

    2.注册累加器

    getRuntimeContext().addAccumulator("num-lines", this.numLines);

    3.使用累加器

    this.numLines.add(1);

    4.获取累加器的结果

    myJobExecutionResult.getAccumulatorResult("num-lines")

    参考地址:https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/api_concepts.html#accumulators--counters

     

    案例:

    package com.djt.flink.BACD;

    import org.apache.flink.api.common.JobExecutionResult;

    import org.apache.flink.api.common.accumulators.IntCounter;

    import org.apache.flink.api.common.functions.RichMapFunction;

    import org.apache.flink.api.java.DataSet;

    import org.apache.flink.api.java.ExecutionEnvironment;

    import org.apache.flink.api.java.operators.DataSource;

    import org.apache.flink.configuration.Configuration;

    /**

     * @author dajiangtai

     * @create 2020-02-18-15:40

     */

    public class CounterDemo {

        public static void main(String[] args) throws Exception{

     

            //获取运行环境

            ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

     

            DataSource<String> data = env.fromElements("火神山", "雷神山", "钟南山", "方舱医院");

     

            DataSet<String> result = data.map(new RichMapFunction<String, String>() {

     

                //1:创建累加器

                private IntCounter numLines = new IntCounter();

     

                @Override

                public void open(Configuration parameters) throws Exception {

                    super.open(parameters);

                    //2:注册累加器

                    getRuntimeContext().addAccumulator("num-lines",this.numLines);

     

                }

     

                int sum = 0;

                @Override

                public String map(String value) throws Exception {

                    //如果并行度为1,使用普通的累加求和即可;如果并行度大于1,需要使用累加器累加

                    sum++;

                    System.out.println("sum:"+sum);

                    this.numLines.add(1);

                    return value;

                }

            }).setParallelism(4);

     

            //批处理中print()方法中已经包含execute,故后面不能重复调用。

            //result.print();

     

            result.writeAsText("D:\\study\\data\\2020\\count2");

            //想获取计数器,又必须显示调用execute获取返回结果

            JobExecutionResult jobResult = env.execute("CounterDemo");

     

            //3:获取累加器

            int numLines = jobResult.getAccumulatorResult("num-lines");

            System.out.println("numLines:"+numLines);

        }

    }

    4.Broadcast和Accumulators的区别

    Broadcast(广播变量)允许程序员将一个只读的变量缓存在每台机器上,而不用在任务之间传递变量。广播变量可以进行共享,但是不可以进行修改。

    Accumulators(累加器)是可以在不同任务中对同一个变量进行累加操作。

    5.Distributed Cache(分布式缓存)

    Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件。

    缓存的工作机制:程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。当程序执行,Flink自动将文件或者目录复制到所有taskmanager节点的本地文件系统,每个task可以通过这个指定的名称查找文件或者目录,然后从taskmanager节点的本地文件系统访问它。

    用法:

    1.注册一个文件

    env.registerCachedFile("hdfs:///path/to/your/file", "hdfsFile") 

    2.访问数据

    File myFile = getRuntimeContext().getDistributedCache().getFile("hdfsFile");

    案例:

    package com.djt.flink.BACD;

    import org.apache.commons.io.FileUtils;

    import org.apache.flink.api.common.functions.RichMapFunction;

    import org.apache.flink.api.java.DataSet;

    import org.apache.flink.api.java.ExecutionEnvironment;

    import org.apache.flink.api.java.operators.DataSource;

    import org.apache.flink.api.java.tuple.Tuple2;

    import org.apache.flink.configuration.Configuration;

    import java.io.File;

    import java.util.ArrayList;

    import java.util.HashMap;

    import java.util.List;

    /**

     * @author dajiangtai

     * @create 2020-02-18-17:29

     */

    public class DistributedCacheDemo {

        public static void main(String[] args) throws Exception{

            //获取运行环境

            ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

            //1.添加分布式缓存文件

            env.registerCachedFile("D:\\study\\data\\2020\\city.txt","city.txt");

            //源数据

            ArrayList<Tuple2<Integer, String>> list = new ArrayList<>();

            list.add(new Tuple2(101,"1.1万亿"));

            list.add(new Tuple2(102,"1万亿"));

            list.add(new Tuple2(103,"8千亿"));

            DataSource<Tuple2<Integer,String>> data = env.fromCollection(list);

            DataSet<String> result = data.map(new RichMapFunction<Tuple2<Integer,String>, String>() {

                HashMap<Integer, String> allMap = new HashMap<Integer, String>();

                @Override

                public void open(Configuration parameters) throws Exception {

                    super.open(parameters);

                    //2.获取分布式缓存文件

                    File myFile = getRuntimeContext().getDistributedCache().getFile("city.txt");

                    List<String> lines = FileUtils.readLines(myFile);

                    for (String line : lines) {

                        String[] split = line.split(",");

                        allMap.put(Integer.parseInt(split[0]),split[1]);

                        System.out.println("line:" + line);

                    }

                }

                @Override

                public String map(Tuple2<Integer,String> value) throws Exception {

                    //3.使用分布式缓存文件

                    String province = allMap.get(value.f0);

                    return province+","+value.f1;

                }

            });

            result.print();

        }

    }

    6.Time&Windows&Watermark实例

    6.1有序数据-窗口触发

    1.测试数据:(完全有序)

    9527,1582359382000               2020-02-22 16:16:22

    9527,1582359384000               2020-02-22 16:16:24

    9527,1582359386000               2020-02-22 16:16:26

    9527,1582359389000               2020-02-22 16:16:29

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359393000               2020-02-22 16:16:33

    9527,1582359394000               2020-02-22 16:16:34

    9527,1582359396000               2020-02-22 16:16:36

    9527,1582359397000               2020-02-22 16:16:37

    2.测试(WatermarkDemo1)
    2.1通过nc发送数据
    [root@hadoop3-1 ~]# nc -lk 9999
    2.2启动flink 应用

    2.3窗口触发

    key:9527,eventtime:[2020-02-22 16:16:34.000],currentMaxTimestamp:[2020-02-22 16:16:34.000],watermark:[2020-02-22 16:16:24.000]

    (9527),窗口元素个数:[1],窗口元素最小值:[2020-02-22 16:16:22.000],窗口元素最大值:[2020-02-22 16:16:22.000],窗口起始值:[2020-02-22 16:16:21.000],窗口结束值:[2020-02-22 16:16:24.000]

    3.测试总结:

    window窗口大小间隔跟TumblingEventTimeWindows.of(Time.seconds(3)设置有关,但是窗口的起始值和结束值跟数据本身没有关系,而是系统定义好的。

    窗口触发计算需要满足两个条件

    a)watermark时间>=window_endtime

    b)[window_starttime,window_endtime)窗口中必须有至少输入数据

    6.2乱序数据-丢弃晚到数据

    1.测试数据集:(乱序数据)

    9527,1582359382000               2020-02-22 16:16:22

    9527,1582359384000               2020-02-22 16:16:24

    9527,1582359386000               2020-02-22 16:16:26

    9527,1582359389000               2020-02-22 16:16:29

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359393000               2020-02-22 16:16:33

    9527,1582359394000               2020-02-22 16:16:34

    9527,1582359396000               2020-02-22 16:16:36

    9527,1582359397000               2020-02-22 16:16:37

    9527,1582359399000               2020-02-22 16:16:39

    9527,1582359391000               2020-02-22 16:16:31

    9527,1582359403000               2020-02-22 16:16:43

    #没有指定允许延时会丢弃

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359391000               2020-02-22 16:16:31

    1. 测试 (WatermarkDemo1)
    2.1通过nc发送数据
    [root@hadoop3-1 ~]# nc -lk 9999
    2.2启动flink 应用

    2.3

    超过水位线的数据,属于真正晚到的数据,会被丢弃,不会再触发计算。

    key:9527,eventtime:[2020-02-22 16:16:32.000],currentMaxTimestamp:[2020-02-22 16:16:43.000],watermark:[2020-02-22 16:16:33.000]

    key:9527,eventtime:[2020-02-22 16:16:31.000],currentMaxTimestamp:[2020-02-22 16:16:43.000],watermark:[2020-02-22 16:16:33.000]

    测试总结:

    输入的数据本来所在的窗口已经执行过,此时输入的数据属于晚到的数据会被丢弃掉。

    6.3乱序数据-晚到的数据保留

    晚到的数据指定允许数据延迟时间。

    1.测试数据集:(乱序数据)

    9527,1582359382000               2020-02-22 16:16:22

    9527,1582359384000               2020-02-22 16:16:24

    9527,1582359386000               2020-02-22 16:16:26

    9527,1582359389000               2020-02-22 16:16:29

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359393000               2020-02-22 16:16:33

    9527,1582359394000               2020-02-22 16:16:34

    9527,1582359396000               2020-02-22 16:16:36

    9527,1582359397000               2020-02-22 16:16:37

    9527,1582359399000               2020-02-22 16:16:39

    9527,1582359391000               2020-02-22 16:16:31

    9527,1582359403000               2020-02-22 16:16:43

    #指定了允许延时,会继续触发

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359391000               2020-02-22 16:16:31

    2.测试(WatermarkDemo2)

    2.1通过nc发送数据
    [root@hadoop3-1 ~]# nc -lk 9999
    2.2启动flink 应用

    硬件环境比较好,可靠性也比较高,正常情况下,再怎么延长3秒钟,也能到了。

    修改代码:

    .allowedLateness(Time.seconds(3))

    2.3

    测试总结(设置了允许延时时间):

    对于window窗口来说,允许延迟3秒的数据到达

    第一次触发条件:

    a)watermark时间>=window_endtime

    b)[window_starttime,window_endtime)窗口中必须有至少输入数据

    第二次(或者多次)触发条件:

    watermark- allowedLateness <window_endtime,而且这个窗口有迟到的数据到达。

    6.4乱序数据-保存迟到的数据

    sideOutputTag ,提供了延迟数据获取的一种方式,这样就不会丢弃数据了

    1.测试数据集:(乱序数据)

    9527,1582359382000               2020-02-22 16:16:22

    9527,1582359384000               2020-02-22 16:16:24

    9527,1582359386000               2020-02-22 16:16:26

    9527,1582359389000               2020-02-22 16:16:29

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359393000               2020-02-22 16:16:33

    9527,1582359394000               2020-02-22 16:16:34

    9527,1582359396000               2020-02-22 16:16:36

    9527,1582359397000               2020-02-22 16:16:37

    9527,1582359399000               2020-02-22 16:16:39

    9527,1582359391000               2020-02-22 16:16:31

    9527,1582359403000               2020-02-22 16:16:43

    #指定了允许延时,会继续触发

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359391000               2020-02-22 16:16:31

    2.测试(WatermarkDemo3)

    2.1通过nc发送数据
    [root@hadoop3-1 ~]# nc -lk 9999
    2.2启动flink 应用

    2.3打印保存迟到的数据

    (9527,1582359392000)

    (9527,1582359391000)

    测试总结:

    针对迟到的数据,都通过 sideOutputLateTag保存到了lateOutputTag。

    6.5并行流中的watermark

    设置多并行度:

    env.setParallelism(3);

    通常情况下, watermark在source函数中生成,但是也可以在source后任何阶段,如果指定多次 ,后面指定的会覆盖前面的值。 source的每个sub task独立生成水印(source1为33,source2为17)。(注意:必须在第一个调用时间的operator之前,生成watermark,否则就没有意义)

    watermark通过operator时,会推进operators处的当前event time,同时operators会为下游生成一个新的watermark。比如W(33),通过map操作时,map处的event time当前时间由29改为33,表示33以前的数据都处理过了。

    多输入operator(union、 keyBy、 partition)的当前event time是其输入流event time的最小值比如windows1操作,数据来自map1(29)和map2(14),此时取最小值14,以w14为准来触发。

    1.测试数据

    9527,1582359382000               2020-02-22 16:16:22

    9527,1582359384000               2020-02-22 16:16:24

    9527,1582359386000               2020-02-22 16:16:26

    9527,1582359389000               2020-02-22 16:16:29

    9527,1582359392000               2020-02-22 16:16:32

    9527,1582359393000               2020-02-22 16:16:33

    9527,1582359394000               2020-02-22 16:16:34

    9527,1582359396000               2020-02-22 16:16:36

    9527,1582359397000               2020-02-22 16:16:37

    2.测试(ParallelismWatermarkDemo)

    窗口触发计算需要满足两个条件

    a)watermark时间>=window_endtime

    b)[window_starttime,window_endtime)窗口中必须有至少输入数据

    分析:

    第一个系统窗口大小为[ 2020-02-22 16:16:21.000, 2020-02-22 16:16:24.000]

    此时a 条件不满足,不触发

    currentThreadId:55,key:9527,eventtime:[2020-02-22 16:16:22.000],currentMaxTimestamp:[2020-02-22 16:16:22.000],watermark:[2020-02-22 16:16:12.000]

    currentThreadId:57,key:9527,eventtime:[2020-02-22 16:16:24.000],currentMaxTimestamp:[2020-02-22 16:16:24.000],watermark:[2020-02-22 16:16:14.000]

    currentThreadId:60,key:9527,eventtime:[2020-02-22 16:16:26.000],currentMaxTimestamp:[2020-02-22 16:16:26.000],watermark:[2020-02-22 16:16:16.000]

    此时a 条件不满足,不触发

    currentThreadId:55,key:9527,eventtime:[2020-02-22 16:16:29.000],currentMaxTimestamp:[2020-02-22 16:16:29.000],watermark:[2020-02-22 16:16:19.000]

    currentThreadId:57,key:9527,eventtime:[2020-02-22 16:16:32.000],currentMaxTimestamp:[2020-02-22 16:16:32.000],watermark:[2020-02-22 16:16:22.000]

    currentThreadId:60,key:9527,eventtime:[2020-02-22 16:16:33.000],currentMaxTimestamp:[2020-02-22 16:16:33.000],watermark:[2020-02-22 16:16:23.000]

    并行流watermark 取最小值,此时最小值为 currentThreadId:55,正好满足a条件,且只有一条数据。

    currentThreadId:55,key:9527,eventtime:[2020-02-22 16:16:34.000],currentMaxTimestamp:[2020-02-22 16:16:34.000],watermark:[2020-02-22 16:16:24.000]

    currentThreadId:57,key:9527,eventtime:[2020-02-22 16:16:36.000],currentMaxTimestamp:[2020-02-22 16:16:36.000],watermark:[2020-02-22 16:16:26.000]

    currentThreadId:60,key:9527,eventtime:[2020-02-22 16:16:37.000],currentMaxTimestamp:[2020-02-22 16:16:37.000],watermark:[2020-02-22 16:16:27.000]

    2> (9527),窗口元素个数:[1],窗口元素最小值:[2020-02-22 16:16:22.000],窗口元素最大值:[2020-02-22 16:16:22.000],窗口起始值:[2020-02-22 16:16:21.000],窗口结束值:[2020-02-22 16:16:24.000]

    7.Flink 中如何保证Exactly Once

    Exactly Once 语义:

    Exactly-Once:指的是每个输入的事件只影响最终结果一次。即使机器或软件出现故障,既没有重复数据,也不会丢数据。

    1.Flink应用程序中的Exactly-Once语义

    Flink 利用checkpoint 对从source—>operator—>sink提供一次完整的容错。

    Flink checkpoint快照包含如下内容:

    1)对于并行输入数据源:快照创建时数据流中的位置偏移

    2)对于 operator:存储在快照中的状态指针

    Flink可以配置一个固定的时间点,定期产生checkpoint,将checkpoint的数据写入持久存储系统(HDFS)。将checkpoint数据写入持久存储是异步发生的,这意味着Flink应用程序在checkpoint过程中可以继续处理数据。

    如果发生机器或软件故障,重新启动后,Flink应用程序将从最新的checkpoint点恢复处理; Flink会恢复应用程序状态,将输入流回滚到上次checkpoint保存的位置(比如kafka offset),然后重新开始运行。这意味着Flink可以像从未发生过故障一样计算结果。

    总结:在Flink 应用程序内部,利用checkpoint 提供了Exactly Once。

    2. Flink应用程序端到端的Exactly-Once语义

    通过Flink的TwoPhaseCommitSinkFunction两阶段提交协议能支持端到端(KafkaSource,KafkaSink)的Exactly-Once语义。

    Exactly Once two-phase commit(两阶段提交)

    从Kafka读取的数据源——>转换操作——>将数据写回Kafka的数据输出端

    要使数据输出端提供Exactly-Once保证,它必须将所有数据通过一个事务提交给Kafka。提交捆绑了两个checkpoint之间的所有要写入的数据。这可确保在发生故障时能回滚写入的数据。Flink使用两阶段提交协议及预提交阶段来解决这个问题。

    Pre-commit (预提交阶段)

    在checkpoint开始的时候(starting Checkpoint),即两阶段提交协议的“预提交”阶段。当checkpoint开始时,Flink的JobManager会将checkpoint barrier(将数据流中的记录分为进入当前checkpoint与进入下一个checkpoint)注入数据流。

    brarrier在operator之间传递。对于每一个operator,它触发operator的状态快照写入到state backend。

    Pre-commit(Checkpoint starts)

    注入checkpoint barrier(1)

     

    数据源保存了消费Kafka的偏移量(offset),之后将checkpoint barrier传递给下一个operator。

    在operator过程中,比如聚合计算得到了sum值,此时表明operator具有内部状态,那么需要将sum 值写入state Backend,Flink负责在checkpoint成功的情况下正确提交这些写入的数据到JobManager,或者在出现故障时中止这些写入的数据JobManager。但在checkpoint 成功之前,不需要在预提交阶段执行任何其他操作。

    Pre-commit without external state(Checkpoint in Progress)

    Snapshot(快照) offset(2)

     

    注入checkpoint barrier(1)

     

    当输出端(DataSink)有输出数据(写入Kafka),就具有了外部状态,需要做些额外的处理,为了提供Exactly-Once保证,在预提交阶段,除了将其状态写入state backend之外,数据输出端还必须预先提交其外部事务,这样才能和两阶段提交协议集成。

    Pre-commit with external state in data sink

    当checkpoint barrier在所有operator都传递了一遍,并且触发的checkpoint回调成功完成时,预提交阶段就结束了。所有触发的状态快照都被视为该checkpoint的一部分。checkpoint是整个应用程序状态的快照,包括预先提交的外部状态。如果发生故障,我们可以回滚到上次成功完成快照的时间点。

    commit (提交阶段)

    JobManager然后通知所有操作 (包括DataSource、Operator、DataSink),checkpoint已经成功了。这是两阶段提交协议的提交阶段,JobManager为应用程序中的每个operator发出checkpoint已完成的回调。

    数据源(Data Source)和Operator没有外部状态,因此在提交阶段,这些operator不必执行任何操作。但是,数据输出端(Data Sink)拥有外部状态,此时应该提交外部事务。

    总结:

    1)一旦所有operator完成预提交(Pre-commit),才会提交一个commit。

    2)如果至少有一个预提交(Pre-commit)失败,则所有其他提交都将中止,我们将回滚到上一个成功完成的checkpoint。

    3)在预提交(Pre-commit)成功之后,提交的commit需要保证最终成功 (operator和外部系统都需要保障这点)。如果commit失败(比如,由于间歇性网络问题),整个Flink应用程序将失败,应用程序将根据用户的重启策略重新启动(Flink会将operator的状态恢复到已经预提交,但尚未真正提交的状态。),还会尝试再提交(commit)。这个过程至关重要,因为如果commit最终没有成功,将会导致数据丢失。

    8.Flink与Kafka集成开发

    8.1.核心代码

    KafkaFlinkMySQL

    package com.djt.flink.news;

    import java.util.Properties;

    import org.apache.flink.api.common.functions.FlatMapFunction;

    import org.apache.flink.api.common.serialization.SimpleStringSchema;

    import org.apache.flink.api.java.tuple.Tuple2;

    import org.apache.flink.streaming.api.datastream.DataStream;

    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

    import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;

    import org.apache.flink.util.Collector;

    public class KafkaFlinkMySQL {

        public static void main(String[] args) throws Exception {

                 //获取Flink的运行环境

            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

            //kafka配置参数

            Properties properties = new Properties();

            properties.setProperty("bootstrap.servers", "hadoop3-1:9092,hadoop3-2:9092,hadoop3-3:9092");

            properties.setProperty("group.id", "sogoulogs");

            //kafka消费者

            FlinkKafkaConsumer<String> myConsumer = new FlinkKafkaConsumer<>("sogoulogs", new SimpleStringSchema(), properties);

            DataStream<String> stream = env.addSource(myConsumer);

           

            //对数据进行过滤

            DataStream<String> filter = stream.filter((value) -> value.split(",").length==6);

           

            DataStream<Tuple2<String, Integer>> newsCounts = filter.flatMap(new LineSplitter()).keyBy(0).sum(1);

            //自定义MySQL sink

            newsCounts.addSink(new MySQLSink());

           

            DataStream<Tuple2<String, Integer>> periodCounts = filter.flatMap(new LineSplitter2()).keyBy(0).sum(1);

            //自定义MySQL sink

            periodCounts.addSink(new MySQLSink2());

            // 执行flink 程序

            env.execute("FlinkMySQL");

        }

       

        public static final class LineSplitter implements FlatMapFunction<String, Tuple2<String, Integer>> {

                       private static final long serialVersionUID = 1L;

                       public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {

                                String[] tokens = value.toLowerCase().split(",");

                                out.collect(new Tuple2<String, Integer>(tokens[2], 1));

                       }

             }

       

        public static final class LineSplitter2 implements FlatMapFunction<String, Tuple2<String, Integer>> {

                       private static final long serialVersionUID = 1L;

                       public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {

                                String[] tokens = value.toLowerCase().split(",");

                                out.collect(new Tuple2<String, Integer>(tokens[0], 1));

                       }

             }

    }

    8.2启动集群服务

    (1)启动Zookeeper集群

    runRemoteCmd.sh "/home/hadoop/app/zookeeper/bin/zkServer.sh stop" all

    (2)启动kafka集群

     bin/kafka-server-start.sh config/server.properties

    (3)打开kafka生产者

    bin/kafka-console-producer.sh --broker-list localhost:9092 --topic sogoulogs

    8.3启动flink 应用

    KafkaFlinkMySQL

    ########## 今天的苦逼是为了不这样一直苦逼下去!##########
  • 相关阅读:
    #2020征文TV#【鸿蒙基地】鸿蒙从窗口开始:Page Ability诞生记
    设计器打开某表单时提示:[某某表单]已经由用户[xxx]打开需解锁
    有效性设置解疑
    表单打开时显示空白页面解决办法
    工作流_知会设置
    单元格中既有公式又可以录入数据,怎么实现?
    更改系统时间
    下拉框改变后,如何清空后面几个单元格的值?
    如何调整人员的部门?
    如何修改iis访问端口
  • 原文地址:https://www.cnblogs.com/ruii/p/14564156.html
Copyright © 2011-2022 走看看