使用flink进行一天的UV(dau)统计

使用flink1.9进行实时dau统计
难点:窗口时间长(24小时),dau统计不能增量统计,需全窗口去重
实现:实现ProcessAllWindowFunction使用mapstate配合和rocksDB做全窗口数据缓存,基于processtime每20s触发一次从0点到当前的去重计算,即为实时dau
优化点:使用evictor方法,移除掉已经缓存进mapstate的数据,使进入到process()方法的数据量减少,减少性能损耗
不足:由于全窗口计算,dau又无需keyby,导致计算部分始终一个并行度在运行,需要提交任务时适度增加资源,下面是代码:

package com.ops


import java.text.SimpleDateFormat
import java.util
import java.util.{Date, Properties}

import com.fasterxml.jackson.databind.ObjectMapper
import com.xsyx.ops.DateUtil.getCurrentDateTimeJdk8
import org.apache.flink.api.common.functions.{AggregateFunction, RichFlatMapFunction}
import org.apache.flink.api.common.restartstrategy.RestartStrategies
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.api.common.state.{ListStateDescriptor, MapState, MapStateDescriptor}
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration
import org.apache.flink.contrib.streaming.state.RocksDBStateBackend
import org.apache.flink.runtime.state.StateBackend
import org.apache.flink.streaming.api.{CheckpointingMode, TimeCharacteristic}
import org.apache.flink.streaming.api.environment.CheckpointConfig.ExternalizedCheckpointCleanup
import org.apache.flink.streaming.api.functions.{AssignerWithPeriodicWatermarks, KeyedProcessFunction}
import org.apache.flink.streaming.api.scala.function.{ProcessAllWindowFunction, ProcessWindowFunction, WindowFunction}
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.assigners.{TumblingEventTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.evictors.TimeEvictor
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.triggers._
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.table.runtime.aggregate.KeyedProcessFunctionWithCleanupState
import org.apache.flink.util.Collector

import scala.collection.JavaConversions._
import scala.util.{Failure, Success, Try}

object RealTimeDau2 {
  def main(args: Array[String]): Unit = {
    val windowTime =TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8))
    val triggerInterval = 20
    var backendFilePath = ""
    val parallelism = 1
    val evictorTime = 20

    val jobName = "FlinkFrontLogDau"
    val topicNames = List("per_log")
    val groupName = "ops-dau2"
    val properties = new Properties()
    var kafkaBrokers: String = null

    //获取运行os 
    val ifOSIsWin = System.getProperty("os.name").startsWith("Windows")
    ifOSIsWin match {
      case true => {
        kafkaBrokers = "192.168.1.225:9092" //"172.16.7.207:9092"
        backendFilePath = "file:///D:/filebackend"
      }
      case _ => {
        kafkaBrokers = "x.x.x.x:9092"
        backendFilePath = backendFilePath //"file:///opt/ops/flinkbackend"
      }
    }
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(parallelism)
    env.setStateBackend(new RocksDBStateBackend(backendFilePath, true)) ///D:/filebackend  /opt/ops/flinkbackend
    env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
    env.enableCheckpointing(20000)
    env.getCheckpointConfig.setFailOnCheckpointingErrors(false)
    env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
    env.getCheckpointConfig.setCheckpointTimeout(10000)
    env.getCheckpointConfig.setMinPauseBetweenCheckpoints(500)
    env.getConfig.setRestartStrategy(RestartStrategies.fixedDelayRestart(4, 1000))
    env.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
    properties.setProperty("bootstrap.servers", kafkaBrokers)
    properties.setProperty("group.id", groupName)

    val myConsumer = new FlinkKafkaConsumer[String](topicNames, new SimpleStringSchema(), properties)
    if (ifOSIsWin) {
      myConsumer.setStartFromLatest()
    }
    val mapper = new ObjectMapper
    val dauStream = env.addSource(myConsumer).filter(_.nonEmpty).map(line => {
      val timeStamp = new Date().getTime
      Try {
        val rootNode = mapper.readTree(line)
        val messageNode = mapper.readTree(rootNode.get("message").toString)
        val guid = messageNode.get("g").asText
        ("", guid.hashCode, timeStamp)
      } match {
        case Success(value) => (value._1, value._2, value._3)
        case Failure(exception) => {
          println(exception.toString)
          ("", 0, timeStamp)
        }
      }
    })
      .assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[(String, Int, Long)] {
        var currentMaxTimestamp = 0L
        val maxOutOfOrderness = 2000L
        var lastEmittedWatermark: Long = Long.MinValue

        override def extractTimestamp(element: (String, Int, Long), previousElementTimestamp: Long): Long = {
          //timestamp
          val timestamp = element._3
          if (timestamp > currentMaxTimestamp) {
            currentMaxTimestamp = timestamp
          }
          timestamp
        }

        // Returns the current watermark
        override def getCurrentWatermark: Watermark = {
          // 允许延迟2秒
          val potentialWM = currentMaxTimestamp - maxOutOfOrderness
          // 保证水印能依次递增
          if (potentialWM >= lastEmittedWatermark) {
            lastEmittedWatermark = potentialWM
          }
          new Watermark(lastEmittedWatermark)
        }
      })
      .windowAll(windowTime)
      // triggerInterval:每20s触发一次计算,每次将全量输出到MyReduceWindowAllFunction的mapstate 数据量会很大
      // 所以设置evictorTime:20s,清除20s之前的数据
      // 这样在source端只保留近20的源数据,输送给MyReduceWindowAllFunction方法,
      // MyReduceWindowAllFunction 20s之前的数据已经在之前的触发计算中在mapstate中缓存了
      // 所以只要每次触发计算后,只要是triggerInterval 20内计算完成,最终结果uv数据就不会错
      // 如果每次触发计算后处理时间大于triggerInterval 20s,如需要25s,那本次计算还没完成,有可能这次20s的数据还没有完全塞进mapstate,
      // 下次计算又开始了,触发了evictor,将本次20s清除了,数据就会有问题,计算出来的uv可能比真是uv少
      .trigger(ContinuousProcessingTimeTrigger.of(Time.seconds(triggerInterval)))
      .evictor(TimeEvictor.of(Time.seconds(evictorTime), true))
      //      .aggregate(new MyAggregateFunction)
      .process(new MyReduceWindowAllFunction)

    ifOSIsWin match {
      case true => dauStream.print()
      case _ => dauStream.addSink(new MysqlSinkPool)
    }
    env.execute(jobName)
  }



  class MyReduceWindowAllFunction extends ProcessAllWindowFunction[(String, Int, Long), (String, Long), TimeWindow] {
    private var userState: MapState[Int, String] = null
    val userStateDesc = new MapStateDescriptor[Int, String]("dau-state", classOf[Int], classOf[String])

    override def process(context: Context, elements: Iterable[(String, Int, Long)], out: Collector[(String, Long)]): Unit = {
      //      获取mapSate
      userState = context.windowState.getMapState(userStateDesc) //context.windowState.getMapState(userStateDesc)

      val currentProcessTime = DateUtil.getCurrentDateTimeJdk8(true)
      //      对窗口类所有数据遍历存入mapState,去重
      for (e <- elements) {
        val key = e._2
        userState.put(key, null)
      }

      //统计mapstate key的个数,即当前dau
      val currentDau = userState.keys().size
      out.collect(currentProcessTime, currentDau)
    }
  }

}

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在这个科技高速发展的时代,经历了PC时代几乎人手一台电脑,随之衍生出站长这个概念;移动互联网时代几乎人手一部智能手机,智能手机一般都会安装很多应用,目前应用呈爆发式的增长;随着产业的不断深入发展,小程序的发展也日益壮大,应用涵盖各个领域;如今一个公司就可能有多个软件应用,对于软件开发商来说,急需一套分析系统帮助软件运营,如果单独开发一个分析系统去针对一个软件进行分析的话,成本会非常的大,这个成本包含开发成本以及以后的维护成本。为了解决了上述的问题,我们开发出了一套云产品:亿级动态数据统计分析系统,本系统可以支持所有的终端  (Web端、移动端、小程序端等 )数据统计,只要简单的使用sdk就可以接入我们的系统,软件开发商可以很轻松的对软件使用的情况进行监控,及时辅助公司对该软件的运营。该产品历经2年的实践,商业价值极高。本套案例是完全基于真实的产品进行开发和讲解的,同时对架构进行全面的升级,采用了全新的 Flink 架构+Node.js+Vue.js等,完全符合目前企业级的使用标准。对于本套课程在企业级应用的问题,可以提供全面的指导。Flink作为第四代大数据计算引擎,越来越多的企业在往Flink转换。Flink在功能性、容错性、性能方面都远远超过其他计算框架,兼顾高吞吐和低延时。Flink能够基于同一个Flink运行时,提供支持流处理和批处理两种类型应用的功能。也就是说同时支持流处理和批处理。Flink将流处理和批处理统一起来,也就是说作为流处理看待时输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。Flink技术特点1. 流处理特性支持高吞吐、低延迟、高性能的流处理支持带有事件时间的窗口(Window)操作支持有状态计算的Exactly-once语义支持高度灵活的窗口(Window)操作,支持基于time、count、session,以及data-driven的窗口操作支持具有Backpressure功能的持续流模型支持基于轻量级分布式快照(Snapshot)实现的容错一个运行时同时支持Batch on Streaming处理和Streaming处理Flink在JVM内部实现了自己的内存管理支持迭代计算支持程序自动优化:避免特定情况下Shuffle、排序等昂贵操作,中间结果有必要进行缓存2. API支持对Streaming数据类应用,提供DataStream API对批处理类应用,提供DataSet API(支持Java/Scala)3. Libraries支持支持机器学习(FlinkML)支持图分析(Gelly)支持关系数据处理(Table)支持复杂事件处理(CEP)4. 整合支持支持Flink on YARN支持HDFS支持来自Kafka的输入数据支持Apache HBase支持Hadoop程序支持Tachyon支持ElasticSearch支持RabbitMQ支持Apache Storm支持S3支持XtreemFS课程所涵盖的知识点包括:Flink、 Node.js、 Vue.js、 Kafka、Flume、Spring、SpringMVC、Dubbo、HDFS、Hbase、Highcharts等等  企业一线架构师讲授,代码在老师指导下可以复用,提供企业解决方案。  版权归作者所有,盗版将进行法律维权。   

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值