Apache Pig的一些基礎概念及用法總結3(轉)

(18)LOAD數據時,如何一次LOAD多個目錄下的數據
例如,我要LOAD兩個HDFS目錄下的數據:/abc/2010 和 /abc/2011,則我們可以這樣寫LOAD語句:

1
A = LOAD '/abc/201{0,1}';

(19)怎樣自己寫一個UDF中的加載函數(load function)
加載函數(load function)是幹什麼的?
先舉一個很簡單的例子,來說明load function的作用。
假設有如下數據:

1
2
3
4
[root@localhost pig]# cat a.txt
1,2,3
a,b,c
9,5,7

我們知道,pig默認是以tab作爲分隔符來加載數據的,所以,如果你沒有指定分隔符的話,將使得每一行都被認爲只有一個字段:

1
2
3
4
5
grunt> B = FOREACH A GENERATE $0;
grunt> DUMP B;
(1,2,3)
(a,b,c)
(9,5,7)

而我們想要以逗號作爲分隔符,則應該使用pig內置函數PigStorage

1
A = LOAD 'a.txt' using PigStorage(',');

這樣的話,我們再用上面的方法DUMP B,得到的結果就是:

1
2
3
(1)
(a)
(9)

這個例子實在太簡單了,在這裏,PigStorage這個函數就是一個加載函數(load function)。
定義:

Load/Store Functions
 
These user-defined functions control how data goes into Pig and comes out of Pig. Often, the same function handles both input and output but that does not have to be the case.

即:加載函數定義了數據如何流入和流出pig。一般來說,同一函數即處理輸入數據,又處理輸出數據,但並不是必須要這樣。
有了這個定義,就很好理解加載函數的作用了。再舉個例子:你在磁盤上保存了只有你自己知道怎麼讀取其格式的數據(例如,數據是按一定規則加密過的,只有你知道如何解密成明文),那麼,你想用pig來處理這些數據,把它們轉換成一個個字段的明文時,你就必須要有這樣一個加載函數(load function),來進行LOAD數據時的轉換工作。這就是加載函數(load function)的作用。
文章來源:http://www.codelast.com/
知道了load function是幹嘛的,現在怎麼寫一個load function?如果你看的是這個鏈接的UDF手冊:Pig Wiki UDF Manual中,會發現它是這樣說的——
加載函數必須要實現 LoadFunc 接口,這個接口類似於下面的樣子:

01
02
03
04
05
06
07
08
09
10
public interface LoadFunc {
    public void bindTo(String fileName, BufferedPositionedInputStream is, long offset, long end) throws IOException;
    public Tuple getNext() throws IOException;
    // conversion functions
    public Integer bytesToInteger(byte[] b) throws IOException;
    public Long bytesToLong(byte[] b) throws IOException;
    ......
    public void fieldsToRead(Schema schema);
    public Schema determineSchema(String fileName, ExecType execType, DataStorage storage) throws IOException;
}

其中:

  • bindTo函數在pig任務開始處理數據之前被調用一次,它試圖將函數與輸入數據關聯起來。
  • getNext函數讀取輸入的數據流並構造下一個元組(tuple)。當完成數據處理時該函數會返回null,當該函數無法處理輸入的元組(tuple)時它會拋出一個IOException異常。
  • 接下來就是一批轉換函數,例如bytesToInteger,bytesToLong等。這些函數的作用是將數據從bytearray轉換成要求的類型。
  • fieldsToRead函數被保留作未來使用,應被留空。
  • determineSchema函數對不同的loader應有不同的實現:對返回真實數據類型(而不是返回bytearray字段)的loader,必須要實現該函數;其他類型的loader只要將determineSchema函數返回null就可以了。

但是,如果你在IDE中import了pig 0.8.1的jar包“pig-0.8.1-core.jar”,會發現 LoadFunc 根本不是一個接口(interface),而是一個抽象類(abstract class),並且要實現的函數也與該文檔中所說的不一致。因此,只能說是文檔過時了。
所以,要看文檔的話,還是要看這個Pig UDF Manual,這裏面的內容纔是對的。
同時,我也推薦另外一個關於Load/Store Function的鏈接:《Programming Pig》Chapter 11. Writing Load and Store Functions。這本書很好很強大。

開始寫一個loader。我們現在寫一個①中所描述的、可以按逗號分隔符加載數據文件的loader——PigStorage已經有這個功能了,不過爲了演示loader是怎麼寫出來的,這裏還是用這個功能來說明。
代碼如下:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.codelast.udf.pig;
 
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.*;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.pig.*;
import org.apache.pig.backend.executionengine.ExecException;
import org.apache.pig.backend.hadoop.executionengine.mapReduceLayer.*;
import org.apache.pig.data.*;
 
import java.io.IOException;
import java.util.*;
 
/**
 * A loader class of pig.
 *
 * @author Darran Zhang (codelast.com)
 * @version 11-10-11
 * @declaration These codes are only for non-commercial use, and are distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 * You must not remove this declaration at any time.
 */
 
public class MyLoader extends LoadFunc {
  protected RecordReader recordReader = null;
 
  @Override
  public void setLocation(String s, Job job) throws IOException {
    FileInputFormat.setInputPaths(job, s);
  }
 
  @Override
  public InputFormat getInputFormat() throws IOException {
    return new PigTextInputFormat();
  }
 
  @Override
  public void prepareToRead(RecordReader recordReader, PigSplit pigSplit) throws IOException {
    this.recordReader = recordReader;
  }
 
  @Override
  public Tuple getNext() throws IOException {
    try {
      boolean flag = recordReader.nextKeyValue();
      if (!flag) {
        return null;
      }
      Text value = (Text) recordReader.getCurrentValue();
      String[] strArray = value.toString().split(",");
      List lst = new ArrayList<String>();
      int i = 0;
      for (String singleItem : strArray) {
        lst.add(i++, singleItem);
      }
      return TupleFactory.getInstance().newTuple(lst);
    } catch (InterruptedException e) {
      throw new ExecException("Read data error", PigException.REMOTE_ENVIRONMENT, e);
    }
  }
}

如上,你的loader類要繼承自LoadFunc虛類,並且需要重寫它的4個方法。其中,getNext方法是讀取數據的方法,它做了讀取出一行數據、按逗號分割字符串、構造一個元組(tuple)並返回的事情。這樣我們就實現了按逗號分隔符加載數據的loader。
文章來源:http://www.codelast.com/
關於load function不得不說的一些話題
如果你要加載一個數據文件,例如:

1
A = LOAD 'myfile' AS (col1:chararray, col2:int);

假設此文件的結構不復雜,你可以手工寫 AS 語句,但如果此文件結構特別複雜,你總不可能每次都手工寫上幾十個甚至上百個字段名及類型定義吧?
這個時候,如果我們可以讓pig從哪裏讀出來要加載的數據的schema(模式),就顯得特別重要了。
在實現load function的時候,我們是通過實現 LoadMetadata 這個接口中的 getSchema 方法來做到這一點的。例如:

1
2
3
4
5
6
public class MyLoadFunc extends LoadFunc implements LoadMetadata {
 
  public ResourceSchema getSchema(String filename, Job job) throws IOException {
    //TODO:
  }
}

實現了 getSchema 方法之後,在pig腳本中加載數據的時候,就可以無需編寫 AS 語句,就可以使用你在 getSchema 方法中指定的模式了。例如:

1
2
3
4
REGISTER 'myUDF.jar';
A = LOAD 'myfile' USING com.codelast.MyLoadFunc();
B = foreach A generate col1;
SOTRE B INTO 'output';

看清楚了,在 LOAD 的時候,我們並沒有寫 AS 語句來指定字段名,但是在後面的 FOREACH 中,我們卻可以使用 col1 這樣的字段名,這正是因爲 getSchema 方法的實現爲我們做到了這一點。在數據文件的結構特別複雜的時候,這個功能幾乎是不可或缺的,否則難以想像會給分析數據的人帶來多大的不便。

(20)重載(overloading)一個UDF
類似於C++的函數重載,pig中也可以重載UDF,例如一個函數ADD可以對兩個int進行操作,也可以對兩個double進行操作,那麼我們可以爲該函數實現 getArgToFuncMapping 方法,該函數返回一個 List<FuncSpec> 對象,這個對象中包含了參數的類型信息。具體怎麼實現,可以看這個鏈接(搜索“Overloading UDFs”定位到所需章節)。

(21)pig運行不起來,提示“org.apache.hadoop.ipc.Client – Retrying connect to server: localhost/127.0.0.1:9000. Already tried 1 time(s)”錯誤的解決辦法
發生這個錯誤時,請先檢查Hadoop的各個進程是否都運行起來了,例如,在我的一次操作中,遇到這個錯誤時,我發現Hadoop namenode進程沒有啓動起來:

1
ps -ef | grep java | grep NameNode

應該有兩個進程啓動起來了:

org.apache.hadoop.hdfs.server.namenode.NameNode
org.apache.hadoop.hdfs.server.namenode.SecondaryNameNode

如果沒有,那麼你要到Hadoop安裝目錄下的“logs”目錄下,查看NameNode的日誌記錄文件(視用戶不同,日誌文件的名字也會有不同),例如,我的NameNone日誌文件 hadoop–namenode-root-XXX.log 的末尾,顯示出有如下錯誤:

ERROR org.apache.hadoop.hdfs.server.namenode.NameNode: org.apache.hadoop.hdfs.server.common.InconsistentFSStateException: Directory /tmp/hadoop-root/dfs/name is in an inconsistent state: storage directory does not exist or is not accessible.

文章來源:http://www.codelast.com/
我到它提示的地方一看,果然不存在最後一級目錄(我是僞分佈式運行的Hadoop,不要覺得奇怪),於是手工創建了這個目錄,然後停掉Hadoop:

1
stop-all.sh

稍候一會兒再重新啓動Hadoop:

1
start-all.sh

然後再去看一下NameNode的日誌,又發現了新的錯誤信息:

ERROR org.apache.hadoop.hdfs.server.namenode.NameNode: java.io.IOException: NameNode is not formatted.

這表明NameNode沒有被格式化。於是將其格式化:

1
[root@localhost bin]# hadoop namenode -format

命令行問你是否要格式化的時候,選擇YES即可。格式化完後會提示:

common.Storage: Storage directory /tmp/hadoop-root/dfs/name has been successfully formatted.

說明成功了。這個時候,再像前面一樣重啓Hadoop進程,再去看NameNode的日誌文件的最後若干行,應該會發現前面的那些錯誤提示沒了。這個時候,再檢查Hadoop各進程是否都成功地啓動了,如果是的話,那麼這個時候你就可以在Hadoop的僞分佈式模式下啓動pig:

1
[root@localhost home]# pig

而不用以本地模式來運行pig了(pig -x local)。
總之,配置一個僞分佈式的Hadoop來調試pig在某些情況下是很有用的,但是比較麻煩,因爲還牽涉到Hadoop的正確配置,但是最好搞定它,以後大有用處啊。

(22)用含有null的字段來GROUP,結果會如何
假設有數據文件 a.txt 內容爲:

1
2
3
4
1 2 5
1   3
1 3
6 9 8

其中,每兩列數據之間是用tab分割的,第二行的第2列、第三行的第3列沒有內容(也就是說,加載到Pig裏之後,對應的數據會變成null),如果把這些數據按第1、第2列來GROUP的話,第1、2列中含有null的行會被忽略嗎?
來做一下試驗:

1
2
3
A = LOAD 'a.txt' AS (col1:int, col2:int, col3:int);
B = GROUP A BY (col1, col2);
DUMP B;

輸出結果爲:

1
2
3
4
((1,2),{(1,2,5)})
((1,3),{(1,3,)})
((1,),{(1,,3)})
((6,9),{(6,9,8)})

從上面的結果(第三行)可見,原數據中第1、2列裏含有null的行也被計入在內了,也就是說,GROUP操作是不會忽略null的,這與COUNT有所不同(見本文前面的部分)。

(23)如何統計數據中某些字段的組合有多少種
假設有如下數據:

1
2
3
4
5
[root@localhost]# cat a.txt
1 3 4 7
1 3 5 4
2 7 0 5
9 8 6 6

現在我們要統計第1、2列的不同組合有多少種,對本例來說,組合有三種:

1
2
3
1 3
2 7
9 8

也就是說我們要的答案是3。
用Pig怎麼計算?
文章來源:http://www.codelast.com/
先寫出全部的Pig代碼:

1
2
3
4
5
A = LOAD 'a.txt' AS (col1:int, col2:int, col3:int, col4:int);
B = GROUP A BY (col1, col2);
C = GROUP B ALL;
D = FOREACH C GENERATE COUNT(B);
DUMP D;

然後再來看看這些代碼是如何計算出上面的結果的:
第一行代碼加載數據,沒什麼好說的。
第二行代碼,得到第1、2列數據的所有組合。B的數據結構爲:

1
2
grunt> DESCRIBE B;
B: {group: (col1: int,col2: int),A: {col1: int,col2: int,col3: int,col4: int}}

把B DUMP出來,得到:

1
2
3
((1,3),{(1,3,4,7),(1,3,5,4)})
((2,7),{(2,7,0,5)})
((9,8),{(9,8,6,6)})

非常明顯,(1,3),(2,7),(9,8)的所有組合已經被排列出來了,這裏得到了若干行數據。下一步我們要做的就是統計這樣的數據一共有多少行,也就得到了第1、2列的組合有多少組。
第三和第四行代碼,就實現了統計數據行數的功能。參考本文前面部分的“怎樣統計數據行數”一節。就明白這兩句代碼是什麼意思了。
這裏需要特別說明的是:
a)爲什麼倒數第二句代碼中是COUNT(B),而不是COUNT(group)?
我們是對C進行FOREACH,所以要先看看C的數據結構:

1
2
grunt> DESCRIBE C;
C: {group: chararray,B: {group: (col1: int,col2: int),A: {col1: int,col2: int,col3: int,col4: int}}}

可見,你可以把C想像成一個map的結構,key是一個group,value是一個包(bag),它的名字是B,這個包中有N個元素,每一個元素都對應到②中所說的一行。根據②的分析,我們就是要統計B中元素的個數,因此,這裏當然就是COUNT(B)了。
b)COUNT函數的作用是統計一個包(bag)中的元素的個數:

COUNT
Computes the number of elements in a bag.
從C的數據結構看,B是一個bag,所以COUNT函數是可以用於它的。
如果你試圖把COUNT應用於一個非bag的數據結構上,會發生錯誤,例如:
1
java.lang.ClassCastException: org.apache.pig.data.BinSedesTuple cannot be cast to org.apache.pig.data.DataBag

這是把Tuple傳給COUNT函數時發生的錯誤。

(24)兩個整型數相除,如何轉換爲浮點型,從而得到正確的結果
這個問題其實很傻,或許不用說你也知道了:假設有int a = 3 和 int b = 2兩個數,在大多數編程語言裏,a/b得到的是1,想得到正確結果1.5的話,需要轉換爲float再計算。在Pig中其實和這種情況一樣,下面就拿幾行數據來做個實驗:

1
2
3
[root@localhost ~]# cat a.txt
3 2
4 5

在Pig中:

1
2
3
4
5
grunt> A = LOAD 'a.txt' AS (col1:int, col2:int);
grunt> B = FOREACH A GENERATE col1/col2;
grunt> DUMP B;
(1)
(0)

可見,不加類型轉換的計算結果是取整之後的值。
那麼,轉換一下試試:

1
2
3
4
5
grunt> A = LOAD 'a.txt' AS (col1:int, col2:int);
grunt> B = FOREACH A GENERATE (float)(col1/col2);
grunt> DUMP B;
(1.0)
(0.0)

這樣轉換還是不行的,這與大多數編程語言的結果一致——它只是把取整之後的數再轉換爲浮點數,因此當然是不行的。
文章來源:http://www.codelast.com/
正確的做法應該是:

1
2
3
4
5
grunt> A = LOAD 'a.txt' AS (col1:int, col2:int);
grunt> B = FOREACH A GENERATE (float)col1/col2;
grunt> DUMP B;
(1.5)
(0.8)

或者這樣也行:

1
2
3
4
5
grunt> A = LOAD 'a.txt' AS (col1:int, col2:int);
grunt> B = FOREACH A GENERATE col1/(float)col2;
grunt> DUMP B;
(1.5)
(0.8)

這與我們的通常做法是一致的,因此,你要做除法運算的時候,需要注意這一點。

(25)UNION的一個例子
假設有兩個數據文件爲:

1
2
3
4
5
6
7
8
[root@localhost ~]# cat 1.txt
0 3
1 5
0 8
 
[root@localhost ~]# cat 2.txt
1 6
0 9

現在要求出:在第一列相同的情況下,第二列的和分別爲多少?
例如,第一列爲 1 的時候,第二列有5和6兩個值,和爲11。同理,第一列爲0的時候,第二列的和爲 3+8+9=20。
計算此問題的Pig代碼如下:

1
2
3
4
5
6
A = LOAD '1.txt' AS (a: int, b: int);
B = LOAD '2.txt' AS (c: int, d: int);
C = UNION A, B;
D = GROUP C BY $0;
E = FOREACH D GENERATE FLATTEN(group), SUM(C.$1);
DUMP E;

輸出爲:

1
2
(0,20)
(1,11)

文章來源:http://www.codelast.com/
我們來看看每一步分別做了什麼:
第1行、第2行代碼分別加載數據到關係A、B中,沒什麼好說的。
第3行代碼,將關係A、B合併起來了。合併後的數據結構爲:

1
2
grunt> DESCRIBE C;
C: {a: int,b: int}

其數據爲:

1
2
3
4
5
6
grunt> DUMP C;
(0,3)
(1,5)
(0,8)
(1,6)
(0,9)

第4行代碼按第1列(即$0)進行分組,分組後的數據結構爲:

1
2
grunt> DESCRIBE D;
D: {group: int,C: {a: int,b: int}}

其數據爲:

1
2
3
grunt> DUMP D;
(0,{(0,9),(0,3),(0,8)})
(1,{(1,5),(1,6)})

最後一行代碼,遍歷D,將D中每一行裏的所有bag(即C)的第2列(即$1)進行累加,就得到了我們要的結果。

轉載:http://www.codelast.com/


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