一、standalone模式
1、上传flink到cdh01节点,并解压
tar -zxvf flink-1.10.0-bin-scala_2.11.tgz -C /opt/module
2、修改配置文件
cd /opt/module/flink-1.10.0/conf/
vim flink-conf.yaml
修改一下下面的几个参数,我这内存比较多,直接加了个0:
修改slaves文件:
cdh01
cdh02
cdh03
修改masters文件:
cdh01:8081
3、分发到其它两台节点
scp -r flink-1.10.0/ root@cdh02:`pwd`
scp -r flink-1.10.0/ root@cdh03:`pwd`
4、启动:
[root@cdh01 bin] ./start-cluster.sh
在cdh01 jps
,有两个进程
86721 TaskManagerRunner
86368 StandaloneSessionClusterEntrypoint
5、访问webui:
http://192.168.66.11:8081/#/overview
6、使用webui提交程序
先打包:
点击add New:
上传之后的效果:
在cdh01启动netcat:
nc -lk 7777
双击jar包名称并且填写主类:
点击提交任务之后的效果:
上面的图有两条任务链
输入数据流:
那么在哪里看 结果呢?
点击最后一个任务链:
可知它在cdh01,我的ip是11结尾:
点进去即可找到结果输出:
二、flink并行度和slot
- slot是静态的概念,是指taskManager具有的并发执行能力
- parallelism是动态的概念,是指应用 程序实际使用的并发能力
建议slot的数量和CPU的核数保持一致
一个slot就是一个线程
并行度可以在四个层面进行设置:
- 算子层面
- 执行环境层面
- 客户端层面
- 系统层面
二、yarn模式
底层 ProcessFunctionAPI
ProcessFunction 是一个低层次的流处理操作,允许返回所有 Stream 的基础构建模块:
- 访问 Event 本身数据(比如:Event 的时间,Event 的当前 Key 等)
- 管理状态 State(仅在 Keyed Stream 中)
- 管理定时器 Timer(包括:注册定时器,删除定时器等)
总而言之,ProcessFunction 是 Flink 最底层的 API,也是功能最强大的。
例如:监控每一个手机,如果在 5 秒内呼叫它的通话都是失败的,发出警告信息。注意:
这个案例中会用到状态编程,请同学们只要知道状态的意思,不需要掌握。后面的章节中会
详细讲解 State 编程。
package com.learning.flink.transformation
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.util.Collector
/**
* 监控每一个手机号,如果在5秒内呼叫它的通话都是失败的,发出警告信息
* 在5秒中内只要有一个呼叫不是fail则不用警告
*/
object ProcessFunctionAPI {
def main(args: Array[String]): Unit = {
//初始化Flink的Streaming(流计算)上下文执行环境
val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
streamEnv.setParallelism(1)
//导入隐式转换,建议写在这里,可以防止IDEA代码提示出错的问题
import org.apache.flink.streaming.api.scala._
//读取文件数据
val data = streamEnv.socketTextStream("hadoop101", 8888)
.map(line => {
var arr = line.split(",")
new StationLog(arr(0).trim,arr(1).trim,arr(2).trim,arr(3).trim,arr(4).trim.toLong,arr(5).trim.to Long)
})
55
//处理数据
data.keyBy(_.callOut)
.process(new MonitorCallFail())
.print()
streamEnv.execute()
}
//监控逻辑
class MonitorCallFail() extends KeyedProcessFunction[String, StationLog, String] {
//使用一个状态记录时间
lazy val timeState: ValueState[Long] = getRuntimeContext.getState(new ValueStateDescriptor[Long]("time", classOf[Long]))
override def processElement(value: StationLog, ctx: KeyedProcessFunction[String,
StationLog, String]#Context, out: Collector[String]): Unit = {
//从状态中取得时间
var time = timeState.value()
if (value.callType.equals("fail") && time == 0) { //表示第一次发现呼叫当前手机号是失败的
//获取当前时间,并注册定时器
var nowTime = ctx.timerService().currentProcessingTime()
var onTime = nowTime + 5000L //5秒后触发
ctx.timerService().registerProcessingTimeTimer(onTime)
timeState.update(onTime)
}
if (!value.callType.equals("fail") && time != 0) {
//表示有呼叫成功了,可以取消触发器
ctx.timerService().deleteProcessingTimeTimer(time)
timeState.clear()
}
}
//时间到了,执行触发器,发出告警
override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[String, StationLog, String]#OnTimerContext, out: Collector[String]): Unit = {
var warnStr = "触发时间:" + timestamp + " 手机号:" + ctx.getCurrentKey
out.collect(warnStr)
timeState.clear()
}
}
}
侧输出流 Side Output
在 flink 处理数据流时,我们经常会遇到这样的情况:在处理一个数据源时,往往需要
将该源中的不同类型的数据做分割处理,如果使用 filter 算子对数据源进行筛选分割的
话,势必会造成数据流的多次复制,造成不必要的性能浪费;flink 中的侧输出就是将数据
流进行分割,而不对流进行复制的一种分流机制。flink 的侧输出的另一个作用就是对延时
迟到的数据进行处理,这样就可以不必丢弃迟到的数据。在后面的章节中会讲到!
案例:根据基站的日志,请把呼叫成功的 Stream(主流)和不成功的 Stream(侧流)
分别输出。
package com.learning.flink.transformation
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.util.Collector
/**
* 把呼叫成功的Stream(主流)和不成功的Stream(侧流)分别输出。
*/
object SideOutputStreamTest {
//导入隐式转换,建议写在这里,可以防止IDEA代码提示出错的问题
import org.apache.flink.streaming.api.scala._
//侧输出流首先需要定义一个流的标签
var notSuccessTag= new OutputTag[StationLog]("not_success")
def main(args: Array[String]): Unit = {
//初始化Flink的Streaming(流计算)上下文执行环境
val streamEnv: StreamExecutionEnvironment =
StreamExecutionEnvironment.getExecutionEnvironment
streamEnv.setParallelism(1)
//读取文件数据
val data = streamEnv.readTextFile(getClass.getResource("/station.log").getPath)
.map(line=>{
var arr =line.split(",")
new StationLog(arr(0).trim,arr(1).trim,arr(2).trim,arr(3).trim,arr(4).trim.toLong,arr(5).trim.to Long)
})
val mainStream: DataStream[StationLog] = data.process(new CreateSideOutputStream(notSuccessTag))
//得到侧流
val sideOutput: DataStream[StationLog] = mainStream.getSideOutput(notSuccessTag)
mainStream.print("main")
sideOutput.print("sideoutput")
streamEnv.execute()
}
class CreateSideOutputStream(tag: OutputTag[StationLog]) extends ProcessFunction[StationLog,StationLog]{
override def processElement(value: StationLog, ctx: ProcessFunction[StationLog, StationLog]#Context, out: Collector[StationLog]): Unit = {
if(value.callType.equals("success")){//输出主流
out.collect(value)
}else{//输出侧流
ctx.output(tag,value)
}
}
}
}
Flink State 管理与恢复
Flink 是一个默认就有状态的分析引擎,前面的 WordCount 案例可以做到单词的数量的
累加,其实是因为在内存中保证了每个单词的出现的次数,这些数据其实就是状态数据。但
是如果一个 Task 在处理过程中挂掉了,那么它在内存中的状态都会丢失,所有的数据都需
要重新计算。从容错和消息处理的语义(At -least-once 和 Exactly-once)上来说,Flink
引入了 State 和 CheckPoint。
State 一般指一个具体的 Task/Operator 的状态,State 数据默认保存在 Java 的堆内存
58
中。
CheckPoint(可以理解为 CheckPoint 是把 State 数据持久化存储了)则表示了一个
Flink Job 在一个特定时刻的一份全局状态快照,即包含了所有 Task/Operator 的状态。
- 常用 State
Flink 有两种常见的 State 类型,分别是:
keyed State(键控状态)
Operator State(算子状态)
- Keyed State (键控状态)
Keyed State:顾名思义就是基于 KeyedStream 上的状态,这个状态是跟特定的 Key 绑
定的。KeyedStream 流上的每一个 Key,都对应一个 State。Flink 针对 Keyed State 提供了
以下可以保存 State 的数据结构:
ValueState: 保存一个可以更新和检索的值(如上所述,每个值都对应到当前的输
入数据的 key,因此算子接收到的每个 key 都可能对应一个值)。 这个值可以通过
update(T) 进行更新,通过 T value() 进行检索。
ListState: 保存一个元素的列表。可以往这个列表中追加数据,并在当前的列表上
进行检索。可以通过 add(T) 或者 addAll(List) 进行添加元素,通过 Iterable
get() 获得整个列表。还可以通过 update(List) 覆盖当前的列表。
ReducingState: 保存一个单值,表示添加到状态的所有值的聚合。接口与
ListState 类似,但使用 add(T) 增加元素,会使用提供的 ReduceFunction 进行聚合。
AggregatingState<IN, OUT>: 保留一个单值,表示添加到状态的所有值的聚合。和
ReducingState 相反的是, 聚合类型可能与 添加到状态的元素的类型不同。 接口与
ListState 类似,但使用 add(IN) 添加的元素会用指定的 AggregateFunction 进行聚
合。
FoldingState<T, ACC>: 保留一个单值,表示添加到状态的所有值的聚合。 与
ReducingState 相反,聚合类型可能与添加到状态的元素类型不同。接口与 ListState
类似,但使用 add(T)添加的元素会用指定的 FoldFunction 折叠成聚合值。
, MapState<UK, UV>: 维护了一个映射列表。 你可以添加键值对到状态中,也可以获得
反映当前所有映射的迭代器。使用 put(UK,UV) 或者 putAll(Map<UK,UV>) 添加映射。
使用 get(UK) 检索特定 key。 使用 entries(),keys() 和 values() 分别检索映射、
键和值的可迭代视图。 - Operator State (算子状态)
Operator State 与 Key 无关,而是与 Operator 绑定,整个 Operator 只对应一个 State。
比如:Flink 中的 Kafka Connector 就使用了 Operator State,它会在每个 Connector 实例
中,保存该实例消费 Topic 的所有(partition, offset)映射。
/**
* 统计每个手机的呼叫间隔时间,并输出
*/
object StateCallInterval {
def main(args: Array[String]): Unit = {
//初始化Flink的Streaming(流计算)上下文执行环境
val streamEnv: StreamExecutionEnvironment =
StreamExecutionEnvironment.getExecutionEnvironment
streamEnv.setParallelism(1)
//导入隐式转换,建议写在这里,可以防止IDEA代码提示出错的问题
import org.apache.flink.streaming.api.scala._
//读取文件数据
val data = streamEnv.readTextFile(getClass.getResource("/station.log").getPath)
.map(line=>{
var arr =line.split(",")
new
StationLog(arr(0).trim,arr(1).trim,arr(2).trim,arr(3).trim,arr(4).trim.toLong,arr(5).trim.to
Long)
})
60
data.keyBy(_.callIn) //按照呼叫手机号分组
.flatMap(new CallIntervalFunction())
.print()
streamEnv.execute()
}
class CallIntervalFunction() extends
RichFlatMapFunction[StationLog,(String,Long)]{
//定义一个保存前一条呼叫的数据的状态对象
private var preData:ValueState[StationLog] =_
override def open(parameters: Configuration): Unit = {
val stateDescriptor =new
ValueStateDescriptor[StationLog]("pre",classOf[StationLog])
preData =getRuntimeContext.getState(stateDescriptor)
}
override def flatMap(in: StationLog, collector: Collector[(String, Long)]): Unit = {
var pre: StationLog = preData.value()
if(pre==null){ //如果状态中没有,则存入
preData.update(in)
}else{ //如果状态中有值则计算时间间隔
var interval =in.callTime - pre.callTime
collector.collect((in.callIn,interval))
}
}
}
}