Apache Flink类型及序列化研读&生产应用|得物技术

一、背景

序列化是指将数据从内存中的对象序列化为字节流,以便在网络中传输或持久化存储。序列化在Apache Flink中非常重要,因为它涉及到数据传输和状态管理等关键部分。Apache Flink以其独特的方式来处理数据类型以及序列化,这种方式包括它自身的类型描述符、泛型类型提取以及类型序列化框架。本文将简单介绍它们背后的概念和基本原理,侧重分享在DataStream、Flink SQL自定义函数开发中对数据类型和序列的应用,以提升任务的运行效率。

二、简单理论阐述(基于Flink 1.13)

主要参考Apache Flink 1.13

支持的数据类型

  • Java Tuples and Scala Case Classes
  • Java POJOs
  • Primitive Types
  • Regular Classes
  • Values
  • Hadoop Writables
  • Special Types

具体的数据类型定义在此就不详细介绍了,具体描述可以前往Flink官网查看。

TypeInformation

Apache Flink量身定制了一套序列化框架,好处就是选择自己定制的序列化框架,对类型信息了解越多,可以在早期完成类型检查,更好地选取序列化方式,进行数据布局,节省数据的存储空间,甚至直接操作二进制数据。

TypeInformation类是Apache Flink所有类型描述符的基类,通过阅读源码,我们可以大概分成以下几种数据类型。

  • Basic types:所有的Java类型以及包装类:void,String,Date,BigDecimal,and BigInteger等。
  • Primitive arrays以及Object arrays
  • Composite types
  • Flink Java Tuples(Flink Java API的一部分):最多25个字段,不支持空字段
  • Scala case classes(包括Scala Tuples):不支持null字段
  • Row:具有任意数量字段并支持空字段的Tuples
  • POJO 类:JavaBeans
  • Auxiliary types (Option,Either,Lists,Maps,…)
  • Generic types:Flink内部未维护的类型,这种类型通常是由Kryo序列化。

我们简单看下该类的方法,核心是createSerializer,获取org.apache.flink.api.common.typeutils.TypeSerializer,执行序列化以及反序列化方法,主要是:

  • org.apache.flink.api.common.typeutils.TypeSerializer#serialize
  • org.apache.flink.api.common.typeutils.TypeSerializer#deserialize(org.apache.flink.core.memory.DataInputView)

何时需要数据类型获取

在Apache Flink中,算子间的数据类型传递是通过流处理的数据流来实现的。数据流可以在算子之间流动,每个算子对数据流进行处理并产生输出。当数据流从一个算子流向另一个算子时,数据的类型也会随之传递。Apache Flink使用自动类型推断机制来确定数据流中的数据类型。在算子之间传递数据时,Apache Flink会根据上下文自动推断数据的类型,并在运行时保证数据的类型一致性。

举个例子:新增一个kafka source,这个时候我们需要指定数据输出类型。

@Experimental
public <OUT> DataStreamSource<OUT> fromSource(
        Source<OUT, ?, ?> source,
        WatermarkStrategy<OUT> timestampsAndWatermarks,
        String sourceName,
        TypeInformation<OUT> typeInfo) {

    final TypeInformation<OUT> resolvedTypeInfo =
            getTypeInfo(source, sourceName, Source.class, typeInfo);

    return new DataStreamSource<>(
            this,
            checkNotNull(source, "source"),
            checkNotNull(timestampsAndWatermarks, "timestampsAndWatermarks"),
            checkNotNull(resolvedTypeInfo),
            checkNotNull(sourceName));
}

那输入类型怎么不需要指定呢?可以简单看下OneInputTransformation(单输入算子的基类)类的getInputType()方法,就是以输入算子的输出类型为输入类型的。

/** Returns the {@code TypeInformation} for the elements of the input. */
public TypeInformation<IN> getInputType() {
    return input.getOutputType();
}

这样source的输出类型会变成下一个算子的输入。整个DAG的数据类型都会传递下去。Apache Flink获取到数据类型后,就可以获取对应的序列化方法。

还有一种情况就是与状态后端交互的时候需要获取数据类型,特别是非JVM堆存储的后端,需要频繁的序列化以及反序列化,例如RocksDBStateBackend

举个例子,当我们使用ValueState时需要调用以下API:

org.apache.flink.streaming.api.operators.StreamingRuntimeContext#getState

@Override
public <T> ValueState<T> getState(ValueStateDescriptor<T> stateProperties) {
    KeyedStateStore keyedStateStore = checkPreconditionsAndGetKeyedStateStore(stateProperties);
    stateProperties.initializeSerializerUnlessSet(getExecutionConfig());
    return keyedStateStore.getState(stateProperties);
}

public void initializeSerializerUnlessSet(ExecutionConfig executionConfig) {
    if (serializerAtomicReference.get() == null) {
        checkState(typeInfo != null, "no serializer and no type info");
        // try to instantiate and set the serializer
        TypeSerializer<T> serializer = typeInfo.createSerializer(executionConfig);
        // use cas to assure the singleton
        if (!serializerAtomicReference.compareAndSet(null, serializer)) {
            LOG.debug("Someone else beat us at initializing the serializer.");
        }
    }
}

可以从org.apache.flink.api.common.state.StateDescriptor#initializeSerializerUnlessSet方法看出,需要通过传入的数据类型来获取具体的序列化器。来执行具体的序列化和反序列化逻辑,完成数据的交互。

数据类型的自动推断

乍一看很复杂,各个环节都需要指定数据类型。其实大部分应用场景下,我们不用关注数据的类型以及序列化方式。Flink会尝试推断有关分布式计算期间交换和存储的数据类型的信息。

这里简单介绍Flink类型自动推断的核心类:

org.apache.flink.api.java.typeutils.TypeExtractor

在数据流操作中,Flink使用了泛型来指定输入和输出的类型。例如,DataStream表示一个具有类型T的数据流。在代码中使用的泛型类型参数T会被TypeExtractor类解析和推断。在运行时,Apache Flink会通过调用TypeExtractor的静态方法来分析操作的输入和输出,并将推断出的类型信息存储在运行时的环境中。

举个例子:用的最多的flatMap算子,当我们不指定返回类型的时候,Flink会调用TypeExtractor类自动去推断用户的类型。

public <R> SingleOutputStreamOperator<R> flatMap(FlatMapFunction<T, R> flatMapper) {
    TypeInformation<R> outType = TypeExtractor.getFlatMapReturnTypes((FlatMapFunction)this.clean(flatMapper), this.getType(), Utils.getCallLocationName(), true);
    return this.flatMap(flatMapper, outType);
}

一般看开源框架某个类的功能我都会先看类的注释,也看TypeExtractor的注释,大概意思这是一个对类进行反射分析的实用程序,用于确定返回的数据类型。

/**
 * A utility for reflection analysis on classes, to determine the return type of implementations of
 * transformation functions.
 *
 * <p>NOTES FOR USERS OF THIS CLASS: Automatic type extraction is a hacky business that depends on a
 * lot of variables such as generics, compiler, interfaces, etc. The type extraction fails regularly
 * with either {@link MissingTypeInfo} or hard exceptions. Whenever you use methods of this class,
 * make sure to provide a way to pass custom type information as a fallback.
 */

我们来看下其中一个核心的静态推断逻辑,org.apache.flink.api.java.typeutils.TypeExtractor#getUnaryOperatorReturnTyp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值