Flink 數據類型 & TypeInformation信息

原生數據類型

Java Tuples類型

Scala Case Class類型

POJOs 類型

Flink Value類型

特殊數據類型

Scala API類型信息

Java API類型信息

自定義TypeInformation


Flink支持非常完善的數據類型,數據類型的描述信息都是由 TypeInformation定義,比較常用的 TypeInformation有BasicTypeInfoTupleTypeInfoCaseClassTypeInfoPojoTypeInfo類等。TypeInformation主要作用是爲了在 Flink系統內有效地對數據結構類型進行管理能夠在分佈式計算過程中對數據的類型進行管理和推斷。同時基於對數據的類型信息管理,Flink內部對數據存儲也進行了相應的性能優化。Flink能夠支持任意的Java或Scala的數據類型,不用像 Hadoop中的org.apache.hadoop.io.Writable 而實現特定的序列化和反序列化接口,從而讓用戶能夠更加容易使用已有的數據結構類型。另外使用 TypeInformation管理數據類型信息,能夠在數據處理之前將數據類型推斷出來,而不是真正在觸發計算後才識別出,這樣能夠及時有效地避免用戶在使用 Flink編寫應用的過程中的數據類型問題。

原生數據類型

Flink通過實現 BasicTypeInfo數據類型,能夠支持任意Java 原生基本類型(裝箱)或 String 類型,例如Integer、String、Double等,如以下代碼所示,通過從給定的元素集中創建 DataStream數據集。

//創建 Int 類型的數據集
DataStreamSource<Integer> integerDataStreamSource = env.fromElements(1, 2, 3, 4, 5);
//創建 String 的類型的數據集
DataStreamSource<String> stringDataStreamSource = env.fromElements("Java", "Scala");

Flink實現另外一種 TypeInfomation是 BasicArrayTypeInfo,對應的是 Java基本類型數組(裝箱)或 String對象的數組,如下代碼通過使用 Array數組和 List集合創建 DataStream數據集。

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
//通過 List 集合創建數據集
DataStreamSource<Integer> integerDataStreamSource1 = env.fromCollection(integers);

Java Tuples類型

通過定義 TupleTypeInfo來描述 Tuple類型數據,Flink在Java接口中定義了元祖類(Tuple)供用戶使用。Flink Tuples是固定長度固定類型的Java Tuple實現,不支持空值存儲。目前支持任意的Flink Java Tuple類型字段數量上限爲25,如果字段數量超過上限,可以通過繼承Tuple類的方式進行拓展。如下代碼所示,創建Tuple數據類型數據集。

//通過實例化 Tuple2 創建具有兩個元素的數據集
DataStreamSource<Tuple2<String, Integer>> tuple2DataStreamSource = env.fromElements(new Tuple2<>("a", 1), new Tuple2<>("b", 2));
//通過實例化 Tuple3 創建具有三個元素的數據集
DataStreamSource<Tuple3<String, Integer, Long>> tuple3DataStreamSource = env.fromElements(new Tuple3<>("a", 1, 3L), new Tuple3<>("b", 2, 3L));

Scala Case Class類型

Flink通過實現 CaseClassTypeInfo支持任意的 Scala Case Class,包括Scala tuples類型,支持的字段數量上限爲22,支持通過字段名稱和位置索引獲取指標,不支持存儲空值。如下代碼實例所示,定義WordCount Case Class數據類型,然後通過fromElements方法創建input數據集,調用 keyBy()方法對數據集根據 word字段重新分區。

//定義 WordCount Case Class 數據結構
case class WordCount(word: Sring, count: Int)
//通過 fromElements 方法創建數據集
val input = env.fromElements(WordCount("hello", 1),WordCount("word",2))
val keyStream1 = input.keyBy("word")//根據word字段爲分區字段,
val keyStream2 = input.keyBy(0)//也可以通過制定position分區

通過使用Scala Tuple創建 DataStream數據集,其他的使用方式和 Case Class相似。需要注意的是,如果根據名稱獲取字段,可以使用 Tuple中的默認字段名稱。

//通過實例化Scala Tuple2 創建具有兩個元素的數據集
val tupleStream: DataStream[Tuple2[String,Int]] = env.fromElements(("a",1),("b",2));
//使用默認名字段獲取字段,表示第一個 tuple字段,相當於下標0
tuple2DataStreamSource.keyBy("_1");

POJOs 類型

POJOs類可以完成複雜數據結構的定義,Flink通過實現 PojoTypeInfo來描述任意的 POJOs,包括 Java 和 Scala類。在Flink中使用POJOs類可以通過字段名稱獲取字段,例如 dataStream.join(otherStream).where("name").equalTo("personName"),對於用戶做數據處理則非常透明和簡單,如代碼所示。如果在 Flink中使用POJOs數據類型,需要遵循以下要求:
【1】POJOs 類必須是 Public修飾且必須獨立定義,不能是內部類;
【2】POJOs類中必須含有默認空構造器
【3】POJOs類中所有的 Fields必須是 Public或者具有 Public修飾的 getter和 setter方法;
【4】POJOs類中的字段類型必須是 Flink支持的

//類和屬性具有 public 修飾
public class Persion{
    public String name;
    public Integer age;
    //具有默認的空構造器
    public Persion(){}
    public Persion(String name,Integer age){
        this.name = name;
        this.age = age;
    };
}

定義好 POJOs Class後,就可以在 Flink環境中使用了,如下代碼所示,使用 fromElements接口構建 Person類的數據集。POJOs類僅支持字段名稱指定字段,如代碼中通過 Person name來指定 Keyby字段。

DataStreamSource<Persion> persionDataStreamSource = env.fromElements(new Persion("zzx", 18), new Persion("fj", 16));
persionData.keyBy("name").sum("age");

Flink Value類型

Value數據類型實現了org.apache.flink.types.Value,其中包括 read()和 write()兩個方法完成序列化和反序列化操作,相對於通用的序列化工具會有着比較高效的性能。目前 Flink提供了內建的 Value類型有 IntValue、DoubleValue以及 StringValue等,用戶可以結合原生數據類型和 Value類型使用。

特殊數據類型

在Flink中也支持一些比較特殊的數據數據類型,例如 Scala中的List、Map、Either、Option、Try數據類型,以及 Java中Either數據類型,還有 Hadoop的 Writable數據類型。如下代碼所示,創建 Map和 List類型數據集。這種數據類型使用場景不是特別廣泛,主要原因是數據中的操作相對不像 POJOs類那樣方便和透明,用戶無法根據字段位置或者名稱獲取字段信息,同時要藉助Types Hint幫助Flink推斷數據類型信息,關於Tyeps Hmt介紹可以參考下一小節。

//創建 map 類型數據集
Map map = new HashMap<>();
map.put("name","zzx");
map.put("age",12);
env.fromElements(map);
//創建 List 類型數據集
env.fromElements(Arrays.asList(1,2,3,4,5),Arrays.asList(3,4,5));

TypeInformation信息獲取:通常情況下 Flink都能正常進行數據類型推斷,並選擇合適的 serializers以及 comparators。但在某些情況下卻無法直接做到,例如定義函數時如果使用到了泛型,JVM就會出現類型擦除的問題,使得 Flink並不能很容易地獲取到數據集中的數據類型信息。同時在 Scala API和 Java API中,Flink分別使用了不同的方式重構了數據類型信息。

Scala API類型信息

Scala API通過使用 Manifest和類標籤,在編譯器運行時獲取類型信息,即使是在函數定義中使用了泛型,也不會像Java API出現類型擦除的問題,這使得Scala API具有非常精密的類型管理機制。同時在Flink中使用到Scala Macros框架,在編譯代碼的過程中推斷函數輸入參數和返回值的類型信息,同時在Flink中註冊成TypeInformation以支持上層計算算子使用。
當使用Scala API開發 Flink應用,如果使用到 Flink已經通過 TypeInformation定義的數據類型,TypeInformation類不會自動創建,而是使用隱式參數的方式引入,代碼不會直接拋出編碼異常,但是當啓動 Flink應用程序時就會報”could not find implicit value for evidence parameter of type TypeInformation”的錯誤。這時需要將 TypeInformation類隱式參數引入到當前程序環境中,代碼實例如下:

import org.apache.flink.api.scala._

Java API類型信息

由於 Java的泛型會出現類型擦除問題,Flink通過 Java反射機制儘可能重構類型信息,例如使用函數簽名以及子類的信息等。同時類型推斷在當輸出類型依賴於輸入參數類型時相對比較容易做到,但是如果函數的輸出類型不依賴於輸入參數的類型信息,這個時候就需要藉助於類型提示(Ctype Himts)來告訴系統函數中傳入的參數類型信息和輸出參數信息。如代碼清單通過在returns方法中傳入TypeHint實例指定輸出參數類型,幫助 Flink系統對輸出類型進行數據類型參數的推斷和收集。

//定義泛型函數,輸入參數 T,O 輸出參數爲 O
class MyMapFucntion<T,O> implements MapFunction<T,O>{
    @Override
    public O map(T t) throws Exception {
        //定義計算邏輯
        return null;
    }
}

//通過 List 集合創建數據集
DataStreamSource<Integer> input = env.fromCollection(integers);
input.flatMap(new MyMapFucntion<String,Integer>()).returns(new TypeHint<Integer>() {//通過returns方法指定返回參數類型
})

在使用Java API定義 POJOs類型數據時,PojoTypeInformation爲 POJOs類中的所有字段創建序列化器,對於標準的類型,例如IntegerStringLong等類型是通過 Flink自帶的序列化器進行數據序列化,對於其他類型數據都是直接調用 Kryo序列化工具來進行序列化。通常情況下,如果 Kryo序列化工具無法對 POJOs類序列化時,可以使用 Avro對 POJOs類進行序列化,如下代碼通過在 ExecutionConfig中調用 enableForceAvro()來開啓Avro序列化。

//獲取運行環境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//開啓 avro 序列化
env.getConfig().enableForceAvro();

如果用戶想使用 Kryo序列化工具來序列化 POJOs所有字段,則在 ExecutionConfig中調用 enableForceKryo()來開啓 Kryo序列化。

//獲取運行環境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//開啓 Kryo 序列化
env.getConfig().enableForceKryo();

如果默認的 Kryo序列化類不能序列化 POJOs對象,通過調用 ExecutionConfig的 addDefaultKryoSerializer()方法向 Kryo中添加自定義的序列化器。

 public void addDefaultKryoSerializer(Class<?> type, Class<? extends Serializer<?>> serializerClass)

自定義TypeInformation

除了使用已有的 TypeInformation所定義的數據格式類型之外,用戶也可以自定義實現 TypeInformation,來滿足的不同的數據類型定義需求。Flink提供了可插拔的 Type Information Factory讓用戶將自定義的 TypeInformation註冊到 Flink類型系統中。如下代碼所示只需要通過實現 org.apache.flink.api.common.typeinfo.TypeInfoFactory接口,返回相應的類型信息。通過@TypeInfo註解創建數據類型,定義 CustomTuple數據類型。

@TypeInfo(CustomTypeInfoFactory.class)
public class CustomTuple<T0,T1>{
	public T0 field0;
	public T1 field1;
}

然後定義CustomTypeInfoFactory 類繼承於TypeInfoFactory,參數類型指定CustomTuple。最後重寫createTypeInfo方法,創建的CustomTupleTypeInfo就是CustomTuple數據類型TypeInformation。

public class CustomTypeInfoFactory extends TypeInfoFactory<CustomTuple>{
	@Override
	public TypeInfomation<CustomTuple> createTypeInfo(Type t, Map<String,TypeInfoFactory<?>> genericParameters){
		return new CustomTupleTypeInfo(genericParameters.get("T0"),genericParameters.get("T1");
	}
}


----關注公衆號,獲取更多內容----

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章