Flink 中窗口是很重要的一个功能,而窗口又经常配合触发器一起使用。

Flink 自带的触发器大概有:

CountTrigger: 指定条数触发

ContinuousEventTimeTrigger:指定事件时间触发
ContinuousProcessingTimeTrigger:指定处理时间触发 ProcessingTimeTrigger: 默认触发器,窗口结束触发
EventTimeTrigger: 默认处理时间触发器,窗口结束触发 NeverTrigger:全局窗口触发器,不触发

但是没有可以指定时间和条数一起作为触发条件的触发器,所有就自己实现了一个(参考:ProcessingTimeTrigger、CountTrigger)

看下调用触发器的窗口代码:

val stream = env.addSource(kafkaSource)
.map(s => {
s
})
.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(60)))
.trigger(CountAndTimeTrigger.of(10, Time.seconds(10)))
.process(new ProcessAllWindowFunction[String, String, TimeWindow] {
override def process(context: Context, elements: Iterable[String], out: Collector[String]): Unit = {
var count = 0 elements.iterator.foreach(s => {
count += 1
})
logger.info("this trigger have : {} item", count)
}
})

很简单的一段代码:定义了一个60秒的窗口,触发器是自己实现的10条数据或者 10 秒触发一次的触发器,窗口函数就输出窗口数据的条数

下面看下自定义触发器 CountAndTimeTrigger 的核心代码如下:

/**
* CountAndTimeTrigger : 满足一定条数和时间触发
* 条数的触发使用计数器计数
* 时间的触发,使用 flink 的 timerServer,注册触发器触发
*
* @param <W>
*/
public class CountAndTimeTrigger<W extends Window> extends Trigger<Object, W> {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 触发的条数
private final long size;
// 触发的时长
private final long interval;
private static final long serialVersionUID = 1L;
// 条数计数器
private final ReducingStateDescriptor<Long> countStateDesc =
new ReducingStateDescriptor<>("count", new ReduceSum(), LongSerializer.INSTANCE);
// 时间计数器,保存下一次触发的时间
private final ReducingStateDescriptor<Long> timeStateDesc =
new ReducingStateDescriptor<>("fire-interval", new ReduceMin(), LongSerializer.INSTANCE); public CountAndTimeTrigger(long size, long interval) {
this.size = size;
this.interval = interval;
} @Override
public TriggerResult onElement(Object element, long timestamp, W window, TriggerContext ctx) throws Exception {
// 注册窗口结束的触发器, 不需要会自动触发
// ctx.registerProcessingTimeTimer(window.maxTimestamp());
// count
ReducingState<Long> count = ctx.getPartitionedState(countStateDesc);
//interval
ReducingState<Long> fireTimestamp = ctx.getPartitionedState(timeStateDesc);
// 每条数据 counter + 1
count.add(1L);
if (count.get() >= size) {
logger.info("countTrigger triggered, count : {}", count.get());
// 满足条数的触发条件,先清 0 条数计数器
count.clear();
// 满足条数时也需要清除时间的触发器,如果不是创建结束的触发器
if (fireTimestamp.get() != window.maxTimestamp()) {
// logger.info("delete trigger : {}, {}", sdf.format(fireTimestamp.get()), fireTimestamp.get());
ctx.deleteProcessingTimeTimer(fireTimestamp.get());
}
fireTimestamp.clear();
// fire 触发计算
return TriggerResult.FIRE;
} // 触发之后,下一条数据进来才设置时间计数器注册下一次触发的时间
timestamp = ctx.getCurrentProcessingTime();
if (fireTimestamp.get() == null) {
// long start = timestamp - (timestamp % interval);
long nextFireTimestamp = timestamp + interval;
// logger.info("register trigger : {}, {}", sdf.format(nextFireTimestamp), nextFireTimestamp);
ctx.registerProcessingTimeTimer(nextFireTimestamp);
fireTimestamp.add(nextFireTimestamp);
}
return TriggerResult.CONTINUE;
} @Override
public TriggerResult onProcessingTime(long time, W window, TriggerContext ctx) throws Exception { // count
ReducingState<Long> count = ctx.getPartitionedState(countStateDesc);
//interval
ReducingState<Long> fireTimestamp = ctx.getPartitionedState(timeStateDesc); // time trigger and window end
if (time == window.maxTimestamp()) {
logger.info("window close : {}", time);
// 窗口结束,清0条数和时间的计数器
count.clear();
ctx.deleteProcessingTimeTimer(fireTimestamp.get());
fireTimestamp.clear();
return TriggerResult.FIRE_AND_PURGE;
} else if (fireTimestamp.get() != null && fireTimestamp.get().equals(time)) {
logger.info("timeTrigger trigger, time : {}", time);
// 时间计数器触发,清0条数和时间计数器
count.clear();
fireTimestamp.clear();
return TriggerResult.FIRE;
}
return TriggerResult.CONTINUE;
}
}

主要是在数据进来的时候,调用  onElement 做条数的计数器,满足条件就触发, onProcessingTime 是 flink 的 timeservice 调用的,作为定时触发的触发器

在时间和条数的定时器都有清除时间和条数计数器的计数,让计数器在下一条数据到的时候,重新开始计数

特别需要注意:窗口结束的时候,会自动触发调用 onProcessingTime ,一定要包含在触发器逻辑里面,不然不能获取窗口的完整数据

// time trigger and window end
if (time == window.maxTimestamp()) {
logger.info("window close : {}", time);
// 窗口结束,清0条数和时间的计数器
count.clear();
ctx.deleteProcessingTimeTimer(fireTimestamp.get());
fireTimestamp.clear();
return TriggerResult.FIRE_AND_PURGE;
}

如在获取到窗口触发时间是窗口的结束时间(即窗口的结束时间减1,Java的时间精度是到毫秒,如 10秒的窗口时间是:(00000, 10000)0000-10000 ,实际上窗口结束时间就是  9999)

看执行的结果:

从 “14:42:00,002 INFO - window close : 1573281719999” 窗口结束

到 “14:42:10,015 INFO - countTrigger triggered, count : 10 ” , “14:42:19,063 INFO - countTrigger triggered, count : 10”  条数触发

到 “14:42:36,499 INFO - timeTrigger trigger, time : 1573281756496” 时间触发

最后 窗口结束 “14:43:00,002 INFO - window close : 1573281779999”

搞定

完整代码:https://github.com/springMoon/flink-rookie/tree/master/src/main/scala/com/venn/stream/api/trigger

欢迎关注Flink菜鸟公众号,会不定期更新Flink(开发技术)相关的推文

Flink 实现指定时长或消息条数的触发器的更多相关文章

  1. Android AlarmManager(全局定时器/闹钟)指定时长或以周期形式执行某项操作

    AlarmManager的使用机制有的称呼为全局定时器,有的称呼为闹钟.通过对它的使用,个人觉得叫全局定时器比较合适,其实它的作用和Timer有点相似.都有两种相似的用法:(1)在指定时长后执行某项操 ...

  2. Android之AlarmManager(全局定时器/闹钟)指定时长或以周期形式执行某项操作

    1.AlarmManager,顾名思义,就是“提醒”,是Android中常用的一种系统级别的提示服务,可以实现从指定时间开始,以一个固定的间隔时间执行某项操作,所以常常与广播(Broadcast)连用 ...

  3. Flink on YARN时,如何确定TaskManager数

    转自: https://www.jianshu.com/p/5b670d524fa5 答案写在最前面:Job的最大并行度除以每个TaskManager分配的任务槽数. 问题 在Flink 1.5 Re ...

  4. MySQL左连接时 返回的记录条数 比 左边表 数量多

    在学MySQL的连接时,为了便于记忆,就将左连接 记做 最后结果的总记录数 和 进行左连接的左表的记录数相同,简单的说就是下面这个公式 count(table A left join table B) ...

  5. JS指定音频audio在某个时间点进行播放,获取当前音频audio的长度,音频时长格式转化

    前言: 今天接到一个需求,需要获取某个.mp3音频文件的时间长度和指定音频audio在某个时间点进行播放(比如说这个视频有4分钟,我要让它默认从第2秒的时候开始播放),这里当然想到了H5中的audio ...

  6. struts2:上传多个文件时实现带进度条、进度详细信息的示范

    上一篇文章讲了上传单个文件与上传多个文件(属性驱动)的例子.本例是上传多个文件(属性驱动),并且显示进度条.进度详细信息的示范. 在文件上传选择界面,允许用户增加.删除选择的文件,且只能上传指定类型的 ...

  7. Vue Snackbar 消息条队列显示,依次动画消失的实现

    效果预览 思路 封装 Snackbar 组件: 在根路由页面下建立全局 Snackbar 控制器,统一管理 Snackbar: 通过事件通知全局 Snackbar 控制器显示消息: 实现 1. 封装 ...

  8. 在使用TCP协议进行消息发送时,对消息分帧

    成帧与解析 阅读 <java TCP/IP Socket 编程>第三章笔记 成帧技术(frame)是解决如何在接收端定位消息的首尾位置的问题.在进行数据收发时,必须指定消息接收者如何确定何 ...

  9. 【Android端 APP 启动时长获取】启动时长获取方案及具体实施

    一.什么是启动时长? 1.启动时长一般包括三种场景,分别是:新装包的首次启动时长,冷启动时长.热启动时长 冷启动 和 热启动 : (1)冷启动:当启动应用时,后台没有该程序的进程,此时启动的话系统会分 ...

随机推荐

  1. php获取时期时间信息函数

    在正式学习日期函数前大家得了解几个概念: 1.时区 2.世界时 3.unix时间戳 时区 这个概念,之前大家听说过很多.我们来啰嗦两句,我们现实生活中使用的实区,在电脑里面也是一样有规定的.大理石机械 ...

  2. Xshell5 安装JDK

    .执行命令yum -y list java*查看可安装java版本.执行成功后可以看见如下的结果 安装java-1.8全部相关 yum install -y java-1.8.0-openjdk* 使 ...

  3. Base64原理解析与使用

    一.Base64编码由来 为什么会有Base64编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就 不能通过邮件传送.这样用途就受到了很大的 ...

  4. 【转】Linux开机启动管理---systemd使用

    常用命令  使某服务自动启动 systemctl enable httpd.service 使某服务不自动启动 systemctl disable httpd.service 检查服务状态 syste ...

  5. PHP隐藏入口脚本文件index.php

    一.nginx下 隐藏入口文件时,配置nginx 首先得开启nginx pathinfo模式: location ~ \.php { #去掉$ root E:/phpStudy/WWW/tp/publ ...

  6. 关于nginx反代jenkins报错 反向代理设置有误

    官方文档地址: https://wiki.jenkins.io/display/JENKINS/Running+Jenkins+behind+Nginx 直接解决的配置文件吧. 这是使用子域名,不使用 ...

  7. VS2019输出信息到调试控制台

    System.Diagnostics.Debug.WriteLine(format, args);

  8. LeetCode之找零钱

    题目:已知不同面值的钞票,求如 何用最少数量的钞票组成某个金额,求可 以使用的最少钞票数量.如果任意数量的已知面值钞票都无法组成该金额, 则返回-1. 示例: Input: coins = [1, 2 ...

  9. 6、vueJs基础知识06

    vue动画 transition 之前1.0版本是以 属性的形式展示的 <p transition="fade"></p> .fade-transition ...

  10. git工作总结

    一.简单介绍 简介:Git是一个开源的分布式版本控制系统,可以有效.高速地处理项目版本管理. 发展史:CSV -> SVN -> Git 优点:Git速度快.开源.完全分布式管理系统 相关 ...