一、Environmen
1.Flink中你可以使用StreamExecutionEnvironment.getExecutionEnvironment创建l流式程序的运行环境。
例:val env:ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
2.如果没有设置并行度,会以Flink-conf.yaml文件的配置为准,默认为1。
3.createLocalEnvironment,返回本地执行环境,需要在调用时指定并行度。
4.createRemoteEnvirometn,返回集群的运行环境,将Jar提交到远程服务器。需要在调用时指定JobManager的IP和端口号,并制定在集群中运行的Jar包。
二、DataSource
1.使用 StreamExecutionEnvironment.addSource来为你的程序添加数据源
,Flink提供好了若干实现好了的source Functions,可以通过实现SourceFunction来自定义非并行的Source或者实现parallelSourceFunction接口或者扩展RichParallelourceFunction来自定义并行的Source。
2.Flinlk在流处理上的source和批处理上的source基本一致,大致有4大类
a.基于本地集合的source(Collecion-based-source)
b.基于本地文件的source(File-based-source)-读取本地文件,即符合TextInputFormat规范的文件,并将其字符串返回
c.基于网络套接字source(Socket-based-source)-从socket读取,元素可以用分隔符切分。
d.自定义source(Custom-source)
Source容错性保证
3.source的案例
a.基于集合的source
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.api.scala._
object CollectionSource {
def main(args: Array[String]): Unit = {
// 1. 创建流处理环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 设置并行度,默认和CPU的核数相同
env.setParallelism(1)
// 2. 指定数据源,加载本地集合
val listDataStream: DataStream[Int] = env.fromElements(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 3. 打印数据
listDataStream.print()
// 4. 执行任务,在批处理时,print方法是可以触发任务的,但是在流环境下,必须手动执行任务
env.execute()
}
}
b.基于文件的Source
package com.itheima.stream.source
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
object CsvSource {
def main(args: Array[String]): Unit = {
// 1. 获取流处理运行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 2. 读取文件
val textDataStream: DataStream[String] = env.readTextFile("hdfs://node01:8020/flink/datas/score.csv")
// 3. 打印数据
textDataStream.print()
// 4. 执行程序
env.execute()
}
}
c.基于网络套接字的source
上面两种方式都是比较固定的,如果需要源源不断的产生数据,可以使用socket方式来获取数据,通过调用socketStream()方法
示例:编写Flink程序接收socket的单词数据,并以空格进行单词拆分打印
步骤:
1.获取流处理运行环境
2.构建socket流数据源,并指定端口和IP
3.对接收到的数据以空格进行拆分
4.打印输出
5.启动执行
6.在Linux中,使用nc -lk 端口号 监听端口,并发送单词
mport org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.api.scala._
object SocketSource {
def main(args: Array[String]): Unit = {
//1. 创建流式环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 2. 构建socket数据源
val socketDataStream: DataStream[String] = env.socketTextStream("node01", 9999)
// 3. 数据转换,空格切分
val mapDataStream: DataStream[String] = socketDataStream.flatMap(_.split(" "))
// 4. 打印
mapDataStream.print()
// 5. 执行任务
env.execute()
}
}
d.使用kafka作为数据源
我们可以通过FlinkKafkaConsumer011来从kafka读取数据消息:
val topic = "test"
val properties = new Properties()
properties.setProperty("bootstrap.servers", "node01:9092")
properties.setProperty("group.id", "test")
val consumer = new FlinkKafkaConsumer011[String](topic,new SimpleStringSchema(),props)
相关的配置参考org.apache.kafka.clients.consumer.ConsumerConfig
示例:使用Flink流处理方式读取kafka 的消息
import java.util
import java.util.Properties
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011
import org.apache.flink.streaming.connectors.kafka.internals.KafkaTopicPartition
import org.apache.flink.api.scala._
/**
* 使用kafka作为flink的数据源进行读取数据
*/
object KafkaSource {
def main(args: Array[String]): Unit = {
// 1. env
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
//2:构建kafka的请求参数
val topic = "test"
//创建请求的参数集合
val props = new Properties()
props.setProperty("bootstrap.servers","node01:9092")
//3. 创建消费者实例
val consumer: FlinkKafkaConsumer011[String] = new FlinkKafkaConsumer011[String](topic,new SimpleStringSchema(),props)
//4.构建kafka数据源
val kafkaDataStream: DataStream[String] = env.addSource(consumer)
//5.打印数据
kafkaDataStream.print()
//6. 启动任务
env.execute("StreamKafkaDemo")
}
}
e.使用MySQL作为数据源
<!-- 指定mysql-connector的依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
import java.sql.{Connection, DriverManager, PreparedStatement}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.source.{RichSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala._
// 1. 创建样例类
case class User(id: String, username: String, password: String, name: String)
object MySqlSource {
def main(args: Array[String]): Unit = {
// 1. env
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 2 使用自定义Source
val mySqlDataStream: DataStream[User] = env.addSource(new MySqlSource)
// 3. 打印结果
mySqlDataStream.print()
// 4. 执行任务
env.execute()
}
}
//1. 自定义Source,继承自RichSourceFunction
class MySqlSource extends RichSourceFunction[User] {
var conn: Connection = _
var ps: PreparedStatement = _
//2. 实现open方法
override def open(parameters: Configuration): Unit = {
//2.1. 加载驱动
Class.forName("com.mysql.jdbc.Driver")
//2.2. 创建连接
conn = DriverManager.getConnection("jdbc:mysql:///test", "root", "123456")
//2.3. 创建PreparedStatement
ps = conn.prepareStatement("select id,username,password,name from user")
}
//3. 实现run方法
override def run(ctx: SourceFunction.SourceContext[User]) = {
//3.1. 执行查询
val resultSet = ps.executeQuery()
//3.2. 遍历查询结果,收集数据
while (resultSet.next()) {
var id = resultSet.getString("id")
var username = resultSet.getString("username")
var password = resultSet.getString("password")
var name = resultSet.getString("name")
ctx.collect(User(id, username, password, name))
}
}
override def close(): Unit = {
ps.close()
conn.close()
}
override def cancel() = {
}
}
三、DataStream的Transformation
**map:**将DataStream中的一个元素转换为另外一个元素。
**flatmap:**将DataStream中的一个元素转换为0…n个元素。
**filter:**过滤出一些符合条件的元素。
**mapPartition:**将一个分区中的元素转换为另一个元素。
**reduce:**可以对一个DataStream或者一个group来进行聚合计算,最终聚合成一个元素。
**reduceGroup:**可以对DataStream或者一个group进行聚合计算,最终聚合成一个元素。
**aggregate:**按照内置的方式进行聚合,Aggregate只用作用于元组上。
**distinct:**取除重复的数据。
**join:**使用join可以将两个DataStream。
**union:**将两个DataSet连接起来,不会去重。
reblance:Flink也会出现数据倾斜的时候,reblance会使用轮询的方式将数据均匀打散。
**hashpartition:**按照指定的key进行hash分区。
**sortPartition:**指定字段对分区中的数据进行排序。
**KeyBy:**按照指定的keyl来进行分区,类似于批处理中的groupby。可以按照指定分组的字段。
**Connect:**Connect用来将两个DataStream组装成一个ConnectStreams,他使用了两个泛型,既不要求两个DataStream的element是同一类型,这样我们可以把不同的数据组装成同一结构。
**split和select:**Split是将DataStream分成多个流,用splitstream来表示DataStream —> SplitStream。
select是获取分流后对应的数据,和split搭配使用,从split搭配使用,从SplitStream中选择一个或多个流SplitStream —> DataStream。
connect与union的区别:(1)Union之前两个流的类型必须一样,Connect可以不一样,在之后coMap中再去调整为一样的。(2)Connect只能操作两个流,Union可以操作多个。
四、支持的数据类型
1.Flink流应用程序处理的是以数据对象表示的事件流
a.需要被序列化的反序列化,以便在网络中传输他们;
b.或者从状态后端、检查点和保存点读取他们;
c.为了有效的做到这一点,Flink应用程需要明确知道所要处理的数据类型;
d.Flink适用类型信息的概念表示数据类型,并未每个数据类型生成特定的序列化器、反序列化器和比较器。
2.Flink类型提取系统
a.该系统分析函数的输入和返回类型,以自动获取类型信息,从而获得序列化器、反序列化器和比较器。
b.在某些情况下,例如lambda函数或泛型类型,需要显式的提供类型信息,才能使应用程序正常工作或提高性能。
3.Flink支持Java和Scala所有常见的数据类型。
a.基础的数据类型,Flink支持Java和Scala所有基础数据类型
b.Java和Scala元组
c.Java简单对象
d.其他,List、Map、Array、Enum
五、Flink在流处理上常见的sink
1、sink kafka
开发步骤
1.创建流处理环境
2.设置并行度
3.添加自定义MySql数据源
4.转换元组数据为字符串
5.构建FlinkKafkaProducer011
6.添加sink
7.执行任务
object DataSink_kafka {
def main(args: Array[String]): Unit = {
// 1. 创建流处理环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 2. 设置并行度
env.setParallelism(1)
// 3. 添加自定义MySql数据源
val source: DataStream[(Int, String, String, String)] = env.addSource(new MySql_source)
// 4. 转换元组数据为字符串
val strDataStream: DataStream[String] = source.map(
line => line._1 + line._2 + line._3 + line._4
)
//5. 构建FlinkKafkaProducer010
val p: Properties = new Properties
p.setProperty("bootstrap.servers", "node01:9092")
val sink = new FlinkKafkaProducer011[String]("test2", new SimpleStringSchema(), p)
// 6. 添加sink
strDataStream.addSink(sink)
// 7. 执行任务
env.execute("flink-kafka-wordcount")
}
}
2、Sink Mysql
开发步骤
1.创建流执行环境
2.准备数据
3.添加sink
构建自定义sink,继承自RichSinkFunction
重写open方法,获取Connection和PrepareStatement
重写invoke方法,执行插入操作
重写close方法,关闭连接操作
4.执行任务
import java.sql.{Connection, DriverManager, PreparedStatement}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
object DataSink_MySql {
def main(args: Array[String]): Unit = {
//1.创建流执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
//2.准备数据
val value: DataStream[(Int, String, String, String)] = env.fromCollection(List(
(10, "dazhuang", "123456", "大壮"),
(11, "erya", "123456", "二丫"),
(12, "sanpang", "123456", "三胖")
))
// 3. 添加sink
value.addSink(new MySqlSink)
//4.触发流执行
env.execute()
}
}
// 自定义落地MySql的Sink
class MySqlSink extends RichSinkFunction[(Int, String, String, String)] {
private var connection: Connection = null
private var ps: PreparedStatement = null
override def open(parameters: Configuration): Unit = {
//1:加载驱动
Class.forName("com.mysql.jdbc.Driver")
//2:创建连接
connection = DriverManager.getConnection("jdbc:mysql:///test", "root", "123456")
//3:获得执行语句
val sql = "insert into user(id , username , password , name) values(?,?,?,?);"
ps = connection.prepareStatement(sql)
}
override def invoke(value: (Int, String, String, String)): Unit = {
try {
//4.组装数据,执行插入操作
ps.setInt(1, value._1)
ps.setString(2, value._2)
ps.setString(3, value._3)
ps.setString(4, value._4)
ps.executeUpdate()
} catch {
case e: Exception => println(e.getMessage)
}
}
//关闭连接操作
override def close(): Unit = {
if (connection != null) {
connection.close()
}
if (ps != null) {
ps.close()
}
}
}