笔记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

posted @ 2021-03-21 22:11  Simon92  阅读(66)  评论(0编辑  收藏  举报