Flink快速入门

本文详细介绍了Apache Flink的核心概念,包括流处理框架的基础架构,如Task和Operator Chains、Job Managers、Task Managers以及Task Slots。接着,深入讲解了DataStream API,涵盖数据源、转换操作和数据接收器,特别强调了状态管理和容错机制,包括Keyed State、Operator State和Checkpoint & SavePoints。最后,讨论了Flink如何确保精准一次的语义操作,以及窗口的概念和触发器。
摘要由CSDN通过智能技术生成

Apache Flink

概述

Flink 是构建在数据流之上的一款有状态的流计算框架,通常被人们称为第三代大数据分析方案

第一代大数据处理方案:基于Hadoop的MapReduce 静态批处理 | Storm 实时流计算 ,两套独立的计算引擎,难度大(2014年9月

第二代大数据处理方案:Spark RDD 静态批处理、Spark Streaming(DStream)实时流计算(实时性差),统一的计算引擎,难度小(2014年2月

第三代大数据分析方案:Apache Flink DataStream 流处理框架、Flink DataSet 批处理框架(2014年12月

可以看出Spark和Flink几乎同时诞生,但是Flink之所以成为第三代大数据处理方案,主要是因为早期人们对大数据分析的认知不够深刻或者业务场景大都局限在批处理领域,从而导致了Flink的发展相比于Spark 较为缓慢,直到2017年人们才开始慢慢将批处理转向流处理

流计算场景:实时计算领域、系统监控、舆情监控、交通预测、国家电网、疾病预测、银行/金融风控等领域

Spark VS Flink

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cOn3sEXi-1609900569234)(assets/1571024224483.png)]

Flink的核心是一个流式的数据流执行引擎,针对数据流的分布式计算,它提供了数据分布、数据通信以及容错机制等功能。基于流执行引擎,Flink提供了诸多更高抽象层的API以便用户编写分布式任务:

DataSet API,对静态数据进行批处理操作,将静态数据抽象成分布式的数据集,用户可以方便地使用Flink提供的各种操作符对分布式数据集进行处理,支持Java、Scala和Python语言。

DataStream API,对数据流进行流处理操作,将流式的数据抽象成分布式的数据流,用户可以方便地对分布式数据流进行各种操作,支持Java和Scala语言。

Table API,对结构化数据进行查询操作,将结构化数据抽象成关系表,并通过类SQL的DSL对关系表进行各种查询操作,支持Java和Scala语言。

此外,Flink还针对特定的应用领域提供了领域库,例如:

Flink ML,Flink的机器学习库,提供了机器学习 Pipelines API,并实现了多种机器学习算法。

Gelly,Flink的图计算库,提供了图计算的相关API及多种图计算的算法实现。

Flink 架构

Flink概念

Tasks and Operator Chains(阶段划分)

对于Flink分布式任务的执行,Flink尝试根据任务计算的并行度,将若干个操作符连接成一个任务Task(相当于Spark框架中的阶段-Stage),一个 Flink 计算任务通常会被拆分成若干个Task,每一个Task都有自己的并行度,每个并行度表示一个线程(SubTask)。

在这里插入图片描述

  • Task等价于Spark任务中的stage

  • Operator Chain Flink通过Operator Chain方式实现Task划分,有点类似Spark的宽窄依赖,Operator Chain方式有两种:forward、hash|rebalance

Job Managers、Task Managers、Clients

JobManagers(Master) - 协调并行计算任务,负责调度Task、协调Checklpoint以及故障恢复,它等价于Spark中的Master+Driver

There is always at least one Job Manager. A high-availability setup will have multiple JobManagers, one of which one is always the leader, and the others are standby.

TaskManagers(Slaves)- 真正负责Task划分的执行节点(执行SubTask或线程),同时需要向JobManagers汇报节点状态以及工作负荷。

client - 与Spark不同,Client并不是集群计算的一部分,它只负责将任务提交Dataflow(类似Spark DAG图)给JobManager,任务提交完成后可以退出,而Spark中的Client被称为Driver,负责生产DAG并且监控整个任务的执行过程和故障恢复。

在这里插入图片描述

Task Slots and Resources

每个Worker(TaskManager)是一个JVM进程,可以执行一个或多个子任务(Thread或SubTask),为了控制Woker能够接受多少个任务,Woker具有所谓的Task Slot(至少一个Task Slot)。

每个Task Slot代表TaskManager资源的固定子集。例如具有3个Task Slot的TaskManager,则每个Task Slot表示占用当前TaskManager进程1/3的内存,每个Job在启动时都有自己的Task Slot,数目固定,这样通过Task Slot的划分就可以避免不同Job的SubTask之间竞争内存资源。以下表示一个Job获取6个Task Slot,但是仅仅只有5个线程,3个Task

在这里插入图片描述

在默认情况下,来自同一个Job的不同Task(阶段)的SubTask可以共享一个Task Slot,Job计算所需Task Slot的个数由Task中的最大并行度所决定。

  • Flink集群所需的任务槽与作业中使用的最高并行度恰好一样多。
  • 更容易获得更好的资源利用率。如果没有Task Slot共享,则非密集型source子任务将阻塞与资源密集型window子任务一样多的资源,通过Task Slot共享可以将任务并行度由2增加到6,可以得到如下资源分配:

在这里插入图片描述

参考:https://ci.apache.org/projects/flink/flink-docs-release-1.9/concepts/runtime.html

Flink基础环境

前提条件
  • JDK1.8+安装完成
  • HDFS正常启动(SSH免密认证)
Flink安装
  • 上传并解压flink
[root@centos ~]# tar -zxf flink-1.8.1-bin-scala_2.11.tgz -C /usr/
  • 配置flink-conf.yaml

[root@centos ~]# vi /usr/flink-1.8.1/conf/flink-conf.yaml

jobmanager.rpc.address: centos
taskmanager.numberOfTaskSlots: 4
parallelism.default: 3
  • 配置slaves
[root@centos ~]# vi /usr/flink-1.8.1/conf/slaves
centos
  • 启动Flink
[root@centos flink-1.8.1]# ./bin/start-cluster.sh
Starting cluster.
Starting standalonesession daemon on host centos.
Starting taskexecutor daemon on host centos.
[root@centos flink-1.8.1]# jps
2912 Jps
2841 TaskManagerRunner
2397 StandaloneSessionClusterEntrypoint

访问:http://centos:8081

在这里插入图片描述

参考:https://ci.apache.org/projects/flink/flink-docs-release-1.9/getting-started/tutorials/local_setup.html

快速入门

  • 引入依赖
<properties>
    <flink.version>1.8.1</flink.version>
    <scala.version>2.11</scala.version>
</properties>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-clients_2.11</artifactId>
    <version>${flink.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-streaming-scala_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>
  • Client程序
//1.创建流处理的环境  -远程发布|本地执行
val env = StreamExecutionEnvironment.getExecutionEnvironment
//2.读取外围系统数据 -细化
val lines:DataStream[String]=env.socketTextStream("centos",9999)
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
// print(env.getExecutionPlan)
//3.执行流计算任务
env.execute("wordcount")
  • 将程序打包
[root@centos flink-1.8.1]# ./bin/flink run --class com.baizhi.demo01.FlinkWordCounts --detached --parallelism 3 /root/original-flink-1.0-SNAPSHOT.jar
Starting execution of program
Job has been submitted with JobID 221d5fa916523f88741e2abf39453b81
[root@centos flink-1.8.1]#
[root@centos flink-1.8.1]# ./bin/flink list -m centos:8081
Waiting for response...
------------------ Running/Restarting Jobs -------------------
14.10.2019 17:15:31 : 221d5fa916523f88741e2abf39453b81 : wordcount (RUNNING)
--------------------------------------------------------------
No scheduled jobs.
  • 取消任务
[root@centos flink-1.8.1]# ./bin/flink cancel -m centos:8081 221d5fa916523f88741e2abf39453b81
Cancelling job 221d5fa916523f88741e2abf39453b81.
Cancelled job 221d5fa916523f88741e2abf39453b81.

程序部署方式

  • 脚本
[root@centos flink-1.8.1]# ./bin/flink run 
                            --class com.baizhi.demo01.FlinkWordCounts 
                            --detached //后台运行
                            --parallelism 3  //指定并行度
                            /root/original-flink-1.0-SNAPSHOT.jar
  • UI页面

在这里插入图片描述

  • 跨平台
val jarFiles="flink\\target\\original-flink-1.0-SNAPSHOT.jar" //测试
val env = StreamExecutionEnvironment.createRemoteEnvironment("centos",8081,jarFiles)
  • 本地模拟
val env = StreamExecutionEnvironment.createLocalEnvironment(3)
或者
val env = StreamExecutionEnvironment.getExecutionEnvironment //自动识别运行环境,一般用于生产

DataStream API

参考:https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/datastream_api.html

Data Sources

Source是程序读取其输入的位置,您可以使用env.addSource(sourceFunction)将Source附加到程序中。Flink内置了许多预先实现的SourceFunction,但是您始终可以通过实现SourceFunction(non-parallel sources)来编写自定义Source,或者通过继承RichParallelSourceFunction或实现ParallelSourceFunction接口来实现并行Source.

File-based

readTextFile(path) - 逐行读取文本文件,底层使用TextInputFormat规范读取文件,并将其作为字符串返回

val env = StreamExecutionEnvironment.getExecutionEnvironment
val lines:DataStream[String]=env.readTextFile("file:///E:\\demo\\words")
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
env.execute("wordcount")

readFile(fileInputFormat, path) - 根据指定的文件输入格式读取文件(仅仅读取一次,类似批处理)

val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
val inputFormat=new TextInputFormat(null)
val lines:DataStream[String]=env.readFile(inputFormat,"file:///E:\\demo\\words")
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
env.execute("wordcount")

readFile(fileInputFormat, path, watchType, interval, pathFilter) - 这是前两个内部调用的方法。它根据给定的FileInputFormat读取指定路径下的文件,可以根据watchType定期检测指定路径下的文件,其中watchType的可选值FileProcessingMode.PROCESS_CONTINUOUSLY或者FileProcessingMode.PROCESS_ONCE,检查的周期由interval参数决定。用户可以使用pathFilter参数排除该路径下需要排除的文件。如果指定watchType的值被设置为PROCESS_CONTINUOUSLY,一旦文件内容发生改变,整个文件内容会被重复处理。

val env = StreamExecutionEnvironment.getExecutionEnvironment
val inputFormat=new TextInputFormat(null)
val lines:DataStream[String]=env.readFile(
    inputFormat,"file:///E:\\demo\\words",
    FileProcessingMode.PROCESS_CONTINUOUSLY,
    5000,new FilePathFilter {
   
        override def filterPath(filePath: Path): Boolean = {
   
            filePath.getPath.endsWith(".txt")
        }
    })
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
env.execute("wordcount")
Socket-based
val env = StreamExecutionEnvironment.getExecutionEnvironment
val lines:DataStream[String]=fsEnv.socketTextStream("centos",9999)
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
env.execute("wordcount")
Collection-based(测试)
val env = StreamExecutionEnvironment.getExecutionEnvironment
val lines:DataStream[String]=env.fromCollection(List("this is a demo","good good"))
//val lines:DataStream[String]=env.fromElements("this is a demo","good good")
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
env.execute("wordcount")
Custom Source
import org.apache.flink.streaming.api.functions.source.{
   ParallelSourceFunction, SourceFunction}
import scala.util.Random

class CustomSourceFunction extends ParallelSourceFunction[String]{
   
  @volatile
  var isRunning:Boolean = true
  val lines:Array[String] = Array("this is a demo","hello word","are you ok")
    
  override def run(ctx: SourceFunction.SourceContext[String]): Unit = {
   
    while(isRunning){
   
      Thread.sleep(1000)
      ctx.collect(lines(new Random().nextInt(lines.length)))//将数据输出给下游
    }
  }

  override def cancel(): Unit = {
   
    isRunning=false
  }
}
val env = StreamExecutionEnvironment.getExecutionEnvironment
val lines:DataStream[String]=env.addSource[String](new CustomSourceFunction)
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
env.execute("wordcount")
FlinkKafkaConsumer√
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka_${scala.version}</artifactId>
    <version>${flink.version}</version>
</dependency>
val env = StreamExecutionEnvironment.getExecutionEnvironment
val props = new Properties()
props.setProperty("bootstrap.servers", "centos:9092")
props.setProperty("group.id", "g1")
val lines=env.addSource(new FlinkKafkaConsumer("topic01",new SimpleStringSchema(),props))
    lines.flatMap(_.split("\\s+"))
    .map((_,1))
    .keyBy(t=>t._1)
    .sum(1)
    .print()
env.execute("wordcount")

如果使用SimpleStringSchema,仅仅能获取value,如果用户希望获取更多信息,比如 key/value/partition/offset ,用户可以通过自定义KafkaDeserializationSchema的子类定制反序列化

import org.apache.flink.api.common.typeinfo.TypeInformation
import org.apache.flink.streaming.connectors.kafka.KafkaDeserializationSchema
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.flink.streaming.api.scala._

class UserKafkaDeserializationSchema extends KafkaDeserializationSchema[(String,String)] {
   
  //这个方法永远返回false
  override def isEndOfStream(nextElement: (String, String)): Boolean = {
   
    false
  }

  override def deserialize(record: ConsumerRecord[Array[Byte], Array[Byte]]): (String, String) = {
   
    var key=""
    if(record.key()!=null && record.key().size!=0){
   
      key=new String(record.key())
    }
    val value=new String(record.value())
    (key,value)
  }
  //告诉Flink tuple元素类型
  override def getProducedType: TypeInformation[(String, String)] = {
   
    createTypeInformation[(String, String)]
  }
}
val env = StreamExecutionEnvironment.getExecutionEnvironment
val props = new Properties()
props.setProperty("bootstrap.servers", "CentOS:9092")
props.setProperty("group.id", "g1")
val lines:DataStream[(String,String)]=env.addSource(new FlinkKafkaConsumer("topic01",new UserKafkaDeserializationSchema(),props))
lines.map(t=>t._2).flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
env.execute("wordcount")

如果Kafka存储的都是json格式的字符串数据,用户可以使用系统自带的一些支持json的Schema,推荐使用:

  • JsonNodeDeserializationSchema:要求value必须是json格式的字符串
  • JSONKeyValueDeserializationSchema(meta):要求key、value都必须是josn格式数据,同时可以携带元数据(分区、 offset等)
val env = StreamExecutionEnvironment.getExecutionEnvironment
val props = new Properties()
props.setProperty("bootstrap.servers", "centos:9092")
props.setProperty("group.id", "g1")
val jsonData:DataStream[ObjectNode]=env.addSource(new FlinkKafkaConsumer("topic01",new JSONKeyValueDeserializationSchema(true),props))
jsonData.map(on=> (on.get("value").get("id").asInt(),on.get("value").get("name")))
.print()
env.execute("wordcount")

Data Sinks

Data Sinks接收DataStream数据,并将其转发到指定文件,socket,外部存储系统或者print它们,Flink预定义一些输出Sink。

file-based

write*:writeAsText|writeAsCsv(…)|writeUsingOutputFormat,请注意DataStream上的write*()方法主要用于调试目的。

val env = StreamExecutionEnvironment.getExecutionEnvironment
val props = new Properties()
props.setProperty("bootstrap.servers", "centos:9092")
props.setProperty("group.id", "g1")
env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(),props))
.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(0)
.sum(1)
.writeAsText("file:///E:/results/text",WriteMode.OVERWRITE)
env.execute("wordcount")

以上的写法只能保证at_least_once的处理,如果是在生产环境下,推荐使用flink-connector-filesystem将数据写到外围系统,可以保证exactly-once处理

val env = StreamExecutionEnvironment.getExecutionEnvironment
val props = new Properties()
props.setProperty("bootstrap.servers", "centos:9092")
props.setProperty("group.id", "g1")
val bucketingSink = new BucketingSink[(String,Int)]("hdfs://centos:9000/BucketingSink")
bucketingSink.setBucketer(new DateTimeBucketer("yyyyMMddHH"))//文件目录
bucketingSink.setBatchSize(1024)
env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(),props))
.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(0)
.sum(1)
.addSink(bucketingSink)
.setParallelism(6)
env.execute("wordcount")
print()/printToErr()
val env = StreamExecutionEnvironment.getExecutionEnvironment
val 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值