文章目录
- 1.flink与spark的对比
- 2.flink内部原理
- 2.1内部组件
- 2.1.1environment
- 2.1.2source
- 2.1.3transform
- 2.1.4sink
- 2.2运行机制
- 2.2.1任务提交模式(yarn)
- 2.3worker和slots
- 2.4EventTime和window
- 2.4.1Eventtime
- 2.4.2window
- 2.4.1
- 3.flink的API操作
- 3.1environment搭建
- 3.2Kafkasource
- 3.3split-select
- 3.4合并
- 3.5Sink
- 3.5.1kafkasink
- 3.5.2redis
- 3.5.3自定义JDBC
1.flink与spark的对比
- spark是批处理,处理有界、持久化、大量需要访问整个数据集才能完成计算,适合做离线计算
- fink是流处理,处理无界、实时,不需要访问整个数据集就能完成计算,适合做实时计算
- spark的实时处理,只是缩短了批处理的时间,但也可以完成实时计算
- flink的实时处理,是真正的流式计算,延迟性要低于spark
- flink是事件驱动型,同样的框架还有kfka,而且Kafka通过两段式提交可以真正的完成exactly-once(对于发送的massage,receiver确保只收到一次)
- flink同时也有缺憾,他对sql语句的支持没有spark那么好用,因此许多大厂会在flink上开发,完成自己的需求
2.flink内部原理
2.1内部组件
2.1.1environment
- flink的环境主要使用getExecutionEnvironment,再本地使用就是本地模式,集群使用就是集群模式
- 还有两个createLocalEnvironment(本地)、createRemoteEnvironment(远程)
2.1.2source
- 主要来源于kafka详见API操作
2.1.3transform
- flink的transform操作中大部分的算子和spark相似,但也有不同的
- flink将spark中的reducebykey分成了两个部分,keyby 和reduce
- flink有一个split-select,类似与flume的拦截器和分区器,将进来的数据打上标签,然后通过select方法获得
- Union和Connect-CoMap:Union是强硬的让两个数据流在一起,但是他们的数据类型必须要相同,而Connect-CoMap可以两个数据流不同,connect之后进入一个缓冲区培养感情(ConnectedStreams),然后CoMap,作用于ConnectedStreams上,功能与map和flatMap一样,对ConnectedStreams中的每一个Stream分别进行map和flatMap处理。
2.1.4sink
- flink的sink要求严格,需要继承官方指定的sink
2.2运行机制
2.2.1任务提交模式(yarn)
- Flink任务提交后,Client向HDFS上传Flink的Jar包和配置
- 向Yarn ResourceManager提交任务,ResourceManager分配Container资源并通知对应的NodeManager启动ApplicationMaster
- ApplicationMaster启动后加载Flink的Jar包和配置构建环境,然后启动JobManager,之后ApplicationMaster向ResourceManager申请资源启动TaskManager
- ResourceManager分配Container资源后,由ApplicationMaster通知资源所在节点的NodeManager启动TaskManager
- NodeManager加载Flink的Jar包和配置构建环境并启动TaskManager,TaskManager启动后向JobManager发送心跳包,并等待JobManager向其分配任务。
2.3worker和slots
-每一个worker(taskmannager)上可以有多个slots,提高了并发度
2.4EventTime和window
2.4.1Eventtime
- 在Flink的流式处理中,绝大部分的业务都会使用eventTime,一般只在eventTime无法使用时,才会被迫使用ProcessingTime或者IngestionTime。
- Watermark:用于处理乱序事件的,而正确的处理乱序事件,通常用Watermark机制结合window来实现。
数据流中的Watermark用于表示timestamp小于Watermark的数据,都已经到达了,因此,window的执行也是由Watermark触发的。
Watermark可以理解成一个延迟触发机制,我们可以设置Watermark的延时时长t,每次系统会校验已经到达的数据中最大的maxEventTime,然后认定eventTime小于maxEventTime - t的所有数据都已经到达,如果有窗口的停止时间等于maxEventTime – t,那么这个窗口被触发执行。
2.4.2window
- 滚动窗口(Tumbling Windows):将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
适用场景:适合做BI统计等(做每个时间段的聚合计算)。 - 滑动窗口(Sliding Windows):滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
特点:时间对齐,窗口长度固定,有重叠。
适用场景:对最近一个时间段内的统计(求某接口最近5min的失败率来决定是否要报警 - 会话窗口(Session Windows):
由一系列事件组合一个指定时间长度的timeout间隙组成,类似于web应用的session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐。
2.4.1
3.flink的API操作
3.1environment搭建
//引入配置
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_2.11</artifactId>
<version>1.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-streaming-scala -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_2.11</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 该插件用于将Scala代码编译成class文件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.4.6</version>
<executions>
<execution>
<!-- 声明绑定到maven的compile阶段 -->
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
//最常用
val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
//local,指定并行度
val env = StreamExecutionEnvironment.createLocalEnvironment(1)
//远程,返回集群执行环境,将Jar提交到远程服务器。需要在调用时指定JobManager的IP和端口号,并指定要在集群中运行的Jar包。
val env = ExecutionEnvironment.createRemoteEnvironment("jobmanager-hostname", 6123,"C://jar//flink//wordcount.jar")
3.2Kafkasource
//Kafka工具类
object MyKafkaUtil {
val prop = new Properties()
prop.setProperty("bootstrap.servers","bigdata1:9092")
prop.setProperty("group.id","test")
def getConsumer(topic:String ):FlinkKafkaConsumer011[String]= {
val myKafkaConsumer:FlinkKafkaConsumer011[String] = new FlinkKafkaConsumer011[String](topic, new SimpleStringSchema(), prop)
myKafkaConsumer
}
}
//主类
def main(args: Array[String]): Unit = {
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
3.3split-select
// 将appstore与其他渠道拆分拆分出来 成为两个独立的流
val splitStream: SplitStream[StartUpLog] = startUplogDstream.split { startUplog =>
var flags:List[String] = null
if ("标签1" == startUplog.ch) {
flags = List(startUplog.ch)
} else {
flags = List("标签2" )
}
flags
}
val appStoreStream: DataStream[StartUpLog] = splitStream.select("标签1")
appStoreStream.print("标签1:").setParallelism(1)
val otherStream: DataStream[StartUpLog] = splitStream.select("标签2")
otherStream.print("标签2:").setParallelism(1)
3.4合并
//connect-CoMap
//合并以后打印
val connStream: ConnectedStreams[StartUpLog, StartUpLog] = appStoreStream.connect(otherStream)
val allStream: DataStream[String] = connStream.map(
(log1: StartUpLog) => log1.ch,
(log2: StartUpLog) => log2.ch
)
allStream.print("connect::")
//Union
//合并以后打印
val unionStream: DataStream[StartUpLog] = appStoreStream.union(otherStream)
unionStream.print("union:::")
3.5Sink
3.5.1kafkasink
//配置
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-connector-kafka-0.11 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.11_2.11</artifactId>
<version>1.7.0</version>
</dependency>
//util
def getProducer(topic:String): FlinkKafkaProducer011[String] ={
new FlinkKafkaProducer011[String](brokerList,topic,new SimpleStringSchema())
//主函数
val myKafkaProducer: FlinkKafkaProducer011[String] = MyKafkaUtil.getProducer("channel_sum")
sumDstream.map( chCount=>chCount._1+":"+chCount._2 ).addSink(myKafkaProducer)
3.5.2redis
//配置
<!-- https://mvnrepository.com/artifact/org.apache.bahir/flink-connector-redis -->
<dependency>
<groupId>org.apache.bahir</groupId>
<artifactId>flink-connector-redis_2.11</artifactId>
<version>1.0</version>
</dependency>
//utils
object MyRedisUtil {
val conf = new FlinkJedisPoolConfig.Builder().setHost("hadoop1").setPort(6379).build()
def getRedisSink(): RedisSink[(String,String)] ={
new RedisSink[(String,String)](conf,new MyRedisMapper)
}
class MyRedisMapper extends RedisMapper[(String,String)]{
override def getCommandDescription: RedisCommandDescription = {
new RedisCommandDescription(RedisCommand.HSET, "channel_count")
// new RedisCommandDescription(RedisCommand.SET )
}
override def getValueFromData(t: (String, String)): String = t._2
override def getKeyFromData(t: (String, String)): String = t._1
}
}
//main
sumDstream.map( chCount=>(chCount._1,chCount._2+"" )).addSink(MyRedisUtil.getRedisSink())
3.5.3自定义JDBC
//配置
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
//utils
class MyJdbcSink(sql:String ) extends RichSinkFunction[Array[Any]] {
val driver="com.mysql.jdbc.Driver"
val url="jdbc:mysql://hadoop2:3306/gmall2019?useSSL=false"
val username="root"
val password="123123"
val maxActive="20"
var connection:Connection=null;
//创建连接
override def open(parameters: Configuration): Unit = {
val properties = new Properties()
properties.put("driverClassName",driver)
properties.put("url",url)
properties.put("username",username)
properties.put("password",password)
properties.put("maxActive",maxActive)
val dataSource: DataSource = DruidDataSourceFactory.createDataSource(properties)
connection = dataSource.getConnection()
}
//反复调用
override def invoke(values: Array[Any]): Unit = {
val ps: PreparedStatement = connection.prepareStatement(sql )
println(values.mkString(","))
for (i <- 0 until values.length) {
ps.setObject(i + 1, values(i))
}
ps.executeUpdate()
}
override def close(): Unit = {
if(connection!=null){
connection.close()
}
}
}
//main
val startUplogDstream: DataStream[StartUpLog] = dstream.map{ JSON.parseObject(_,classOf[StartUpLog])}
val jdbcSink = new MyJdbcSink("insert into z_startup values(?,?,?,?,?)")
startUplogDstream.map(startuplog=>Array(startuplog.mid,startuplog.uid,startuplog.ch,startuplog.area, startuplog.ts)).addSink(jdbcSink)