Flink水印机制和应用

1. Flink流处理时间方式

  • EventTime[事件时间]

    事件发生的时间,例如:点击网站上的某个链接的时间

  • IngestionTime[摄入时间]

    某个Flink节点的source operator接收到数据的时间,例如:某个source消费到kafka中的数据

  • ProcessingTime[处理时间]

    某个Flink节点执行某个operation的时间,例如:timeWindow接收到数据的时间

2. 水印机制产生的原因

在实际环境中,经常会出现,因为网络原因,数据有可能会延迟一会才到达Flink实时处理系统。

我们来设想一下下面这个场景:

  1. 使用时间窗口来统计10分钟内的用户流量
  2. 有一个时间窗口
    • 开始时间为:2017-03-19 10:00:00
    • 结束时间为:2017-03-19 10:10:00
  3. 有一个数据,因为网络延迟
    • 事件发生的时间为:2017-03-19 10:10:00
    • 但进入到窗口的时间为:2017-03-19 10:10:02,延迟了2秒中
  4. 时间窗口并没有将59这个数据计算进来,导致数据统计不正确

这种处理方式,根据消息进入到window时间,来进行计算。在网络有延迟的时候,会引起计算误差。

3. 使用水印解决网络延迟问题

水印(watermark)就是一个时间戳,Flink可以给数据流添加水印,可以理解为:收到一条消息后,额外给这个消息添加了一个时间字段,这就是添加水印

  • 水印并不会影响原有Eventtime
  • 当数据流添加水印后,会按照水印时间来触发窗口计算
  • 一般会设置水印时间,比Eventtime小几秒钟
  • 当接收到的水印时间 >= 窗口的endTime,则触发计算

示例

编写代码, 计算5秒内,用户的订单总额

订单数据(订单ID——UUID、用户ID、时间戳、订单金额),要求添加水印来解决网络延迟问题。

步骤

  1. 创建流处理运行环境
  2. 设置处理时间为EventTime
  3. 创建一个订单样例类Order,包含四个字段(订单ID、用户ID、订单金额、时间戳)
  4. 创建一个自定义数据源
    • 随机生成订单ID(UUID)
    • 随机生成用户ID(0-2)
    • 随机生成订单金额(0-100)
    • 时间戳为当前系统时间
    • 每隔1秒生成一个订单
  5. 添加水印
    • 允许延迟2秒
    • 在获取水印方法中,打印水印时间、事件时间和当前系统时间
  6. 按照用户进行分流
  7. 设置5秒的时间窗口
  8. 进行聚合计算
  9. 打印结果数据
  10. 启动执行流处理

参考代码

 import java.util.UUID
import java.util.concurrent.TimeUnit
import org.apache.commons.lang.time.FastDateFormat
import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.functions.source.{RichSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.time.Time

import scala.util.Random

object WaterMarkDemo {

  // 3. 创建一个订单样例类`Order`,包含四个字段(订单ID、用户ID、订单金额、时间戳)
  case class Order(orderId: String, userId: Int, money: Long, timestamp: Long)

  def main(args: Array[String]): Unit = {
    // 1. 创建流处理运行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //   2. 设置处理时间为`EventTime`
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    // 4. 创建一个自定义数据源
    val orderDataStream: DataStream[Order] = env.addSource(new RichSourceFunction[Order] {
      var isRunning = true

      override def run(ctx: SourceFunction.SourceContext[Order]): Unit = {
        while (isRunning) {
          //   - 随机生成订单ID(UUID)
          // - 随机生成用户ID(0-2)
          // - 随机生成订单金额(0-100)
          // - 时间戳为当前系统时间
          // - 每隔1秒生成一个订单
          val order = Order(UUID.randomUUID().toString, Random.nextInt(3), Random.nextInt(101), new java.util.Date().getTime)
          ctx.collect(order)
          TimeUnit.SECONDS.sleep(1)
        }
      }

      override def cancel(): Unit = isRunning = false
    })
    // 5. 添加水印
    val watermarkDataStream = orderDataStream.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[Order] {
      var currentTimestamp = 0L
      val delayTime = 2000

      override def getCurrentWatermark: Watermark = {
        //   - 允许延迟2秒
        // - 在获取水印方法中,打印水印时间、当前事件时间和当前系统时间
        val watermark = new Watermark(currentTimestamp - delayTime)
        val dateFormat = FastDateFormat.getInstance("HH:mm:ss")

        println(s"当前水印时间:${dateFormat.format(watermark.getTimestamp)}, 当前事件时间: ${dateFormat.format(currentTimestamp)}, 当前系统时间: ${dateFormat.format(System.currentTimeMillis())}")
        watermark
      }

      override def extractTimestamp(element: Order, previousElementTimestamp: Long): Long = {
        val timestamp = element.timestamp
        currentTimestamp = Math.max(currentTimestamp, timestamp)
        currentTimestamp
      }
    })
    // 6. 按照用户进行分流
    // 7. 设置5秒的时间窗口
    // 8. 进行聚合计算
    // 9. 打印结果数据
    // 10. 启动执行流处理
    watermarkDataStream.keyBy(_.userId)
      .timeWindow(Time.seconds(5))
      .reduce {
        (order1, order2) =>
          Order(order2.orderId, order2.userId, order1.money + order2.money, 0)
      }
      .print()
    env.execute("WarkMarkDemoJob")
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值