Flink的Windows计算

flink经典实战案例 一、java版flink-wordcount-离线计算版 1.1maven构建flink,加入依赖 <!-- flink包依赖配置-start --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-java</artifactId> <version>${flink.version 阅读详情


Windows窗口计算是流式计算中非常重要的数据计算方式之一。通过按照固定时间或长度将数据流切分成不同的窗口,让后对数据进行相应额聚合运算,从而得到一定范围内的统计结果。例如统计淘宝网近5分钟内的物品浏览数据,此时用户浏览数据不断的生成,但通过5分钟的窗口将数据限定在固定时间范围内,就可以对该范围内的有界数据进行分析,做好物品推荐。
Flink中DataStream将窗口抽象成独立的Operator。DataStream提供了大量内建窗口算子。

Keyde Windows算子对窗口计算流程

stream.keyBy(...)
     .window(指定窗口分配器)
     [.trigger(指定窗口触发器类型)]
                [.evictor(**指定evicator)]
                [.allowedLateness(指定是否延迟处理数据)]
                [.sideOutputLateData(指定Out Tag)]
                .reduce()[.apply()/fold()/aggregate()等窗口计算函数]
                [.getSideOutput(根据Tag输出数据)]
                

Non-Keyed Windows算子对窗口计算流程

stream
       .windowAll(...)           <-  required: "assigner"
      [.trigger(...)]            <-  optional: "trigger" (else default trigger)
      [.evictor(...)]            <-  optional: "evictor" (else no evictor)
      [.allowedLateness(...)]    <-  optional: "lateness" (else zero)
      [.sideOutputLateData(...)] <-  optional: "output tag" (else no side output for late data)
       .reduce/aggregate/fold/apply()      <-  required: "function"
      [.getSideOutput(...)]      <-  optional: "output tag"
DataStream API含义说明
.window/windowAll(…)指定窗口分配器所有窗口算子都必须指定Windows Assigner,其指定窗口的类型,定义如何将流数据分配到一个或多个窗口
.trigger()指定窗口触发器类型(可选)指定窗口触发的时机,定义窗口满足什么条件时触发计算。
.evictor()指定evicator(可选)数据剔除器,主要用于数据剔除
.allowedLateness()(可选)指定是否延迟处理数据时延设定,标记是否处理迟到数据,当迟到数据到达窗口中是否触发计算
.sideOutputLateData()(可选)指定Out Tag标记输出标签,然后再通过getSideOutput将窗口中的苏家根据标签输出;
.reduce()窗口计算函数定义窗口上数据处理逻辑,例如对数据进行reduce操作,此外还有.apply()/fold()/aggregate()等窗口计算函数,Windows function也是必须指定的
.getSideOutput()(可选)根据Tag输出数据

1 Windows Assigner窗口分配器

1.1 Keyed和Non-Keyed窗口

根据上游数据是否为KeyedStream类型(将数据集安装Key分区),对应的Windows分配器也会有所不同。

  • KeyedStream类型数据集,则调用DataStream的window()方法指定Windows分配器,数据会根据Key在不同的Task实例中并行分别计算,最后得出针对每个Key统计的结果。
  • Non-Keyed类型数据集,则调用windowsAll()方法指定Windows分配器,所有的数据都会在窗口算子中路由到一个Task实例中计算,并得到全局统计结果。

由上面可以知,如果用户选择针对Key进行分区,就能够将相同的Key数据分配在同一个分区,例如统计一个网站在五分钟内不同用户的点击数。如果用户没有指定Key,此时需要对窗口上的数据进行全局统计计算,这种窗口被称为GlobalWindows,例如统计某一段时间内某网站所有的请求数。

//Keyed Stream,调用window方法指定Windows分配器
dataStream.keyBy(0)
          .window(new MyWindowAssigner())
          .sum(1);
//Non-Keyed Stream,对DataStream数据集,直接调用windowAll指定Windows分配器
dataStream.windowAll(new MyWindowAssigner())
          .sum(1);

1.2 Windows分配器

Flink支持基于时间的窗口和基于数据的窗口两种类型的窗口。

  • 基于时间的窗口
    窗口基于起始时间戳(闭区间)和终止时间戳(开区间)来决定窗口的大小。数据根据时间戳被分配到不同的窗口汇总完成计算。Flink使用TimeWindow类来获取窗口的起始时间和终止时间,以及该窗口允许进入的最新时间戳信息等元数据。
  • 基于数量的窗口
    根据固定的数量定义窗口的大小,例如每1000条数据形成一个窗口,**窗口中接入的数据依赖于数据接入到算子中的顺序,如果数据出现乱序情况,将导致窗口的计算结果不确定。**Flink中可以通过调用DataStream的countWindows()来定义基于数量的窗口。

Windows分配器将接入数据分配到不同的窗口,根据Windows分配器数据分配方式的不同将Windows分为4大类,分别是滚动窗口(Tumbling Windows)、滑动窗口(Sliding Windows)、会话窗口(Session Windows)和全局窗口(Global Windows),这些窗口都已在Flink中实现了,可直接调用windows()或者windowsALL方法来指定WindowsAssigner即可。

1.2.1 Tumbling Windows

滚动窗口是根据固定时间或大小进行切分,且窗口和窗口之间的元素互不重叠。这种类型的窗口比较简单,但可能会导致某些有前后关系的数据计算结果不正确,而对于按照固定大小和周期统计某一指标的这种类型的窗口计算就比较适合,同时实现起来也比较方便。
DataStream中提供了基于EventTime和ProcessTime两种类型的Tumbling窗口。它们分别是TumblingEventTimeWindows和TumblingProcessingTimeWindows,可以用window或者windowAll指定。示例如下

//Keyed Stream,调用window方法指定Windows分配器
dataStream.keyBy(0)
        //或者
        //.window(TumblingEventTimeWindows.of(Time.minutes(5)))
        .window(TumblingProcessingTimeWindows.of(Time.minutes(5)))
        .process(...);//定义窗口函数
        
//Non-Keyed Stream,对DataStream数据集,直接调用windowAll指定Windows分配器
dataStream.windowAll(TumblingEventTimeWindows.of(Time.minutes(5)))
        //或者
        //.windowAll(TumblingProcessingTimeWindows.of(Time.minutes(5)))
        .process(...);//定义窗口函数

代码中用of方法定义窗口大小,其时间单位可以用Time类指定。
此外,还可以用DataStream的timeWindow()方法,快捷方法定义TumblingEventTimeWindows和TumblingProcessingTimeWindows,如下

 dataStream.keyBy(0)
         //窗口类型根据设置的time characteristic确定。
         .timeWindow(Time.minutes(5))
         .process(...);//定义窗口函数
 //Non-Keyed Stream,对DataStream数据集,直接调用windowAll指定Windows分配器
 // 窗口类型根据设置的time characteristic确定。
 dataStream.timeWindowAll(Time.minutes(5))
         .process(...);//定义窗口函数

窗口类型根据用户在ExecationEnvironment中设定的Time characteristic确定。默认窗口时间的时区是UTC-0,其他区均需要通过设定时间偏移量调整时区,爱国内需要指定Time.hours(-8)的偏移量。

1.2.2 Sliding Windows

滑动窗口是在滚动窗口的基础上增加了窗口的滑动时间(slide time),且允许窗口数据发生重叠。
windows size固定后,窗口并不像滚动窗口按照windows size向前移动,而是根据设定的Slide time向前滑动。滑动窗口的数据重叠大小根据windows size和slide time决定。

  • windows size>slide time,发生窗口重叠
  • window size = slide time,Sliding窗口变成了Tumbling窗口。
  • window size < slide time,窗口不连续,数据可能出现不落入任何一个窗口的情况。

滑动窗口帮助用户根据设定的统计频率计算指定窗口大小的统计指标。如每个30s统计最近5分钟内活跃用户数等。
Flink提供了基于Event Time和基于Process Time的滑动窗口。

dataStream.keyBy(0)
        .window(SlidingProcessingTimeWindows.of(Time.minutes(5), Time.seconds(30)))
        //或者
        // .window(SlidingEventTimeWindows.of(Time.minutes(5),Time.seconds(30)))
        .process(...);//定义窗口函数
dataStream
        .windowAll(SlidingProcessingTimeWindows.of(Time.minutes(5), Time.seconds(30)))
        //或者
        // .windowAll(SlidingEventTimeWindows.of(Time.minutes(5),Time.seconds(30)))
        .process(...);//定义窗口函数

timeWindow()方法快捷设置

dataStream.keyBy(0)
        .timeWindow(Time.minutes(5), Time.seconds(30))
        .process(...);//定义窗口函数
dataStream
        .timeWindowAll(Time.minutes(5), Time.seconds(30))
        .process(...);//定义窗口函数

timeWindow指定的参数分别是windows size、slide time。如果在国内设定为Time.hours(-8)。
窗口的类型仍然根据用户在ExecationEnvironment中设定的Time characteristic确定。

1.2.3 Session Windows

会话窗口住哟啊将某短时间内活跃度较高的数据聚合在一个窗口进行计算。窗口的触发条件是Session Gap,是在规定的时间内如果没有数据活跃接入,则认为窗口结束,然后触发窗口计算结果。如果数据一直不间断地进入窗口,会导致窗口始终不触发。Session窗口不需要固定windows size和slide time,只需要定义session gap,来规定不活跃数据的时间上限制,然后窗口根据这个时间来判断市局是否属于同一活跃数据集,从而将数据切分成不同的窗口进行计算。
session窗口适合非连续型数据或周期性数据的场景。同样flink提供了基于EventTime和ProcessTime的session窗口,分别是EventTimeSessionWindows和ProcessingTimeSessionWindows。
SessionWindows提供了两种方法创建对应类型的SessionWindows,分别是withGap和withDynamicGap。

  • withGap方法用于创建固定gap的session窗口。固定session gap的示例
dataStream.keyBy(0)
        //.window(EventTimeSessionWindows.withGap(Time.minutes(4)))
        .window(ProcessingTimeSessionWindows.withGap(Time.minutes(5)))
        .process(...);//定义窗口函数
  • withDynamicGap方法用于创建可动态调整gap的session窗口。动态调整session gap需要实现SessionWindowTimeGapExtractor接口,该接口定义如下:
@PublicEvolving
public interface SessionWindowTimeGapExtractor<T> extends Serializable {
	/**
	 * Extracts the session time gap.
	 * @param element The input element.
	 * @return The session time gap in milliseconds.
	 */
	long extract(T element);
 }

extract方法用于实现动态session gap的抽取逻辑。用户将实现好的动态session Gap抽取器传入withDynamicGap方法中即可。
Session窗口本质上没有固定的起止时间点,其flink底层计算逻辑实现上与Tumbling窗口及Sliding窗口有一定区别。SessionWindows会每个进入的数都创建一个窗口,最后再将距离SessionGap最近的窗口进行合并,然后计算窗口结果。因此SessionWindows需要能够合并的Trigger和WindowsFunction,如ReduceFunction、AggregateFunction、FoldFunction和ProcessWindowFunction等

1.2.4 Global Windows

全局窗口将所有相同Key的数据分配到单个窗口中计算结构,窗口没有起始和结束时间,窗口需要借助Trigger来触发计算,如果不对全局窗口指定Trigger,窗口是不会触发计算的,因此使用该类型窗口需慎重,用户需要非常明确自己在整个窗口中统计出的结果是什么,并指定对应的触发器,同时还需要指定相应的数据清理机制,否则数据将一直留在内存中。

dataStream.keyBy(0)
        //通过GlobalWindows定义全局窗口
        .window(GlobalWindows.create())
        .process(...)

全局窗口通过GlobalWindows创建。关于Trigger的定义,下面会将。

1.3 WindowsFunction

对数据集的处理中定义了Window分配器后,下一步就是实现窗口内数据的计算逻辑,即WindowsFunction的定义。
目前Flink系统提供了WindowsFunction,如ReduceFunction、AggregateFunction、FoldFunction和ProcessWindowFunction等四种类型的WindowsFunction。这四种窗口计算函数按照计算原理可分为两大类:增量聚合函数(如ReduceFunction、AggregateFunction和FoldFunction)和全量窗口函数(如ProcessWindowFunction)。

  • 增量聚合函数
    该类函数基于中间状态的计算结果,窗口中只维护中间结果状态值,不需要缓存原始数据,因此该类函数计算性能较高,占用存储空间少。
  • 全量窗口函数
    相比于增量聚合函数,全量窗口函数性能比较弱,占用存储空间较大,这是因为此时算子需要对所属于该窗口的接入数据进行缓存,然后等到窗口触发的时候,对所有的原始数据进行汇总计算。如果接入数据量比较大或窗口时间比较长,就比较有可能导致计算性能的下降。

1.3.1 ReduceFunction

Reduce函数定义了对输入的两个相同类型的数据按照预先定义的计算方法进行聚合处理,然后输出一个结果数据,输出类型与输入类型相同。在reduce()方法中指定ReduceFunction计算逻辑。有两种方法定义计算逻辑,一种是创建ReduceFunction接口的实现类,另一种是以Lambada表达式定义计算逻辑。

  1. 创建ReduceFunction接口的实现类
    接口ReduceFunction定义如下
@Public
@FunctionalInterface
public interface ReduceFunction<T> extends Function, Serializable {

	/**
	 * The core method of ReduceFunction, combining two values into one value of the same type.
	 * The reduce function is consecutively applied to all values of a group until only a single value remains.
	 *
	 * @param value1 The first value to combine.
	 * @param value2 The second value to combine.
	 * @return The combined value of both input values.
	 *
	 * @throws Exception This method may throw exceptions. Throwing an exception will cause the operation
	 *                   to fail and may trigger recovery.
	 */
	T reduce(T value1, T value2) throws Exception;
}

其中,T reduce(T value1, T value2)函数主要用来实现计算逻辑,其有两个参数,第一个参数value1是上一次调用reduce函数计算的结果,value2是数据集下一个元素。因此Reduce是一种基于中间状态的计算结果的增量计算函数,即下一次的reduce计算依赖于本次调用reduce函数计算的结果。一个简单的接口实现示例如下:

class MyReduceFunction implements ReduceFunction<Tuple2<String, Long>> {
    @Override
    public Tuple2<String, Long> reduce(Tuple2<String, Long> value1, Tuple2<String, Long> value2) throws Exception {
        return new Tuple2<>(value1.f0, value1.f1 * value1.f1);
    }
}

示例中ReduceFunction逻辑是将数据的第二个元素求积。
reduce函数指定ReduceFunction

dataStream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<Tuple2<String, Long>>() {
            @Override
            public long extractAscendingTimestamp(Tuple2<String, Long> element) {
                return element.f1;
            }
        })
                .keyBy(0)
                                .timeWindow(Time.seconds(60))
                .reduce(new MyReduceFunction())
                .print();
  1. Lambada表达式定义ReduceFunction计算逻辑
    Lambada表达式实现比较简单,直接在reduce方法中用lambada表达式方式实现计算逻辑。
//Lambada表达式实现
dataStream.
        ...
        .reduce((v1, v2) -> new Tuple2<>(v1.f0, v1.f1 * v2.f1));

1.3.2 AggregateFunction

AggregateFunction与ReduceFunction一样,也是一种基于中间计算结果的增量计算函数。AggregateFunction接口定义如下。

/**
 * @param <IN>  待聚合数据的类型(输入数据)
 * @param <ACC> accumulator类型 (中间聚集状态).
 * @param <OUT> 聚合结果的类型
 */
@PublicEvolving
public interface AggregateFunction<IN, ACC, OUT> extends Function, Serializable {

	/**
	 * 创建一个accumulator,开始聚合
	 * 除非通过add(Object, Object)添加值,否则新的accumulator通常是没有意义的
	 * The accumulator is the state of a running aggregation. When a program has multiple
	 * aggregates in progress (such as per key and window), the state (per key and window)
	 * is the size of the accumulator.
	 * @return A new accumulator, corresponding to an empty aggregate.
	 */
	ACC createAccumulator();

	/**
	 * 将给定的输入值添加到给定的accumulator,并返回新的累加器值。
	 * 为了提高效率,可以修改输入的累加器,然后再返回。
	 * @param value The value to add
	 * @param accumulator The accumulator to add the value to
	 */
	ACC add(IN value, ACC accumulator);

	/**
	 * 从accumulator获取聚合的结果
	 * @param accumulator The accumulator of the aggregation
	 * @return The final aggregation result.
	 */
	OUT getResult(ACC accumulator);

	/**
	 * 合并两个accumulators,并返回带有合并状态的accumulator 。
	 * <p>This function may reuse any of the given accumulators as the target for the merge
	 * and return that. The assumption is that the given accumulators will not be used any
	 * more after having been passed to this function.
	 *
	 * @param a An accumulator to merge
	 * @param b Another accumulator to merge
	 *
	 * @return The accumulator with the merged state
	 */
	ACC merge(ACC a, ACC b);
}

在所提供的函数中,add()方法定义数据的添加逻辑,getResult()定义由accumulator计算结果的逻辑,merge()方法定义合并accumnlator的逻辑。以下示例实现了对数据集中字段求平均值的聚合运算。

import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.java.tuple.Tuple2;

public class AverageAggregateFunction implements AggregateFunction<Tuple2<String, Long>, Tuple2<Long, Long>, Double> {
    // 创建累加器
    @Override
    public Tuple2<Long, Long> createAccumulator() {
        return new Tuple2<>(0L, 0L);
    }

    // 定义输入的数据累加地逻辑
    @Override
    public Tuple2<Long, Long> add(Tuple2<String, Long> value, Tuple2<Long, Long> accumulator) {
        return new Tuple2<>(accumulator.f0 + value.f1, ++accumulator.f1);
    }

    // 由累加器计算结果
    @Override
    public Double getResult(Tuple2<Long, Long> accumulator) {
        return accumulator.f0 * 1.0 / accumulator.f1;
    }

    // 定义累加器合并的逻辑
    @Override
    public Tuple2<Long, Long> merge(Tuple2<Long, Long> a, Tuple2<Long, Long> b) {
        return new Tuple2<>(a.f0 + a.f0, a.f1 + a.f1);
    }
}

使用定义好的聚合函数

dataStream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<Tuple2<String, Long>>() {
    @Override
    public long extractAscendingTimestamp(Tuple2<String, Long> element) {
        return element.f1;
    }
}).keyBy(0)
        .timeWindow(Time.seconds(60))
        //指定聚合函数逻辑
        .aggregate(new AverageAggregateFunction())
        .print("avg");

1.3.3 FoldFunction

FoldFunction定义了如何将窗口中的输入元素与外部的元素合并的逻辑。但FoldFunction已经被@Deprecated标记,未来可能会被移除,Flink建议用AggregateFunction来替换实现。

1.3.4 ProcessWindowFunction

ProcessWindowFunction比ReduceFunction和AggregateFunction更加灵活,用户可以利用其实现更加复杂的计算逻辑,更加灵活的支持基于窗口中全部数据或者需要操作窗口中的某些状态数据和窗口元数据等更复杂的指标统计逻辑,诸如统计窗口数据某一字段的中位数和众数。抽象类ProcessWindowFunction定义如下。

@PublicEvolving
public abstract class ProcessWindowFunction<IN, OUT, KEY, W extends Window> extends AbstractRichFunction {
	/**
	 * 评估窗口并且定义窗口输出的元素
	 * @param key The key for which this window is evaluated.
	 * @param context The context in which the window is being evaluated.
	 * @param elements The elements in the window being evaluated.
	 * @param out A collector for emitting elements.
	 *
	 * @throws Exception The function may throw exceptions to fail the program and trigger recovery.
	 */
	public abstract void process(KEY key, Context context, Iterable<IN> elements, Collector<OUT> out) throws Exception;

	/**
	 * 定义每个窗口计算结束后中间状态的清理逻辑。
	 * @param context The context to which the window is being evaluated
	 * @throws Exception The function may throw exceptions to fail the program and trigger recovery.
	 */
	public void clear(Context context) throws Exception {}

	/**
	 * 承载窗口元数据的上下文
	 */
	public abstract class Context implements java.io.Serializable {
		/**
		 * 返回正在评估的窗口
		 */
		public abstract W window();

		/** 返回窗口当前的处理时间. */
		public abstract long currentProcessingTime();

		/** 返回窗口当前的event-time的watermark. */
		public abstract long currentWatermark();

		/**
		 * State accessor for per-key and per-window state.
		 * 返回每个窗口中间状态
		 *
		 * <p><b>NOTE:</b>If you use per-window state you have to ensure that you clean it up
		 * by implementing {@link ProcessWindowFunction#clear(Context)}.
		 */
		public abstract KeyedStateStore windowState();

		/**
	     * 返回每个key对应的中间状态
		 */
		public abstract KeyedStateStore globalState();

		/**
		 * 根据OutputTag的标志输出数据记录
		 * @param outputTag the {@code OutputTag} that identifies the side output to emit to.
		 * @param value The record to emit.
		 */
		public abstract <X> void output(OutputTag<X> outputTag, X value);
	}
}

下面展示了一个基于Key统计数据最小值、最大值、平均值和求和的ProcessWindowFunction子类示例。

public class StatisticsProcessWindowFunction extends ProcessWindowFunction<Tuple2<String, Long>/*IN*/,
        Tuple6<String, Long, Long, Long, Double, Long>/*OUT*/,
        String/*KEY*/,
        TimeWindow/*W*/> {

    @Override
    public void process(String key, Context context,
                        Iterable<Tuple2<String, Long>> elements,
                        Collector<Tuple6<String, Long, Long, Long, Double, Long>> out) throws Exception {


        Long size = 0L;
        Tuple6<String, Long, Long, Long, Double, Long> tuple6 = new Tuple6<>(key, 0L, null, null, 0.0, 0L);
        elements.forEach(t -> {
            tuple6.f1 += t.f1;
            tuple6.f2 = min(tuple6.f2, t.f1);
            tuple6.f3 = max(tuple6.f3, t.f1);
            tuple6.f4 += 1;
        });
        Double avg = tuple6.f1 * 1.0 / tuple6.f4;
        Long windowsEndTime = context.window().getEnd();
        tuple6.f4 = avg;
        tuple6.f5 = windowsEndTime;

        //通过out.collect()返回计算结果
        out.collect(tuple6);

    }

    private Long min(Long a, Long b) {
        if (null == a) {
            return b;
        }
        if (null == b) {
            return b;
        }
        return Math.min(a, b);
    }

    private Long max(Long a, Long b) {
        if (null == a) {
            return b;
        }
        if (null == b) {
            return b;
        }
        return Math.max(a, b);
    }
}

示例并不操作状态数据,只需要实现Process方法即可。使用StatisticsProcessWindowFunction 只需要在API中用process方法指定即可,如下所示。

dataStream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<Tuple2<String, Long>>() {
    @Override
    public long extractAscendingTimestamp(Tuple2<String, Long> element) {
        return element.f1;
    }
}).keyBy(0)
        .timeWindow(Time.seconds(60))
        .process(new StaticProcessWindowFunction())
        .print("pro");

使用ProcessWindowFunction完成简单的聚合运算非常浪费,使用ProcessWindowFunction时,要明确自己的业务场景,选择合适的WindowFunction来统计,没必要不不建议用ProcessWindowFunction。
增量聚合函数如ReduceFunction和AggregateFunction虽然在一定程度上能够提升窗口计算的性能,但这些窗口的灵活性不足。若计算逻辑涉及到对窗口桩体数据的操作以及对窗口中元数据信息的获取等就无法用ReduceFunction等函数实现,要用ProcessWindowFunction完成。
如果用ProcessWindowFunction完成一些基础的增量计算运算计算相对比较浪费系统资源,此时可以利用IncrementalAggregateWindowFunction和ProcessWindowFunction相结合的方式实现,以充分利用两种函数各自的优势。Flink DataStream API提供了实现ProcessWindowFunction和IncrementalAggregateWindowFunction整合的方法。

ProcessWindowFunction状态操作

与RichFunction所操作的Keyed State不同,ProcessWindowFunction操作的是基于窗口之上的状态数据:Per-Window State。状态数据针对指定的Key在窗口上存储,例如将用户ID作为Key,计算每个用户最近一个小时在线情况,如果平台上一共藕1000用户,则窗口计算中会创建1000个窗口实例,每个窗口实例中都会保存每个key的状态数据。可以通过ProcessWindowFunction提供的上下文Context获取并操作Per-window State数据。Per-window state在ProcessWindowFunction有两种类型:

  • globalSate
    窗口中的keyed state数据不限定在某个窗口中;
  • windowState
    Key state限定在固定的窗口中。

这些状态数据适合于针对迟到数据触发窗口计算,或在同一窗口多次触发计算的场景。使用Per-window state数据要注意及时清理状态数据,清理状态数据可调用ProcessWindowFunction的clear()方法。

1.3.5 ProcessWindowFunction整合IncrementalAggregateWindowFunction实现

Flink DataStream API提供了实现ProcessWindowFunction和IncrementalAggregateWindowFunction整合的方法。

1.4 窗口触发器(Trigger)

Trigger主要触发windowFunction的计算。Trigger定义了触发窗口计算的条件,不同类型的窗口有不同的窗口触发机制。

1.4.1 Flink的窗口触发器

目前Flink的每类窗口都有响应的Trigger,保证每次接入窗口的数据都能够安装触发逻辑触发计算。Flink定义了EventTimeTrigger、ProcessTimeTrigger和CountTrigger等窗口触发机制。

  • EventTimTrigger
    通过对比Watermark和窗口EndTime确定是否触发计算。如果Watermark大于EndTime则触发计算,否则窗口继续等待;
  • ProcessTrigger
    通过对比ProcessTime和窗口EndTime确定是否触发窗口计算。如果ProcessTime大于EndTime则触发计算,否则窗口继续等待;
  • ContinuousEventTimeTrigger
    根据间隔时间周期性触发窗口计算或者window的结束时间小于当前EventTime触发计算。
  • ContinuousProcessingTrigger
    根据间隔周期性触发窗口计算或者window的结束时间小于当前ProcessTime触发计算。
  • CountTrigger
    根据接入的数据量是否超过设定的阈值确定是否触发计算。
  • DeltaTrigger
    根据接入的数据计算出的Delta指标是否超过设定的阈值,判断是否触发计算。
  • PurgingTrigger
    可以将任意触发器作为参数转换为Purge类型触发器,数据将在计算完成后被清理掉。

1.4.2 自定义窗口触发器

如果以上Flink提供的触发器无法满足用户需求,可以通过继承并实现抽象类Trigger自定义触发器。

@PublicEvolving
public abstract class Trigger<T, W extends Window> implements Serializable {

	/**
	 * Called for every element that gets added to a pane. The result of this will determine
	 * whether the pane is evaluated to emit results.
	 *
	 * @param element The element that arrived.
	 * @param timestamp The timestamp of the element that arrived.
	 * @param window The window to which the element is being added.
	 * @param ctx A context object that can be used to register timer callbacks.
	 */
	public abstract TriggerResult onElement(T element, long timestamp, W window, TriggerContext ctx) throws Exception;

	/**
	 * Called when a processing-time timer that was set using the trigger context fires.
	 *
	 * @param time The timestamp at which the timer fired.
	 * @param window The window for which the timer fired.
	 * @param ctx A context object that can be used to register timer callbacks.
	 */
	public abstract TriggerResult onProcessingTime(long time, W window, TriggerContext ctx) throws Exception;

	/**
	 * Called when an event-time timer that was set using the trigger context fires.
	 *
	 * @param time The timestamp at which the timer fired.
	 * @param window The window for which the timer fired.
	 * @param ctx A context object that can be used to register timer callbacks.
	 */
	public abstract TriggerResult onEventTime(long time, W window, TriggerContext ctx) throws Exception;

	/**
	 * Returns true if this trigger supports merging of trigger state and can therefore
	 * be used with a
	 * {@link org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner}.
	 *
	 * <p>If this returns {@code true} you must properly implement
	 * {@link #onMerge(Window, OnMergeContext)}
	 */
	public boolean canMerge() {
		return false;
	}

	/**
	 * Called when several windows have been merged into one window by the
	 * {@link org.apache.flink.streaming.api.windowing.assigners.WindowAssigner}.
	 *
	 * @param window The new window that results from the merge.
	 * @param ctx A context object that can be used to register timer callbacks and access state.
	 */
	public void onMerge(W window, OnMergeContext ctx) throws Exception {
		throw new UnsupportedOperationException("This trigger does not support merging.");
	}

	/**
	 * Clears any state that the trigger might still hold for the given window. This is called
	 * when a window is purged. Timers set using {@link TriggerContext#registerEventTimeTimer(long)}
	 * and {@link TriggerContext#registerProcessingTimeTimer(long)} should be deleted here as
	 * well as state acquired using {@link TriggerContext#getPartitionedState(StateDescriptor)}.
	 */
	public abstract void clear(W window, TriggerContext ctx) throws Exception;

}

函数说明
onElement对每一个接入窗口的数据元素决定是否触发操作
onProcessingTime根据接入窗口 eventTime进行触发操作
onEventTime根据接入窗口的processTime进行触发操作
onMerge对多个窗口进行合并操作,同时进行状态的合并
clear执行窗口及状态数据的清除

onElement方法返回结果TriggerResult是一个枚举类,有以下类型。

类型说明
CONTINUE当前不触发计算,继续等待
FIRE_AND_PURGE触发计算,并清除对应的数据。
FIRE触发计算,但是数据继续保留
PURGE清除窗口内部数据,但不触发计算

onElemen根据预先定义的触发逻辑,返回以上状态给Flink,由Flink在窗口计算过程中,根据返回的状态决定是否触发对当前窗口的数据进行计算。

1.5 数据剔除器(Evictor)

数据剔除器是窗口机制的可选组件,其主要作用是在trigger触发后,数据进入窗口,WindowFunction被执行之前或执行之后的数据进行剔除处理。Evictor由方法evictor(...)指定。

1.5.1 Flink数据剔除器

  1. CountEvictor
    保持在窗口汇总具有预先指定的最大数量的数据。超过指定的最大数量maxCount的数据将在窗口中剔除。其核心剔除函数实现源码如
private void evict(Iterable<TimestampedValue<Object>> elements, int size, EvictorContext ctx) {
	if (size <= maxCount) {
		return;
	} else {
		int evictedCount = 0;
		for (Iterator<TimestampedValue<Object>> iterator = elements.iterator(); iterator.hasNext();){
			iterator.next();
			evictedCount++;
			if (evictedCount > size - maxCount) {
				break;
			} else {
				iterator.remove();
			}
		}
	}
}

其中size是窗口中元素的总数,maxCount是设定的窗口中应保留的数据最大量。
2. DeltaEvictor
基于定义的DeltaFunction函数和指定的threshold,计算窗口中元素与新进入元素之间额Delta,如果超过了阈值,将剔除超过阈值之后的新进入元素。其核心剔除函数实现源码如:

private void evict(Iterable<TimestampedValue<T>> elements, int size, EvictorContext ctx) {
		TimestampedValue<T> lastElement = Iterables.getLast(elements);
		for (Iterator<TimestampedValue<T>> iterator = elements.iterator(); iterator.hasNext();){
			TimestampedValue<T> element = iterator.next();
			if (deltaFunction.getDelta(element.getValue(), lastElement.getValue()) >= this.threshold) {
				iterator.remove();
			}
		}
	}
  1. TimeEvictor
    通过指定windowSize,以窗口中最新元素的Timestamp作为current_time减去windowSize,计算相应的current_time - keep_time,其中current_time是窗口中最新元素的Timestamp,keep_time是windowSize。如果元素的Timestamp时间小于该值,就剔除该值。TimeEvictor的本质是讲具有最新时间的数据选择处理,删掉过时的数据。TimeEvictor的核心剔除函数实现源码如:
private void evict(Iterable<TimestampedValue<Object>> elements, int size, EvictorContext ctx) {
   	if (!hasTimestamp(elements)) {
   		return;
   	}

   	long currentTime = getMaxTimestamp(elements);
   	long evictCutoff = currentTime - windowSize;

   	for (Iterator<TimestampedValue<Object>> iterator = elements.iterator(); iterator.hasNext(); ) {
   		TimestampedValue<Object> record = iterator.next();
   		if (record.getTimestamp() <= evictCutoff) {
   			iterator.remove();
   		}
   	}
   }

参数elements是窗口中所有的元素,currentTime = getMaxTimestamp(elements)

以上每个剔除器都有多个of方法用于创建剔除器,默认是在WindowsFunction计算前剔除数据,如要在之后剔除数据,可以用相应的of()方法将参数doEvictAfter设为true。

1.5.2 自定义数据剔除器

flink提供的剔除器不满足需求时,可以通过实现Evictor接口自定义剔除器。接口Evictor有两个方法,如下

void evictBefore(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext);
void evictAfter(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext);

方法evictBefore() 定义了数据在window function计算之前剔除的逻辑, 而 evictAfter() 则定义了在计算之后剔除的逻辑。参数elements代表当前窗口中所有的数据元素。CountEvictor等剔除器都是在evict方法中定义了一个共用的剔除逻辑,然后分别在evictBefore和evictAfter调用evict方法。
应用剔除器需要注意

  • Specifying an evictor prevents any pre-aggregation, as all the elements of a window have to be passed to the evictor before applying the computation.
  • Flink provides no guarantees about the order of the elements within a window. This implies that although an evictor may remove elements from the beginning of the window, these are not necessarily the ones that arrive first or last.

1.6 延迟数据处理

watermark机制一定程度上解决了数据乱序问题,但是如果数据延时非常严重,watermark机制也无法保证数据全部进入到窗口再处理。Flink默认会将这些延迟数据丢弃不处理。但有些场景即使数据延迟到达,要希望这些延迟数据也能按照流程处理并输出结果,此时就需要使用allowed Lateness机制来对延迟 数据进行额外处理。DataStreamAPI中提供了allowedLateness方法来指定是否对延迟数据进行处理。

DataStream<T> input = ...;
input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .allowedLateness(<time>)
    .<windowed transformation>(<window function>);

allowedLateness参数time是Time类型的时间大小,表示允许延时的最大时间,window函数计算过程中会将窗口EndTime加上该值作为窗口最后被释放的结束时间(EndTime+time),当数据的EventTime<(EndTime+time),但watermark已经超过EndTime时,直接触发窗口计算。如果EventTime>(EndTime+time),则丢弃数据。
GlobalWindows的最大时延时间为 Long.MAX_VALUE,即永不超时,数据会源源不断地累积到窗口中,等待触发。其他窗口的默认延迟时间为0,即不允许有延时数据。

  • 延时数据的处理结果的单独输出

延时数据处理后,并不一定要立刻混入正常的计算流程中,而是希望将延时数据或处理结果存储到数据库或其他存储系统中,便于后期对延时数据的分析。此时可利用side output机制处理,先调用sideOutputLateData(OutputTag) 方法标记延时数据计算的结果,然后调用getSideOutput(lateOutputTag)方法从窗口结果流中获取lateOutputTag标签标记的数据,最后再用DataStream流处理方法对数据做处理。

//创建延时数据的OutputTag
final OutputTag<T> lateOutputTag = new OutputTag<T>("late-data"){};
DataStream<T> input = ...;

SingleOutputStreamOperator<T> result = input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .allowedLateness(<time>)
    //对结果延时数据进行标记
    .sideOutputLateData(lateOutputTag)
    .<windowed transformation>(<window function>);
//通过lateOutputTag从窗口结果红获取标记的结果数据
DataStream<T> lateStream = result.getSideOutput(lateOutputTag);
未完待续

参考文档

Flink窗口 Flink窗口计算 / 流计算 参考:https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/windows.html 概述 窗口计算是流计算的核心,通过窗口将一个无线的数据流在时间轴上切分成有限大小的数据集-bucket,然后在对切分后的数据做计算。Flink根据流的特点将窗口计算分为两大类... 阅读详情

相关推荐

Flink大数据实时计算系列-Flink的windows join

Flink大数据实时计算系列-Flink的windows join 目录 Flink的windows joinFlink参考链接Flink系列博客 Flink的windows join Flink参考链接 【Flink 官网】Apache Flink 【海牛学院】海牛社区资料 【海牛学院】大数据2020最新版Flink-青牛老师倾力打造 Flink系列博客 Flink 第一个 JAVA代码 Flink JAVA Lambda 实现 Flink JAVA Funcation 实现 Fli

大模型与Agent智能体 483

Flink中的Window计算-增量计算&全量计算

flink是一个流处理引擎,可以实现基于每条消息实时计算,但是在有些业务系统开发中,并不需要按照消息维度的数据计算,更多的是指定时间内的一批消息的计算,比如:过去1分钟内,产生消息个数、消息中的最大值等。这个时候,就需要对数据流按照时间切分成一个个小的“时间窗口”,然后对这个时间窗口内的数据按照自定义的处理逻辑进行处理。

weixin_45701550的博客 3068

Flink大数据实时计算系列-Flink的Session Windows使用

Flink大数据实时计算系列-Flink的Session Windows使用 目录 Flink的Session Windows使用Flink参考链接Flink系列博客 Flink的Session Windows使用 示例:不能按用户点击时间,也不能按用户点击次数统计,需按用户Session统计时间窗口。 代码实战 package com.flinkniu.flink.windows; import jdk.nashorn.internal.runtime.regexp.joni.Config; im

大模型与Agent智能体 691

Flink的Window(窗口计算)

Window(窗口计算) 窗口计算是流计算的核心,通过使用窗口对无限的流数据划分成固定大小的 buckets,然后基于落入同一个bucket(窗口)中的元素执行计算。Flink将窗口计算分为两大类。 一类基于keyed-stream窗口计算。 stream .keyBy(...) <- 分组 .window(...) <- 必须: "assigner" 窗口分配器 [.trigger(...)]

weixin_45316851的博客 1707

Flink大数据实时计算系列-Flink的Keyed Windows 对比 Non-Keyed Windows的区别

Flink大数据实时计算系列-Flink的Keyed Windows 对比 Non-Keyed Windows的区别 目录 Flink的Keyed Windows 对比 Non-Keyed Windows的区别Flink参考链接Flink系列博客 Flink的Keyed Windows 对比 Non-Keyed Windows的区别 Flink参考链接 【Flink 官网】Apache Flink 【海牛学院】海牛社区资料 【海牛学院】大数据2020最新版Flink-青牛老师倾力打造 Flin

大模型与Agent智能体 782

Flink大数据实时计算系列-Flink的Sliding Windows使用

Flink大数据实时计算系列-Flink的Sliding Windows使用 目录 Flink的Sliding Windows使用Flink参考链接Flink系列博客 Flink的Sliding Windows使用 Flink参考链接 【Flink 官网】Apache Flink 【海牛学院】海牛社区资料 【海牛学院】大数据2020最新版Flink-青牛老师倾力打造 Flink系列博客 Flink 第一个 JAVA代码 Flink JAVA Lambda 实现 Flink JAVA Fu

大模型与Agent智能体 660

Flink的触发器Trigger介绍和使用

触发器定义了window何时会被求值以及何时发送求值结果。触发器可以到了特定的时间触发也可以碰到特定的事件触发。例如:观察到事件数量符合一定条件或者观察到了特定的事件。 默认的触发器将会在两种情况下触发 处理时间:机器时间到达处理时间 事件时间:水位线超过了窗口的结束时间 触发器可以访问流的时间属性以及定时器,还可以对state状态编程。所以触发器和process function一样强大。例如我...

sghuu的博客 8453

Flink大数据实时计算系列-Flink的windows的窗口函数练习

Flink大数据实时计算系列-Flink的windows的窗口函数练习 目录 Flink的windows的窗口函数练习使用reduce案例代码使用AggregateFunction案例代码apply 方法获取窗口全量数据Flink参考链接Flink系列博客 Flink的windows的窗口函数练习 使用reduce案例代码 package com.flinkniu.flink.windows; import org.apache.flink.api.common.functions.ReduceFunc

大模型与Agent智能体 585

【深入浅出flink】第10篇:聊聊flink窗口计算的触发器(Trigger)

Trigger(触发器)决定了什么时候窗口准备就绪了,一旦窗口准备就绪就可以使用WindowFunction(窗口计算操作)进行计算。每一个 WindowAssigner(窗口分配器) 都会有一个默认的Trigger。如果默认的Trigger不满足用户的需求,用户可以自定义Trigger。

雷恩Layne 8369

Flink大数据实时计算系列-Flink的Global Windows使用(trigger、evictor)

Flink大数据实时计算系列- 目录 Flink的Global Windows使用Flink参考链接Flink系列博客 Flink的Global Windows使用 package com.flinkniu.flink.windows; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple.Tuple3; import org.apache.flink.configu

大模型与Agent智能体 945

边表法

边表法是用来绘制填充一个面的,边表法的关键是边表和连接边表的扫描线。 对于边的描述利用边表数据结构,包含边与扫描线的交点x坐标,边的最大y坐标,这样可以知道边加入到哪些扫描线上,便还有一个指针来记录扫描线上其它边。 //边数据结构 #pragma once #ifndef __CAETZ__ #define __CAETZ__ #include "pch.h" #include "CPoin...

weixin_42376458的博客 2101

浅谈Flink 窗口计算(Windows)

窗⼝计算是流计算的核⼼,窗⼝将流数据切分成有限⼤⼩的“buckets”,我们可以对这个“buckets”中的有 限数据做运算。 在Flink中整体将窗⼝计算按分为两⼤类:keyedstream窗⼝、datastream窗⼝,以下是代码结构: Keyed Windows: Non-Keyed Windows: Window Lifecycle (窗口生命周期) 当有第⼀个元素落⼊到窗⼝中的时候窗...

qq_45536740的博客 628

在边缘部署实时视觉应用程序

克隆代码库后,压缩源文件夹的内容,并将生成的存档文件上传到上一节中创建的 S3 存储桶中。在本教程中,我们将使用 AWS Greengrass,因为它支持大多数 Windows 和 Linux 设备,包括我们的目标 Jetson AGX Xavier。此外,模型还可以量化到更低的精度,从而加快推理速度,但对于较大的模型,此过程通常较长(约 45 分钟)。本例中的脚本只是启动一个 Bash 环境,切换到解压后的构件目录,最后使用所需的命令行参数运行 Deepstream 应用。每个模型类型都有其独有的格式。

2501_91623519的博客 1286

Flink Windows窗口计算

Flink Windows窗口计算 点关注不迷路,欢迎再访! 滚动窗口完整案例 /** * @author ex_sunqi * */ @Component public class KafkaFlinkJob implements ApplicationRunner { private final static Logger logger = LoggerFactor...

最初的节奏 829

生产实习-实习报告-Raspberry Pi的应用

生产实习-Raspberry Pi的应用 一.设计任务: 1、搭建树莓派开发环境 主要包括烧录树莓派操作系统,远程访问树莓派,树莓派接口配置以及树莓派与windows实现文件共享等。 2、树莓派的基本操作 控制GPIO点亮LED灯以及树莓派舵机云台控制 3、安装OpenCV及图像采集实验 OpenCV配合树莓派,直接读取来自树莓派摄像头Pi Camera的视频,进行人脸识别、边缘检测、颜色判别等各种计算机视觉开发。 4、树莓派小车远程Wi-Fi控制 使用树莓派驱动L298N,用树莓派板载Wi-Fi控制

青竹小轩的博客 750

Cadence Virtuoso VIVA波形分析:从背景色修改到线宽调整的完整指南

本文详细介绍了Cadence Virtuoso VIVA波形分析工具的核心使用技巧,包括背景色修改、线宽调整等高级功能。通过具体操作步骤和配置方法,帮助工程师优化波形显示效果,提升工作效率。特别强调了与Calculator组件的配合使用,以及专业波形图的输出技巧。

weixin_27330481的博客 621

Flink Windows 窗口计算

Flink WindowsKeyed WindowsWindow Lifecycle 窗口生命周期Keyed vs Non-Keyed WindowsWindow Assigners 窗口指定Tumbling Windows 滚动窗口Sliding Windows 滑动窗口Session Windows 会话窗口Global Windows 全局窗口Window FunctionsReduc...

weixin_46001623的博客 1376

STM32 USART高级功能全解析:IrDA、DMA、硬件流控与低功耗唤醒

USART是嵌入式系统中最基础的串行通信外设,其核心原理在于同步/异步时序控制与数据帧收发机制。现代MCU如STM32通过深度集成IrDA调制解调、DMA直传通道、RS232/RS485硬件流控及Stop模式唤醒等扩展能力,显著提升通信可靠性、吞吐效率与能效比。其中,IrDA SIR采用RZI编码实现抗干扰红外传输,DMA通过TXE/RXNE事件驱动实现零CPU干预的连续数据流,而硬件流控(RTS/CTS/DE)和低功耗唤醒(WUF+UESM)则分别保障工业现场鲁棒性与电池级超长待机。本文围绕STM32平台

weixin_28922227的博客 211

Flink流式计算之windows介绍与说明

1 介绍 streaming流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增长的本质上无限的数据集,而window是一种切割无限数据为有限块进行处理的手段。 Window可以分成两类:  CountWindow:按照指定的数据条数生成一个Window,与时间无关。  TimeWindow:按照时间生成Window。 对于TimeWindow,可以根据窗口实现原理的不同分成三类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)和会话窗口(

不以物喜的博客 409

手把手教你用OpenCompass评估Qwen1.5-1.8B模型(含数据集配置详解)

本文提供了使用OpenCompass评估Qwen1.5-1.8B-Chat模型的详细实战教程。内容涵盖从创建独立Python环境、下载模型与核心数据集,到编写配置文件、执行评测命令及解读结果的完整流程。重点解析了模型与数据集的配置细节,并分享了性能调优与常见问题排查方法,旨在帮助开发者快速上手系统化的模型评估。

weixin_29228781的博客 137

基于知识图谱的时空事件关联推理能力培养

随着信息技术的飞速发展,大量的时空事件数据被产生和收集,如交通流量数据、气象观测数据、社交媒体上的地理定位信息等。这些数据蕴含着丰富的知识和规律,但要从中提取有价值的信息并非易事。基于知识图谱的时空事件关联推理旨在通过构建知识图谱,将时空事件数据进行整合和关联,从而实现对事件之间潜在关系的挖掘和推理。本文的目的是系统地介绍基于知识图谱的时空事件关联推理能力的培养方法,包括核心概念、算法原理、数学模型、实际应用等方面。

操作系统内核探秘的博客 750

(十)Flink Datastream API 编程指南 算子-2 Windows窗口计算

窗口是处理无限流的核心。Windows将流分成有限大小的“桶”,我们可以在桶上应用计算。本文主要讨论如何在Flink中执行窗口操作,以及程序员如何最大限度地利用Flink提供的功能。一个带窗口的Flink程序的总体结构如下所示。第一个片段引用键控流,第二个片段引用非键控流。可以看到,唯一的区别是键控流的keyBy(…)调用和非键控流的window(…)调用,后者变成windowall(…)。这也将作为页面其余部分的路线图。Keyed Windows Non-Keyed Windows 上面的方括号([…])

京河小蚁的博客 854

南 京 农 业 大 学 试 题 纸 附表(rape.csv)是25个油菜材料,测得每个材料的千粒重、亚油酸等的含量. (1) 对物质含量的八个指标作主成分分析,画出碎石图和主成分得分图; (2) 选

(1)请用n1

Mrrunsen的博客 498

Flink静态Session Windows

1.Session Windows 会话窗口分配器按活动会话对元素进行分组。与翻滚窗口和滑动窗口相比,会话窗口不重叠并且没有固定的开始和结束时间。当会话窗口在一段时间内没有接收到元素时,即当发生不活动的间隙时,会话窗口关闭。会话窗口分配器可以设置静态会话间隙和动态会话间隙(其中会话间隙为session gap)。如下图: 2.使用Flink自带的示例: package org.apache.fl...

weixin_42412645的博客 295

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

窗口的类型_Flink的窗口类型详解

这是我的第87篇原创窗口函数真奇妙,聚合计算快又好,数据分析宝中宝,表哥表妹不能少,不!能!少!在我刚入行的时候,还不懂啥窗口函数,想出一张报表那叫一个费劲啊,各种自关联、行转列、列转行玩的不亦乐乎。后来知道有个叫“窗口函数/聚合函数”的东西,简直把我惊呆了,感觉好几年的功夫都白练了。之前给大家分享过Hive的窗口函数全解,点击查看:【面试必问】窗口函数全解-HIVE。无限数据流的统计问...

weixin_29865939的博客 866

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

pulsar+flink组合流测试

pulsar解决了消息传输的问题。但是实时计算采用什么技术呢,之前楼主一直使用的是spark,但是偶然的机会发现了flink(阿里开源的blink就是基于此做的,预计会在1.9版本将blink糅合到flink中),因为最近需要将流计算的东西整理一下,和现在线上环境pulsar组合到一起使用。所以做了一下pulsar与flink流计算的测试。 pulsar git代码位置 https://githu...

weixin_43832846的博客 3419

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: 如何在Flink中生成WaterMark
下一篇: tf.nn.conv1d(或tf.compat.v1.nn.conv1d)函数
Accelerating
博客等级 码龄10年 3220粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值