一、背景说明:

有需求需要对数据进行统计,要求每隔5分钟输出最近1小时内点击量最多的前N个商品,数据格式预览如下:

543462,1715,1464116,pv,1511658000
662867,2244074,1575622,pv,1511658000
561558,3611281,965809,pv,1511658000
894923,3076029,1879194,pv,1511658000
834377,4541270,3738615,pv,1511658000
315321,942195,4339722,pv,1511658000
625915,1162383,570735,pv,1511658000
578814,176722,982926,pv,1511658000
....

最后统计输出结果如下:

==============2017-11-26 09:05:00.0==============
Top1 ItemId:5051027 Counts:3
Top2 ItemId:3493253 Counts:3
Top3 ItemId:4261030 Counts:3
Top4 ItemId:4894670 Counts:2
Top5 ItemId:3781391 Counts:2
==============2017-11-26 09:05:00.0============== ==============2017-11-26 09:10:00.0==============
Top1 ItemId:812879 Counts:5
Top2 ItemId:2600165 Counts:4
Top3 ItemId:2828948 Counts:4
Top4 ItemId:2338453 Counts:4
Top5 ItemId:4261030 Counts:4
==============2017-11-26 09:10:00.0==============

二、实现过程

  1. 实现思路:

    ①建立环境,设置并行度及CK。

    ②定义watermark策略及事件时间,获取数据并对应到JavaBean,筛选pv数据。

    ③第一次聚合,按商品id分组开窗聚合,使用aggregate算子进行增量计算。

    ④第二次聚合,按窗口聚合,使用ListState存放数据,并定义定时器,在watermark达到后1秒触发,对窗口数据排序输出。

    ⑤打印结果及执行。

  2. 代码细节说明:

    2.1 、第一次聚合代码:

//第一次聚合
SingleOutputStreamOperator<ItemCount> aggregateDS = userBehaviorDS
.map(new MapFunction<UserBehavior, Tuple2<Long, Integer>>() {
@Override
public Tuple2<Long, Integer> map(UserBehavior value) throws Exception {
return new Tuple2<>(value.getItemId(), 1);
}})
.keyBy(data -> data.f0)
.window(SlidingEventTimeWindows.of(Time.hours(1), Time.minutes(5)))
.aggregate(new ItemCountAggFunc(), new ItemCountWindowFunc());

①第一次聚合这里将商品id进行提取并转换为Tuple2<id,1>的格式,再对id进行keyby后聚合,避免直接使用对应的JavaBean进行分组聚合提高效率:

②这里使用aggregate算子进行增量计算,Flink的window function来负责一旦窗口关闭, 去计算处理窗口中的每个元素。window function 是如下三种:

  • ReduceFunction (增量聚合函数) 输入及输出类型得一致
  • AggregateFunction(增量聚合函数)输入及输出类型可以不一致
  • ProcessWindowFunction(全窗口函数)

ReduceFunction,AggregateFunction更加高效, 原因就是Flink可以对到来的元素进行增量聚合 .

ProcessWindowFunction 可以得到一个包含这个窗口中所有元素的迭代器, 以及这些元素所属窗口的一些元数据信息.

ProcessWindowFunction不能被高效执行的原因是Flink在执行这个函数之前, 需要在内部缓存这个窗口上所有的元素

2.2、重写AggregateFunction函数代码

public static class ItemCountAggFunc implements AggregateFunction<Tuple2<Long,Integer>,Integer,Integer>{
@Override
public Integer createAccumulator() { return 0; }
@Override
public Integer add(Tuple2<Long, Integer> value, Integer accumulator) { return accumulator+1; }
@Override
public Integer getResult(Integer accumulator) { return accumulator; }
@Override
public Integer merge(Integer a, Integer b) { return a+b; }
}

这里对AggregateFunction函数里面四个方法进行重写自定义计数规则,入参<IN,ACC,OUT>对应为Tuple2,累加器用Integer过度,输出结果为Integer。

  • createAccumulator

    这个方法首先要创建一个累加器,要进行一些初始化的工作,这里初始值为0.
  • add

    add方法就是做聚合的时候的核心逻辑,这里这是对tuple的第二位整数进行累加。
  • merge

    Flink是一个分布式计算框架,可能计算是分布在很多节点上同时进行的,如果计算在多个节点进行,需要对结果进行合并,这个merge方法就是做这个工作的,所以入参和出参的类型都是中间结果类型ACC。
  • getResult

    这个方法就是将每个用户最后聚合的结果经过处理之后,按照OUT的类型返回,返回的结果也就是聚合函数的输出结果了。

这里也是AggregateFunction和ReduceFunction区别的地方,reduce的input为Tuple2,则output也必须是Tuple2。

三、完整代码

这里处理的是顺序数据,如果是乱序数据,在窗口触发计算后迟到数据统计会有问题,优化思路为在窗口关闭后再触发键控状态的清除,及使用MapState来避免同个产品Id多个结果的问题。

package com.test.topN;

import bean.ItemCount;
import bean.UserBehavior;
import org.apache.commons.compress.utils.Lists;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author: Rango
* @create: 2021-05-24 10:37
* @description: 每隔5分钟输出最近1小时内点击量最多的前N个商品
**/
public class ProductTopN {
public static void main(String[] args) throws Exception {
//1.建立环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1); //2.设置watermark及定义事件时间,从socket获取数据并对应到JavaBean,筛选只取pv数据
WatermarkStrategy<UserBehavior> wms = WatermarkStrategy
.<UserBehavior>forBoundedOutOfOrderness(Duration.ofSeconds(1))
.withTimestampAssigner(new SerializableTimestampAssigner<UserBehavior>() {
@Override
public long extractTimestamp(UserBehavior element, long recordTimestamp) {
return element.getTimestamp() * 1000L;
}
});
SingleOutputStreamOperator<UserBehavior> userBehaviorDS = env
//.socketTextStream("hadoop102", 9999)
.readTextFile("input/UserBehavior.csv")
.map(new MapFunction<String, UserBehavior>() {
@Override
public UserBehavior map(String value) throws Exception {
String[] split = value.split(",");
return new UserBehavior(Long.parseLong(split[0]),
Long.parseLong(split[1]),
Integer.parseInt(split[2]),
split[3],
Long.parseLong(split[4]));
}
})
.filter(data -> "pv".equals(data.getBehavior()))
.assignTimestampsAndWatermarks(wms); //3.第一次聚合,按商品id分组开窗聚合,使用aggregate进行增量计算,将商品id用tuple2抽离出来提高效率
SingleOutputStreamOperator<ItemCount> aggregateDS = userBehaviorDS
.map(new MapFunction<UserBehavior, Tuple2<Long, Integer>>() {
@Override
public Tuple2<Long, Integer> map(UserBehavior value) throws Exception {
return new Tuple2<>(value.getItemId(), 1);
}})
.keyBy(data -> data.f0)
.window(SlidingEventTimeWindows.of(Time.hours(1), Time.minutes(5)))
.aggregate(new ItemCountAggFunc(), new ItemCountWindowFunc()); //4.第二次聚合,按窗口聚合,基于状态编程实现窗口内有序
SingleOutputStreamOperator<String> processDS = aggregateDS.keyBy(ItemCount::getTime)
.process(new ItemCountProcessFunc(5)); //5.打印结果并执行
processDS.print();
env.execute();
} public static class ItemCountAggFunc implements AggregateFunction<Tuple2<Long,Integer>,Integer,Integer>{
@Override
public Integer createAccumulator() { return 0; }
@Override
public Integer add(Tuple2<Long, Integer> value, Integer accumulator) { return accumulator+1; }
@Override
public Integer getResult(Integer accumulator) { return accumulator; }
@Override
public Integer merge(Integer a, Integer b) { return a+b; }
}
public static class ItemCountWindowFunc implements WindowFunction<Integer, ItemCount,Long, TimeWindow>{
@Override
public void apply(Long key, TimeWindow window, Iterable<Integer> input, Collector<ItemCount> out) throws Exception {
Integer next = input.iterator().next();
out.collect(new ItemCount(key,new Timestamp(window.getEnd()).toString(),next));
}
}
public static class ItemCountProcessFunc extends KeyedProcessFunction<String,ItemCount,String>{
//定义构造器可以按入参取排名
private Integer topN;
public ItemCountProcessFunc(Integer topN) {
this.topN = topN;
} //使用liststatus并初始化
private ListState <ItemCount>listState;
@Override
public void open(Configuration parameters) throws Exception {
listState= getRuntimeContext()
.getListState(new ListStateDescriptor<ItemCount>("list-state",ItemCount.class));
}
//定时器
@Override
public void processElement(ItemCount value, Context ctx, Collector<String> out) throws Exception {
listState.add(value);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ctx.timerService().registerEventTimeTimer(sdf.parse(value.getTime()).getTime()+1000L);
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
//1.获取状态的数据并转为List
Iterator<ItemCount> iterator = listState.get().iterator();
ArrayList<ItemCount> itemCounts = Lists.newArrayList(iterator); //2.排序
itemCounts.sort(((o1, o2) -> o2.getCount() - o1.getCount())); //3.获取前n
StringBuilder sb = new StringBuilder();
sb.append("==============")
.append(new Timestamp(timestamp - 1000L))
.append("==============")
.append("\n");
for (int i = 0; i < Math.min(topN,itemCounts.size()); i++) {
ItemCount itemCount = itemCounts.get(i);
sb.append("Top").append(i+1);
sb.append(" ItemId:").append(itemCount.getItem());
sb.append(" Counts:").append(itemCount.getCount());
sb.append("\n");
}
sb.append("==============")
.append(new Timestamp(timestamp - 1000L))
.append("==============")
.append("\n")
.append("\n");
listState.clear();
out.collect(sb.toString());
Thread.sleep(200);//方便查看结果时间休眠
}}}

学习交流,有任何问题还请随时评论指出交流。

Flink使用二次聚合实现TopN计算的更多相关文章

  1. Flink使用二次聚合实现TopN计算-乱序数据

    一.背景说明: 在上篇文章实现了TopN计算,但是碰到迟到数据则会无法在当前窗口计算,需要对其中的键控状态优化 Flink使用二次聚合实现TopN计算 本次需求是对数据进行统计,要求每隔5秒,输出最近 ...

  2. ElasticSearch7.3学习(二十七)----聚合概念(bucket和metric)及其示例

    一.两个核心概念:bucket和metric 1.1 bucket 有如下数据 city name  北京 张三  北京 李四 天津 王五 天津 赵六 天津 王麻子 划分出来两个bucket,一个是北 ...

  3. MySQL聚合函数在计算时,不会自动匹配与之相对应的数据

    学习mysql过程中遇到了一个困惑,纠结了我半天时间,刚刚又重新复习了一下,终于知道问题所在 以下是一个需求: 取得平均薪水最高的部门的部门编号 代码如下: select deptno, avg(sa ...

  4. iNeuOS工业互联平台,聚合和变化率计算、设备IO和通讯状态、组态快捷键、创建文件夹、选择应用图标等,发布:v3.6版本

    目       录 1.      概述... 2 2.      平台演示... 2 3.      聚合和变化率计算... 2 4.      设备IO和通讯状态监测... 3 5.      组 ...

  5. Apache Flink 为什么能够成为新一代大数据计算引擎?

    众所周知,Apache Flink(以下简称 Flink)最早诞生于欧洲,2014 年由其创始团队捐赠给 Apache 基金会.如同其他诞生之初的项目,它新鲜,它开源,它适应了快速转的世界中更重视的速 ...

  6. kafka传数据到Flink存储到mysql之Flink使用SQL语句聚合数据流(设置时间窗口,EventTime)

    网上没什么资料,就分享下:) 简单模式:kafka传数据到Flink存储到mysql 可以参考网站: 利用Flink stream从kafka中写数据到mysql maven依赖情况: <pro ...

  7. 大数据笔记(二十二)——大数据实时计算框架Storm

    一. 1.对比:离线计算和实时计算 离线计算:MapReduce,批量处理(Sqoop-->HDFS--> MR ---> HDFS) 实时计算:Storm和Spark Sparki ...

  8. Flink笔记(二) DataStream Operator(数据流操作)

    DataStream Source 基于文件 readTextFile(path) 读取 text 文件的数据 readFile(fileInputFormat, path) 通过自定义的读取方式, ...

  9. sql server聚合函数sum计算出来为空,怎样返回0

    通常我们计算数据库中表的数据有几个常用的聚合函数 1.count : 计数 2.sum: 计算总和 3.avg: 取平均值 4.max: 取最大值 5.min: 取最小值 6.isnull: 当返回数 ...

随机推荐

  1. Spring的循环依赖

    本文简要介绍了循环依赖以及Spring解决循环依赖的过程 一.定义 循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环.这里的对象特指单例对象. 二.表现形式 对象之间的循环 ...

  2. MarkDown-简单学习

    标题 注意:首部添加1-6个"#"号来设置标题大小: 字体 1:粗体 (注意:首尾同时添加2个"*"号来设置) 2:斜体 (注意:首尾同时添加1个"* ...

  3. $@ 与 $* 差在哪?-- Shell十三问<第九问>

    $@ 与 $* 差在哪?-- Shell十三问<第九问> 要说 $@ 与 $* 之前,需得先从 shell script 的 positional parameter 谈起.我们都已经知道 ...

  4. Java单例模式实现,一次性学完整,面试加分项

    单例模式是设计模式中使用最为普遍的一种模式.属于对象创建模式,它可以确保系统中一个类只产生一个实例.这样的行为能带来两大好处: 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而 ...

  5. Java代码度量分析工具:DesigniteJava简介

    前言 在Java面向对象课程的学习过程中,我们需要使用度量工具来分析自己程序的代码结构.受OO课程组以及前辈们博客提醒,笔者找到了DesigniteJava这款软件,现对此软件进行简单的说明. 一.D ...

  6. MySQL提升笔记(2):存储引擎盘点

    在前面我们了解了server层调用存储引擎层接口来完成sql的执行,使用存储引擎的好处是:每个存储引擎都有各自的特点,能够根据具体的应用建立不同存储引擎表. 需要注意的是,存储引擎是基于表的,而不是数 ...

  7. Leedcode算法专题训练(链表)

    1.发现两个链表的交点 160.两个链表的交集(容易) Leetcode /力扣 public class Solution { public ListNode getIntersectionNode ...

  8. HMS Toolkit助力开发者高效集成HMS Core

    当你的应用想集成华为HMS Core服务和上线华为应用市场,或当你已经开发了一个Android应用并集成了第三方移动服务,需要迁移使用HMS Core服务和上线华为应用市场的时候,如何快速.便捷.高效 ...

  9. 学习WEB前端是应该自学还是参加培训机构?

    先说观点,我强烈建议每个人都要自学,不要参加培训班. 我干web前端工程师这个职位已经有6年多的时间,之前在蚂蚁金服做过2年,后来离开是因为加班实在熬不住才走的,像这些已经上市的互联网公司几乎没有不加 ...

  10. Day14_77_反射( newInstance() 方法)

    newInstance() 方法 * 通过反射获取class类型的对象之后,可以通过该对象创建所对应的class类型的对象 * newInstance() 用来创建Class获取的类所表示的一个新实例 ...