最近有個業務建表使用了 RegexSerDe,之前雖然也它來解析nginx日誌,但是沒有做深入的瞭解。這次看了下其實現方式。

建表語句:

CREATE external TABLE ods_cart_log
(
time_local STRING,
request_json  STRING,
trace_id_num STRING
)
PARTITIONED BY
(
dt string,
hour string
)
ROW FORMAT SERDE `org.apache.hadoop.hive.contrib.serde2.RegexSerDe`
WITH SERDEPROPERTIES
("input.regex" =
"\[(.*?)\] .*\|(.*?) (.*?) \[(.*?)\]",
"output.format.string" ="%1$s %2$s  %4$s")
STORED AS TEXTFILE;

測試資料:

[2014-07-24 15:54:54] [6] OperationData.php: 
:89|{"action":"add","redis_key_hash":9,"time":"1406188494.73745500","source":"web",
"mars_cid":"","session_id":"","info":{"cart_id":26885,"user_id":4,"size_id":"2784145",
"num":"1","warehouse":"VIP_NH","brand_id":"7379","cart_record_id":26885,"channel":"te"}}
 trace_id [40618849399972881308]

這裡trace_id_num按照猜想應該是第4個欄位(即40618849399972881308),但是實際輸出了第3個欄位(trace_id)

檢視其程式碼實現:

RegexSerDe主要由下面三個引數:

1)input.regex 正則

2)output.format.string 輸出格式

3)input.regex.case.insensitive  大小寫是否敏感

其中input.regex用在反序列化方法中,即資料的讀取(hive讀取hdfs檔案),相對的output.format.string 用在序列化的方法中,即資料的寫入(hive寫入hdfs檔案)。

在反序列化的方法deserialize中有如下程式碼,用於返回代表匹配欄位的資料:

   for (int c = 0; c < numColumns; c++) {   //numColumns是按表中column的數量算的(
   比如這個例子columnNames 是[time_local, request_json, trace_id_num]   | numColumns = columnNames.size();
      try {
        row.set(c, m.group(c + 1));  //可以看到欄位的匹配從0開始,中間不會有跳躍,
        所以這裡select  trace_id_num 欄位是正則裡面的第3個組,而和output.format.string沒有關係
          } catch (RuntimeException e) {
        partialMatchedRows++;
        if (partialMatchedRows >= nextPartialMatchedRows) {
          nextPartialMatchedRows = getNextNumberToDisplay(nextPartialMatchedRows);
          // Report the row
          LOG.warn("" + partialMatchedRows
              + " partially unmatched rows are found, " + " cannot find group "
              + c + ": " + rowText);
        }
        row.set(c, null);
      }
    }

  work around的方法有兩個,1個是把所有正則匹配的欄位列出,另一個就是更改正則的分組,只拿自己care的分組,比如上面可以改為

\[(.*?)\] .*\|(.*?) .*? \[(.*?)\]

  這裡output.format.string的設定仔細想想貌似沒什麼用,首先RegexSerDe的方式只在textfile下生效,即可以用load向hive的表中匯入資料,但是load是一個hdfs層面的檔案操作,不涉及到序列化,如果想使用序列化,需要使用insert into select的方式插入資料,但是這種方式插入的資料又和select的資料有關係,和output.format.string沒什麼關係了。。

其實regexserde類有兩個

分別位於

./serde/src/java/org/apache/hadoop/hive/serde2/RegexSerDe.java 和
./contrib/src/java/org/apache/hadoop/hive/contrib/serde2/RegexSerDe.java

都是擴充套件了AbstractSerDe這個抽象類。通過程式碼可以看到contrib下的這個類是實現了serialize 和 deserialize 方法,而上面這個只實現了deserialize 方法,由此看來RegexSerDe中的serialize 方法可能是沒什麼用的。。

另外需要注意幾點:

1.如果一行匹配不上,整個行的欄位輸出都是null

 if (!m.matches()) {
      unmatchedRows++;
      if (unmatchedRows >= nextUnmatchedRows) {
        nextUnmatchedRows = getNextNumberToDisplay(nextUnmatchedRows);
        // Report the row
        LOG.warn("" + unmatchedRows + " unmatched rows are found: " + rowText);
      }
      return null;
    }

2.表的欄位型別必須都是string,否則會報錯,如果需要別的欄位,可以在select中使用cast做轉換

    for ( int c = 0; c < numColumns ; c++) {
      if (!columnTypes.get(c).equals( TypeInfoFactory.stringTypeInfo)) {
        throw new SerDeException(getClass().getName()
            + " only accepts string columns, but column[" + c + "] named "
            + columnNames.get(c) + " has type " + columnTypes.get(c));
      }
    }