写点什么

Flink 类型系统的根及相关接口

作者:编程江湖
  • 2022 年 1 月 04 日
  • 本文字数:1989 字

    阅读完需:约 7 分钟

类型的根 Value

Value位于所有类型的继承链的最顶端,可以说是所有类型的根。它代指所有可被序列化为 Flink 二进制表示的类型。该接口本身并不提供任何接口方法,但它继承自两个接口。下图是它的继承关系图:



从上图可以看出任何实现了Value接口的特定类型,都需要满足Value继承的两个接口的契约:

  • Serializable :标记实现该接口的类可被序列化

  • IOReadableWritable :Flink 核心 IO 包种的接口,实现该接口用于将类的实例序列化为二进制的表示形式

IOReadableWritable提供了读写数据的 write/read 方法,另外IOReadableWritable对接口的实现者的一个要求是其必须有一个默认的(无参)构造器。

容器类型 ListValue 和 MapValue

Value下,Flink 直接提供了两个抽象的容器类型:ListValueMapValue。它们都有几个共同点:

  • 容器中存储的元素的类型都是Value类型(通过泛型类型约束)

  • 容器类型自身也实现了Value,也即自身也可被序列化

  • 都实现了 JDK Java 集合框架中各自的接口(ListMap

关于上面的第三点,Flink 其实采用的是装饰器模式。比如,我们拿MapValue来举例:



它内部有一个map字段,该字段的初始化可能来自从构造方法传入的外部被装饰的Map实例,大数据培训也可能是从无参构造方法中直接实例化的Map实例。而MapValue中实现的Map接口的方法,大都通过调用map的实例方法实现。ListValue的做法类似,不再赘述。

值得一提的是,它们对IOReadableWritableread/write方法的实现。

我们先来看一下read方法的实现:

public void read(final DataInputView in) throws IOException {    int size = in.readInt();    this.map.clear();
   try {        for (; size > 0; size--) {            final K key = this.keyClass.newInstance();            final V val = this.valueClass.newInstance();            key.read(in);            val.read(in);            this.map.put(key, val);        }    } catch (final InstantiationException | IllegalAccessException e) {        throw new RuntimeException(e);    }}复制代码
复制代码

它首先读取一个整型值size,该整型值表示的是元素对的个数。然后循环读取每个keyvalue同时反序列化之后将其加入内部的map中。

write方法的实现,则是序列化每个元素的过程:

public void write(final DataOutputView out) throws IOException {        out.writeInt(this.map.size());        for (final Entry<K, V> entry : this.map.entrySet()) {            entry.getKey().write(out);            entry.getValue().write(out);        }    }复制代码
复制代码

当然,也是将mapsize先写入二进制结果的头部。结构示意如下图:


下面会我们来看所有具体的类型需要实现的三个接口。

基本类型实现的接口

ResettableValue 接口

该接口提供了一个方法:

void setValue(T value);复制代码
复制代码

用于将一个外部的value赋值给内部的同类型的对象。

CopyableValue 接口

该接口提供一些拷贝方法以方便基本类型的拷贝。其类图如下:



接口方法中,值得关注的是三个copy相关的方法。前两个:copyTocopy都必须提供深拷贝的实现。而最后一个copy方法,提供在 Flink 的二进制表示层面上的拷贝(等价于对IOReadableWritableread以及write的先后调用,但这里copy方法的优势是,中间不需要进行反序列化的过程)。

NormalizableKey 接口

该接口指定了实现规范化的键(normalizable key)需要满足的契约。先来解释一下什么叫作“规范化的键”,规范化的键指一种在二进制表示的方式下可以进行逐字节比较的键。而要使两个规范化的键能够比较,首先对于同一种类型,它们的最大字节长度要是相等的。对于这个条件,通过接口方法getMaxNormalizedKeyLen来定义。它针对一种类型通常都会返回一个常数值。比如对于 32 位的整型,它会返回常数值 4。但一个规范化的键所占用的字节数不一定要跟该类型的最大字节数相等。当它比规定的最大的字节数小时,可以认为它只是该规范化键的一种“前缀”。

两个规范化的键进行比较,但满足两个条件的其中之一后会停止:

  • 所有的字节都比较完成

  • 两个相同位置的字节不相等

关于比较的结果,如果在相同的位置,两个字节的值不相等则值小的一个键被认为其整个键会小于另外一个键。

除此之外该接口还提供了将实现类型的值(规范化的键)写入给定的目标字节数组中去的方法。

void copyNormalizedKey(MemorySegment memory, int offset, int len);复制代码
复制代码

对于该接口,值得一提的是,如果真正需要写入的字节数小于给定的len,那么它将会被填充一些特定的字符以进行补齐。

NormalizableKey 接口直接继承自 Key 接口,Key用来使得一个类型可以作为键以建立跟值之间的关系。并且键 Key 要求是可被比较的,因为它实现了Comparable接口。目前Key接口被标记为“Deprecated”的,在未来的版本中可能会被废弃。

用户头像

编程江湖

关注

IT技术分享 2021.11.23 加入

还未添加个人简介

评论

发布
暂无评论
Flink类型系统的根及相关接口