flink小记
Flink核心概念
1、Flink是为分布式、高性能、随时可用以及准确的流处理应用程序打造的开源流处理框架
2、Flink底层是以Java编写的,并为开发人员同时提供了完整的Java和Scala API。
3、Flink运行时包括JobManager(Master)和TaskManager(Worker),JobManager用于协调分布式执行,分发任务给众多TaskManager,而TaskManager用于执行任务,是真正“干活的人”,通常有多个TaskManager。
4、Yarn部署:客户端把Flink应用提交给ResourceManager,RM向NodeManager申请容器。在这些容器上,Flink会部署JobManager和TaskManager的实例,从而启动集群。(Yarn几个组件的介绍在Spark一文中简单阐述)
TaskManager
每一个TaskManager是一个JVM进程,它可以启动多个独立的线程来并行执行多个子任务subtask。
每个TaskManager包含一个或多个task slots,slot是资源调度的最小单位,每个task slot表示TaskManager拥有资源的一个固定大小的子集,限制了TashManager并行处理的任务数量。
假如一个TaskManager 有三个 slot,那么它会将其管理的内存分成三份给各个 slot。这里不会涉及到CPU的隔离,slot目前仅仅用来隔离task的受管理的内存
一个 TaskManager多个slot意味着更多的subtask可以共享同一个JVM。而在同一个JVM进程中的task将共享TCP连接(基于多路复用)和心跳消息。它们也可能共享数据集和数据结构,因此这减少了每个task的负载。
Task Slot是静态的概念,是指TaskManager具有的并发执行能力,可以通过taskmanager.numberOfTaskSlots配置。并行度parallelism是动态概念,即TaskManager运行程序时实际使用的并发能力,可以通过parallelism.default配置
DataFlow
Flink程序由Source、Transformation、Sink三个核心组件组成,Source负责数据读取,Transformation负责数据转换,Sink负责数据输出,在各个组件之间流转的数据称为流streams
一个stream可以看成一个中间结果,一个transformations是以一个或多个stream作为输入的operation,该operation利用这些stream进行计算产生一个或多个result stream
flink运行程序会被映射成streaming dataflows,每个dataflow以一个或多个sources开始,以一个或多个sinks结束,dataflow类似于任意的DAG。
算子并行度
一个stream包含多个stream partition,而一个operator(算子)包含多个算子substask,这些算子subtask在不同的线程,不同的物理机或不同的容器中完全独立地执行
一个特定算子的subtask个数称之为parallelism(并行度),不同算子可能具有不同并行度。
任务链
stream在算子间的传输可以是forward或redistributing模式,forward意味着上下游算子维持着分区以及元素顺序,map/filter/flatMap等算子都是forard。redistributing会改变分区个数,类似spark的shuffle,每一个subtask发送数据到不同的subtask,比如keyBy重分区,rebalance随机重分区。
将相同并行度的forward传输的算子链接在一起形成一个task,原来的算子成为里面的subtask。这样的算子链技术可以减小本地通信的开销。
// 全局禁用算子链
env.disableOperatorChaining()
// 禁用算子链
.map().disableChaining()
// 从当前算子开始新链
.map().startNewChain()
Flink DataStream API
Flink程序架构
1、获得执行环境(Execution Environment)
2、加载数据(Source)
3、转换数据(Transformation)
4、输出数据(Sink)
5、触发程序执行,env.execute("")
获得执行环境
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
val env = StreamExecutionEnvironment.getExecutionEnvironment
Source
// checkpoint配置
env.enableCheckpointing(300000)
env.getCheckpointConfig.setCheckpointInterval(300000)
env.getCheckpointConfig.setCheckpointTimeout(300000)
// 基于File
env.readTextFile
env.readFile
// 基于Socket,开启后可以在终端通过nc -l 9995监听端口后,敲数据往端口导数
env.socketTextStream("localhost",9995)
// 基于集合
env.fromCollection(List(1,2,3))
env.fromElement(1,2,3)
// 基于kafka等消息中间件
import org.apache.flink.api.common.serialization.AbstractDeserializationSchema
import java.io.IOException
import java.util.Properties
class ByteArrayDeserializationSchema[T] extends AbstractDeserializationSchema[Array[Byte]] {
@throws[IOException]
override def deserialize(message: Array[Byte]): Array[Byte] = message
}
val properties = new Properties()
properties.setProperty("bootstrap.servers", "域名1:9092,域名2:9092,域名3:9092")
properties.setProperty("group.id", "订阅者group_id")
properties.put("enable.auto.commit", "true")
properties.put("auto.commit.interval.ms", "60000")
val kafkaConsumer = new FlinkKafkaConsumer("topic名称", new ByteArrayDeserializationSchema(), properties)
val stream = env.addSource(kafkaConsumer).rebalance
// pulsar消息中间件
val prop = new Properties()
prop.put("topic", "topic名称")
prop.put("pulsar.reader.subscriptionRolePrefix", "订阅者subscription")
prop.setProperty("auth-plugin-classname", "org.apache.pulsar.client.impl.auth.AuthenticationToken")
prop.setProperty("auth-params", "订阅者token")
// 需自定义pulsar topic反序列化的schema
val input_source = new FlinkPulsarSource(serviceUrl, adminUrl, new PulsarRecordSerializeSchema(), prop)
input_source.setStartFromSubscription("订阅者subscription", MessageId.latest) // 去掉第二个参数,则从之前消费的地方消费
val stream = env.addSource(input_source).rebalance
Transformation
// map
stream.map(t=>(t._1, "blabla"))
stream.map(new MyMap("也可以在这里传参数给map用"))
// 指定两个泛型,输入数据类型和输出数据类型
class MyMap(remark:String) extends RichMapFunction[(Long, String, Boolean), (Long, String, Boolean, HashMap[String, AnyRef])] {
override def open(parameters: Configuration): Unit = {
super.open(parameters)
// consul客户端获取redis缓存的ip端口
// 构建redis客户端
// 其它服务的一次性连接建立
}
override def close(): Unit = {
super.close()
// 关闭连接
}
override def map(t:(Long, String, Boolean)): (Long, String, Boolean, HashMap[String, AnyRef]) = {
// 各种转换逻辑,比如获取redis(特征),数据运算,请求其它服务等
// 返回
( )
}
}
// filter
stream.filter(t=>t._4)
// 与自定义RichMap类型类似,只是重写map改成重写filter,返回值为Boolean
stream.filter(new MyFilter())
// flatMap
stream.map(new MyFlatMap())
class MyFlatMap extends FlatMapFunction[String, (Long, String, Boolean)] {
override def flatMap(t: String, out: Collector[(Long, String, Boolean)]): Unit = {
// 解析json入参
val jsonVal = JSON.parseObject(t)
jsonVal.getLong("")
jsonVal.getString("")
val arr = jsonVal.getJSONArray("")
for(i <- 0 until arr.size()) {
val r1 = arr.getJSONObject(i).getLong("")
val r2 = arr.getJSONObject(i).getString("")
val r3 = arr.getJSONObject(i).getBoolean("")
out.collect((r1, r2, r3))
}
}
}
// keyBy将相同key的数据分到一个分区里
stream.keyBy(t=>t._1)
// keyBy后可以做一些简单聚合:sum, min, max等,也可以做reduce归约聚合
// 多流转换:union、connect、join等
数据重分布
shuffle:随机打乱,均匀传递到下游分区
rebalance:轮询round-robin,每个上游单分区将输入平均分配到下游所有分区
rescale:类似轮询,但上游单分区只将数据发到下游的一部分分区
broadcast:广播,下游每个分区都保留一份
global:所有输入发送到下游算子第一个并行子任务
Custom:自定义分区
stream.partitionCustom(new Partitioner[String](){
override def partition(String key, Int numPartitions) {
return key.hasCode() % numPartitions
}, 0 // 使用第一个字段作为分区字段
})
Sink
Flink中所有对外的输出操作,一般都是利用Sink算子完成的。
一般情况下Sink算子的创建是通过调用DataStream的addSink方法实现的。
Flink官方提供了一部分Sink连接器,比如KafKa,文件系统,MySQL-JDBC等.
// 自定义Sink
stream.addSink(new MySinkFunction())
class MySinkFunction extends RichSinkFunction[(String,Long,Boolean)] {
override def open(parameters: Configuration): Unit = {
super.open(parameters)
// 比如缓存服务的连接建立
}
override def close(): Unit = {
super.close()
// 比如缓存服务的关闭
}
override def invoke(value: (String,Long,Boolean), context: SinkFunction.Context): Unit = {
// 比如数据写入缓存
}
}
// 写入KafKa
stream.addSink(new FlinkKafkaProducer[String](
"集群域名1:9092, 集群域名2:9092",
"topic名字",
new SimpleStringSchema()
))
checkpoint和savepoint
1、checkpoint是周期性保存应用程序状态的机制,目的是发生故障时恢复,不影响作业逻辑的准确性。而savepoint侧重点是维护,需要更新程序代码或版本升级时不丢失状态的场景下是更合适的选择。
2、checkpoint触发频率较高,存储格式轻量,作为trade-off牺牲了可移植性的东西,比如不保证并行度和升级的兼容性。而savepoint以二进制形式存储所有状态数据和元数据,执行起来比较慢且成本更高,但能保证兼容性,比如并行度改变或代码升级后仍能正常恢复。
3、checkpoint配置后由flink内部触发和清理,不需要用户干预。savepoint需要用户显式触发。
4、savepoint是通过checkpoing机制创建的,所以saveppint本质上是特殊的checkpoint(拥有额外元数据的checkpoing)