Flink-算子-Process Function

ProcessFunction 是一个低级流处理操作,可以访问所有(非循环)流应用程序的基本构建块:

  • events (stream elements) 事件 (流元素)
  • state (fault-tolerant, consistent, only on keyed stream)
    状态(容错、一致、仅在键控流上)
  • timers (event time and processing time, only on keyed stream)
    计时器(事件时间和处理时间,仅在键控流上)

ProcessFunction 可以看作是一个 FlatMapFunction,可以访问键控状态计时器。它通过为输入流中接收的每个事件(数据)调用来处理事件。

对于容错状态ProcessFunction 允许访问 Flink 的 keyed state,可通过 RuntimeContext 访问容错状态,类似于其他有状态函数访问 keyed state 的方式。

计时器允许应用程序对处理时间事件时间的变化做出反应。每次调用函数 processElement(...) 都会获得一个 Context 对象,该对象可以访问元素的事件时间戳和 TimerServiceTimerService 可用于为将来的事件/处理时间 Moment 注册回调。对于事件时间计时器,当前水印前进到或超过计时器的时间戳时,将调用 onTimer(...) 方法,而对于处理时间计时器,当挂钟时间达到指定时间时,将调用 onTimer(...)。在该调用期间,所有状态的作用域再次限定为创建计时器时使用的键,从而允许计时器操作键控状态。

如果要访问键控状态和计时器,则必须在键控流上应用 ProcessFunction

stream.keyBy(...).process(new MyProcessFunction());

Low-level Joins(低级联接

若要对两个 输入流 实现低级联接操作,应用程序可以使用 CoProcessFunction 或 KeyedCoProcessFunction。此函数绑定到两个不同的输入,并获取对来自两个不同输入的记录的 processElement1(...) 和 processElement2(...) 的单独调用。

实现低级别联接通常遵循以下模式:

  • Create a state object for one input (or both)
    为一个 Input(或两个)创建一个 state 对象
  • Update the state upon receiving elements from its input
    从输入接收元素时更新状态
  • Upon receiving elements from the other input, probe the state and produce the joined result
    从另一个 input 接收到元素后,探测 state 并生成 join 结果

例如,您可能将客户数据联接到金融交易中,同时保留客户数据的状态。如果您关心在面对无序事件时具有完整和确定性的联接,则可以使用计时器来评估并在客户数据流的水印超过该交易的时间时发出该交易的联接。

Example # 示例 

在以下示例中,KeyedProcessFunction 维护每个键的计数,并在一分钟过去时(在事件时间内)发出一个键/计数对,而该键没有更新:

      1. count、key 和 last-modification-timestamp 存储在 ValueState 中,该 ValueState 的作用域由 key 隐式限定。

      2. 对于每条记录,KeyedProcessFunction 递增计数器并设置上次修改时间戳

      3.该函数还会在未来一分钟(以事件时间为单位)安排回调

      4.在每次回调时,它会根据存储计数的最后修改时间检查回调的事件时间戳,如果它们匹配,则发出 key/count(即,在该分钟内没有发生进一步的更新)

这个简单的示例可以通过会话窗口实现。我们在这里使用 KeyedProcessFunction 来说明它提供的基本模式。

import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction.Context;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction.OnTimerContext;
import org.apache.flink.util.Collector;


// the source data stream
DataStream<Tuple2<String, String>> stream = ...;

// apply the process function onto a keyed stream
DataStream<Tuple2<String, Long>> result = stream
    .keyBy(value -> value.f0)
    .process(new CountWithTimeoutFunction());

/**
 * The data type stored in the state,存储在状态中的数据类型
 */
public class CountWithTimestamp {

    public String key;
    public long count;
    public long lastModified;
}

/**
 * The implementation of the ProcessFunction that maintains the count and timeouts
* 维护计数和时间的过程函数的实现
 */
public class CountWithTimeoutFunction 
        extends KeyedProcessFunction<Tuple, Tuple2<String, String>, Tuple2<String, Long>> {

    /** The state that is maintained by this process function,此过程函数所维护的状态 */
    private ValueState<CountWithTimestamp> state;

    @Override
    public void open(OpenContext openContext) throws Exception {
        state = getRuntimeContext().getState(new ValueStateDescriptor<>("myState", CountWithTimestamp.class));
    }

    @Override
    public void processElement(
            Tuple2<String, String> value, 
            Context ctx, 
            Collector<Tuple2<String, Long>> out) throws Exception {

        // retrieve the current count 检索当前计数
        CountWithTimestamp current = state.value();
        if (current == null) {
            current = new CountWithTimestamp();
            current.key = value.f0;
        }

        // update the state's count
        current.count++;

        // set the state's timestamp to the record's assigned event time timestamp,将状态的时间戳设置为记录的指定事件时间戳
        current.lastModified = ctx.timestamp();

        // write the state back
        state.update(current);

        // schedule the next timer 60 seconds from the current event time,安排下一个计时器从当前事件时间开始60秒
        ctx.timerService().registerEventTimeTimer(current.lastModified + 60000);
    }

    @Override
    public void onTimer(
            long timestamp, 
            OnTimerContext ctx, 
            Collector<Tuple2<String, Long>> out) throws Exception {

        // get the state for the key that scheduled the timer,获取调度计时器的密钥的状态
        CountWithTimestamp result = state.value();

        // check if this is an outdated timer or the latest timer,
        if (timestamp == result.lastModified + 60000) {
            // emit the state on timeout,在超时状态下发出状态
            out.collect(new Tuple2<String, Long>(result.key, result.count));
        }
    }
}

The KeyedProcessFunction

KeyedProcessFunction 作为 ProcessFunction 的扩展,在其 onTimer(...) 方法中提供对计时器键的访问;

@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<OUT> out) throws Exception {
    K key = ctx.getCurrentKey();
    // ...
}

Timers # 计时器

两种类型的计时器(processing-time 和 event-time)都由 TimerService 内部维护,并排队等待执行。

TimerService 按键和时间戳删除重复的计时器,即每个键和时间戳最多有一个计时器。如果为同一时间戳注册了多个计时器,则 onTimer() 方法将只调用一次。

Flink 同步 onTimer() 和 processElement() 的调用。因此,用户不必担心 state 的并发修改。

Fault Tolerance (容错)

计时器具有容错能力,并与应用程序的状态一起设置检查点。如果发生故障恢复或从 Savepoint 启动应用程序,计时器将被恢复。

本应在恢复之前触发的检查点处理时间计时器将立即触发。当应用程序从故障中恢复或从 Savepoint 启动时,可能会发生这种情况。

计时器总是异步检查点的,除了 RocksDB 后端 / 与增量快照 / 基于堆的计时器的组合(将通过 FLINK-10026 解决)。请注意,大量计时器可能会增加检查点时间,因为计时器是检查点状态的一部分。有关如何减少计时器数量的建议,请参阅 “Timer Coalescing” 部分

Timer Coalescing(计时器合并)

由于 Flink 为每个键和时间戳只维护一个计时器,因此您可以通过降低计时器分辨率来合并它们来减少计时器的数量。

对于 1 秒(事件或处理时间)的计时器分辨率,您可以将目标时间四舍五入到整秒。计时器将以毫秒级精度触发,但不会晚于请求的 1 秒。因此,每个键和秒最多有一个计时器。

long coalescedTime = ((ctx.timestamp() + timeout) / 1000) * 1000;
ctx.timerService().registerProcessingTimeTimer(coalescedTime);

由于事件时间计时器仅在 watermark 传入时触发,因此您还可以使用当前 watermark 来安排这些计时器并将其与下一个 watermark 合并:

long coalescedTime = ctx.timerService().currentWatermark() + 1;
ctx.timerService().registerEventTimeTimer(coalescedTime);

还可以按如下方式停止和删除计时器: 

停止处理时间计时器:

long timestampOfTimerToStop = ...;
ctx.timerService().deleteProcessingTimeTimer(timestampOfTimerToStop);

停止事件时间计时器:

long timestampOfTimerToStop = ...;
ctx.timerService().deleteEventTimeTimer(timestampOfTimerToStop);

如果未注册具有给定时间戳的此类计时器,则停止计时器不起作用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值