最近有個業務建表使用了 RegexSerDe,之前雖然也它來解析nginx日志,但是沒有做深入的了解。這次看了下其實作方式。
建表語句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<code>CREATE external TABLE ods_cart_log</code>
<code>(</code>
<code>time_local STRING,</code>
<code>request_json STRING,</code>
<code>trace_id_num STRING</code>
<code>)</code>
<code>PARTITIONED BY</code>
<code>dt string,</code>
<code>hour string</code>
<code>ROW FORMAT SERDE </code><code>'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'</code>
<code>WITH SERDEPROPERTIES</code>
<code>(</code><code>"input.regex"</code> <code>=</code>
<code>"\\\[(.*?)\\\] .*\\\|(.*?) (.*?) \\\[(.*?)\\\]"</code><code>,</code>
<code>"output.format.string"</code> <code>=</code><code>"%1$s %2$s %4$s"</code><code>)</code>
<code>STORED AS TEXTFILE;</code>
測試資料:
<code>[2014-07-24 15:54:54] [6] OperationData.php: </code>
<code>:89|{</code><code>"action"</code><code>:</code><code>"add"</code><code>,</code><code>"redis_key_hash"</code><code>:9,</code><code>"time"</code><code>:</code><code>"1406188494.73745500"</code><code>,</code><code>"source"</code><code>:</code><code>"web"</code><code>,</code>
<code>"mars_cid"</code><code>:</code><code>""</code><code>,</code><code>"session_id"</code><code>:</code><code>""</code><code>,</code><code>"info"</code><code>:{</code><code>"cart_id"</code><code>:26885,</code><code>"user_id"</code><code>:4,</code><code>"size_id"</code><code>:</code><code>"2784145"</code><code>,</code>
<code>"num"</code><code>:</code><code>"1"</code><code>,</code><code>"warehouse"</code><code>:</code><code>"VIP_NH"</code><code>,</code><code>"brand_id"</code><code>:</code><code>"7379"</code><code>,</code><code>"cart_record_id"</code><code>:26885,</code><code>"channel"</code><code>:</code><code>"te"</code><code>}}</code>
<code> </code><code>trace_id [40618849399972881308]</code>
這裡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中有如下代碼,用于傳回代表比對字段的資料:
<code> </code><code>for</code> <code>(</code><code>int</code> <code>c = </code><code>0</code><code>; c < numColumns; c++) { </code><code>//numColumns是按表中column的數量算的(</code>
<code> </code><code>比如這個例子columnNames 是[time_local, request_json, trace_id_num] | numColumns = columnNames.size();</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>row.set(c, m.group(c + </code><code>1</code><code>)); </code><code>//可以看到字段的比對從0開始,中間不會有跳躍,</code>
<code> </code><code>是以這裡select trace_id_num 字段是正則裡面的第</code><code>3</code><code>個組,而和output.format.string沒有關系</code>
<code> </code><code>} </code><code>catch</code> <code>(RuntimeException e) {</code>
<code> </code><code>partialMatchedRows++;</code>
<code> </code><code>if</code> <code>(partialMatchedRows >= nextPartialMatchedRows) {</code>
<code> </code><code>nextPartialMatchedRows = getNextNumberToDisplay(nextPartialMatchedRows);</code>
<code> </code><code>// Report the row</code>
<code> </code><code>LOG.warn(</code><code>""</code> <code>+ partialMatchedRows</code>
<code> </code><code>+ </code><code>" partially unmatched rows are found, "</code> <code>+ </code><code>" cannot find group "</code>
<code> </code><code>+ c + </code><code>": "</code> <code>+ rowText);</code>
<code> </code><code>}</code>
<code> </code><code>row.set(c, </code><code>null</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
work around的方法有兩個,1個是把所有正則比對的字段列出,另一個就是更改正則的分組,隻拿自己care的分組,比如上面可以改為
<code>\\\[(.*?)\\\] .*\\\|(.*?) .*? \\\[(.*?)\\\]</code>
這裡output.format.string的設定仔細想想貌似沒什麼用,首先RegexSerDe的方式隻在textfile下生效,即可以用load向hive的表中導入資料,但是load是一個hdfs層面的檔案操作,不涉及到序列化,如果想使用序列化,需要使用insert into select的方式插入資料,但是這種方式插入的資料又和select的資料有關系,和output.format.string沒什麼關系了。。
其實regexserde類有兩個
分别位于
<code>./serde/src/java/org/apache/hadoop/hive/serde2/RegexSerDe.java 和</code>
<code>./contrib/src/java/org/apache/hadoop/hive/contrib/serde2/RegexSerDe.java</code>
都是擴充了AbstractSerDe這個抽象類。通過代碼可以看到contrib下的這個類是實作了serialize 和 deserialize 方法,而上面這個隻實作了deserialize 方法,由此看來RegexSerDe中的serialize 方法可能是沒什麼用的。。
另外需要注意幾點:
1.如果一行比對不上,整個行的字段輸出都是null
<code> </code><code>if</code> <code>(!m.matches()) {</code>
<code> </code><code>unmatchedRows++;</code>
<code> </code><code>if</code> <code>(unmatchedRows >= nextUnmatchedRows) {</code>
<code> </code><code>nextUnmatchedRows = getNextNumberToDisplay(nextUnmatchedRows);</code>
<code> </code><code>// Report the row</code>
<code> </code><code>LOG.warn(</code><code>""</code> <code>+ unmatchedRows + </code><code>" unmatched rows are found: "</code> <code>+ rowText);</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
2.表的字段類型必須都是string,否則會報錯,如果需要别的字段,可以在select中使用cast做轉換
<code> </code><code>for</code> <code>( </code><code>int</code> <code>c = </code><code>0</code><code>; c < numColumns ; c++) {</code>
<code> </code><code>if</code> <code>(!columnTypes.get(c).equals( TypeInfoFactory.stringTypeInfo)) {</code>
<code> </code><code>throw</code> <code>new</code> <code>SerDeException(getClass().getName()</code>
<code> </code><code>+ </code><code>" only accepts string columns, but column["</code> <code>+ c + </code><code>"] named "</code>
<code> </code><code>+ columnNames.get(c) + </code><code>" has type "</code> <code>+ columnTypes.get(c));</code>
本文轉自菜菜光 51CTO部落格,原文連結:http://blog.51cto.com/caiguangguang/1532987,如需轉載請自行聯系原作者