Flink学习笔记(一)

什么是Flink

Apache Flink是由Apache软件基金会开发的开源流处理框架,其核心是用Java和Scala编写的分布式流数据流引擎

Flink可以做什么

Flink以数据并行和流水线方式执行任意流数据程序,Flink的流水线运行时系统可以执行批处理和流处理程序。此外,Flink的运行时本身也支持迭代算法的执行。

  • 实时推荐系统
  • 实时报表
  • 实时数仓与ETL
  • 实时欺诈与实时信用评估
  • 大数据安全监测
  • 等等

目前,阿里巴巴、腾讯、美团、华为、滴滴出行、携程、饿了么、爱奇艺、有赞、唯品会等大厂都已经将 Flink 实践于公司大型项目中
和其他所有的计算框架一样,flink也有一些基础的开发步骤以及基础,核心的API,从开发步骤的角度来讲,主要分为四大部分

  1. Environment(环境)
  2. Source(输入)
  3. Transform(转换)
  4. Sink(输出)

Environment

要了解一个系统,一般都是从架构开始。我们关心的问题是:系统部署成功后各个节点都启动了哪些服务,各个服务之间又是怎么交互和协调的。下方是 Flink 集群启动后架构图。
在这里插入图片描述
当 Flink 集群启动后,首先会启动一个 JobManger 和一个或多个的 TaskManager。由 Client 提交任务给 JobManager,JobManager 再调度任务到各个 TaskManager 去执行,然后 TaskManager 将心跳和统计信息汇报给 JobManager。TaskManager 之间以流的形式进行数据的传输。上述三者均为独立的 JVM 进程。

  • Client 为提交 Job 的客户端,可以是运行在任何机器上(与 JobManager 环境连通即可)。提交 Job 后,Client可以结束进程(Streaming的任务),也可以不结束并等待结果返回。
  • JobManager 主要负责调度 Job 并协调 Task 做 checkpoint,职责上很像 Storm 的 Nimbus。从
    Client 处接收到 Job 和 JAR 包等资源后,会生成优化后的执行计划,并以 Task 的单元调度到各个 TaskManager去执行。
  • TaskManager 在启动的时候就设置好了槽位数(Slot),每个 slot 能启动一个 Task,Task 为线程。从JobManager 处接收需要部署的 Task,部署启动后,与自己的上游建立 Netty 连接,接收数据并处理。

Flink架构的详细解读可以点击这里
在编写代码时,flink获取执行环境非常简单

// 批处理环境
val env = ExecutionEnvironment.getExecutionEnvironment
// 流式数据处理环境
val env = StreamExecutionEnvironment.getExecutionEnvironment

Source

flink本身封装好的有许多开箱即用的source方法,如:

  • readTextFile
  • socketTextStream
  • readFile
  • fromCollection

还有一些是集成flink的连接器,如kafka,MySQL等等,这些只需要导入连接器的依赖,按照需要填好参数调用addSource即可,也十分方便

val properties = new Properties()
    properties.setProperty("bootstrap.servers", "hadoop02:9092")
    properties.setProperty("group.id", "consumer-group")
    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 kafkaDS: DataStream[String] = env.addSource(
      new FlinkKafkaConsumer011[String](
        "sensor",
        new SimpleStringSchema(),
        properties)
    )

这些都是flink自身或者别人封装好的source,我们拿过来用就可以了,如果有些时候没有这些封装好的source,我没有可以自己写一个source类继承 SourceFunction,然后使用使用addSource加入我们自定义的source即可。

class MySensorSource() extends SourceFunction[SensorReading]{
  var running:Boolean = true

  override def run(sourceContext: SourceFunction.SourceContext[SensorReading]): Unit = {
    val cur = 1.to(10).map(i=>("sensor_"+i , System.currentTimeMillis() , 36.5+Random.nextGaussian()))
    while (running){
      cur.foreach(t=>sourceContext.collect(SensorReading(t._1,t._2,t._3)))
      Thread.sleep(500)
    }
  }

  override def cancel(): Unit = running = false
}

Transform

在Spark中,算子分为转换算子和行动算子,转换算子的作用可以通过算子方法的调用将一个RDD转换另外一个RDD,Flink中也存在同样的操作,可以将一个数据流转换为其他的数据流。
flink提供了许多封装好的方法,可以直接调用,比如:map,reduce,keyby,filter,select,union,comap,flatmap等等。如果这些方法满足不了我们处理的逻辑,我们可以自定义udf函数。Flink 暴露了所有 udf 函数的接口(实现方式为接口或者抽象类)。例MapFunction,FilterFunction,ProcessFunction 等等。

class FilterFilter extends FilterFunction[String] {
	override def filter(value: String): Boolean = {
		value.contains("flink")
	}
}
val flinkTweets = tweets.filter(new FlinkFilter)

还可以将函数实现成匿名类

val flinkTweets = tweets.filter(
	new RichFilterFunction[String] {
		override def filter(value: String): Boolean = {
		value.contains("flink")
		}
	})

我们 filter 的字符串"flink"还可以当作参数传进去。

val tweets: DataStream[String] = ...
val flinkTweets = tweets.filter(new KeywordFilter("flink"))
class KeywordFilter(keyWord: String) extends FilterFunction[String] {
	override def filter(value: String): Boolean = {
		value.contains(keyWord)
	}
}

“富函数”是 DataStream API 提供的一个函数类的接口,所有 Flink 函数类都
有其 Rich 版本。它与常规函数的不同在于,可以获取运行环境的上下文,并拥有一
些生命周期方法,所以可以实现更复杂的功能。
Rich Function 有一个生命周期的概念。典型的生命周期方法有:

  • open()方法是 rich function 的初始化方法,当一个算子例如 map 或者 filter 被调用之前
    open()会被调用。
  • close()方法是生命周期中的最后一个调用的方法,做一些清理工作。
  • getRuntimeContext()方法提供了函数的 RuntimeContext 的一些信息,例如函 数执行的并行度,任务的名字,以及 state 状态
class MyFlatMap extends RichFlatMapFunction[Int, (Int, Int)] {
	var subTaskIndex = 0
	override def open(configuration: Configuration): Unit = {
		subTaskIndex = getRuntimeContext.getIndexOfThisSubtask
// 以下可以做一些初始化工作,例如建立一个和 HDFS 的连接
	}
	override def flatMap(in: Int, out: Collector[(Int, Int)]): Unit = {
		if (in % 2 == subTaskIndex) {
			out.collect((subTaskIndex, in))
		}
	}
}

Sink

Flink 没有类似于 spark 中 foreach 方法,让用户进行迭代的操作。虽有对外的输出操作都要利用 Sink 完成。最后通过类似如下方式完成整个任务最终输出操作。
Sink和Source一样,除了flink自带的一些方法还有一些集成的连接器(如kafka,elastic search,Redis),如果我们要输出的格式没有封装好的sink,我们就需要自定义了。

class MyJdbcSink() extends RichSinkFunction[SensorReading]{
  var conn: Connection = _
  var insertStmt: PreparedStatement = _
  var updateStmt: PreparedStatement = _
  override def open(parameters: Configuration): Unit = {
    conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/work", "root", "root")
    insertStmt = conn.prepareStatement("INSERT INTO flink_sensor (id, sensor, time) VALUES (?, ?, ?)")
    updateStmt = conn.prepareStatement("UPDATE flink_sensor SET sensor = ?,time = ? WHERE id = ?")
  }
  override def invoke(value: SensorReading, context: SinkFunction.Context[_]): Unit = {
    updateStmt.setDouble(1,value.sensor)
    updateStmt.setString(2,value.time.toString)
    updateStmt.setString(3,value.id)
    updateStmt.execute()
    if(updateStmt.getUpdateCount == 0){
      insertStmt.setString(1,value.id)
      insertStmt.setDouble(2,value.sensor)
      insertStmt.setString(3,value.time.toString)
      insertStmt.execute()
    }
  }
  override def close(): Unit = {
    insertStmt.close()
    updateStmt.close()
    conn.close()
  }
}

下一篇是flink的一些高级操作,比如开窗,定时,状态恢复,Wartermark等等。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值