Flink-数据类型以及序列化

Apache Flink 以其独特的方式来处理数据类型以及序列化,这种方式包括它自身的类型描述符、泛型类型提取以及类型序列化框架。 本文档描述了它们背后的概念和基本原理.

支持的数据类型

Flink 对 DataStream 中可以包含的元素类型有一些限制。这样做的原因是系统分析类型以确定有效的执行策略.

数据类型有七种不同类别:

  1. Java 元组和 Scala 样例类
  2. Java POJO
  3. Primitive Types 基元类型
  4. Regular Classes 常规课程
  5. Values 值
  6. Hadoop Writables Hadoop 可写
  7. Special Types 特殊类型

  • 元组和 Case 类

元组是复合类型,包含固定数量的各种类型的字段。Java API 提供从 Tuple1 到 Tuple25 的类。元组的每个字段都可以是任意的 Flink 类型,包括更多的元组,从而产生嵌套的元组。可以使用字段名称 tuple.f4 或使用通用 getter 方法 tuple.getField(int position) 直接访问元组的字段。字段索引从 0 开始。

DataStream<Tuple2<String, Integer>> wordCounts = env.fromElements(
    new Tuple2<String, Integer>("hello", 1),
    new Tuple2<String, Integer>("world", 2));

wordCounts.map(new MapFunction<Tuple2<String, Integer>, Integer>() {
    @Override
    public Integer map(Tuple2<String, Integer> value) throws Exception {
        return value.f1;
    }
});

wordCounts.keyBy(value -> value.f0);
  • POJO
    如果 Java 和 Scala 类满足以下要求,则 Flink 会将它们视为特殊的 POJO 数据类型:
  • 该类必须是 public 类
  • 它必须具有不带参数的公共构造函数 (默认构造函数)
  • 所有字段要么是公共的,要么必须可以通过 getter 和 setter 函数访问。对于名为 foo 的字段,getter 和 setter 方法必须命名为 getFoo() 和 setFoo()。
  • 注册的序列化程序必须支持字段的类型

        POJO 通常用 PojoTypeInfo 表示,并使用 PojoSerializer 进行序列化(使用 Kryo 作为可配置的回退)。例外情况是,当 POJO 实际上是 Avro 类型(Avro 特定记录)或作为“Avro Reflect Types”生成时。在这种情况下,POJO 由 AvroTypeInfo 表示,并使用 AvroSerializer 进行序列化。如果需要,您还可以注册自己的自定义序列化程序;有关更多信息,请参阅 序列化 。

Flink 分析 POJO 类型的结构,即学习 POJO 的字段。因此,POJO 类型比一般类型更易于使用。此外,Flink 可以比一般类型更高效地处理 POJO。

你可以通过 org.apache.flink.types.PojoTestUtils#assertSerializedAsPojo() flink-test-utils 测试你的类是否符合 POJO 要求。如果您还想确保 POJO 的任何字段都不会使用 Kryo 进行序列化,请改用 use assertSerializedAsPojoWithoutKryo() .

以下示例显示了一个具有两个 public 字段的简单 POJO。

public class WordWithCount {

    public String word;
    public int count;

    public WordWithCount() {}

    public WordWithCount(String word, int count) {
        this.word = word;
        this.count = count;
    }
}

DataStream<WordWithCount> wordCounts = env.fromElements(
    new WordWithCount("hello", 1),
    new WordWithCount("world", 2));

wordCounts.keyBy(value -> value.word);
基元类型(Primitive Types )

Flink 支持所有 Java 和 Scala 基元类型,例如 IntegerString 和 Double

常规类类型(General Class Types)

Flink 支持大多数 Java 和 Scala 类(API 和自定义)。限制适用(不能适用)于包含无法序列化的字段的类,例如文件指针、I/O 流或其他本机资源。

所有未标识为 POJO 类型的 class(参见上面的 POJO 要求)都由 Flink 作为通用 class 类型处理。Flink 将这些数据类型视为黑盒,无法访问其内容(例如,为了高效排序)。通用类型使用序列化框架 Kryo 进行反序列化。

值(Values 

类型手动描述它们的序列化和反序列化。他们不是通过通用的序列化框架,而是通过使用 read 和 write 方法实现 org.apache.flink.types.Value 接口来为这些操作提供自定义代码。当通用序列化效率非常低时,使用 Value 类型是合理的。例如,将元素的稀疏向量实现为数组的数据类型。知道数组大部分为零,可以对非零元素使用特殊编码,而通用序列化将简单地写入所有数组元素。

余下的具体描述,可以参考该链接: 概览 | Apache Flink

最常见的问题

用户需要与 Flink 的数据类型处理交互的最常见的问题是:

注册子类型:如果函数签名只描述超类型,但它们在执行过程中实际上使用了这些超类型的子类型,那么让 Flink 知道这些子类型可能会大大提高性能。为此,请在 StreamExecutionEnvironment 上为每个子类型调用 .registerType(clazz)。

注册自定义序列化程序:Flink 对于它自己不透明地处理的类型,会回退到 Kryo。并非所有类型都可以由 Kryo(以及 Flink)无缝处理。例如,默认情况下,许多 Google Guava 集合类型无法正常工作。解决方案是为导致问题的类型注册其他序列化程序。调用 .getConfig().addDefaultKryoSerializer(clazz, serializer) StreamExecutionEnvironment。许多库中都提供了其他 Kryo 序列化器。

添加类型提示:有时,当 Flink 尽管使用了所有技巧还是无法推断出泛型类型时,用户必须传递一个类型提示。这通常仅在 Java API 中是必需的。Type Hints 部分对此进行了更详细的描述.

手动创建 TypeInformation对于某些 API 调用来说,这可能是必要的,因为 Java 的泛型类型擦除导致 Flink 无法推断数据类型.

类 TypeInformation 所有类型描述符的基类。它揭示了类型的一些基本属性,并且可以生成序列化程序,在专用化中,还可以生成类型的比较器。(请注意,Flink 中的 comparator 所做的不仅仅是定义顺序 - 它们基本上是处理 key 的工具

在内部,Flink 对类型进行了以下区分:

基本类型:所有 Java 基元及其装箱形式,以及 voidStringDateBigDecimal 和 BigInteger

基元数组和 Object 数组

Composite types 复合类型:

        Flink Java 元组(Flink Java API 的一部分):最多 25 个字段,不支持 null 字段

        Scala case 类(包括 Scala 元组):不支持 null 字段

        Row:具有任意数量的字段并支持 null 字段的元组

        POJO:遵循某种类似 bean 模式的类

辅助类型(Option、Either、Lists、Maps 等)

泛型类型:这些不会由 Flink 本身序列化,而是由 Kryo 序列化

POJO 特别有趣,因为它们支持创建复杂类型。它们对运行时也是透明的,并且 Flink 可以非常有效地处理它们.(建议可以多用该类型)

POJO 类型的规则

如果满足以下条件,Flink 会将数据类型识别为 POJO 类型(并允许 “by-name” 字段引用):

1.该类是公共的和独立的(没有非静态内部类)

2.该类具有公共无参数构造函数

3.类(以及所有超类)中的所有非静态、非瞬态字段要么是 public(和非 final),要么具有遵循 getter 和 setter 的 Java bean 命名约定的公共 getter 和 setter 方法。

请注意,当用户定义的数据类型无法识别为 POJO 类型时必须将其处理为 GenericType 并使用 Kryo 进行序列化

创建 TypeInformation 或 TypeSerializer

要为类型创建 TypeInformation 对象,请使用特定于语言的方式:

因为 Java 通常会擦除泛型类型信息,所以你需要将类型传递给 TypeInformation 构造

        对于非泛型类型,可以使用 Class:

TypeInformation<String> info = TypeInformation.of(String.class);

对于泛型类型,您需要通过 TypeHint “捕获” 泛型类型信息

TypeInformation<Tuple2<String, Double>> info = TypeInformation.of(new TypeHint<Tuple2<String, Double>>(){});

在内部,这会创建一个 TypeHint 的匿名子类,该子类捕获泛型信息以将其保留到运行时

有两种方法可以创建 TypeSerializer

第一种是简单地调用 typeInfo.createSerializer(config) TypeInformation 对象。config 参数的类型为 ExecutionConfig,并保存有关程序的已注册自定义序列化程序的信息。在可能的情况下,尝试向程序传递正确的 ExecutionConfig。您通常可以通过调用 getExecutionConfig() 从 DataStream 获取它。

第二种是在函数中使用 getRuntimeContext().createSerializer(typeInfo)。在函数(如 MapFunction)中,您可以通过将函数设为 Rich Function 并调用 getRuntimeContext().createSerializer(typeInfo) .

Type Hints in the Java API(Java API 中的类型提示

在 Flink 无法重建被擦除的泛型类型信息的情况下,Java API 会提供所谓的类型提示。类型提示告诉系统函数生成的数据流或数据集的类型:

DataStream<SomeType> result = stream
    .map(new MyGenericNonInferrableFunction<Long, SomeType>())
        .returns(SomeType.class);

returns 语句指定生成的类型,在本例中通过类。提示支持类型定义

1.类, 用于非参数化类型(无泛型)

2.TypeHints 格式为 returns(new TypeHint<Tuple2<Integer, SomeType>>(){}) .TypeHint 类可以捕获泛型类型信息,并将其保留用于运行时(通过匿名子类)

POJO 类型的序列化

PojoTypeInfo 正在为 POJO 中的所有字段创建序列化器。标准类型,如 int、long、String 等,由我们随 Flink 一起提供的序列化器处理。对于所有其他类型的 Cookie,我们回退到 Kryo

如果 Kryo 无法处理该类型,您可以要求 PojoTypeInfo 使用 Avro 序列化 POJO。为此,您必须调用:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.getConfig().enableForceAvro();

请注意,Flink 会自动使用 Avro 序列化器序列化 Avro 生成的 POJO

如果希望 Kryo 序列化器处理整个 POJO 类型,请将:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.getConfig().enableForceKryo();

如果 Kryo 无法序列化你的 POJO,你可以使用:
 

env.getConfig().addDefaultKryoSerializer(Class<?> type, Class<? extends Serializer<?>> serializerClass);

无法进行序列化的用户自定义类型,Flink 会回退到通用的 Kryo 序列化器。

 可以使用 Kryo 注册自己的序列化器序列化系统,比如 Google Protobuf 或 Apache Thrift。 使用方法是在 Flink 程序中的 ExecutionConfig 注册类类型以及序列化器

final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

// 为类型注册序列化器类
env.getConfig().registerTypeWithKryoSerializer(MyCustomType.class, MyCustomSerializer.class);

// 为类型注册序列化器实例
MySerializer mySerializer = new MySerializer();
env.getConfig().registerTypeWithKryoSerializer(MyCustomType.class, mySerializer);

需要确保你的自定义序列化器继承了 Kryo 的序列化器类。 对于 Google Protobuf 或 Apache Thrift,这一点已经为你做好了:

final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

// 使用 Kryo 注册 Google Protobuf 序列化器
env.getConfig().registerTypeWithKryoSerializer(MyCustomType.class, ProtobufSerializer.class);

// 注册 Apache Thrift 序列化器为标准序列化器
// TBaseSerializer 需要初始化为默认的 kryo 序列化器
env.getConfig().addDefaultKryoSerializer(MyCustomType.class, TBaseSerializer.class);

为了使上面的例子正常工作,需要在 Maven 项目文件中(pom.xml)包含必要的依赖。 为 Apache Thrift 添加以下依赖:

<dependency>
	<groupId>com.twitter</groupId>
	<artifactId>chill-thrift</artifactId>
	<version>0.7.6</version>
	<!-- exclusions for dependency conversion -->
	<exclusions>
		<exclusion>
			<groupId>com.esotericsoftware.kryo</groupId>
			<artifactId>kryo</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!-- libthrift is required by chill-thrift -->
<dependency>
	<groupId>org.apache.thrift</groupId>
	<artifactId>libthrift</artifactId>
	<version>0.11.0</version>
	<exclusions>
		<exclusion>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
		</exclusion>
	</exclusions>
</dependency>
对于 Google Protobuf 需要添加以下 Maven 依赖:


<dependency>
	<groupId>com.twitter</groupId>
	<artifactId>chill-protobuf</artifactId>
	<version>0.7.6</version>
	<!-- exclusions for dependency conversion -->
	<exclusions>
		<exclusion>
			<groupId>com.esotericsoftware.kryo</groupId>
			<artifactId>kryo</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!-- We need protobuf for chill-protobuf -->
<dependency>
	<groupId>com.google.protobuf</groupId>
	<artifactId>protobuf-java</artifactId>
	<version>3.7.0</version>
</dependency>

请根据需要调整两个依赖库的版本。

使用 Kryo JavaSerializer 可能存在的问题

如果你为自定义类型注册 Kryo 的 JavaSerializer,即使你提交的 jar 中包含了自定义类型的类,也可能会遇到 ClassNotFoundException 异常。 这是由于 Kryo JavaSerializer 的一个已知问题,它可能使用了错误的类加载器。

在这种情况下,你应该使用 org.apache.flink.api.java.typeutils.runtime.kryo.JavaSerializer 来解决这个问题。 这个类是在 Flink 中对 JavaSerializer 的重新实现,可以确保使用用户代码的类加载器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值