FlinkX的數據類型

FlinkX的數據類型

從上一章節裏面看到:

DataStream<Row> dataStream = dataReader.readData();

這個簡單的代碼裏面我們可以得出

  • 每一行數據都轉化爲了Row對象
  • 數據轉化爲了數據流

我們下面看一下Row是如何滿足所有的數據類型的?

FlinkX 中的 Row

這裏的Row是指的org.apache.flink.types.Row

A Row can have arbitrary number of fields and contain a set of fields, which may all be different types. The fields in Row can be null. Due to Row is not strongly typed, Flink’s type extraction mechanism can’t extract correct field types. So that users should manually tell Flink the type information via creating a RowTypeInfo.
The fields in the Row can be accessed by position (zero-based) getField(int). And can set fields by setField(int, Object).
Row is in principle serializable. However, it may contain non-serializable fields, in which case serialization will fail.

Row 介紹

下面先看一下Row 在整個Flink的定位

img

Flink 在其內部構建了一套自己的類型系統,Flink 現階段支持的類型分類如圖所示,從圖中可以看到 Flink 類型可以分爲基礎類型(Basic)數組(Arrays)複合類型(Composite)輔助類型(Auxiliary)泛型和其它類型(Generic)。Flink 支持任意的 Java 或是 Scala 類型。不需要像 Hadoop 一樣去實現一個特定的接口(org.apache.hadoop.io.Writable),Flink 能夠自動識別數據類型。

示例

image-20200519223022360

所以Row不是FlinkX的概念,而是Flinx的概念,就是一行數據的抽象。同樣的在DataX中是Record、在Hbase中也是Row,Hive的一行數據,關係數據庫的一行數據,等等…

Mysql 讀取Row

public Row nextRecordInternal(Row row) throws IOException {
        if (!hasNext) {
            return null;
        }
        row = new Row(columnCount);

        try {
            for (int pos = 0; pos < row.getArity(); pos++) {
                Object obj = resultSet.getObject(pos + 1);
                if(obj != null) {
                    if(CollectionUtils.isNotEmpty(descColumnTypeList)) {
                        String columnType = descColumnTypeList.get(pos);
                        if("year".equalsIgnoreCase(columnType)) {
                            java.util.Date date = (java.util.Date) obj;
                            obj = DateUtil.dateToYearString(date);
                        } else if("tinyint".equalsIgnoreCase(columnType)
                                    || "bit".equalsIgnoreCase(columnType)) {
                            if(obj instanceof Boolean) {
                                obj = ((Boolean) obj ? 1 : 0);
                            }
                        }
                    }
                    obj = clobToString(obj);
                }

                row.setField(pos, obj);
            }
            return super.nextRecordInternal(row);
        }catch (Exception e) {
            throw new IOException("Couldn't read data - " + e.getMessage(), e);
        }
    }

image-20200519231413885

可以看到上圖的第5行先初始化row

然後第9行根據ResultSet獲取對應的字段值,

中間會做一些處理與轉換(year類型、bit=>0,1、 clob => string)

最後通過row.setField爲row賦值。

以上是關係型數據庫Mysql的實現。


那麼非關係型數據源呢?

使用Row的優勢?當存在嵌套類型的時候怎麼解決?
測試一下MongoDB

MongoDB 讀寫Row

構造客戶端

client = MongodbClientUtil.getClient(mongodbConfig);
        MongoDatabase db = client.getDatabase(mongodbConfig.getDatabase());
        MongoCollection<Document> collection = db.getCollection(mongodbConfig.getCollectionName());

        if(filter == null){
            findIterable = collection.find();
        } else {
            findIterable = collection.find(filter);
        }

        findIterable = findIterable.skip(split.getSkip())
                .limit(split.getLimit())
                .batchSize(mongodbConfig.getFetchSize());
        cursor = findIterable.iterator();
  1. 連接mongo獲取mongodb連接?(沒有連接池的概念的)
  2. 採用基礎的mongoCollection的 find方法。根據split的skip和limit過濾,並且使用batchSize 進行批量的數據拉取
  3. 返回MongoCursor在調用find時,MongoDB shell並不立即查詢數據庫,而是在等待真正開始獲取數據時才發送查詢。(類似Linq中IQueryable),你可以通過遊標來對最終結果進行控制。
  4. 以下處理每一條數據。
Document doc = cursor.next();
        if(metaColumns.size() == 1 && ConstantValue.STAR_SYMBOL.equals(metaColumns.get(0).getName())){
            row = new Row(doc.size());
            String[] names = doc.keySet().toArray(new String[0]);
            for (int i = 0; i < names.length; i++) {
                row.setField(i,doc.get(names[i]));
            }
        } else {
            row = new Row(metaColumns.size());
            for (int i = 0; i < metaColumns.size(); i++) {
                MetaColumn metaColumn = metaColumns.get(i);

                Object value = null;
                if(metaColumn.getName() != null){
                    value = doc.get(metaColumn.getName());
                    if(value == null && metaColumn.getValue() != null){
                        value = metaColumn.getValue();
                    }
                } else if(metaColumn.getValue() != null){
                    value = metaColumn.getValue();
                }

                if(value instanceof String){
                    value = StringUtil.string2col(String.valueOf(value),metaColumn.getType(),metaColumn.getTimeFormat());
                }

                row.setField(i,value);
            }
        }

        return row;

image-20200522231814880

DEBUG org.mongodb.driver.protocol.command - Sending command '{"find": "bond_info", "limit": 37, "batchSize": 100}' with request id 32 to database data on connection [connectionId{localValue:7, serverValue:30}] to server 192.168.1.101:27017
23:31:01.902 [Legacy Source Thread - Source: mongodbreader -> Sink: mysqlwriter (1/3)] DEBUG org.mongodb.driver.protocol.command - Sending command '{"find": "bond_info", "skip": 37, "limit": 37, "batchSize": 100}' with request id 31 to database data on connection [connectionId{localValue:6, serverValue:31}] to server 192.168.1.101:27017
23:31:01.902 [Legacy Source Thread - Source: mongodbreader -> Sink: mysqlwriter (2/3)] DEBUG org.mongodb.driver.protocol.command - Sending command '{"find": "bond_info", "skip": 74, "limit": 37, "batchSize": 100}' with request id 30 to database data on connection [connectionId{localValue:8, serverValue:32}] to server 192.168.1.101:27017
23:31:01.905 [Legacy Source Thread - Source: mongodbreader -> Sink: mysqlwriter (3/3

從日誌可以看到,在我們配置了並行度爲3的時候,會分爲三個區間分頁查詢語句執行。

可以知道,上面的也不支持嵌套數據的簡單映射。

番外 clobToString

public static Object clobToString(Object obj) throws Exception{
        String dataStr;
        if(obj instanceof Clob){
            Clob clob = (Clob)obj;
            BufferedReader bf = new BufferedReader(clob.getCharacterStream());
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bf.readLine()) != null){
                stringBuilder.append(line);
            }
            dataStr = stringBuilder.toString();
        } else {
            return obj;
        }

        return dataStr;
    }

可以看出來clob類型是字符流

總結

本文對FlinkX中的每一行數據的抽象類Row進行了詳解,我們可以知道這個對象是Flink的原生類型。所以FlinkX可以對所有的類型進行很好的支持,這都取決於Flink的能力。

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