一、背景说明:

在上篇文章实现了TopN计算,但是碰到迟到数据则会无法在当前窗口计算,需要对其中的键控状态优化

Flink使用二次聚合实现TopN计算

本次需求是对数据进行统计,要求每隔5秒,输出最近10分钟内访问量最多的前N个URL,数据流预览如下(每次一条从端口传入):

208.115.111.72 - - 17/05/2015:10:25:49 +0000 GET /?N=A&page=21   //15:50-25:50窗口数据
208.115.111.72 - - 17/05/2015:10:25:50 +0000 GET /?N=A&page=21
208.115.111.72 - - 17/05/2015:10:25:51 +0000 GET /?N=A&page=21
208.115.111.72 - - 17/05/2015:10:25:52 +0000 GET /?N=A&page=21 //第一次触发计算,15:50-25:50窗口
208.115.111.72 - - 17/05/2015:10:25:47 +0000 GET /?N=A& //迟到数据,不同url
208.115.111.72 - - 17/05/2015:10:25:53 +0000 GET /?N=A&page=21 //第二次触发计算,15:50-25:50窗口
208.115.111.72 - - 17/05/2015:10:25:46 +0000 GET /?N=A&page=21 //迟到数据
208.115.111.72 - - 17/05/2015:10:25:54 +0000 GET /?N=A&page=21 //第三次触发计算

最后统计输出结果如下(迟到数据均在25:50窗口):

==============2015-05-17 10:25:50.0==============               //第一次触发计算结果
Top1 Url:/?N=A&page=21 Counts:1
==============2015-05-17 10:25:50.0============== ==============2015-05-17 10:25:50.0============== //第二次触发计算结果
Top1 Url:/?N=A&page=21 Counts:1
Top2 Url:/?N=A& Counts:1
==============2015-05-17 10:25:50.0============== ==============2015-05-17 10:25:50.0============== //第三次触发计算结果
Top1 Url:/?N=A&page=21 Counts:2
Top2 Url:/?N=A& Counts:1
==============2015-05-17 10:25:50.0==============

二、实现过程

  1. 实现思路:

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

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

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

    ④第二次聚合,按窗口聚合,使用MapState存放数据,定义第一个定时器,在watermark达到后1秒触发,对窗口数据排序输出,定义第二个定时器,窗口关闭后才清楚状态。

    ⑤打印结果及执行。

ps:乱序数据不能使用读取本地文本文件的方式测试,文件读取加载比较快,无法观察到迟到数据处理效果,乱序数据的开发测试这里从服务器端口获取数据的方式测试

  1. 代码细节说明:

只针对优化部分代码说明,其他代码可以在顺序数据篇文章查看,这里提取重写KeyedProcessFunction里面方法的部分代码

@Override
public void processElement(UrlCount value, Context ctx, Collector<String> out) throws Exception {
//状态装入数据
mapState.put(value.getUrl(), value);
//定时器,窗口一秒后触发
ctx.timerService().registerEventTimeTimer(value.getWindowEnd()+1L);
//再加一个定时器来清除状态用,在窗口关闭后再清除状态,这样延迟数据到达后窗口还能做排序
ctx.timerService().registerEventTimeTimer(value.getWindowEnd()+61001L);
}
//定时器内容
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
if (timestamp == ctx.getCurrentKey()+61001L){
mapState.clear();
return;}
...
  • 这里改用MapState,如若使用ListState,进来迟到数据后,则会出现同个url在同个窗口的统计出现多个计数的情况,列表状态不具备去重功能,故在这里使用map状态来实现去重。
  • 这里使用定时器来清除状态,原写法是在onTimer最后排序完直接清除状态,则会导致迟到数据到达后,原窗口其他数据被清除掉无法实现排名的输出,这里定时器的时间是在61001毫秒后清除状态数据。
  • 定时器61001毫秒 = 允许迟到数据1秒(forBoundedOutOfOrderness)+窗口迟到数据1分钟(allowedLateness)+第一个定时器1毫秒。

三、完整代码

package com.test.topN;

import bean.ApacheLog;
import bean.UrlCount;
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.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
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;
import java.util.Map;
/**
* @author: Rango
* @create: 2021-05-26 10:16
* @description: 每隔5秒,输出最近10分钟内访问量最多的前N个URL
**/
public class URLTopN3 {
public static void main(String[] args) throws Exception { //1.建立环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment().setParallelism(1); //2.读取端口数据并映射到JavaBean,并定义watermark时间语义
WatermarkStrategy<ApacheLog> wms = WatermarkStrategy
.<ApacheLog>forBoundedOutOfOrderness(Duration.ofSeconds(1))
.withTimestampAssigner(new SerializableTimestampAssigner<ApacheLog>() {
@Override
public long extractTimestamp(ApacheLog element, long recordTimestamp) {
return element.getTs();
}}); SingleOutputStreamOperator<ApacheLog> apacheLogDS = env.socketTextStream("hadoop102", 9999)
.map(new MapFunction<String, ApacheLog>() {
@Override
public ApacheLog map(String value) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy:HH:mm:ss");
String[] split = value.split(" ");
return new ApacheLog(split[0],
split[2],
sdf.parse(split[3]).getTime(),
split[5],
split[6]);
}})
.assignTimestampsAndWatermarks(wms); //3.第一次聚合,按url转为tuple2分组,开窗,增量聚合
SingleOutputStreamOperator<UrlCount> aggregateDS = apacheLogDS
.map(new MapFunction<ApacheLog, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(ApacheLog value) throws Exception {
return new Tuple2<>(value.getUrl(), 1);
}}).keyBy(data -> data.f0)
.window(SlidingEventTimeWindows.of(Time.minutes(10),Time.seconds(5)))
.allowedLateness(Time.minutes(1))
.aggregate(new HotUrlAggFunc(), new HotUrlWindowFunc()); //4.第二次聚合,对第一次聚合输出按窗口分组,再全窗口聚合,建立定时器你,每5秒钟触发一次
SingleOutputStreamOperator<String> processDS = aggregateDS
.keyBy(data -> data.getWindowEnd())
.process(new HotUrlProcessFunc(5)); processDS.print();
env.execute();
}
//实现AggregateFunction类中的方法
public static class HotUrlAggFunc implements AggregateFunction<Tuple2<String, Integer>,Integer,Integer>{
@Override
public Integer createAccumulator() {return 0;}
@Override
public Integer add(Tuple2<String, 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; }
}
//实现窗口函数的apply方法,把累加函数输出的整数结果,转换为javabean类urlcount来做输出,方便后续按窗口聚合
public static class HotUrlWindowFunc implements WindowFunction<Integer, UrlCount,String, TimeWindow> {
@Override
public void apply(String urls, TimeWindow window, Iterable<Integer> input, Collector<UrlCount> out) throws Exception {
//获取按key相加后的次数并新建javabean(urlcount)作为返回
Integer count = input.iterator().next();
out.collect(new UrlCount(urls,window.getEnd(),count));
}
}
//继承KeyedProcessFunction方法,重写processElemnt方法
public static class HotUrlProcessFunc extends KeyedProcessFunction<Long,UrlCount,String>{
//定义TopN为入参
private Integer TopN;
public HotUrlProcessFunc(Integer topN) {
TopN = topN;
}
//定义状态
private MapState <String,UrlCount>mapState;
//open方法中初始化状态
@Override
public void open(Configuration parameters) throws Exception {
mapState = getRuntimeContext()
.getMapState(new MapStateDescriptor<String, UrlCount>("map-state",String.class,UrlCount.class));
}
@Override
public void processElement(UrlCount value, Context ctx, Collector<String> out) throws Exception {
//状态装入数据
mapState.put(value.getUrl(), value);
//定时器,窗口一秒后触发
ctx.timerService().registerEventTimeTimer(value.getWindowEnd()+1L);
//再加一个定时器来清除状态用,在窗口关闭后再清除状态,这样延迟数据到达后窗口还能做排序
ctx.timerService().registerEventTimeTimer(value.getWindowEnd()+61001L);
}
//定时器内容
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
if (timestamp == ctx.getCurrentKey()+61001L){
mapState.clear();
return;} //取出状态数据
Iterator<Map.Entry<String, UrlCount>> iterator = mapState.iterator();
ArrayList<Map.Entry<String, UrlCount>> entries = Lists.newArrayList(iterator); //排序
entries.sort(((o1, o2) -> o2.getValue().getCount()-o1.getValue().getCount())); //排序后装入StringBulider作为输出TopN
StringBuilder sb = new StringBuilder();
sb.append("==============")
.append(new Timestamp(timestamp - 1L))
.append("==============")
.append("\n");
for (int i = 0; i < Math.min(TopN,entries.size()); i++) {
UrlCount urlCount = entries.get(i).getValue();
sb.append("Top").append(i+1);
sb.append(" Url:").append(urlCount.getUrl());
sb.append(" Counts:").append(urlCount.getCount());
sb.append("\n");
}
sb.append("==============")
.append(new Timestamp(timestamp - 1L))
.append("==============")
.append("\n")
.append("\n"); out.collect(sb.toString());
Thread.sleep(200);
}}}

映射数据源的JavaBean

package bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; @Data
@NoArgsConstructor
@AllArgsConstructor
public class ApacheLog {
private String ip;
private String userId;
private Long ts;
private String method;
private String url;
}

第一次聚合输出的JavaBean

package bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; @Data
@NoArgsConstructor
@AllArgsConstructor
public class UrlCount {
private String url;
private Long windowEnd;
private Integer count;
}

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

Flink使用二次聚合实现TopN计算-乱序数据的更多相关文章

  1. Flink使用二次聚合实现TopN计算

    一.背景说明: 有需求需要对数据进行统计,要求每隔5分钟输出最近1小时内点击量最多的前N个商品,数据格式预览如下: 543462,1715,1464116,pv,1511658000 662867,2 ...

  2. Apache Flink 如何正确处理实时计算场景中的乱序数据

    一.流式计算的未来 在谷歌发表了 GFS.BigTable.Google MapReduce 三篇论文后,大数据技术真正有了第一次飞跃,Hadoop 生态系统逐渐发展起来. Hadoop 在处理大批量 ...

  3. Flink 实践教程-进阶(5):排序(乱序调整)

    作者:腾讯云流计算 Oceanus 团队 流计算 Oceanus 简介 流计算 Oceanus 是大数据产品生态体系的实时化分析利器,是基于 Apache Flink 构建的具备一站开发.无缝连接.亚 ...

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

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

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

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

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

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

  7. AI芯片:高性能卷积计算中的数据复用

    随着深度学习的飞速发展,对处理器的性能要求也变得越来越高,随之涌现出了很多针对神经网络加速设计的AI芯片.卷积计算是神经网络中最重要的一类计算,本文分析了高性能卷积计算中的数据复用,这是AI芯片设计中 ...

  8. Flink 实践教程 - 入门(4):读取 MySQL 数据写入到 ES

    ​作者:腾讯云流计算 Oceanus 团队 流计算 Oceanus 简介 流计算 Oceanus 是大数据产品生态体系的实时化分析利器,是基于 Apache Flink 构建的具备一站开发.无缝连接. ...

  9. Blazor和Vue对比学习(基础1.8):Blazor中实现计算属性和数据监听

    1.7章<传递UI片断>,需要做几个案例,这部分暂停消化几天.我们先把基础部分相对简单的最后两章学习了. 计算属性和数据监听是Vue当中的概念,本质上都是监听数据的变化,然后做出响应.两者 ...

随机推荐

  1. LevelDB 源码解析之 Varint 编码

    GitHub: https://github.com/storagezhang Emai: debugzhang@163.com 华为云社区: https://bbs.huaweicloud.com/ ...

  2. OpenCV 之 平面单应性

    上篇 OpenCV 之 图象几何变换 介绍了等距.相似和仿射变换,本篇侧重投影变换的平面单应性.OpenCV相关函数.应用实例等. 1  投影变换 1.1  平面单应性 投影变换 (Projectiv ...

  3. CodeForces CF877D题解(BFS+STL-set)

    解法\(1:\) 正常的\(bfs\)剪枝是\(\Theta(nm4k)\),这个时间复杂度是只加一个\(vis\)记录的剪枝的,只能保证每个点只进队一次,并不能做到其他的减少时间,所以理论上是过不了 ...

  4. Python数据分析入门(十):数据清洗和准备

    数据清洗是数据分析关键的一步,直接影响之后的处理工作 数据需要修改吗?有什么需要修改的吗?数据应该怎么调整才能适用于接下来的分析和挖掘? 是一个迭代的过程,实际项目中可能需要不止一次地执行这些清洗操作 ...

  5. “知疫”疫情防控可视化平台——NABCD分析

    "知疫"疫情防控可视化平台 项目 内容 这个作业属于那个课程 2021春季学期软件工程(罗杰.任健) 这个作业的要求在哪里 初次邂逅,需求分析 1 NABCD分析 NEED 目前多 ...

  6. 几十行代码实现ASP.NET Core自动依赖注入

    在开发.NET Core web服务的时候,我们习惯使用自带的依赖注入容器来进行注入. 于是就会经常进行一个很频繁的的重复动作:定义一个接口->写实现类->注入 有时候会忘了写Add这一步 ...

  7. SpringBoot项目打包部署

    部署方式 SpringBoot项目可以通过jar包或者war包部署在服务器上,因为jar包更适合前后端分离的项目,所以这里我们使用jar包的方式. 添加maven支持 <!-- 这个插件,可以将 ...

  8. 详细Tomcat安装及问题排查

    一.安装 1.下载官网:https://tomcat.apache.org/ 2.将下载后的包解压到目录中会出现以下页面 3.设置环境变量,向path中添加tomcat的bin目录地址 4.cmd进入 ...

  9. 记一次 医院.NET公众号系统 线程CPU双高分析

    一:背景 1. 讲故事 上周四有位朋友加wx咨询他的程序出现 CPU + 线程 双高的情况,希望我能帮忙排查下,如下图: 从截图看只是线程爆高,没看到 cpu 爆高哈,有意思的是这位朋友说他: 一直在 ...

  10. 另类的曲线方式定时Start up/Shut down VM 的解决方案

    一,引言 最近看到一位小兄弟在为了做 Azure 云虚拟机的自动关机开启 在群里求助,最后也不知道结果咋样了. 至于他提到的利用 Automation Account 我是没有接触过,并且也没有看资料 ...