Flink协调器Coordinator及自定义Operator

Flink协调器Coordinator及自定义Operator

最近的项目开发过程中,使用到了Flink中的协调器以及自定义算子相关的内容,本篇文章主要介绍Flink中的协调器是什么,如何用,以及协调器与算子间的交互。

协调器Coordinator

Flink中的协调器是用来协调运行时的算子,运行在JobManager中,通过事件的方式与算子通信。例如Source和Sink算子中的协调器是用来发现和分配工作或者聚合和提交元数据。

线程模型

所有协调器方法都由作业管理器的主线程(邮箱线程)调用。这意味着这些方法在任何情况下都不得执行阻塞操作(如 I/ O 或等待锁或或Futures)。这很有可能使整个 JobManager 瘫痪。
因此,涉及更复杂操作的协调器应生成线程来处理 I/ O 工作。上 OperatorCoordinator. Context 的方法可以安全地从另一个线程调用,而不是从调用协调器方法的线程调用。

一致性

与调度程序的视图相比,协调器对任务执行的视图高度简化,但允许与在并行子任务上运行的操作员进行一致的交互。具体而言,保证严格按顺序调用以下方法:

  1. executionAttemptReady(int, int, OperatorCoordinator.SubtaskGateway):在子任务就绪的时候调用一次。SubtaskGateway是用来与子任务交互的网关。这是与子任务尝试交互的开始。
    executionAttemptFailed(int, int, Throwable):在尝试失败或取消后立即调用每个子任务。此时,应停止与子任务尝试的交互。
  2. subtaskReset(int, long) 或 resetToCheckpoint(long, byte[]):一旦调度程序确定了要还原的检查点,这些方法就会通知协调器。前一种方法在发生区域故障/ 恢复(可能影响子任务的子集)时调用,后一种方法在全局故障/ 恢复的情况下调用。此方法应用于确定要恢复的操作,因为它会告诉要回退到哪个检查点。协调器实现需要恢复自还原的检查点以来与相关任务的交互。只有在子任务的所有尝试被调用后 executionAttemptFailed(int, int, Throwable) ,才会调用它。
  3. executionAttemptReady(int, int, OperatorCoordinator. SubtaskGateway):在恢复的任务(新尝试)准备就绪后再次调用。这晚于 subtaskReset(int, long),因为在这些方法之间,会计划和部署新的尝试。

接口方法说明

实现自定义的协调器需要实现OperatorCoordinator接口方法,各方法说明如下所示:

public interface OperatorCoordinator extends CheckpointListener, AutoCloseable {
   


    // ------------------------------------------------------------------------

    /**
     * 启动协调器,启动时调用一次当前方法在所有方法之前
     * 此方法抛出的异常都会导致当前作业失败
     */
    void start() throws Exception;

    /**
     * 释放协调器时调用当前方法,此方法应当释放持有的资源
     * 此方法抛出的异常不会导致作业失败
     */
    @Override
    void close() throws Exception;

    // ------------------------------------------------------------------------

    /**
     * 处理来自并行算子实例的事件
     * 此方法抛出的异常会导致作业失败并恢复
     */
    void handleEventFromOperator(int subtask, int attemptNumber, OperatorEvent event)
            throws Exception;

    // ------------------------------------------------------------------------

    /**
     * 为协调器做checkpoint,将当前协调器中的状态序列化到checkpoint中,执行成功需要调用CompletableFuture的complete方法,失败需要调用CompletableFuture的completeExceptionally方法
     */
    void checkpointCoordinator(long checkpointId, CompletableFuture<byte[]> resultFuture)
            throws Exception;

    /**
     * We override the method here to remove the checked exception. Please check the Java docs of
     * {@link CheckpointListener#notifyCheckpointComplete(long)} for more detail semantic of the
     * method.
     */
    @Override
    void notifyCheckpointComplete(long checkpointId);

    /**
     * We override the method here to remove the checked exception. Please check the Java docs of
     * {@link CheckpointListener#notifyCheckpointAborted(long)} for more detail semantic of the
     * method.
     */
    @Override
    default void notifyCheckpointAborted(long checkpointId) {
   }

    /**
     * 从checkpoint重置当前的协调器
     */
    void resetToCheckpoint(long checkpointId, @Nullable byte[] checkpointData) throws Exception;

    // ------------------------------------------------------------------------

    /**
     * 子任务重置时调用此方法
     */
    void subtaskReset(int subtask, long checkpointId);

    /**
     * 子任务失败时调用此方法 
     */
    void executionAttemptFailed(int subtask, int attemptNumber, @Nullable Throwable reason);

    /**
     * 子任务就绪时调用此方法
     */
    void executionAttemptReady(int subtask, int attemptNumber, SubtaskGateway gateway);
}

算子Operator

Flink中执行计算任务的算子,像使用DataStream API时调用的map、flatmap、process传入的自定义函数最终都会封装为一个一个的算子。使用UDF已经能够满足大多数的开发场景,但涉及到与协调器打交道时需要自定义算子,自定义算子相对比较好简单,具体可以参考org.apache.flink.streaming.api.operators.KeyedProcessOperator的实现。

自定义算子需要实现AbstractStreamOperator和OneInputStreamOperator接口方法

实现定时器功能,需要实现Triggerable接口方法

实现处理协调器的事件功能,需要实现OperatorEventHandler接口方法

示例

自定义算子

这里实现一个自定义的算子,用来处理KeyedStream的数据,它能够接受来自协调器的事件,并且能够给协调器发送事件。

MyKeyedProcessOperator实现代码如下:

package com.examples.operator;

import com.examples.event.MyEvent;
import org.apache.flink.runtime.operators.coordination.OperatorEvent;
import org.apache.flink.runtime.operators.coordination.OperatorEventGateway;
import org.apache.flink.runtime.operators.coordination.OperatorEventHandler;
import org.apache.flink.runtime.state.VoidNamespace;
import org.apache.flink.runtime.state.VoidNamespaceSerializer;
import org.apache.flink.streaming.api.SimpleTimerService;
import org.apache.flink.streaming.api.TimerService;
import org.apache.flink.streaming.api.operators.*;
import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
import org.apache.flink.streaming.runtime.tasks.ProcessingTimeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 自定义的KeyedProcessOperator
 * @author shirukai
 */
public class MyKeyedProcessOperator<KEY, IN, OUT> extends AbstractStreamOperator<OUT>
        implements OneInputStreamOperator<IN, OUT>,
        Triggerable<KEY, VoidNamespace>,
        OperatorEventHandler {
   
    private static final long serialVersionUID = 1L;
    private static final Logger LOG = LoggerFactory.getLogger(MyKeyedProcessOperator.class);
    private transient TimestampedCollector<OUT> collector;
    private transient TimerService timerService;
    private final OperatorEventGateway operatorEventGateway;

    public MyKeyedProcessOperator(ProcessingTimeService processingTimeService, OperatorEventGateway operatorEventGateway) {
   
        this.processingTimeService = processingTimeService;
        this.operatorEventGateway = operatorEventGateway;


    }

    @Override
    public void open() throws Exception {
   
        super.open();
        collector = new TimestampedCollector<>(output);
        InternalTimerService<VoidNamespace> internalTimerService =
                getInternalTimerService("user-timers", VoidNamespaceSerializer.INSTANCE, this);
        timerService = new SimpleTimerService(internalTimerService);
    }

    @Override
    public void processElement(StreamRecord<IN> element) throws Exception {
   
        LOG.info("processElement: {}", element);

        collector.setTimestamp(element);
        // 注册事件时间定时器
        timerService.registerEventTimeTimer(element.getTimestamp() + 10);

        // 注册处理时间定时器
        timerService.registerProcessingTimeTimer(element.getTimestamp() + 100);

        // 给协调器发送消息
        operatorEventGateway.sendEventToCoordinator(new MyEvent("hello,I'm from operator"));

        // 不做任何处理直接发送到下游
        collector.collect((OUT) element.getValue());
    }


    @Override
    public void onEventTime(InternalTimer<KEY, VoidNamespace> timer
### 实现 Flink 自定义 CDC 转换器 为了实现在 Apache Flink 中处理来自 MySQL 的变更数据捕获 (CDC),可以创建一个自定义的转换器来适配特定需求。下面展示了一个简单的例子,该示例展示了如何构建一个继承 `DebeziumDeserializationSchema` 接口并重写其方法以解析 JSON 格式的 CDC 数据流。 ```java import org.apache.flink.api.common.typeinfo.TypeInformation; import org.apache.flink.connector.base.debezium.DebeziumDeserializationSchema; import org.apache.kafka.connect.source.SourceRecord; public class CustomCdcTransformer implements DebeziumDeserializationSchema<String> { @Override public void deserialize(SourceRecord record, Collector<String> out) throws Exception { // 解析 SourceRecord 并将其转化为字符串形式或其他所需格式 String value = parseSourceRecord(record); out.collect(value); } private String parseSourceRecord(SourceRecord record){ // 提取记录中的键和值字段,并按照业务逻辑进行处理 Struct sourceValue = ((Struct)record.value()); Map<String, Object> map = new HashMap<>(); // 假设我们知道表结构,这里只提取两个字段作为示范 if(sourceValue != null && !sourceValue.fields().isEmpty()){ for(Field field : sourceValue.schema().fields()){ map.put(field.name(), sourceValue.get(field)); } } return JsonUtil.toJson(map); // 使用工具类将Map转成JSON串 } @Override public TypeInformation<String> getProducedType() { return TypeInformation.of(String.class); } } ``` 此代码片段实现了 `DebeziumDeserializationSchema` 接口中定义的方法,用于接收 Kafka Connect 所产生的 `SourceRecord` 对象,并通过自定义的方式对其进行序列化操作。在这个过程中,可以根据实际应用场景调整 `parseSourceRecord()` 方法内部的具体实现细节[^1]。 对于更复杂的场景,可能还需要考虑其他因素,比如支持多种数据库类型的兼容性、优化性能以及确保高可用性和容错能力等特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值