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 <path> --output <path></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")
案例:
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
- 测试 (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)
|
数据源保存了消费Kafka的偏移量(offset),之后将checkpoint barrier传递给下一个operator。
在operator过程中,比如聚合计算得到了sum值,此时表明operator具有内部状态,那么需要将sum 值写入state Backend,Flink负责在checkpoint成功的情况下正确提交这些写入的数据到JobManager,或者在出现故障时中止这些写入的数据JobManager。但在checkpoint 成功之前,不需要在预提交阶段执行任何其他操作。
Pre-commit without external state(Checkpoint in Progress)
|
|
当输出端(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