Flink(Apache Flink)是一个流式处理和批处理的开源框架,用于高性能、可伸缩的数据流处理。它提供了强大的数据流处理能力,适用于实时数据分析、事件驱动应用和大规模数据处理。
-
强调数据流的连续性处理:
- Flink 主要用于处理实时数据流和批处理数据。与传统的 Java 应用程序不同,Flink 强调数据流的连续性处理,可以处理无界数据流,使得它适用于实时数据处理场景。
-
工作中的应用:
Flink 在工作中常常用于处理实时数据流、批处理任务和事件驱动的应用,包括但不限于:- 实时数据分析和计算:通过对实时数据流进行处理和分析,可以实时监测、控制和响应系统的状态变化。
- 数据清洗和转换:从不同的数据源中读取数据,对数据进行清洗、转换和整合,然后将处理后的数据写入到其他系统中。
- 复杂事件处理:识别和处理复杂的事件模式,例如金融领域的交易欺诈检测或工业领域的设备故障预测。
- 实时仪表盘和监控:将实时数据可视化展示,帮助用户了解系统的状态和趋势。
-
从Kafka读取数据存储到ClickHouse的应用场景:
Flink 在大数据处理中的一个常见用例是从数据源中读取数据,然后进行转换和分析,最终将结果存储到目标系统中。例如,从 Kafka 中读取数据并将其存储到 ClickHouse 中:
- 数据摄取:Kafka 通常用于实时数据流摄取。Flink 可以从 Kafka 中读取实时流数据,这些数据可能是用户活动、传感器数据、日志等。
- 数据处理:Flink 可以将从 Kafka 中读取的数据进行实时处理、转换、过滤等,例如计算每分钟的平均值、过滤异常数据等。
- 数据存储:ClickHouse 是一个列式数据库,适用于高速的分析查询。Flink 可以将处理后的数据写入到 ClickHouse 数据库中,供后续查询和分析使用。
总之,Flink 是一个强大的流式处理和批处理框架,可用于处理实时数据、复杂事件处理和大规模数据分析。它能够在实时场景下提供高性能和低延迟的数据处理能力,也能够处理大规模批处理任务。在实际工作中,Flink 可以用于数据清洗、转换、分析和存储,为企业提供了强大的数据处理和分析能力。
一、环境设置
1. 创建一个 Maven 项目。
2. 添加 Flink 依赖:在项目的配置文件中添加 Flink 的依赖。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<target.java.version>1.8</target.java.version>
<!--占位符 统一依赖版本,方便一改全改-->
<flink.version>1.12.7</flink.version>
<scala.binary.version>2.12</scala.binary.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!--flink依赖,类比成spring-context依赖-->
<!-- This dependency is provided, because it should not be packaged into the JAR file. -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--flink客户端依赖,可以用来本地发布ui界面-->
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-clients -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--flink融合kafka的依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--flink融合jdbc的依赖-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>
<!--clickhouse数据库驱动-->
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.3.2</version>
</dependency>
</dependencies>
3. 编写 Flink 程序
创建一个 Flink 应用程序,通常是一个继承自 org.apache.flink.streaming.api.functions.source.SourceFunction
的数据源,并使用 Flink 提供的算子进行数据转换和处理。
/**
* flink的第一个demo程序
*/
public class FlinkDemo01 {
public static void main(String[] args) throws Exception {
//上面的四个环节 environment(环境) -> source(源头) -> transform(process)(转化/处理) -> sink(沉没)
//有两个环节是绝对不能缺少的, 1.环境 2.源头
//StreamExecutionEnvironment 流式执行环境
//获取执行环境的意思: 代码会自动检测当前有无计算引擎平台, 有平台则走平台流程; 无则自行内部创建平台
//刚学spring项目的时候, 是不是一定要结合外部环境(tomcat)
//后台SpringBoot项目内置了tomcat
StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
//添加一个数据源
//需要写一个java代码, 实现SourceFunction接口, 在接口中指明数据的来源, 数据是字符串
DataStreamSource<String> streamSource = environment.addSource(new SourceFunction<String>() {
/**
* @param sourceContext 数据源上下文
*/
@Override
public void run(SourceContext<String> sourceContext) throws Exception {
while (true) {
String data = String.valueOf(System.currentTimeMillis());
//collect:收集, 采集
sourceContext.collect(data);
TimeUnit.SECONDS.sleep(2);
}
}
@Override
public void cancel() {
}
});
//在当前流式环境中没有算子(函数/方法)可以执行
//No operators defined in streaming topology. Cannot execute.
//必须要加入一个算子
//这个streamSource对象中, 会源源不断的产生字符串数据
//最简单的算子, 打印算子, 将接收到的任何数据打印出来
streamSource.print();
//让刚刚的任务正式执行
environment.execute();
}
}
该代码会不断生成当前时间的数据,并将数据发送给 Flink 数据流,然后使用打印算子将生成的数据打印出来。
在这个代码中,addSource 方法接受一个 SourceFunction 实例,该实例实现了数据的生成和发送逻辑。然后,使用打印算子 print 将数据打印到控制台上。
SourceFunction
是 Apache Flink 中用于实现数据源的基础接口。它允许你自定义数据源,并定义数据的产生逻辑。数据源可以是单并行度(单线程产生数据)或多并行度(多线程并行产生数据)的。
这个接口定义了两个主要方法:
-
void run(SourceContext<T> ctx) throws Exception
:这个方法是数据源实际产生数据的地方。你需要在这个方法中使用ctx.collect()
方法将数据发送到数据流。这个方法会在一个单独的线程中执行,并且会一直运行直到数据源被取消。 -
void cancel()
:这个方法是用来停止数据源的产生。当 Flink 任务取消时,或者任务达到指定的停止条件时,Flink 会调用这个方法来停止数据源的产生。
最后,使用 environment.execute() 启动 Flink 作业的执行。
4. 算子
在 Flink 中,算子(Operator)是指对数据流进行转换和处理的操作单元。Flink 的数据流处理模型是基于数据流图的,图中的节点就是算子,边表示数据流。算子可以对数据进行处理、转换、聚合等操作,从而实现具体的业务逻辑。
在代码中,streamSource
是一个数据源(Source),它产生数据并发送到 Flink 数据流中。然而,如果只有数据源而没有任何的算子,Flink 作业是没有实际操作的。Flink 的数据流处理是通过构建数据流图来实现的,数据流图中需要包含源算子(Source)和至少一个转换算子(Transformation Operator),最后可能会有一个或多个接收算子(Sink)。
转换算子可以对数据进行处理、过滤、聚合等操作,它们是构成数据流处理逻辑的核心部分。在代码中,通过添加 streamSource.print()
,使用了一个打印算子,将数据打印到控制台上。这个操作虽然简单,但它也是一个转换算子。
总之,加入一个算子是因为 Flink 的数据流处理模型是基于数据流图构建的,算子是图中的节点,是实际进行数据处理的地方。没有算子,作业就没有实际的操作逻辑。
二、获取数据
1. 从集合,数组等中获取数据。
通过 environment.fromElements()
方法从集合中获取数据,并将数据转化为数据流。
class FlinkSourceDemo01{
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
//1. 从集合中获取数据
DataStreamSource<String> streamSource = environment.fromElements("丁丁", "迪西", "拉拉", "小波");
streamSource.print();
environment.execute();
}
}
运行结果:
7> 拉拉
5> 丁丁
6> 迪西
8> 小波
同一个线程之间是按照顺序的,不同的线程之间顺序不一定
使用environment.fromCollection()
方法从一个数组集合中获取数据,并将数据转化为一个数据流。数据流中包含数组中的每个元素。
//从数组中获取数据
DataStreamSource<Object> streamSource = environment.fromCollection(Arrays.asList("其其", "么么公主", "嘟嘟", "库比"));
使用 environment.fromSequence()
方法创建一个连续的数据流,从1到25的范围内生成整数数据。
//指定范围生成数据
DataStreamSource<Long> streamSource = environment.fromSequence(1, 25);
2. 从文件, io流中获取数据
使用environment.readTextFile()
从文件中读取数据,创建一个数据流包含文件中的每一行文本。在这里,需要指定文件的路径和编码方式。
//2. 从文件, io流中获取数据
DataStreamSource<String> streamSource = environment.readTextFile("大耳朵图图.txt", "UTF-8");
3. 自定义sinkFunction来指明读取kafka数据的方式
/**
* flink读取kafka数据 自定义读取方式
*/
class FlinkSourceDemo02{
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> streamSource = environment.addSource(new SourceFunction<String>() {
@Override
public void run(SourceContext<String> sourceContext) throws Exception {
Properties properties = new Properties();
properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "服务器IP地址:9092,服务器IP地址:9093");
properties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "kafka_flink");
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer(properties);
kafkaConsumer.subscribe(Arrays.asList("alert_log"));
while (true) {
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofSeconds(2));
for (ConsumerRecord<String, String> record : records) {
String data = record.value();
sourceContext.collect(data);
}
}
}
@Override
public void cancel() {}
});
streamSource.print();
environment.execute();
}
}
4. flink使用官方组件读取kafka数据
/**
* flink读取kafka数据 使用官方组件, 1.简便 2.官方内部采用了kafka-streaming, 极大提升了读取性能
*/
class FlinkSourceDemo03{
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
Properties properties = new Properties();
properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "62.234.60.20:9092,62.234.60.20:9093");
properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "kafka_reader_1");
DataStreamSource<String> streamSource = environment.addSource(new FlinkKafkaConsumer("alert_log", new SimpleStringSchema(), properties));
streamSource.print();
environment.execute();
}
}
三、实现多并行度
自定义source, 实现多并行度
线程安全问题三要素 :
- 存在多线程环境
- 存在多线程操作同一个变量(共享变量)
- 对这个共享变量具有写行为
三个要素必须同时集齐才会存在线程安全问题
在 Apache Flink 中,如果想为自定义的 Source 函数设置多并行度,需要遵循一些规则。默认情况下,非并行操作符的并行度必须为 1
,这是因为 Flink 的数据流中的源操作符通常不支持并行处理。
直接加.setParallelism()
的话会出现报错:“IllegalArgumentException: The parallelism of non parallel operator must be 1.”。
如果想要实现自定义的 Source 并设置多并行度,可以使用 ParallelSourceFunction 接口。我们需要new ParallelSourceFunction<Object>()
之后才能使用.setParallelism()
关于ParallelSourceFunction接口:
)
和Serializable接口一样,定义Serializable接口时就没有定义任何的抽象方法,它只是作为对象序列化的一个标志而已,没有这个标志则该对象不能实现序列化与反序列化。
ParallelSourceFunction
是 Apache Flink 中用于实现并行数据源的接口。它允许你自定义数据源,并指定数据的产生逻辑。在 Flink 流处理中,数据源可以并行地生成数据,即不同的并行任务可以同时产生数据。
/**
* 自定义source, 实现多并行度 实现并行度sourceFunction即可
*/
class FlinkSource03 {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> streamSource = environment.addSource(new ParallelSourceFunction<String>() {
@Override
public void run(SourceContext<String> sourceContext) throws Exception {
while (true){
String data = "" + System.currentTimeMillis();
sourceContext.collect(data);
TimeUnit.SECONDS.sleep(2);
}
}
@Override
public void cancel() {}
}).setParallelism(7);
streamSource.print();
environment.execute();
}
}
以上代码实现了一个自定义的并行 Source,它会不断地生成当前时间的数据,并且将数据发送给 Flink 数据流,然后通过设置并行度为 7进行并行处理,并将生成的数据打印出来。
使用 ParallelSourceFunction 可以充分利用多核处理器,实现高效的并行数据产生。在 Flink 中,数据源是构建流处理应用的第一步,可以根据具体的需求实现自定义的数据源,从文件、网络、消息队列等各种数据源中读取数据,并将其转化为数据流,供后续的处理操作使用。
如果你想深入了解 Flink 的更多特性和高级功能,建议查阅官方文档和示例代码。Flink官网