zoukankan      html  css  js  c++  java
  • flink模拟项目:实时热门商品统计

    首先要实现的是实时热门商品统计,我们将会基于UserBehavior数据集来进行分析。

    项目主体用Scala编写,采用IDEA作为开发环境进行项目编写,采用maven作为项目构建和管理工具。首先我们需要搭建项目框架。

    2.1 创建Maven项目

    2.1.1 项目框架搭建

    打开IDEA,创建一个maven项目,命名为UserBehaviorAnalysis。由于包含了多个模块,我们可以以UserBehaviorAnalysis作为父项目,并在其下建一个名为HotItemsAnalysis的子项目,用于实时统计热门top N商品。

    在UserBehaviorAnalysis下新建一个 maven module作为子项目,命名为HotItemsAnalysis。

    父项目只是为了规范化项目结构,方便依赖管理,本身是不需要代码实现的,所以UserBehaviorAnalysis下的src文件夹可以删掉。

     

    2.1.2 声明项目中工具的版本信息

    我们整个项目需要的工具的不同版本可能会对程序运行造成影响,所以应该在最外层的UserBehaviorAnalysis中声明所有子模块共用的版本信息。

    在pom.xml中加入以下配置:

    UserBehaviorAnalysis/pom.xml

    <properties>
        <flink.version>1.7.2</flink.version>

    <scala.binary.version>2.11</scala.binary.version>
        <kafka.version>2.2.0</kafka.version>
    </properties>

    2.1.3 添加项目依赖

    对于整个项目而言,所有模块都会用到flink相关的组件,所以我们在UserBehaviorAnalysis中引入公有依赖:

    UserBehaviorAnalysis/pom.xml

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-scala_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-scala_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
    <dependency>
            <groupId>org.apache.kafka</groupId>
    <artifactId>kafka_${scala.binary.version}</artifactId>
    <version>${kafka.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-connector-kafka_${scala.binary.version}</artifactId>
        <version>${flink.version}</version>
    </dependency>
    </dependencies>

    同样,对于maven项目的构建,可以引入公有的插件:

    <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>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>
    

     

    在HotItemsAnalysis子模块中,我们并没有引入更多的依赖,所以不需要改动pom文件。

     

    2.2 模块代码实现

    我们将实现一个“实时热门商品”的需求,可以将“实时热门商品”翻译成程序员更好理解的需求:每隔5分钟输出最近一小时内点击量最多的前N个商品。将这个需求进行分解我们大概要做这么几件事情:

    • 抽取出业务时间戳,告诉Flink框架基于业务时间做窗口
    • 过滤出点击行为数据
    • 按一小时的窗口大小,每5分钟统计一次,做滑动窗口聚合(Sliding Window)
    • 按每个窗口聚合,输出每个窗口中点击量前N名的商品

     

    2.2.1 程序主体

    在src/main/scala下创建HotItems.scala文件,新建一个单例对象。定义样例类UserBehavior和ItemViewCount,在main函数中创建StreamExecutionEnvironment 并做配置,然后从UserBehavior.csv文件中读取数据,并包装成UserBehavior类型。代码如下:

    HotItemsAnalysis/src/main/scala/HotItems.scala

    case class UserBehavior(userId: Long, itemId: Long, categoryId: Int, behavior: String, timestamp: Long)
    case class ItemViewCount(itemId: Long, windowEnd: Long, count: Long)
    
    object HotItems {
      def main(args: Array[String]): Unit = {
    // 创建一个 StreamExecutionEnvironment
        val env = StreamExecutionEnvironment.getExecutionEnvironment
        // 设定Time类型为EventTime
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
        // 为了打印到控制台的结果不乱序,我们配置全局的并发为1,这里改变并发对结果正确性没有影响
    env.setParallelism(1)
        val stream = env
    // 以window下为例,需替换成自己的路径
          .readTextFile("YOUR_PATH\resources\UserBehavior.csv")
          .map(line => {
            val linearray = line.split(",")
            UserBehavior(linearray(0).toLong, linearray(1).toLong, linearray(2).toInt, linearray(3), linearray(4).toLong)
          })
      // 指定时间戳和watermark
    .assignAscendingTimestamps(_.timestamp * 1000)
    
        env.execute("Hot Items Job")
      }
    

      

    这里注意,我们需要统计业务时间上的每小时的点击量,所以要基于EventTime来处理。那么如果让Flink按照我们想要的业务时间来处理呢?这里主要有两件事情要做。

    第一件是告诉Flink我们现在按照EventTime模式进行处理,Flink默认使用ProcessingTime处理,所以我们要显式设置如下:

    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

    第二件事情是指定如何获得业务时间,以及生成Watermark。Watermark是用来追踪业务事件的概念,可以理解成EventTime世界中的时钟,用来指示当前处理到什么时刻的数据了。由于我们的数据源的数据已经经过整理,没有乱序,即事件的时间戳是单调递增的,所以可以将每条数据的业务时间就当做Watermark。这里我们用 assignAscendingTimestamps来实现时间戳的抽取和Watermark的生成。

    注:真实业务场景一般都是乱序的,所以一般不用assignAscendingTimestamps而是使用BoundedOutOfOrdernessTimestampExtractor

    .assignAscendingTimestamps(_.timestamp * 1000)

    这样我们就得到了一个带有时间标记的数据流了,后面就能做一些窗口的操作。

    2.2.2 过滤出点击事件

    在开始窗口操作之前,先回顾下需求“每隔5分钟输出过去一小时内点击量最多的前N个商品”。由于原始数据中存在点击、购买、收藏、喜欢各种行为的数据,但是我们只需要统计点击量,所以先使用filter将点击行为数据过滤出来。

    .filter(_.behavior == "pv")
    

     

    2.2.3 设置滑动窗口,统计点击量

    由于要每隔5分钟统计一次最近一小时每个商品的点击量,所以窗口大小是一小时,每隔5分钟滑动一次。即分别要统计[09:00, 10:00), [09:05, 10:05), [09:10, 10:10)…等窗口的商品点击量。是一个常见的滑动窗口需求(Sliding Window)。

        .keyBy("itemId")
        .timeWindow(Time.minutes(60), Time.minutes(5))
        .aggregate(new CountAgg(), new WindowResultFunction());
    

    我们使用.keyBy("itemId")对商品进行分组,使用.timeWindow(Time size, Time slide)对每个商品做滑动窗口(1小时窗口,5分钟滑动一次)。然后我们使用 .aggregate(AggregateFunction af, WindowFunction wf) 做增量的聚合操作,它能使用AggregateFunction提前聚合掉数据,减少state的存储压力。较之 .apply(WindowFunction wf) 会将窗口中的数据都存储下来,最后一起计算要高效地多。这里的CountAgg实现了AggregateFunction接口,功能是统计窗口中的条数,即遇到一条数据就加一。

    // COUNT统计的聚合函数实现,每出现一条记录就加一
    class CountAgg extends AggregateFunction[UserBehavior, Long, Long] {
      override def createAccumulator(): Long = 0L
      override def add(userBehavior: UserBehavior, acc: Long): Long = acc + 1
      override def getResult(acc: Long): Long = acc
      override def merge(acc1: Long, acc2: Long): Long = acc1 + acc2
    }
    

    聚合操作.aggregate(AggregateFunction af, WindowFunction wf)的第二个参数WindowFunction将每个key每个窗口聚合后的结果带上其他信息进行输出。我们这里实现的WindowResultFunction将<主键商品ID,窗口,点击量>封装成了ItemViewCount进行输出。

    // 商品点击量(窗口操作的输出类型)
    case class ItemViewCount(itemId: Long, windowEnd: Long, count: Long)
    

    代码如下:

    // 用于输出窗口的结果
    class WindowResultFunction extends WindowFunction[Long, ItemViewCount, Tuple, TimeWindow] {
      override def apply(key: Tuple, window: TimeWindow, aggregateResult: Iterable[Long],
                         collector: Collector[ItemViewCount]) : Unit = {
        val itemId: Long = key.asInstanceOf[Tuple1[Long]].f0
        val count = aggregateResult.iterator.next
        collector.collect(ItemViewCount(itemId, window.getEnd, count))
      }
    }
    

      现在我们就得到了每个商品在每个窗口的点击量的数据流

     

  • 相关阅读:
    Mongodb
    Java原子类
    volatile
    uniapp输入空格
    看不见的的html
    小程序隐藏scroll-view滚动条的方法
    云函数调用云函数 openid不存在
    vue路由中 Navigating to current location ("/xxx") is not allowed
    Vue: 单页面应用如何保持登录状态
    letter-spacing
  • 原文地址:https://www.cnblogs.com/tesla-turing/p/13273678.html
Copyright © 2011-2022 走看看