standLone是不建议的,真正的是yarn模式的。
./yarn-session.sh -n 2 -s 2 -jm 1024 -tm 1024 -nm test -d
实际的生产使用的yarn的。
看下这个包,默认是没有这个包的。
其中yarn的resiurceManager是独享的,flnk的resourceManager是共享的。
参数的意思:
-n是taskManager的数量
一个cpu在一个时间点是只能执行一个线程的。
关于yarn和k8s。
不细讲了。
---01---
正常情况下每一个job会被一个jobManager执行。
yarn-session的所有的job是共享一个resourceManager和jobManager的。
k8s的底层是docker。
箭头是找的yarn的。
---02---
这个图是yarn的job提交方式。
yarn的session模式。
keyBy是不能设置并行度的。
4.3任务调度。
算子和任务:
不同算子的任务是可以放在同一个slot里面的。
每一个算子是一个任务,每一个算子有一个并行度,每一个任务有子任务。
1.什么是任务,任务就是一步操作而已,具体的一步计算是可以设置并行度的,比如sum等。但是keyBy是不可以设置并行度的。
一段代码生成多少个任务?
什么样的任务可以合并到一起呢?
这个就合并在一起了
1.one to one
2.并行度相同的
我们回忆之前的操作:
这么看这里面的任务是远远超过6个的。
2.slot到底是什么,一段代码需要多少个slot执行。
一个taskManager相当于一个jvm的进程。slot相当于一个线程推荐一个核心。一个slot允许有不同的任务的子任务。
笔记总结:
TM -- process 进程
Task on slot -- thread 线程
每个线程执行在固定的计算资源上,这个资源就是slot;
slot之间内存是独享的,CPU不是独享的
所以slot数量最好配成 CPU 核心数
并行的概念:
数据并行 —— 同一个任务,不同的并行子任务,同时处理不同的数据
任务并行 —— 同一时间,不同的slot在执行不同的任务
允许slot共享,可以提高资源的利用率
一个流处理程序需要的slot数量,其实就是所有任务中最大的那个并行度
如果并行度相同、one-to-one数据传输,那么可以把多个算子合并成一个任务
TM的数量和slot数量,决定了并行处理的最大能力(静态)
但是不一定程序执行时一定都用到。程序执行时的并行度才是用到的能力(动态)。
------------------------------------------------------
核心问题:
1. 任务是什么?一段代码到底会生成多少任务?
代码中定义的每一步操作(算子,operator)就是一个任务
算子可以设置并行度,所以每一步操作可以有多个并行的子任务,子任务就是算子的多个并行。
Flink可以将前后执行的不同的任务合并起来
2. slot到底是什么,slot跟任务的关系?一段代码到底需要多少个slot来执行?
slot是TM拥有的计算资源的一个子集,一个任务必须在一个slot上执行
每一个算子的并行任务,必需执行在不同的slot上
如果是不同算子的任务,可以共享一个slot
一般情况下,一段代码执行需要的slot数量,就是并行度最大的算子的并行度
3. 并行度和slot数量的关系?
并行度和任务有关,就是每一个算子拥有的并行任务数量;动态概念
slot数量只跟TM的配置有关,代表TM并行处理数据的能力,静态概念
4. 什么样的任务能够合并在一起?
one-to-one操作,并行度相同
不同算子的子任务是可以放在同一个slot里面去跑的。
所以slot的数量去取决于并行度最大的算子,并行度就是slot的数量。
一个taskmanager可以认为是jvm的进程,一个slot是线程。
一个算子并行的子任务一定在不同的slot上执行。
------------------02-03--------------
不是多少个任务多少个slot就是算子的最大并行度个slot。
首先看下我们的能力,这个最大支持的并行度是9
代码>提交>集群配置文件
并行度是taskmanager*slot的值。
程序流图:
总的流程图:
拆开是按照并行度的拆开的。
一个很经典的图:
圆圈是算子,底下的角标是并行度。
16个任务。4个slot。
其中a和b是可以不跨分区的。
举例:
这个有5个任务,只要有2个slot就可以跑起来了。
跨slot跨RM是有开销的。
如果不是一对一就是reblance,否则就是keyBy。
---
ketBy得到的是:
这一步是不可以设置并行度的。
一台机器一般是一个taskManager,但是没有限制的。
---04---
快手flink
session当前是公用的RM。
---05---
流处理就这几步:
source:代码
我们在集合读取数据
在文件读取:
val stream1: DataStream[SensorReading] = env.fromCollection( List(
SensorReading("sensor_1", 1547718199, 35.8),
SensorReading("sensor_6", 1547718201, 15.4),
SensorReading("sensor_7", 1547718202, 6.7),
SensorReading("sensor_10", 1547718205, 38.1),
SensorReading("sensor_1", 1547718207, 37.2),
SensorReading("sensor_1", 1547718212, 33.5),
SensorReading("sensor_1", 1547718215, 38.1)
) )
scala的类:https://www.runoob.com/scala/scala-classes-objects.html
代码:
package com.atguigu.apitest
import org.apache.flink.streaming.api.scala._
case class SensorReadingMy(id: String, timestamp: Long, temperature: Double)
object SoucetestMy {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream1: DataStream[SensorReadingMy] = env.fromCollection(List(
SensorReadingMy("sensor_1", 1547718199, 35.8),
SensorReadingMy("sensor_6", 1547718201, 15.4),
SensorReadingMy("sensor_7", 1547718202, 6.7),
SensorReadingMy("sensor_10", 1547718205, 38.1),
SensorReadingMy("sensor_1", 1547718207, 37.2),
SensorReadingMy("sensor_1", 1547718212, 33.5),
SensorReadingMy("sensor_1", 1547718215, 38.1)
))
stream1.print("stream2")
env.execute("stream123")
}
}
print也是sink。
package com.atguigu.apitest
import org.apache.flink.streaming.api.scala._
case class SensorReadingMy(id: String, timestamp: Long, temperature: Double)
object SoucetestMy {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream1: DataStream[String] = env.readTextFile("D:\\codeMy\\CODY_MY_AFTER__KE\\sggBigData\\20-flink\\FlinkTutorial\\src\\main\\resources\\sensor.txt")
stream1.print("stream")
env.execute("stream")
}
}
val properties = new Properties()
properties.setProperty("bootstrap.servers", "192.168.244.133:9092")
//properties.setProperty("group.id", "ab")
properties.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
properties.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
properties.setProperty("auto.offset.reset", "latest")
val stream4 = env.addSource( new FlinkKafkaConsumer011[String]("testinfo", new SimpleStringSchema(), properties) )
011继承010,010继承009
在kafka中读取数据,需要也纳入kafka的包:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.11_2.11</artifactId>
<version>1.10.0</version>
</dependency>
我们需要启动下zk和kafka:https://blog.csdn.net/qq_28764557/article/details/109913185
我在kafka创建了一个topic:spark
我们是可以消费成功的:
我的代码:
---06---
同一个算子并行的子任务必须占据不同的slot。不同的算子是可以共享同一个slot的。
这里面直接是5个任务依次排开。
我们自己控制每一个任务放在不同的solt里面。
这个意思是在同一个共享组里面的才可以共享slot的,这个组的名字是1和2
不同组的必须放在不同的solt里面去的,同一个组是可以共享slot的。
没有指定的是在default里面。
指定的和后面的任务在自己指定的组里面。
这个是6个任务的,两个slot就够了。
一般是选取并行度最大的,其他的是slot选取并行度最大的。
按照分组之后呢?
source是1个slot 两个fm是两个 后面的可以共享slot 5个slot
---
这个是不合并算子链,就是不合并为一个大的任务。
只断开前面不断开后面,如果不断开的话会自己进行连接的。主要是用来断开任务的。
全局可配置。dis是断开前后的。
---07---
看一个代码:
实际做项目时候往往都是kafka的。
实现一个自定义的sourceFunction:可以自动生成测试数据。
代码:
package com.atguigu.apitest
import org.apache.flink.streaming.api.functions.source.SourceFunction
import org.apache.flink.streaming.api.scala._
import scala.util.Random
case class SensorReadingMy(id: String, timestamp: Long, temperature: Double)
object SoucetestMy {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val stream = env.addSource( new MySensorSourcMy() )
stream.print("stream")
env.execute("stream")
}
}
// 实现一个自定义的 SourceFunction,自动生成测试数据
class MySensorSourcMy() extends SourceFunction[SensorReading]{
// 定义一个flag,表示数据源是否正常运行
var running: Boolean = true
override def cancel(): Unit = running = false
// 随机生成 SensorReading数据
override def run(ctx: SourceFunction.SourceContext[SensorReading]): Unit = {
// 定义一个随机数发生器
val rand = new Random()
// 随机生成 10个传感器的温度值,并且不停在之前温度基础上更新(随机上下波动)
// 首先生成 10个传感器的初始温度
var curTemps = 1.to(10).map(
i => ("sensor_" + i, 60 + rand.nextGaussian() * 20)
)
// 无限循环,生成随机数据流
while(running){
// 在当前温度基础上,随机生成微小波动
curTemps = curTemps.map(
data => (data._1, data._2 + rand.nextGaussian())
)
// 获取当前系统时间
val curTs = System.currentTimeMillis()
// 包装成样例类,用ctx发出数据 ctx搜集起来就发送到flink里面去了
curTemps.foreach(
data => ctx.collect(SensorReading(data._1, curTs, data._2))
)
// 定义间隔时间
Thread.sleep(1000L)
}
}
}
---08---
map等操作是不需要keyBy的。
什么是转换算子,除了输入输出都是transform算子。
keyby是根据hashcode做的重分区的。
经过keyBy才会走聚合函数sum等。在keyBy之后可以应用的算子是滚动的聚合算子,只能在keyBy之后运用的。
keyby之后会就会形成keyedStream的。
reduce算子:https://blog.csdn.net/dinghua_xuexi/article/details/107766222
tranform:
1. keyBy
基于key的hash code重分区;
同一个key只能在一个分区内处理,一个分区内可以有不同的key的数据
keyBy之后的KeyedStream上的所有操作,针对的作用域都只是当前的key
2. 滚动聚合操作
DataStream没有聚合操作,目前所有的聚合操作都是针对KeyedStream
3. 多流转换算子
split-select, connect-comap/coflatmap 成对出现
先转换成 SplitStream,ConnectedStreams,然后再通过select/comap操作转换回来DataStream
所谓coMap,其实就是基于ConnectedStreams的map方法,里面传入的参数是CoMapFunction
4. 富函数
是函数类的增强版,可以有生命周期方法,还可以获取运行时上下文,在运行时上下文可以对state进行操作
flink有状态的流式计算,做状态编程,就是基于RichFunction的
代码:
scala不new就可以新建对象:https://blog.csdn.net/qq_36330643/article/details/75268850
这个代码十分重要的。
第一种传递的方法:注意这个指的是第一个位置的元素:
第二种传递方法:也可以传字段的名称:
第三种传递方法:还可以传一个函数的,T是DataStream的元素,K是输出就是键Key。
第四种传递方式:还可以是keySelector
// 自定义函数类,key选择器 泛型前面的类型是input的类型 后i面是返回的key的类型
class MyIDSelector() extends KeySelector[SensorReading, String]{
// 里面必须要实现一个getKey的方法
override def getKey(value: SensorReading): String = value.id
}
实现的代码:
// 自定义函数类,key选择器 泛型前面的类型是input的类型 后i面是返回的key的类型
class MyIDSelector() extends KeySelector[SensorReading, String]{
// 里面必须要实现一个getKey的方法
override def getKey(value: SensorReading): String = value.id
}
看下keyBy的类型,keyBy之后得到的类型就是KeyedStream。称作分区或者键控流。我们看到keyedStream是一个元组的类型的,其中T是输入的类型,key的类型是一个元组,因为flink不知道是什么类型的。
四步:
sum滚动聚合,可以给一个字段或者position。
注意keyBy后面针对的各种操作都是针对当前的分组是有效的。相当于groupBy key,flink不知道我们的key是什么类型的,所以只能包装为元组的类型。
min和minBy的区别。
min后面的各种操作都只针对的是当前的分组。时间戳是按照第一个输出的,时间戳是按照第一个时间戳输出的,所以是有些不一样的。
minBy是完全对应的。
---
reduce是在当前的基础上再次聚合,类型是相同的。
看下reduce,这是十分重要的:
我们的需求,最大的timeStemap和最小的温度值。
注意类型不能变的。
聚合之后在开始的基础上再次聚合。
自定义:reduce
有一个需求,其中time取最大值,温度取最小值。
自己写reduceFunction:
获得侧输出流。
---09---
split分流:是把一个流在逻辑上分成两个流,只是相当于盖个戳而已。
// 3. 分流
val splitStream: SplitStream[SensorReading] = dataStream
.split( data => {
if( data.temperature > 30 )
Seq("high")
else
Seq("low")
} )
val highTempStream: DataStream[SensorReading] = splitStream.select("high")
val lowTempStream: DataStream[SensorReading] = splitStream.select("low")
val allTempStream: DataStream[SensorReading] = splitStream.select("high", "low")
env.execute("transform test job")
合流操作,合流只是形式上合在了一起,其实底层还是各自是分开的。
package com.atguigu.apitest
import org.apache.flink.streaming.api.scala.{DataStream, SplitStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.scala._
object split {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val inputStreamFromFile: DataStream[String] = env.readTextFile("D:\\codeMy\\CODY_MY_AFTER__KE\\sggBigData\\20-flink\\FlinkTutorial\\src\\main\\resources\\sensor.txt")
val dataStream: DataStream[SensorReading] = inputStreamFromFile
.map( data => {
// 首先是,切分
val dataArray = data.split(",")
// 相当于直接new的对象
SensorReading( dataArray(0), dataArray(1).toLong, dataArray(2).toDouble )
} )
val splitStream: SplitStream[SensorReading] = dataStream// 加上标签变为splitStream。
.split( data => {
if( data.temperature > 30 )
Seq("high")
else
Seq("low")
} )
val highTempStream: DataStream[SensorReading] = splitStream.select("high")
val lowTempStream: DataStream[SensorReading] = splitStream.select("low")
val allTempStream: DataStream[SensorReading] = splitStream.select("high", "low")
val warningStream: DataStream[(String, Double)] = highTempStream.map(
data => (data.id, data.temperature)
)
val connectedStreams: ConnectedStreams[(String, Double), SensorReading] = warningStream
.connect(highTempStream)
val resultStream: DataStream[Object] = connectedStreams.map(// 一国两制的
warningData => (warningData._1, warningData._2, "high temp warning"),
lowTempData => (lowTempData.id, "normal")
)
resultStream.print("result")
env.execute("transform test job")
}
}
// 4. 合流
val warningStream: DataStream[(String, Double)] = highTempStream.map(
data => (data.id, data.temperature)
// new MyMapper
)
// 泛型是二元组+Double
val connectedStreams: ConnectedStreams[(String, Double), SensorReading] = warningStream
.connect(lowTempStream)
val resultStream: DataStream[Object] = connectedStreams.map(
warningData => (warningData._1, warningData._2, "high temp warning"),
lowTempData => (lowTempData.id, "normal")
)
map和flatMap的区别:https://www.cnblogs.com/yucy/p/10260014.html
多流的应用场景,就是我们要联合两条流的数据进行分析的,主要是会有这样的场景,就是假如我们监控森林的火警,但是,需要温度的流是不够的还需要我们有湿度的流。
---
union:
union的两条流的类型必须是一摸一样的。union可以连接多条流,但是connect的话就只能是两条流。
val unionStream: DataStream[SensorReading] = highTempStream.union(lowTempStream, allTempStream)
总结:
这个图是十分主要的。
---10---
UDF函数。
flink支持的数据类型:不细说了
自定义一个MapFunction
// 自定义MapFunction
class MyMapper extends MapFunction[SensorReading, (String, Double)]{
override def map(value: SensorReading): (String, Double) = (value.id, value.temperature)
}
什么都有什么Function的。
富函数:上下文就是当前的线程的上下文
什么都有什么的Function。
富函数。
class MyRichMapper extends RichMapFunction[SensorReading, Int]{
// 创建富函数调用的初始化的方法
override def open(parameters: Configuration): Unit = {}
// 获取运行时上下文 获取分区子任务的编号
getRuntimeContext.getIndexOfThisSubtask
override def map(value: SensorReading): Int = value.timestamp.toInt
// 函数关闭的调用方法
override def close(): Unit = {}
}
---11---
1. keyBy
基于key的hash code重分区;
同一个key只能在一个分区内处理,一个分区内可以有不同的key的数据
keyBy之后的KeyedStream上的所有操作,针对的作用域都只是当前的key
2. 滚动聚合操作
DataStream没有聚合操作,目前所有的聚合操作都是针对KeyedStream
3. 多流转换算子
split-select, connect-comap/coflatmap 成对出现
先转换成 SplitStream,ConnectedStreams,然后再通过select/comap操作转换回来DataStream
所谓coMap,其实就是基于ConnectedStreams的map方法,里面传入的参数是CoMapFunction
4. 富函数
是函数类的增强版,可以有生命周期方法,还可以获取运行时上下文,在运行时上下文可以对state进行操作
flink有状态的流式计算,做状态编程,就是基于RichFunction的
---12---