复习:
keyBy:类似于分组。相当于GroupBy key。

处理的流程任务是不动的。

算子的分区。

先不看了。

---01---

flink没有spark的forEach方法,因为flink是流,是来一个处理一个的。

flinkcdc 做维表 flink csv_flink

redis的安装:https://baijiahao.baidu.com/s?id=1667197295239073048&wfr=spider&for=pc

Sink,注意source不是sink。

这个课程给了写入csv的资料。

代码:

flinkcdc 做维表 flink csv_flink_02

启起来我的kafka:

机器:

flinkcdc 做维表 flink csv_apache_03

起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")
  }
}

flinkcdc 做维表 flink csv_flink_04

---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处理数据是桶的。一个数据流可能在多个桶中,来了放在桶中,而不是数据到期了,我们去框数据。

flinkcdc 做维表 flink csv_flinkcdc 做维表_05

窗口是左闭右开的。

滚动和滑动  即有时间也有会话窗口。

keyBy之后开窗,相当于对每个key开窗的。  

会话窗口:session,会话窗口就不看了。

flinkcdc 做维表 flink csv_flink_06

---07---

flink窗口类似于桶,数据丢到桶里。

开窗之后并没有做任何的计算的。

window api:

flinkcdc 做维表 flink csv_flinkcdc 做维表_07

flinkcdc 做维表 flink csv_flinkcdc 做维表_08

红框是一个完整的窗口操作。

.wsindow必须在keyBy之后操作的。

基于DataStream可以做window吗,其实也是可以的,windowAll是不被提倡的,内部回设置并行度为1,是不能并行计算的。

flinkcdc 做维表 flink csv_flink_09

但是我们使用的话用的是timeWindow写的。

滚动窗口就是滑动步长等于窗口长度的滑动窗口。

我们看下timeWindow的代码:

flinkcdc 做维表 flink csv_apache_10

flinkcdc 做维表 flink csv_apache_11

窗口的创建:

flinkcdc 做维表 flink csv_apache_12

flinkcdc 做维表 flink csv_kafka_13

代码:

 

flinkcdc 做维表 flink csv_flink_14

后面跟的是偏移量,表示时区的。时间戳针对的是格林尼治时间的。

其实点进去源码已经说的很明显了:

flinkcdc 做维表 flink csv_flink_15

中国比伦敦的标准时间是早八小时的就减去,变为格林尼治时间。

当前时间东方早要+西方要-,变回去就相反。

这个是针对天的。不然的话就是每天早上八点统计了到第二天早上八点。

滑动设置偏移量:

flinkcdc 做维表 flink csv_flinkcdc 做维表_16

flinkcdc 做维表 flink csv_flinkcdc 做维表_17

这里需要传下windowAssigner。

flinkcdc 做维表 flink csv_flinkcdc 做维表_18

flinkcdc 做维表 flink csv_apache_19

这个代表每10个 统计一次。

flinkcdc 做维表 flink csv_kafka_20

这个是每10个数统计一次,隔两个数再每10个统计一次。

---08---

windowFunction

flinkcdc 做维表 flink csv_flink_21

增量聚合是比较效率高的,增量聚合函数就是每,来一个数据就计算下,然后把结果记录下来。

全窗口函数就是都到齐了再处理下。

flinkcdc 做维表 flink csv_flinkcdc 做维表_22

allowedLateness意思就是到了时间则窗口关闭触发计算,但是新来的数据还是回计算的。

flinkcdc 做维表 flink csv_kafka_23

---09---

代码测试window和其特点:

flinkcdc 做维表 flink csv_apache_24

测试1:

flinkcdc 做维表 flink csv_kafka_25

这个是滚动窗口,滑动窗口是滑动时间才输出一次的。

窗口是左闭右开的。

窗口默认是整点的窗口,而不是在采集到数据才开始的。

flinkcdc 做维表 flink csv_flinkcdc 做维表_26

flinkcdc 做维表 flink csv_flink_27

aggregation和reduce不一样的地方就是其输入可以和输出不一样的,这样就可以做很多事情,其还多了ACC累加器。

测试2:

全窗口函数:

flinkcdc 做维表 flink csv_kafka_28

// 自定义一个全窗口函数
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

flinkcdc 做维表 flink csv_flink_29

// 自定义一个全窗口函数
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))
  }
}

flinkcdc 做维表 flink csv_apache_30

关于全窗口函数:

窗口的起始点时候如何定义的?

是滑动步长的整倍数。

滚动是窗口长度为起始点整倍数。

flinkcdc 做维表 flink csv_flinkcdc 做维表_31

可以看到是当前的时间戳-取得余数。

flinkcdc 做维表 flink csv_flinkcdc 做维表_32

 

flinkcdc 做维表 flink csv_kafka_33

---10---

时间语义:

为什么数据有迟到的意思呢?数据可能是先产生出来但是我们能处理的时候是滞后的。

在Flink的流式处理中,绝大部分的业务都会使用eventTime,一般只在eventTime无法使用时,才会被迫使用ProcessingTime或者IngestionTime。

flinkcdc 做维表 flink csv_flinkcdc 做维表_34

flinkcdc 做维表 flink csv_apache_35

flinkcdc 做维表 flink csv_kafka_36

如何设置时间语义?

flinkcdc 做维表 flink csv_kafka_37

flink怎么识别事件时间呢?需要指定当前事件时间戳是哪个字段,如果数据是升序的排列的只要这个就可以了。

flinkcdc 做维表 flink csv_flinkcdc 做维表_38

---11---

如何处理要和水位线整合起来的。

flinkcdc 做维表 flink csv_apache_39

---12---