复习:
keyBy:类似于分组。相当于GroupBy key。
处理的流程任务是不动的。
算子的分区。
先不看了。
---01---
flink没有spark的forEach方法,因为flink是流,是来一个处理一个的。
redis的安装:https://baijiahao.baidu.com/s?id=1667197295239073048&wfr=spider&for=pc
Sink,注意source不是sink。
这个课程给了写入csv的资料。
代码:
启起来我的kafka:
机器:
起zk:zkServer.sh start
起kafka:kafka-server-start.sh /usr/local/apps/kafka/config/server.properties
测试:直接看下代码
package com.atguigu.apitest.sinktest
import java.util.Base64.Encoder
import java.util.Properties
import com.atguigu.apitest.SensorReading
import org.apache.flink.api.common.serialization.{SimpleStringEncoder, SimpleStringSchema}
import org.apache.flink.core.fs.Path
import org.apache.flink.streaming.api.functions.sink.filesystem.StreamingFileSink
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.kafka.{FlinkKafkaConsumer011, FlinkKafkaProducer011}
/**
* Copyright (c) 2018-2028 尚硅谷 All Rights Reserved
*
* Project: FlinkTutorial
* Package: com.atguigu.apitest.sinktest
* Version: 1.0
*
* Created by wushengran on 2020/4/18 9:27
*/
object KafkaSinkTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
// val inputStream = env.readTextFile("D:\\Projects\\BigData\\FlinkTutorial\\src\\main\\resources\\sensor.txt")
val properties = new Properties()
properties.setProperty("bootstrap.servers", "192.168.244.133: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 inputStream = env.addSource( new FlinkKafkaConsumer011[String]("sensor", new SimpleStringSchema(), properties) )
val dataStream: DataStream[String] = inputStream
.map(data => {
val dataArray = data.split(",")
SensorReading(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble).toString
})
// dataStream.writeAsCsv("D:\\Projects\\BigData\\FlinkTutorial\\src\\main\\resources\\out.txt")
// dataStream.addSink(
// StreamingFileSink.forRowFormat(
// new Path("D:\\Projects\\BigData\\FlinkTutorial\\src\\main\\resources\\out.txt"),
// new SimpleStringEncoder[String]("UTF-8"))
// .build() )
inputStream.print()
dataStream.addSink(new FlinkKafkaProducer011[String]("hadoop102:9092", "sinkTest", new SimpleStringSchema()))
env.execute("kafka sink test")
}
}
---02-03---
redis_sink:
package com.atguigu.apitest.sinktest
import com.atguigu.apitest.SensorReading
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.{FlinkJedisConfigBase, FlinkJedisPoolConfig}
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
/**
* Copyright (c) 2018-2028 尚硅谷 All Rights Reserved
*
* Project: FlinkTutorial
* Package: com.atguigu.apitest.sinktest
* Version: 1.0
*
* Created by wushengran on 2020/4/18 10:32
*/
object RedisSinkTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val inputStream = env.readTextFile("D:\\codeMy\\CODY_MY_AFTER__KE\\sggBigData\\20-flink\\FlinkTutorial\\src\\main\\resources\\sensor.txt")
val dataStream: DataStream[SensorReading] = inputStream
.map(data => {
val dataArray = data.split(",")
SensorReading(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
})
// 定义一个redis的配置类
val conf = new FlinkJedisPoolConfig.Builder()
.setHost("hadoop102")
.setPort(6379)
.build()
// 定义一个 RedisMapper key和value是怎么实现的呢
val myMapper = new RedisMapper[SensorReading] {
// 定义保存数据到redis的命令,hset table_name key value
override def getCommandDescription: RedisCommandDescription = {
// sensor_temp 表名
new RedisCommandDescription( RedisCommand.HSET, "sensor_temp" )
}
override def getValueFromData(data: SensorReading): String = data.temperature.toString
override def getKeyFromData(data: SensorReading): String = data.id
}
dataStream.addSink(new RedisSink[SensorReading](conf, myMapper))
env.execute("redis sink test")
}
}
---04---
es先不看了,我没有安装。
package com.atguigu.apitest.sinktest
import java.util
import com.atguigu.apitest.SensorReading
import org.apache.flink.api.common.functions.RuntimeContext
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.connectors.elasticsearch.{ElasticsearchSinkFunction, RequestIndexer}
import org.apache.flink.streaming.connectors.elasticsearch6.ElasticsearchSink
import org.apache.http.HttpHost
import org.elasticsearch.client.Requests
/**
* Copyright (c) 2018-2028 尚硅谷 All Rights Reserved
*
* Project: FlinkTutorial
* Package: com.atguigu.apitest.sinktest
* Version: 1.0
*
* Created by wushengran on 2020/4/18 10:54
*/
object EsSinkTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val inputStream = env.readTextFile("D:\\codeMy\\CODY_MY_AFTER__KE\\sggBigData\\20-flink\\FlinkTutorial\\src\\main\\resources\\sensor.txt")
val dataStream: DataStream[SensorReading] = inputStream
.map(data => {
val dataArray = data.split(",")
SensorReading(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
})
// 定义一个httpHosts
val httpHosts = new util.ArrayList[HttpHost]()
httpHosts.add(new HttpHost("hadoop102", 9200))
// 定义一个 ElasticsearchSinkFunction
val esSinkFunc = new ElasticsearchSinkFunction[SensorReading] {
// 每每一条数据来了 我到底怎么样往es里面写数据
override def process(element: SensorReading, ctx: RuntimeContext, indexer: RequestIndexer): Unit = {
// 包装写入es的数据
val dataSource = new util.HashMap[String, String]()
dataSource.put("sensor_id", element.id)
dataSource.put("temp", element.temperature.toString)
dataSource.put("ts", element.timestamp.toString)
// 创建一个index request
val indexRequest = Requests.indexRequest()
.index("sensor_temp")
.`type`("readingdata")
.source(dataSource)
// 用indexer发送 http请求
indexer.add( indexRequest )
println( element + " saved successfully")
}
}
dataStream.addSink( new ElasticsearchSink.Builder[SensorReading](httpHosts, esSinkFunc).build() )
env.execute("es sink test")
}
}
---05---
package com.atguigu.apitest.sinktest
import java.sql.{Connection, DriverManager, PreparedStatement}
import com.atguigu.apitest.{MySensorSource, SensorReading}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.sink.{RichSinkFunction, SinkFunction}
import org.apache.flink.streaming.api.scala._
/**
* Copyright (c) 2018-2028 尚硅谷 All Rights Reserved
*
* Project: FlinkTutorial
* Package: com.atguigu.apitest.sinktest
* Version: 1.0
*
* Created by wushengran on 2020/4/18 11:29
*/
object JdbcSinkTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
// val inputStream = env.readTextFile("D:\\Projects\\BigData\\FlinkTutorial\\src\\main\\resources\\sensor.txt")
val inputStream = env.addSource( new MySensorSource() )
val dataStream: DataStream[SensorReading] = inputStream
// .map(data => {
// val dataArray = data.split(",")
// SensorReading(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
// })
dataStream.addSink( new MyJdbcSink() )
env.execute("jdbc sink test")
}
}
// 自定义一个 SinkFunction
class MyJdbcSink() extends RichSinkFunction[SensorReading]{
// 首先定义sql连接,以及预编译语句
var conn: Connection = _
var insertStmt: PreparedStatement = _
var updateStmt: PreparedStatement = _
// 在open生命周期方法中创建连接以及预编译语句
override def open(parameters: Configuration): Unit = {
conn = DriverManager.getConnection("jdbc:mysql://hadoop102:3306/test", "root", "123456")
insertStmt = conn.prepareStatement("insert into temp (sensor, temperature) values (?,?)")
updateStmt = conn.prepareStatement("update temp set temperature = ? where sensor = ?")
}
override def invoke(value: SensorReading, context: SinkFunction.Context[_]): Unit = {
// 执行更新语句
updateStmt.setDouble(1, value.temperature)
updateStmt.setString(2, value.id)
updateStmt.execute()
// 如果刚才没有更新数据,那么执行插入操作
if( updateStmt.getUpdateCount == 0 ){
insertStmt.setString(1, value.id)
insertStmt.setDouble(2, value.temperature)
insertStmt.execute()
}
}
// 关闭操作
override def close(): Unit = {
insertStmt.close()
updateStmt.close()
conn.close()
}
}
---06---
我们感兴趣的不一定是从头到尾的没头没尾的。
窗口是什么就是我们不能基于所有的数据去更新数据的,这样是很累的。
flink处理数据是桶的。一个数据流可能在多个桶中,来了放在桶中,而不是数据到期了,我们去框数据。
窗口是左闭右开的。
滚动和滑动 即有时间也有会话窗口。
keyBy之后开窗,相当于对每个key开窗的。
会话窗口:session,会话窗口就不看了。
---07---
flink窗口类似于桶,数据丢到桶里。
开窗之后并没有做任何的计算的。
window api:
红框是一个完整的窗口操作。
.wsindow必须在keyBy之后操作的。
基于DataStream可以做window吗,其实也是可以的,windowAll是不被提倡的,内部回设置并行度为1,是不能并行计算的。
但是我们使用的话用的是timeWindow写的。
滚动窗口就是滑动步长等于窗口长度的滑动窗口。
我们看下timeWindow的代码:
窗口的创建:
代码:
后面跟的是偏移量,表示时区的。时间戳针对的是格林尼治时间的。
其实点进去源码已经说的很明显了:
中国比伦敦的标准时间是早八小时的就减去,变为格林尼治时间。
当前时间东方早要+西方要-,变回去就相反。
这个是针对天的。不然的话就是每天早上八点统计了到第二天早上八点。
滑动设置偏移量:
这里需要传下windowAssigner。
这个代表每10个 统计一次。
这个是每10个数统计一次,隔两个数再每10个统计一次。
---08---
windowFunction
增量聚合是比较效率高的,增量聚合函数就是每,来一个数据就计算下,然后把结果记录下来。
全窗口函数就是都到齐了再处理下。
allowedLateness意思就是到了时间则窗口关闭触发计算,但是新来的数据还是回计算的。
---09---
代码测试window和其特点:
测试1:
这个是滚动窗口,滑动窗口是滑动时间才输出一次的。
窗口是左闭右开的。
窗口默认是整点的窗口,而不是在采集到数据才开始的。
aggregation和reduce不一样的地方就是其输入可以和输出不一样的,这样就可以做很多事情,其还多了ACC累加器。
测试2:
全窗口函数:
// 自定义一个全窗口函数
class MyWindowFun() extends WindowFunction[SensorReading, (Long, Int), Tuple, TimeWindow]{
override def apply(key: Tuple, window: TimeWindow, input: Iterable[SensorReading], out: Collector[(Long, Int)]): Unit = {
out.collect((window.getStart, input.size))
}
}
这几个参数是输入 输出 key类型 TimeWindow
// 自定义一个全窗口函数
class MyWindowFun() extends WindowFunction[SensorReading, (Long, Int), Tuple, TimeWindow]{
override def apply(key: Tuple, window: TimeWindow, input: Iterable[SensorReading], out: Collector[(Long, Int)]): Unit = {
out.collect((window.getStart, input.size))
}
}
关于全窗口函数:
窗口的起始点时候如何定义的?
是滑动步长的整倍数。
滚动是窗口长度为起始点整倍数。
可以看到是当前的时间戳-取得余数。
---10---
时间语义:
为什么数据有迟到的意思呢?数据可能是先产生出来但是我们能处理的时候是滞后的。
在Flink的流式处理中,绝大部分的业务都会使用eventTime,一般只在eventTime无法使用时,才会被迫使用ProcessingTime或者IngestionTime。
如何设置时间语义?
flink怎么识别事件时间呢?需要指定当前事件时间戳是哪个字段,如果数据是升序的排列的只要这个就可以了。
---11---
如何处理要和水位线整合起来的。
---12---