pig学习教程

pig学习教程

一、pig的概念及优点:

     Pig是一个基于Hadoop的大规模数据分析平台,它提供的SQL-LIKE语言叫Pig Latin,该语言的编译器会把类SQL的数据分析请求转换为一系列经过优化处理的MapReduce运算。使用pig可以在处理海量数据时达到事半功倍的效果,比使用Java,C++等语言编写大规模数据处理程序的难度要小N倍,实现同样的效果的代码量也小N倍。Pig为复杂的海量数据并行计算提供了一个简单的操作和编程接口。

Pig的实现有5个主要的部分构成:
如图所示:

1.Pig自己实现的一套框架对输入、输出的人机交互部分的实现,就是Pig Latin 。

2.Zebra是Pig与HDFS/Hadoop的中间层、Zebra是MapReduce作业编写的客户端,Zerbra用结构化的语言实现了对hadoop物理存储元数据的管理也是对Hadoop的数    据抽象层,在Zebra中有2个核心的类 TableStore(写)/TableLoad(读)对Hadoop上的数据进行操作。

3.Pig中的Streaming主要分为4个组件: 1. Pig Latin 2. 逻辑层(Logical Layer) 3. 物理层(Physical Layer) 4. Streaming具体实现(Implementation),Streaming会     创建一个Map/Reduce作业,并把它发送给合适的集群,同时监视这个作业的在集群环境中的整个执行过程。 

4.MapReduce在每台机器上进行分布式计算的框架(算法)。

5.HDFS最终存储数据的部分。

二、关系(relation)、包(bag)、元组(tuple)、字段(field)、数据(data)的关系

 一个关系(relation)是一个包(bag),更具体地说,是一个外部的包(outer bag)。

 一个(bag)是一个元组(tuple)的集合。在pig中表示数据时,用大括号{}括起来的东西表示一个包——无论是在教程中的实例演示,还是在pig交互模式下的输出,都遵循这样的约定,请牢记这一点,因为不理解的话就会对数据结构的掌握产生偏差

 一个元组(tuple)是若干字段(field)的一个有序集(ordered set)。在pig中表示数据时,用小括号()括起来的东西表示一个元组

 一个字段是一块数据(data)。

“元组”这个词很抽象,你可以把它想像成关系型数据库表中的一行,它含有一个或多个字段,其中,每一个字段可以是任何数据类型,并且可以有或者没有数据
“关系”可以比喻成关系型数据库的一张表,而上面说了,“元组”可以比喻成数据表中的一行,那么这里有人要问了,在关系型数据库中,同一张表中的每一行都有固定的字段数,pig中的“关系”与“元组”之间,是否也是这样的情况呢?不是的。“关系”并不要求每一个“元组”都含有相同数量的字段,并且也不会要求各“元组”中在相同位置处的字段具有相同的数据类型(太随意了,是吧?)

三、一个 计算多维度组合下的平均值 的实际例子
  为了帮助大家理解pig的一个基本的数据处理流程,我造了一些简单的数据来举个例子——假设有数据文件:a.txt(各数值之间是以tab分隔的):

1

2

3

4

5

6

7

[root@localhost pig]$cata.txt

a 1 2 3 4.2 9.8

a 3 0 5 3.5 2.1

b 7 9 9 - -

a 7 9 9 2.6 6.2

a 1 2 5 7.7 5.9

a 1 2 3 1.4 0.2

 

 

问题如下:怎样求出在第2、3、4列的所有组合的情况下,最后两列的平均值分别是多少?
例如,第2、3、4列有一个组合为(1,2,3),即第一行和最后一行数据。对这个维度组合来说,最后两列的平均值分别为:
(4.2+1.4)/2=2.8
(9.8+0.2)/2=5.0
而对于第2、3、4列的其他所有维度组合,都分别只有一行数据,因此最后两列的平均值其实就是它们自身。
特别地,组合(7,9,9)有两行记录:第三、四行,但是第三行数据的最后两列没有值,因此它不应该被用于平均值的计算,也就是说,在计算平均值时,第三行是无效数据。所以(7,9,9)组合的最后两列的平均值为 2.6 和 6.2。
我们现在用pig来算一下,并且输出最终的结果。
先进入本地调试模式(pig -x local),再依次输入如下pig代码:

1

2

3

4

A =LOAD'a.txt'AS(col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double);

B =GROUPABY(col2, col3, col4);

C = FOREACH B GENERATEgroup,AVG(A.col5),AVG(A.col6);

DUMP C;

 

 

pig输出结果如下:

1

2

3

4

((1,2,3),2.8,5.0)

((1,2,5),7.7,5.9)

((3,0,5),3.5,2.1)

((7,9,9),2.6,6.2)

 

 

这个结果对吗?手工算一下就知道是对的。
文章来源:http://www.codelast.com/
下面,我们依次来看看每一句pig代码分别得到了什么样的数据。
加载 a.txt 文件,并指定每一列的数据类型分别为 chararray(字符串),intintintdoubledouble。同时,我们还给予了每一列别名,分别为 col1col2,……,col6。这个别名在后面的数据处理中会用到——如果你不指定别名,那么在后面的处理中,就只能使用索引($0$1,……)来标识相应的列了,这样可读性会变差,因此,在列固定的情况下,还是指定别名的好。
将数据加载之后,保存到变量A中,A的数据结构如下:

1

A: {col1: chararray,col2:int,col3:int,col4:int,col5:double,col6:double}

 

可见,A是用大括号括起来的东西。根据本文前面的说法,A是一个包(bag)。
这个时候,A与你想像中的样子应该是一致的,也就是与前面打印出来的 a.txt 文件的内容是一样的,还是一行一行的类似于“二维表”的数据。
文章来源:http://www.codelast.com/
按照A的第234列,对A进行分组。pig会找出所有第234列的组合,并按照升序进行排列,然后将它们与对应的包A整合起来,得到如下的数据结构:

1

B:{group:(col2:int,col3:int,col4:int),

A:{col1: chararray,col2:int,col3:int,col4:int,col5:double,col6:double}}

可见,A的第234列的组合被pig赋予了一个别名:group,这很形象。同时我们也观察到,B的每一行其实就是由一个group和若干个A组成的——注意,是若干个A。这里之所以只显示了一个A,是因为这里表示的是数据结构,而不表示具体数据有多少组。
实际的数据为:

1

2

3

4

((1,2,3),{(a,1,2,3,4.2,9.8),(a,1,2,3,1.4,0.2)})

((1,2,5),{(a,1,2,5,7.7,5.9)})

((3,0,5),{(a,3,0,5,3.5,2.1)})

((7,9,9),{(b,7,9,9,,),(a,7,9,9,2.6,6.2)})

 

可见,与前面所说的一样,组合(123)对应了两行数据,组合(799)也对应了两行数据。
这个时候,B的结构就不那么明朗了,可能与你想像中有一点不一样了。

计算每一种组合下的最后两列的平均值。
根据上面得到的B的数据,你可以把B想像成一行一行的数据(只不过这些行不是对称的),FOREACH 的作用是对 的每一行数据进行遍历,然后进行计算。
GENERATE 可以理解为要生成什么样的数据,这里的 group 就是上一步操作中B的第一项数据(即pigA的第234列的组合赋予的别名),所以它告诉了我们:在数据集 的每一行里,第一项就是B中的group——类似于(125)这样的东西)。
而 AVG(A.col5) 这样的计算,则是调用了pig的一个求平均值的函数 AVG,用于对 的名为 col5 的列求平均值。前文说了,在加载数据到A的时候,我们已经给每一列起了个别名,col5就是倒数第二列。
到这里,可能有人要迷糊了:难道 AVG(A.col5) 不是表示对 col5这一列求平均值吗?也就是说,在遍历BFOREACH B)的每一行时候,计算结果都是相同的啊!
事实上并不是这样。我们遍历的是B,我们需要注意到,B的数据结构中,每一行数据里,一个group对应的是若干个A,因此,这里的 A.col5,指的是B的每一行中的A,而不是包含全部数据的那个A。拿B的第一行来举例:
((1,2,3),{(a,1,2,3,4.2,9.8),(a,1,2,3,1.4,0.2)})
遍历到B的这一行时,要计算AVG(A.col5)pig会找到 (a,1,2,3,4.2,9.8) 中的4.2,以及(a,1,2,3,1.4,0.2)中的1.4,加起来除以2,就得到了平均值。
同理,我们也知道了AVG(A.col6)是怎么算出来的。但还有一点要注意的:对(7,9,9)这个组,它对应的数据(b,7,9,9,,)里最后两列是无值的,这是因为我们的数据文件对应位置上不是有效数字,而是两个“-”,pig在加载数据的时候自动将它置为空了,并且计算平均值的时候,也不会把这一组数据考虑在内(相当于忽略这组数据的存在)。
到了这里,我们不难理解,为什么C的数据结构是这样的了:

1

C: {group: (col2:int,col3:int,col4:int),double,double}

 

文章来源:http://www.codelast.com/
DUMP C就是将C中的数据输出到控制台。如果要输出到文件,需要使用:

1

STORE CINTO'output';

 

这样pig就会在当前目录下新建一个“output”目录(该目录必须事先不存在),并把结果文件放到该目录下。

请想像一下,如果要实现相同的功能,用JavaC++写一个Map-Reduce应用程序需要多少时间?可能仅仅是写一个build.xml或者Makefile,所需的时间就是写这段pig代码的几十倍了!
正因为pig有如此优势,它才得到了广泛应用。
文章来源:http://www.codelast.com/
四、怎样统计数据行
SQL语句中,要统计表中数据的行数,很简单:

1

SELECTCOUNT(*)FROMtable_nameWHEREcondition

pig中,也有一个COUNT函数,在pig手册中,对COUNT函数有这样的说明:

Computes the number of elements in a bag.

假设要计算数据文件a.txt的行数:

1

2

3

4

5

6

7

[root@localhost pig]$cata.txt

a 1 2 3 4.2 9.8

a 3 0 5 3.5 2.1

b 7 9 9 - -

a 7 9 9 2.6 6.2

a 1 2 5 7.7 5.9

a 1 2 3 1.4 0.2

 

你是否可以这样做呢:

1

2

3

A =LOAD'a.txt'AS(col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double);

B =COUNT(*);

DUMP B;

 

答案是:绝对不行pig会报错。pig手册中写得很明白:

Note: You cannot use the tuple designator (*) with COUNT; that is, COUNT(*) will not work.

那么,这样对某一列计数行不行呢:

1

B = COUNT(A.col2);

 

答案是:仍然不行。pig会报错。
这就与我们想像中的“正确做法”有点不一样了:我为什么不能直接统计一个字段的数目有多少呢?刚接触pig的时候,一定非常疑惑这样明显“不应该出错”的写法为什么行不通。
要统计A中含col2字段的数据有多少行,正确的做法是:

1

2

3

4

A = LOAD'a.txt'AS(col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double);

B = GROUP A ALL;

C = FOREACH B GENERATE COUNT(A.col2);

DUMP C;

 

输出结果:

1

(6)

 

表明有6行数据。
如此麻烦?没错。这是由pig的数据结构决定的。
文章来源:http://www.codelast.com/
在这个例子中,统计COUNT(A.col2)COUNT(A)的结果是一样的,但是,如果col2这一列中含有空值:

1

2

3

4

5

6

7

[root@localhost pig]$cata.txt

a 1 2 3 4.2 9.8

a   0 5 3.5 2.1

b 7 9 9 - -

a 7 9 9 2.6 6.2

a 1 2 5 7.7 5.9

a 1 2 3 1.4 0.2

 

则以下pig程序及执行结果为:

1

2

3

4

5

grunt> A = LOAD'a.txt'AS(col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double);

grunt> B = GROUP A ALL;

grunt> C = FOREACH B GENERATE COUNT(A.col2);

grunt> DUMP C;

(5)

 

可见,结果为5行。那是因为你LOAD数据的时候指定了col2的数据类型为int,而a.txt的第二行数据是空的,因此数据加载到A以后,有一个字段就是空的:

1

2

3

4

5

6

7

grunt> DUMP A;

(a,1,2,3,4.2,9.8)

(a,,0,5,3.5,2.1)

(b,7,9,9,,)

(a,7,9,9,2.6,6.2)

(a,1,2,5,7.7,5.9)

(a,1,2,3,1.4,0.2)

 

COUNT的时候,null的字段不会被计入在内,所以结果是5

The COUNT function follows syntax semantics and ignores nulls. What this means is that a tuple in the bag will not be counted if the first field in this tuple is NULL. If you want to include NULL values in the count computation, use COUNT_STAR.

五、FLATTEN操作符的作用

这个玩意一开始还是挺让我费解的。从字面上看,flatten就是“弄平”的意思,但是在对一个pig的数据结构操作时,flatten到底是“弄平”了什么,又有什么作用呢?
我们还是采用前面的a.txt数据文件来说明:

1

2

3

4

5

6

7

[root@localhost pig]$cata.txt

a 1 2 3 4.2 9.8

a 3 0 5 3.5 2.1

b 7 9 9 - -

a 7 9 9 2.6 6.2

a 1 2 5 7.7 5.9

a 1 2 3 1.4 0.2

 

如果我们按照前文的做法,计算多维度组合下的最后两列的平均值,则:

1

2

3

4

5

6

7

8

grunt> A = LOAD'a.txt'AS(col1:chararray, col2:int, col3:int, col4:int, col5:double col6:double);

grunt> B = GROUP A BY(col2, col3, col4);

grunt> C = FOREACH B GENERATE group,AVG(A.col5),AVG(A.col6);

grunt> DUMP C;

((1,2,3),2.8,5.0)

((1,2,5),7.7,5.9)

((3,0,5),3.5,2.1)

((7,9,9),2.6,6.2)

 

可见,输出结果中,每一行的第一项是一个tuple(元组),我们来试试看 FLATTEN 的作用:

1

2

3

4

5

6

7

8

grunt> A = LOAD'a.txt' AS (col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double);

grunt> B = GROUP A BY (col2, col3, col4);

grunt> C = FOREACH B GENERATE FLATTEN(group), AVG(A.col5), AVG(A.col6);

grunt> DUMP C;

(1,2,3,2.8,5.0)

(1,2,5,7.7,5.9)

(3,0,5,3.5,2.1)

(7,9,9,2.6,6.2)

看到了吗?被 FLATTEN group本来是一个元组,现在变成了扁平的结构了。按照pig文档的说法,FLATTEN用于对元组(tuple)和包(bag)“解嵌套”(un-nest

The FLATTEN operator looks like a UDF syntactically, but it is actually an operator that changes the structure of tuples and bags in a way that a UDF cannot. Flatten un-nests tuples as well as bags. The idea is the same, but the operation and result is different for each type of structure.

For tuples, flatten substitutes the fields of a tuple in place of the tuple. For example, consider a relation that has a tuple of the form (a, (b, c)). The expression GENERATE $0, flatten($1), will cause that tuple to become (a, b, c).

文章来源:http://www.codelast.com/
所以我们就看到了上面的结果。
在有的时候,不“解嵌套”的数据结构是不利于观察的,输出这样的数据可能不利于外围数程序的处理(例如,pig将数据输出到磁盘后,我们还需要用其他程序做后续处理,而对一个元组,输出的内容里是含括号的,这就在处理流程上又要多一道去括号的工序),因此,FLATTEN提供了一个让我们在某些情况下可以清楚、方便地分析数据的机会。

六、关于GROUP操作符
在上文的例子中,已经演示了GROUP操作符会生成什么样的数据。在这里,需要说得更理论一些:

 用于GROUPkey如果多于一个字段(正如本文前面的例子),则GROUP之后的数据的key是一个元组(tuple),否则它就是与用于GROUPkey相同类型的东西。

 GROUP的结果是一个关系(relation),在这个关系中,每一组包含一个元组(tuple),这个元组包含两个字段:1第一个字段被命名为“group”——这一点非常容易与GROUP关键字相混淆,但请区分开来。该字段的类型与用于GROUPkey类型相同。2第二个字段是一个包(bag),它的类型与被GROUP的关系的类型相同。

七、把数据当作“元组”(tuple)来加载
还是假设有如下数据:

1

2

3

4

5

6

7

[root@localhost pig]$cata.txt

a 1 2 3 4.2 9.8

a 3 0 5 3.5 2.1

b 7 9 9 - -

a 7 9 9 2.6 6.2

a 1 2 5 7.7 5.9

a 1 2 3 1.4 0.2

如果我们按照以下方式来加载数据:

1

A = LOAD 'a.txt'AS(col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double);

那么得到的A的数据结构为:

1

2

grunt> DESCRIBE A;

A: {col1: chararray,col2:int,col3:int,col4:int,col5:double,col6:double}

 

如果你要把A当作一个元组(tuple)来加载:

1

A =LOAD'a.txt'AS(T : tuple (col1:chararray, col2:int, col3:int, col4:int, col5:double

  , col6:double));

也就是想要得到这样的数据结构:

1

2

grunt> DESCRIBE A;

A: {T: (col1: chararray,col2:int,col3:int,col4:int,col5:double,col6:double)}

 

那么,上面的方法将得到一个空的A

1

2

3

4

5

6

7

grunt> DUMP A;

()

()

()

()

()

()

 

那是因为数据文件a.txt的结构不适合于这样加载成元组(tuple)。
文章来源:http://www.codelast.com/
如果有数据文件b.txt

1

2

3

4

5

6

7

[root@localhost pig]$catb.txt

(a,1,2,3,4.2,9.8)

(a,3,0,5,3.5,2.1)

(b,7,9,9,-,-)

(a,7,9,9,2.6,6.2)

(a,1,2,5,7.7,5.9)

(a,1,2,3,1.4,0.2)

 

则使用上面所说的加载方法及结果为:

1

2

3

4

5

6

7

8

grunt> A = LOAD'b.txt'AS (T : tuple (col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double));

grunt> DUMP A;

((a,1,2,3,4.2,9.8))

((a,3,0,5,3.5,2.1))

((b,7,9,9,,))

((a,7,9,9,2.6,6.2))

((a,1,2,5,7.7,5.9))

((a,1,2,3,1.4,0.2))

 

可见,加载的数据的结构确实被定义成了元组(tuple)。

八、在多维度组合下,如何计算某个维度组合里的不重复记录的条数
以数据文件 c.txt 为例:

1

2

3

4

5

6

7

[root@localhost pig]$catc.txt

a 1 2 3 4.2 9.8 100

a 3 0 5 3.5 2.1 200

b 7 9 9 - - 300

a 7 9 9 2.6 6.2 300

a 1 2 5 7.7 5.9 200

a 1 2 3 1.4 0.2 500

 

问题:如何计算在第234列的所有维度组合下,最后一列不重复的记录分别有多少条?例如,第234列有一个维度组合是(123),在这个维度维度下,最后一列有两种值:100 和 500,因此不重复的记录数为2。同理可求得其他的记录条数。
pig代码及输出结果如下:

1

2

3

4

5

6

7

8

grunt> A = LOAD'c.txt'AS (col1:chararray, col2:int, col3:int, col4:int, col5:double, col6:double, col7:int);

grunt> B = GROUP A BY (col2, col3, col4);

grunt> C = FOREACH B {D = DISTINCT A.col7; GENERATE group, COUNT(D);};

grunt> DUMP C;

((1,2,3),2)

((1,2,5),1)

((3,0,5),1)

((7,9,9),1)

 

我们来看看每一步分别生成了什么样的数据:
LOAD不用说了,就是加载数据;
GROUP也不用说了,和前文所说的一样。GROUP之后得到了这样的数据:

1

2

3

4

5

grunt> DUMP B;

((1,2,3),{(a,1,2,3,4.2,9.8,100),(a,1,2,3,1.4,0.2,500)})

((1,2,5),{(a,1,2,5,7.7,5.9,200)})

((3,0,5),{(a,3,0,5,3.5,2.1,200)})

((7,9,9),{(b,7,9,9,,,300),(a,7,9,9,2.6,6.2,300)})

 

其实到这里,我们肉眼就可以看出来最后要求的结果是什么了,当然,必须要由pig代码来完成,要不然怎么应对海量数据?
文章来源:http://www.codelast.com/
这里的 FOREACH 与前面有点不一样,第一次看到这种写法,肯定会觉得很奇怪。先看一下用于去重DISTINCT关键字的说明:

Removes duplicate tuples in a relation.

然后再解释一下:FOREACH 是对B的每一行进行遍历,其中,B的每一行里含有一个包(bag),每一个包中含有若干元组(tupleA,因此,FOREACH 后面的大括号里的操作,其实是对所谓的“内部包”(inner bag)的操作(详情请参看FOREACH的说明),在这里,我们指定了对Acol7这一列进行去重,去重的结果被命名为D,然后再对D计数(COUNT),就得到了我们想要的结果。
输出结果数据,与前文所述的差不多。
这样就达成了我们的目的。从总体上说,刚接触pig不久的人会觉得这些写法怪怪的,就是扭不过来,但是要坚持,时间长了,连倒影也会让你觉得是正的了。

九、如何将关系(relation)转换为标量(scalar)
在前文中,我们要统计符合某些条件的数据的条数,使用了COUNT函数来计算,但在COUNT之后,我们得到的还是一个关系(relation),而不是一个标量的数字,如何把一个关系转换为标量,从而可以在后续处理中便于使用呢?
具体请看这个链接

十、pig中如何使用shell进行辅助数据处理
pig中可以嵌套使用shell进行辅助处理,下面,就以一个实际的例子来说明。
假设我们在某一步pig处理后,得到了类似于下面 b.txt 中的数据:

1

2

3

4

[root@localhost pig]$catb.txt

1 5 98  = 7

34  8 6 3 2

62  0 6 = 65

 

问题:如何将数据中第4列中的“=”符号全部替换为9999
pig代码及输出结果如下:

1

2

3

4

5

6

grunt> A = LOAD'b.txt'AS (col1:int, col2:int, col3:int, col4:chararray, col5:int);

grunt> B = STREAM A THROUGH `awk'{if($4 == "=") print $1"\t"$2"\t"$3"\t9999\t"$5; else print $0}'`;

grunt> DUMP B;

(1,5,98,9999,7)

(34,8,6,3,2)

(62,0,6,9999,65)

 

我们来看看这段代码是如何做到的:
加载数据,这个没什么好说的。
通过“STREAM … THROUGH …”的方式,我们可以调用一个shell语句,用该shell语句对A的每一行数据进行处理。此处的shell逻辑为:当某一行数据的第4列为“=”符号时,将其替换为“9999”;否则就照原样输出这一行。
输出B,可见结果正确。

十一、向pig脚本中传入参数
假设你的pig脚本输出的文件是通过外部参数指定的,则此参数不能写死,需要传入。在pig中,使用传入的参数如下所示:

1

STORE AINTO'$output_dir';

则这个“output_dir”就是个传入的参数。在调用这个pig脚本的shell脚本中,我们可以这样传入参数:

1

pig -param output_dir="/home/my_ourput_dir/"my_pig_script.pig

这里传入的参数“output_dir”的值为“/home/my_output_dir/”。
十二、就算是同样一段pig代码,多次计算所得的结果也有可能是不同的
例如用AVG函数来计算平均值时,同样一段pig代码,多次计算所得的结果中,小数点的最后几位也有可能是不相同的(当然也有可能相同),大概是因为精度的原因吧。不过,一般来说小数点的最后几位已经不重要了。例如我对一个数据集进行处理后,小数点后13位才开始有不同,这样的精度完全足够了。

十三、如何编写及使用自定义函数(UDF)
请看这个链接:《Apache Pig中文教程(进阶)

十四、什么是聚合函数(Aggregate Function)
pig中,聚合函数就是那些接受一个输入包(bag),返回一个标量(scalar)值的函数。COUNT函数就是一个例子。

十五、COGROUP做了什么
GROUP操作符一样,COGROUP也是用来分组的,不同的是,COGROUP可以按多个关系中的字段进行分组。
还是以一个实例来说明,假设有以下两个数据文件:

01

02

03

04

05

06

07

08

09

10

[root@localhost pig]$cata.txt

uidk  12  3

hfd 132 99

bbN 463 231

UFD 13  10

 

[root@localhost pig]$catb.txt

908 uidk  888

345 hfd 557

28790 re  00000

现在我们用pig做如下操作及得到的结果为:

1

2

3

4

5

6

7

8

9

grunt> A =LOAD'a.txt'AS(acol1:chararray, acol2:int, acol3:int);

grunt> B =LOAD'b.txt'AS(bcol1:int, bcol2:chararray, bcol3:int);

grunt> C = COGROUP ABYacol1, BBYbcol2;

grunt> DUMP C;

(re,{},{(28790,re,0)})

(UFD,{(UFD,13,10)},{})

(bbN,{(bbN,463,231)},{})

(hfd,{(hfd,132,99)},{(345,hfd,557)})

(uidk,{(uidk,12,3)},{(908,uidk,888)})

 

每一行输出的第一项都是分组的key,第二项和第三项分别都是一个包(bag),其中,第二项是根据前面的key找到的A中的数据包,第三项是根据前面的key找到的B中的数据包。
来看看第一行输出:“re”作为groupkey时,其找不到对应的A中的数据,因此第二项就是一个空的包“{}”,“re”这个keyB中找到了对应的数据(28790    re    00000),因此第三项就是包{(28790,re,0)}
其他输出数据也类似。

十六、安装pig后,运行pig命令时提示“Cannot find hadoop configurations in classpath”等错误的解决办法
pig安装好后,运行pig命令时提示以下错误:

ERROR org.apache.pig.Main - ERROR 4010: Cannot find hadoop configurations in classpath (neither hadoop-site.xml nor core-site.xml was found in the classpath).If you plan to use local mode, please put -x local option in command line

显而易见,提示找不到与hadoop相关的配置文件。所以我们需要把hadoop安装目录下的“conf”子目录添加到系统环境变量PATH中:
修改 /etc/profile 文件,添加:

1

2

3

4

exportHADOOP_HOME=/usr/local/hadoop

exportPIG_CLASSPATH=$HADOOP_HOME/conf

 

PATH=$JAVA_HOME/bin:$HADOOP_HOME/bin:$PIG_CLASSPATH:$PATH

 

然后重新加载 /etc/profile 文件:

1

source/etc/profile

 

文章来源:http://www.codelast.com/
十七、piggybank是什么东西

Pig also hosts a UDF repository called piggybank that allows users to share UDFs that they have written.

说白了就是Apache把大家写的自定义函数放在一块儿,起了个名字,就叫做piggybank。你可以把它理解为一个SVN代码仓库。具体请看这里

十八、UDF的构造函数会被调用几次
你可能会想在UDF的构造函数中做一些初始化的工作,例如创建一些文件,等等。但是你不能假设UDF的构造函数只被调用一次,因此,如果你要在构造函数中做一些只能做一次的工作,你就要当心了——可能会导致错误。

十九、LOAD数据时,如何一次LOAD多个目录下的数据
例如,我要LOAD两个HDFS目录下的数据:/abc/2010 和 /abc/2011,则我们可以这样写LOAD语句:

1

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

二十、怎样自己写一个UDF中的加载函数(load function)
请看这个链接:《Apache Pig中文教程(进阶)

二十一:重载(overloading)一个UDF
请看这个链接:《Apache Pig中文教程(进阶)》。

二十二、pig运行不起来,提示“org.apache.hadoop.ipc.Client - Retrying connect to server: 
请看这个链接:《Apache Pig中文教程(进阶)

二十三、用含有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的话,第12列中含有null的行会被忽略吗?
来做一下试验:

1

2

3

A =LOAD'a.txt'AS(col1:int, col2:int, col3:int);

B =GROUPABY(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)})

 

从上面的结果(第三行)可见,原数据中第12列里含有null的行也被计入在内了,也就是说,GROUP操作是不会忽略null的,这与COUNT有所不同(见本文前面的部分)。

二十四、如何统计数据中某些字段的组合有多少种
假设有如下数据:

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

 

现在我们要统计第12列的不同组合有多少种,对本例来说,组合有三种:

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 =GROUPABY(col1, col2);

C =GROUPBALL;

D = FOREACH C GENERATECOUNT(B);

DUMP D;

 

然后再来看看这些代码是如何计算出上面的结果的:
第一行代码加载数据,没什么好说的。
第二行代码,得到第12列数据的所有组合。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)的所有组合已经被排列出来了,这里得到了若干行数据。下一步我们要做的就是统计这样的数据一共有多少行,也就得到了第12列的组合有多少组。
第三和第四行代码,就实现了统计数据行数的功能。参考本文前面部分的“怎样统计数据行数”一节。就明白这两句代码是什么意思了。
这里需要特别说明的是:
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是一个groupvalue是一个包(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函数时发生的错误。

二十五、两个整型数相除,如何转换为浮点型,从而得到正确的结果
这个问题其实很傻,或许不用说你也知道了:假设有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)

 

这与我们的通常做法是一致的,因此,你要做除法运算的时候,需要注意这一点。

二十六、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

 

现在要求出:在第一列相同的情况下,第二列的和分别为多少?
例如,第一列为 的时候,第二列有56两个值,和为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 =UNIONA, B;

D =GROUPCBY$0;

E = FOREACH D GENERATE FLATTEN(group),SUM(C.$1);

DUMP E;

 

输出为:

1

2

(0,20)

(1,11)

 

文章来源:http://www.codelast.com/
我们来看看每一步分别做了什么:
1行、第2行代码分别加载数据到关系AB中,没什么好说的。
3行代码,将关系AB合并起来了。合并后的数据结构为:

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)进行累加,就得到了我们要的结果。

二十七、错误“ERROR org.apache.pig.tools.grunt.Grunt - ERROR 2042: Error in new logical plan. Try-Dpig.usenewlogicalplan=false.”的可能原因
Pigbug,详见此链接
其他原因。我遇到并解决了一例。具体的代码不便在此陈列,但是基本可以说是由于自己写的Pig代码对复杂数据结构的处理不当导致的,后来我尝试更改了一种实现方式,就绕过了这个问题。关于这点,确实还是要具体问题具体分析的,在这里没有实例的话,无法给大家一个明确的解决问题的指南。

二十八、如何在Pig中使用正则表达式对字符串进行匹配
假设你有如下数据文件:

1

2

3

4

5

[root@localhost ~]# cat a.txt

1 http://ui.qq.com/abcd.html

5 http://tr.qq.com/743.html

8 http://vid.163.com/trees.php

9 http:auto.qq.com/us.php

 

现在要找出该文件中,第二列符合“*//*.qq.com/*”模式的所有行(此处只有前两行符合条件),怎么做?
Pig代码如下:

1

2

3

A =LOAD'a.txt'AS(col1:int, col2: chararray);

B = FILTER ABYcol2 matches'.*//.*\\.qq\\.com/.*'

DUMP B;

 

我们看到,matches关键字对 col2 进行了正则匹配,它使用的是Java格式的正则表达式匹配规则。
表示任意字符,表示字符出现任意次数;\. 对 进行了转义,表示匹配 这个字符;就是表示匹配 这个字符。
这里需要注意的是,在引号中,用于转义的字符 需要打两个才能表示一个,所以上面的 \\. 就是与正则中的 \. 是一样的,即匹配 这个字符。所以,如果你要匹配数字的话,应该用这种写法(\d表示匹配数字,在引号中必须用\\d):

1

B = FILTER A BY (col matches'\\d.*');

 

文章来源:http://www.codelast.com/
最后输出结果为:

1

2

(1,http://ui.qq.com/abcd.html)

(5,http://tr.qq.com/743.html)

 

可见结果是正确的。

二十九、如何截取一个字符串中的某一段
在处理数据时,如果你想提取出一个日期字符串的年份,例如提取出“2011-10-26”中的“2011”,可以用内置函数 SUBSTRING 来实现:

SUBSTRING

Returns a substring from a given string.

Syntax

SUBSTRING(string, startIndex, stopIndex)

下面举一个例子。假设有数据文件:

1

2

3

4

5

6

[root@localhost ~]# cat a.txt

2010-05-06  abc

2008-06-18  uio

2011-10-11  tyr

2010-12-23  fgh

2011-01-05  vbn

 

第一列是日期,现在要找出所有不重复的年份有哪些,可以这样做:

1

2

3

4

A =LOAD'a.txt'AS(dateStr: chararray, flag: chararray);

B = FOREACH A GENERATESUBSTRING(dateStr, 0, 4);

C =DISTINCTB;

DUMP C;

 

输出结果为:

1

2

3

(2008)

(2010)

(2011)

 

可见达到了我们想要的效果。
上面的代码太简单了,不必多言,唯一需要说明一下的是 SUBSTRING 函数,它的第一个参数是要截取的字符串,第二个参数是起始索引(从0开始),第三个参数是结束索引。
三十、如何拼接两个字符串
假设有以下数据文件:

1

2

3

4

5

[root@localhost ~]# cat 1.txt

abc 123

cde 456

fgh 789

ijk 200

 

现在要把第一列和第二列作为字符串拼接起来,例如第一行会变成“abc123”,那么使用CONCAT这个求值函数(eval function)就可以做到:

1

2

3

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

B = FOREACH A GENERATE CONCAT(col1, (chararray)col2);

DUMP B;

 

输出结果为:

1

2

3

4

(abc123)

(cde456)

(fgh789)

(ijk200)

 

注意这里故意在加载数据的时候把第二列指定为int类型,这是为了说明数据类型不一致的时候CONCAT会出错(你可以试验一下):

ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1045: Could not infer the matching function for org.apache.pig.builtin.CONCAT as multiple or none of them fit. Please use an explicit cast.

所以在后面CONCAT的时候,对第二列进行了类型转换。
另外,如果数据文件内容为:

1

2

3

4

5

[root@localhost ~]# cat 1.txt

5 123

7 456

8 789

0 200

 

那么,如果对两列整数CONCAT

1

2

A =LOAD'1.txt'AS(col1:int, col2:int);

B = FOREACH A GENERATE CONCAT(col1, col2);

 

同样也会出错

ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1045: Could not infer the matching function for org.apache.pig.builtin.CONCAT as multiple or none of them fit. Please use an explicit cast.

要注意这一点。
有人可能会问:要拼接几个字符串的话怎么办?CONCAT 套 CONCAT 就要可以了(有点笨,但管用): CONCAT(a, CONCAT(b, c))

三十一、如何求两个数据集的重合 & 不同的数据类型JOIN会失败
假设有以下两个数据文件:

1

2

3

4

5

[root@localhost ~]# cat 1.txt

123

456

789

200

 

以及:

1

2

3

4

[root@localhost ~]# cat 2.txt

200

333

789

 

现在要找出两个文件中,相同的数据有多少行,怎么做?这也就是所谓的求两个数据集的重合
用关系操作符JOIN,我们可以达到这个目的。在处理海量数据时,经常会有求重合的需求。所以JOINPig中一个极其重要的操作。
在本例中,两个文件中有两个相同的数据行:789以及200,因此,结果应该是2
我们先来看看正确的代码:

1

2

3

4

5

6

A =LOAD'1.txt'AS(a:int);  

B =LOAD'2.txt'AS(b:int);

C =JOINABYa, BBYb;

D =GROUPCALL;

E = FOREACH D GENERATECOUNT(C);

DUMP E;

 

解释一下:
第一、二行是加载数据,不必多言。
第三行按A的第1列、B的第二列进行“结合”,JOIN之后,ab两列不相同的数据就被剔除掉了。C的数据结构为:

1

C: {A::a: int,B::b: int}

 

C的数据为:

1

2

(200,200)

(789,789)

 

由于我们要统计的是数据行数,所以上面的Pig代码中的第45行就进行了计数的运算。
如果文件 2.txt 多一行数据“200”,结果会是什么?答案是:结果为3行。这个时候C的数据为:

1

2

3

(200,200)

(200,200)

(789,789)

 

所以如果你要去除重复的,还需要用DISTINCEC处理一下:

1

2

3

4

5

6

7

A =LOAD'1.txt'AS(a:int);

B =LOAD'2.txt'AS(b:int);

C =JOINABYa, BBYb;

uniq_C =DISTINCTC;

D =GROUPuniq_CALL;

E = FOREACH D GENERATECOUNT(uniq_C);

DUMP E;

 

这样得到的结果就是2了。
文章来源:http://www.codelast.com/
尤其需要注意的是,如果JOIN的两列具有不同的数据类型,是会失败的。例如以下代码:

1

2

3

4

5

6

A =LOAD'1.txt'AS(a:int);  

B =LOAD'2.txt'AS(b: chararray);

C =JOINABYa, BBYb;

D =GROUPCALL;

E = FOREACH D GENERATECOUNT(C);

DUMP E;

 

在语法上是没有错误的,但是一运行就会报错:

ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1107: Cannot merge join keys, incompatible types

这是因为ab具有不同的类型:intchararray

三十二、使用三目运算符“ ? : ”有时候必须加括号
假设有以下数据文件:

1

2

3

4

[root@localhost ~]# cat a.txt

5 8 9

6   0

4 3 1

 

其中,第二行的第二列数据是有缺失的,因此,加载数据之后,它会成为null。顺便废话一句,在处理海量数据时,数据有缺失是经常遇到的现象。
现在,我们如果要把所有缺失的数据填为 -1, 可以使用三目运算符来操作:

1

2

3

A =LOAD'a.txt'AS(col1:int, col2:int, col3:int);

B = FOREACH A GENERATE col1, ((col2isnull)? -1 : col2), col3;

DUMP B;

 

输出结果为:

1

2

3

(5,8,9)

(6,-1,0)

(4,3,1)

 

((col2 is null)? -1 : col2) 的含义不用解释你也知道,就是当col2null的时候将其置为-1,否则就保持原来的值,但是注意,它最外面是用括号括起来的,如果去掉括号,写成 (col2 is null)? -1 : col2,那么就会有语法错误:

ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1000: Error during parsing. Encountered " "is" "is "" at line 1, column 36.

Was expecting one of (后面省略)

错误提示有点不直观。所以,有时候使用三目运算符是必须要使用括号的。

三十三、如何补上缺失的数据
通过前面的文章,我们已经知道了如何按自己的需求补上缺失的数据,那么这里还有一个例子,可以让你多了解一些特殊的情况。
数据文件如下:

1

2

3

4

5

6

[root@localhost ~]# cat 1.txt

1 (4,9)

5

8 (3,0)

5 (9,2)

6

 

这些数据的布局比较怪,我们要把它加载成什么样的schema呢?答:第一列为一个int,第二列为一个tuple,此tuple又含两个int。加载成这样的模式不是为了制造复杂度,而是为了说明后面的问题而设计的。
同时,我们也注意到,第二列数据是有缺失的。
问题:怎样求在第一列数据相同的情况下,第二列数据中的第一个整数的和分别为多少?
例如,第一列为1的数据只有一行(即第一行),因此,第二列的第一个整数的和就是4
但是对最后一行,也就是第一列为6时,由于其第二列数据缺失,我们希望它输出的结果是0
先来看看Pig代码:

1

2

3

4

5

A =LOAD'1.txt'AS(a:int, b:tuple(x:int, y:int));

B = FOREACH A GENERATE a, FLATTEN(b);

C =GROUPBBYa;

D = FOREACH C GENERATEgroup,SUM(B.x);

DUMP D;

 

结果为:

1

2

3

4

(1,4)

(5,9)

(6,)

(8,3)

 

我们注意到,(5,9) 这一行是由数据文件 1.txt 的第 24行计算得到的,其中,第2行数据有缺失,但这并不影响求和计算,因为另一行数据没有缺失。你可以这样想:一个包(bag)中有多个数,当其中一个为null,而其他不为null时,把它们相加会自动忽略null
然而,第三行 (6,) 是不是太刺眼了?没错,因为数据文件 1.txt 的最后一行缺失了第二列,所以,在 SUM(B.x) 中的 B.x null就会导致计算结果为null,从而什么也输出不了。
这就与我们期望的输出有点不同了。我们希望这种缺失的数据不要空着,而是输出0。该怎么做呢?
文章来源:http://www.codelast.com/
想法1

1

D = FOREACH C GENERATEgroup, ((IsEmpty(B.x)) ? 0 :SUM(B.x));

 

输出结果为:

1

2

3

4

(1,4)

(5,9)

(6,)

(8,3)

 

可见行不通。从这个结果我们知道,IsEmpty(B.x) false,即B.x不是empty的,所以不能这样做。
想法2

1

D = FOREACH C GENERATEgroup, ((B.xisnull) ? 0 :SUM(B.x));

 

输出结果还是与上面一样!仍然行不通。这更奇怪了:B.x既非empty,也非null,那么它是什么情况?按照我的理解,当group6时,它应该是一个非空的包(bag),里面有一个null的东西,所以,这个包不是empty的,它也非null。我不知道这样理解是否正确,但是它看上去就像是这样的。
想法3

1

2

3

D = FOREACH C GENERATEgroup,SUM(B.x)ASs;

E = FOREACH D GENERATEgroup, ((sisnull) ? -1 : s);

DUMP E;

 

输出结果为:

1

2

3

4

(1,4)

(5,9)

(6,-1)

(8,3)

 

可见达到了我们想要的结果。这与本文前面部分的做法是一致的,即:先得到含null的结果,再把这个结果中的null替换为指定的值。
有人会问:就没有办法在生成数据集D的时候,就直接通过判断语句来实现这个效果吗?据我目前所知是不行的,如果哪位读者知道,不妨告知。

三十四、DISTINCT操作用于去重,正因为它要把数据集合到一起,才知道哪些数据是重复的,因此,它会产生reduce过程。同时,在map阶段,它也会利用combiner来先去除一部分重复数据以加快处理速度。

三十五、如何将Pig job的优先级设为HIGH
Pig job运行太慢?只需在Pig脚本的开头加上一句:

1

set job.priority HIGH;

 

即可将Pig job的优先级设为高了。

三十六、“Scalars can be only used with projections”错误的原因
这个错误提示比较不直观,光看这句话是不容易发现错误所在的,但是,只要你一Google,可能就找到原因了,例如这个链接里的反馈。
在这里,我也想用一个简单的例子给大家用演示一下产生这个错误的原因之一。
假设有如下数据文件:

1

2

3

4

5

6

7

8

9

[root@localhost ~]$cat1.txt

a 1

b 8

c 3

c 3

d 6

d 3

c 5

e 7

 

现在要统计:在第1列的每一种组合下,第二列为36的数据分别有多少条?
例如,当第1列为 时,第二列为3的数据有2条,为6的数据有0条;当第1列为d时,第二列为3的数据有1条,为6的数据有1条。其他的依此类推。
Pig代码如下:

1

2

3

4

5

6

7

8

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

B =GROUPABYcol1;

C = FOREACH B {

  D = FILTER ABYcol2 == 3;

  E = FILTER ABYcol2 == 6;

  GENERATEgroup,COUNT(D),COUNT(E);

};

DUMP C;

 

输出结果为:

1

2

3

4

5

(a,0,0)

(b,0,0)

(c,2,0)

(d,1,1)

(e,0,0)

 

可见结果是正确的。
文章来源:http://www.codelast.com/
那么,如果我在上面的代码中,把“D = FILTER A BY col2 == 3”不小心写成了“D = FILTER B BY col2 == 3”,就肯定会得到“Scalars can be only used with projections”的错误提示。
说白了,还是要时刻注意你每一步生成的数据的结构,眼睛睁大,千万不要用错了relation

三十七、什么是嵌套的FOREACH/内部的FOREACH
嵌套的(nestedFOREACH和内部的(innerFOREACH是一个意思,正如你在本文第(35)条中所见,一个FOREACH可以对每一条记录施以多种不同的关系操作,然后再GENERATE得到想要的结果,这就是嵌套的/内部的FOREACH

三十八、错误“Could not infer the matching function for org.apache.pig.builtin.CONCAT”的原因之一
如果你遇到这个错误,那么有可能是你在多级CONCAT嵌套的时候,没有写对语句,例如“CONCAT(CONCAT(CONCAT(a, b), c), d)”这样的嵌套,由于括号众多,所以写错了是一点也不奇怪的。我遇这个错误的时候,是由于CONCAT太多,自己多写了一个都没有发现。希望我的提醒能给你一点解决问题的提示。

三十九、用Pig加载HBase数据时遇到的错误“ERROR 2999: Unexpected internal error. could not instantiate 'com.twitter.elephantbird.pig.load.HBaseLoader' with arguments XXX”的原因之一
请看这个链接:《Apache Pig中文教程(进阶)

四十、错误“ERROR 1039: In alias XX, incompatible types in EqualTo Operator left hand side:XXX right hand side:XXX”的原因
其实这个错误提示太明显了,就是类型不匹配造成的。上面的XXX可以指代不同的类型。
这说明,前面可能有一个类型为long的字段,后面你却把它当chararray来用了,例如:

1

2

3

4

5

A =LOAD'1.txt'AS(col1:int, col2: long);

B = FILTER ABYcol2 =='123456789';

C =GROUPBALL;

D = FOREACH C GENERATECOUNT(B);

DUMP D;

 

就会出错:

ERROR 1039: In alias B, incompatible types in EqualTo Operator left hand side:long right hand side:chararray

只要把col2强制类型转换一下(或者一开始就将其类型指定为chararray)就可以解决问题。
文章来源:http://www.codelast.com/
不仅在进行数据比较中,在JOIN时也经常出现数据类型不匹配导致的错误问题。我在实际工作中发现,有的同学写了比较长的Pig代码,出现了这样的错误却不会仔细去看错误提示,而是绞尽脑汁地逐句去检查语法(语法是没有错的),结果费了很大的劲才知道是类型问题,得不偿失,还不如仔细看错误提示想想为什么。

四十一、在grunt交互模式下,如何在编辑Pig代码的时候跳到行首和行末/行尾
grunt模式下,如果你写了一句超长的Pig代码,那么,你想通过HOME/END键跳到行首和行末是做不到的。
HOME时,Pig会在你的光标处插入一个“1~”:

1

A =LOAD'1.txt'AS(col: int1~);

 

END时,Pig会在你的光标处插入一个“4~”:

1

A =LOAD'1.txt'AS(col: int4~);

 

正确的做法是:按Ctrl+A 和 Ctrl+E 代替 HOME 和 END,就可以跳到行首和行末了。

四十二、不能对同一个关系(relation)进行JOIN
假设有如下文件:

1

2

3

4

5

[root@localhost ~]# cat 1.txt

1 a

2 e

3v

4 n

 

我想对第一列这样JOIN

1

2

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

B =JOINABYcol1, ABYcol1;

 

那么当你试图 DUMP B 的时候,会报如下的错:

ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1108: Duplicate schema alias: A::col1 in "B"

这是因为Pig会弄不清JOIN之后的字段名——两个字段均为A::col1,使得一个关系(relation)中出现了重复的名字,这是不允许的。
文章来源:http://www.codelast.com/
要解决这个问题,只需将数据LOAD两次,并且给它们起不同的名字就可以了:

01

02

03

04

05

06

07

08

09

10

grunt> A =LOAD'1.txt'AS(col1:int, col2: chararray);

grunt> B =LOAD'1.txt'AS(col1:int, col2: chararray);

grunt> C =JOINABYcol1, BBYcol1;                  

grunt> DESCRIBE C;

C: {A::col1:int,A::col2: chararray,B::col1:int,B::col2: chararray}

grunt> DUMP C;

(1,a,1,a)

(2,e,2,e)

(3,v,3,v)

(4,n,4,n)

 

从上面的 schema,你可以看出来,如果对同一个关系A的第一列进行JOIN,会导致schema中出现相同的字段名,所以当然会出错。

四十三、外部的JOIN(outer JOIN)
初次使用JOIN时,一般人使用的都是所谓的“内部的JOIN(inner JOIN),也即类似于 C = JOIN A BY col1, B BY col2 这样的JOINPig也支持“外部的JOIN(outer JOIN),下面就举一个例子。
假设有文件:

1

2

3

4

5

[root@localhost ~]# cat 1.txt

1 a

2 e

3v

4 n

 

以及:

1

2

3

4

5

[root@localhost ~]# cat 2.txt

9 a

2 e

3v

0 n

 

现在来对这两个文件的第一列作一个outer JOIN

01

02

03

04

05

06

07

08

09

10

grunt> A =LOAD'1.txt'AS(col1:int, col2: chararray);

grunt> B =LOAD'2.txt'AS(col1:int, col2: chararray);

grunt> C =JOINABYcol1LEFTOUTER, BBYcol1;

grunt> DESCRIBE C;

C: {A::col1:int,A::col2: chararray,B::col1:int,B::col2: chararray}

grunt> DUMP C;

(1,a,,)

(2,e,2,e)

(3,v,3,v)

(4,n,,)

 

outer JOIN中,“OUTER”关键字是可以省略的。从上面的结果,我们注意到:如果换成一个inner JOIN,则两个输入文件的第一、第四行都不会出现在结果中(因为它们的第一列不相同),而在LEFT OUTER JOIN中,文件1.txt的第一、四行却被输出了,所以这就是LEFT OUTER JOIN的特点:对左边的记录来说,即使它与右边的记录不匹配,它也会被包含在输出数据中
文章来源:http://www.codelast.com/
同理可知RIGHT OUTER JOIN的功能——把上面的 LEFT 换成 RIGHT,结果如下:

1

2

3

4

(,,0,n)

(2,e,2,e)

(3,v,3,v)

(,,9,a)

 

可见,与左边的记录不匹配的右边的记录被保存了下来,而左边的记录没有保存下来(两个逗号表明其为空),这就是RIGHT OUTER JOIN的效果,与我们想像的一样。
有人会问,OUTER JOIN在实际中可以用来做什么?举一个例子:可以用来求“不在某数据集中的那些数据(即:不重合的数据)”。还是以上面的两个数据文件为例,现在我要求出 1.txt 中,第一列不在 2.txt 中的第一列的那些记录,肉眼一看就知道,14这两个数字在 2.txt 的第一列里没有出现,而23出现了,因此,我们要找的记录就是:

1

2

1 a

4 n

 

要实现这个效果,Pig代码及结果为:

01

02

03

04

05

06

07

08

09

10

grunt> A =LOAD'1.txt'AS(col1:int, col2: chararray);

grunt> B =LOAD'2.txt'AS(col1:int, col2: chararray);

grunt> C =JOINABYcol1LEFTOUTER, BBYcol1;       

grunt> DESCRIBE C;                

C: {A::col1:int,A::col2: chararray,B::col1:int,B::col2: chararray}

grunt> D = FILTER CBY(B::col1isnull);

grunt> E = FOREACH D GENERATE A::col1AScol1, A::col2AScol2;

grunt> DUMP E;

(1,a)

(4,n)

 

可见,我们确实找出了“不重合的记录”。在作海量数据分析时,这种功能是极为有用的。
最后来一个总结:
假设有两个数据集(在1.txt2.txt中),分别都只有1列,则如下代码:

1

2

3

4

5

6

A =LOAD'1.txt'AS(col1: chararray);

B =LOAD'2.txt'AS(col1: chararray); 

C =JOINABYcol1LEFTOUTER, BBYcol1;

D = FILTER CBY(B::col1isnull);

E = FOREACH D GENERATE A::col1AScol1;                

DUMP E;

 

计算结果为:在A中,但不在B中的记录。

四十四、JOIN的优化
请看这个链接:《Apache Pig中文教程(进阶)

四十五、GROUP时按所有字段分组可以用GROUP ALL吗
假设你有如下数据文件:

1

2

3

4

5

6

7

8

[root@localhost ~]# cat 3.txt

1 9

2 2

3 3

4 0

1 9

1 9

4 0

 

现在要找出第12列的组合中,每一种的个数分别为多少,例如,(1,9)组合有3个,(4,0)组合有两个,依此类推。
显而易见,我们只需要用GROUP就可以轻易完成这个任务。于是写出如下代码:

1

2

3

4

A =LOAD'3.txt'AS(col1:int, col2:int);

B =GROUPAALL;

C = FOREACH B GENERATEgroup,COUNT(A);

DUMP C;

 

可惜,结果不是我们想要的:

1

(all,7)

 

为什么呢?我们的本意是按所有列来GROUP,于是使用了GROUP ALL,但是这实际上变成了统计行数,下面的代码就是一段标准的统计数据行数的代码:

1

2

3

4

A =LOAD'3.txt'AS(col1:int, col2:int);

B =GROUPAALL;

C = FOREACH B GENERATECOUNT(A);

DUMP C;

 

因此,上面的 C = FOREACH B GENERATE group, COUNT(A) 也无非就是多打印了一个group的名字(all)而已——group的名字被设置为“all”,这是Pig帮你做的。
文章来源:http://www.codelast.com/
正确的做法很简单,只需要按所有字段GROUP,就可以了:

1

2

3

4

A =LOAD'3.txt'AS(col1:int, col2:int);

B =GROUPABY(col1, col2);

C = FOREACH B GENERATEgroup,COUNT(A);

DUMP C;

 

结果如下:

1

2

3

4

((1,9),3)

((2,2),1)

((3,3),1)

((4,0),2)

 

这与我们前面分析的正确结果是一样的。

四十六、在Pig中使用中文字符串
有读者来信问我,如何在Pig中使用中文作为FILTER的条件?我做了如下测试,结论是可以使用中文。
数据文件 data.txt 内容为(每一列之间以TAB为分隔符):

1

2

3

4

5

北京市 a

上海市 b

北京市 c

北京市 f

天津市 e

 

Pig脚本文件 test.pig 内容为:

1

2

3

A =LOAD'data.txt'AS(col1:int, col2: chararray, col3: chararray);

B = FILTER ABY(col2 =='北京市');

DUMP B;

 

首先,我这两个文件的编码都是UTF-8(BOM),在Linux命令行下,我直接以本地模式执行Pig脚本 test.pig

1

pig -x local test.pig

 

得到的输出结果为:

1

2

3

(1,北京市,a)

(3,北京市,c)

(4,北京市,f)

 

可见结果是正确的。
文章来源:http://www.codelast.com/
但是,如果我在grunt交互模式下,把 test.pig 的内容粘贴进去执行,是得不到任何输出结果的:

1

2

3

grunt> A =LOAD'data.txt'AS(col1:int, col2: chararray, col3: chararray);

grunt> B = FILTER ABY(col2 =='北京市');

grunt> DUMP B;

 

具体原因我不清楚,但是至少有一点是肯定的:可以使用中文作为FILTER的条件,只要不在交互模式下执行你的Pig脚本即可。

四十七、如何统计 tuple 中的 field 数,bag 中的 tuple 数,map 中的 key/value 组数一句话:用Pig内建的 SIZE 函数:

Computes the number of elements based on any Pig data type.

具体可看这个链接。

四十八、一个字符串为null,与它为空不一定等价
在某些情况下,要获取“不为空”的字符串,仅仅用 is not null 来判断是不够的,还应该加上 SIZE(field_name) > 0 的条件:

1

B = FILTER ABY(field_nameisnotnullAND(SIZE(field_name) > 0));

 

注意,这只是在某些情况下需要这样做,在一般情况下,仅用 is not null 来过滤就可以了。我并没有总结出特殊情况是哪些情况,我只能说我遇到了一例,所以才有了这一个结论。

四十九、Pig中的各operator(操作符),哪些会触发reduce过程
GROUP:由于GROUP操作会将所有具有相同key的记录收集到一起,所以数据如果正在map中处理的话,就会触发shufflereduce的过程。
ORDER:由于需要将所有相等的记录收集到一起(才能排序),所以ORDER会触发reduce过程。同时,除了你写的那个Pig job之外,Pig还会添加一个额外的M-R job到你的数据流程中,因为Pig需要对你的数据集做采样,以确定数据的分布情况,从而解决数据分布严重不均的情况下job效率过于低下的问题。
DISTINCT:由于需要将记录收集到一起,才能确定它们是不是重复的,因此DISTINCT会触发reduce过程。当然,DISTINCT也会利用combinermap阶段就把重复的记录移除。
JOINJOIN用于求重合,由于求重合的时候,需要将具有相同key的记录收集到一起,因此,JOIN会触发reduce过程。
LIMIT:由于需要将记录收集到一起,才能统计出它返回的条数,因此,LIMIT会触发reduce过程。
COGROUP:与GROUP类似(参看本文前面的部分),因此它会触发reduce过程。
CROSS:计算两个或多个关系的叉积。

五十、如何统计一个字符串中包含的指定字符数
这可以不算是个Pig的问题了,你可以把它认为是一个shell的问题。从本文前面部分我们已经知道,Pig中可以用 STREAM ... THROUGH 来调用shell进行辅助数据处理,所以在这我们也能这样干。
假设有文本文件:

1

2

3

4

[root@localhost ~]$cat1.txt

123 abcdef:243789174

456 DFJKSDFJ:3646:555558888

789 yKDSF:00000%0999:2343324:11111:33333

 

现在要统计:每一行中,第二列里所包含的冒号(“:”)分别为多少?代码如下:

1

2

3

A =LOAD'1.txt'AS(col1: chararray, col2: chararray);

B = STREAM A THROUGH `awk -F":"'{print NF-1}'`AS(colon_count:int);

DUMP B;

 

结果为:

1

2

3

(1)

(2)

(4)

 

文章来源:http://www.codelast.com/
五十一、UDF是区分大小写的:
因为UDF是由Java类来实现的,所以区分大小写,就这么简单。

五十二、LIMIT操作并不会减少读入的数据量
如果你只需要输出一个小数据集,通常你可以使用LIMIT来实现,例如:

1

2

3

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

B = LIMIT A 5;

DUMP B;

 

Pig会只加载5条记录,就不再读取其他的记录了吗?答案是:不会。Pig将读取数据文件中的所有记录,然后再从中挑5条。这是Pig可以做优化、却没有做的一点。
【更新】Pig 0.10已经有了这功能了:

Push Limit into Loader

Pig optimizes limit query by pushing limit automatically to the loader, thus requiring only a fraction of the entire input to be scanned.

按我的理解,上面这段话的含义是:PigLIMIT查询自动优化到loader中,这样就只会扫描整个输入数据集的一部分(而不是全部)。

文章来源:http://www.codelast.com/
五十三、使用UDF不一定要在Pig脚本中REGISTER,也可以在命令行指定
大家知道,使用UDF需要在Pig脚本中REGISTERUDFjar包,但你可能不知道,你也可以不在Pig脚本中REGISTER它,而是通过命令行指定:

1

pig -Dpig.additional.jars=/home/codelast/a.jar:/home/codelast/b.jar:/home/codelast/c.jartest.pig

 

以上命令告诉了我们几件事:
我们让Pig执行了test.pig脚本;
我们向Pig传入了“pig.additional.jars”这样一个参数,此参数的作用相当于在Pig脚本中REGISTER jar包;
如果你要REGISTER多个jar包,只需像上面的例子一样,用分号(:)把多个jar包路径隔开即可;
test.pig必须写在最后,而不能写成“pig test.pig -Dpig.additional.jars=XXX”这样,否则Pig直接报错:

ERROR 2999: Unexpected internal error. Encountered unexpected arguments on command line - please check the command line.

当然,为了可维护性好,你最好把REGISTER jar包写在Pig脚本中,不要通过命令行传入。

五十四:使用ORDER排序时,null会比所有值都小
ORDER按一个字段排序,如果该字段的所有值中有null,那么null会比其他值都小。

五十五、如何按指定的几个字段来去重
去重,即去除重复的记录。通常,我们使用DISTINCT来去除整行重复的记录,但是,如果我们只想用几个字段来去重,怎么做?
假设有以下数据文件:

1

2

3

4

5

6

7

[root@localhost ~]$cat1.txt

1 2 3 4 uoip

1 2 3 4 jklm

9 7 5 3 sdfa

8 8 8 8 dddd

9 7 5 3 qqqq

8 8 8 8 sfew

 

我们要按第1234个字段来去重,也就是说,去重结果应为:

1

2

3

1 2 3 4 uoip

9 7 5 3 sdfa

8 8 8 8 dddd

 

那么,我们可以这样做:

1

2

3

4

5

6

7

A =LOAD'1.txt'AS(col1: chararray, col2: chararray, col3: chararray, col4: chararray, col5: chararray);

B =GROUPABY(col1, col2, col3, col4);

C = FOREACH B {

  D = LIMIT A 1;

  GENERATE FLATTEN(D);

};

DUMP C;

 

文章来源:http://www.codelast.com/
输出结果为:

1

2

3

(1,2,3,4,uoip)

(8,8,8,8,dddd)

(9,7,5,3,sdfa)

 

代码很简单,就是利用了GROUP时会自动对groupkey去重的功能,这里不用多解释大家应该也能看懂。

五十六、如何设置Pig job的名字,使得在Hadoop jobtracker中可以清晰地识别出来
Pig脚本中的一开始处,写上这一句:

1

setjob.name'This is my job';

 

将使得Pig job name被设置为“This is my job”,从而在Hadoop jobtrackerweb界面中可以很容易地找到你的job。如果不设置的话,其名字将显示为“PigLatin:DefaultJobName”。

五十七、“scalar has more than one row in the output”错误的一个原因
遇到了这个错误?我来演示一下如何复现这个错误。
假设有两个文件:

1

2

3

4

5

6

[root@localhost ~]$cata.txt

1 2

3 4

[root@localhost ~]$catb.txt

3 4

5 6

 

现在我们来做一个JOIN

1

2

3

4

5

A =LOAD'a.txt'AS(col1:int, col2:int);

B =LOAD'b.txt'AS(col1:int, col2:int);

C =JOINABYcol1, BBYcol1;

D = FOREACH C GENERATE A.col1;

DUMP D;

 

这段代码是必然会fail的,错误提示为:

1

org.apache.pig.backend.executionengine.ExecException: ERROR 0: Scalar has more than one row in the output. 1st : (1,2), 2nd :(3,4)

 

文章来源:http://www.codelast.com/
乍一看,似乎代码简单得一点问题都没有啊?其实仔细一看,“A.col1”的写法根本就是错误的,应该写成“A::col1”才对,因为你只要 DESCRIBE 一下 schema就明白了:

1

C: {A::col1: int,A::col2: int,B::col1: int,B::col2: int}

 

Pig的这个错误提示得很不直观,在这个链接中也有人提到过了。

五十八、如何输出LZO压缩格式的文本文件
还是借助于elephant-bird,可以轻易完成这个工作:

1

2

LOAD'input';

STORE AINTO'output'USING com.twitter.elephantbird.pig.store.LzoPigStorage();

 

结果就会得到一堆名称类似于“part-m-00000.lzo”的文件。
注意以上省略了一堆的“REGISTER XXX.jar”代码,你需要自己添加上你的jar包路径。
有人说,那加载LZO压缩的文本文件呢?很简单:

1

A =LOAD'output'USING com.twitter.elephantbird.pig.store.LzoPigStorage(',');

 

这表示指定了分隔符为逗号,如果不想指定,省略括号中的内容即可。

五十九、如何输出 gz 及 bz2 压缩格式的文件

首先请看这个链接的说明。摘录一段话:

Compression

 

Storing to a directory whose name ends in ".bz2" or ".gz" or ".lzo" (if you have installed support for LZO compression in Hadoop) will automatically use the corresponding compression codec.

output.compression.enabled and output.compression.codec job properties also work.

Loading from directories ending in .bz2 or .bz works automatically; other compression formats are not auto-detected on loading.

说得简单点就是:当你把保存的目录名设置为以 .bz2 或 .gz 结尾时,输出的文件就自动会被压缩为对应的文件格式。
因此,我就有了下面这两段极其简单的示例代码:

1

2

3

4

5

6

7

--压缩率稍低

A =LOAD'1.txt';

STORE AINTO'z.gz';

 

--压缩率较高

A =LOAD'1.txt';

STORE AINTO'z.bz2';

 

正如上面的注释所示,bz2 的压缩率比 gz 高。
最后生成的目录下,文件名类似于:

1

2

3

part-m-00000.gz

part-m-00001.gz

part-m-00002.gz

 

如果是 bz2,则后缀名为 .bz2

六十、如何编写及使用自定义函数(UDF)
首先给出一个链接:Pig 0.8.1 API,还有Pig UDF Manual。这两个文档能提供很多有用的参考。
自定义函数有何用?这里以一个极其简单的例子来说明一下。
假设你有如下数据:

1

2

3

4

5

[root@localhost pig]$ cat a.txt

uidk  12  3

hfd 132 99

bbN 463 231

UFD 13  10

 

现在你要将第二列的值先+500,再-300,然后再÷2.6,那么我们可以这样写:

1

2

3

4

5

6

7

grunt> A =LOAD'a.txt'AS(col1:chararray, col2:double, col3:int);

grunt> B = FOREACH A GENERATE col1, (col2 + 500 - 300)/2.6, col3;

grunt> DUMP B;

(uidk,81.53846153846153,3)

(hfd,127.6923076923077,99)

(bbN,255.0,231)

(UFD,81.92307692307692,10)

 

我们看到,对第二列进行了 (col2 + 500 - 300)/2.6 这样的计算。麻烦不?或许这点小意思没什么。但是,如果有比这复杂得多的处理,每次你需要输入多少pig代码呢?我们希望有这样一个函数,可以让第二行pig代码简化如下:

1

grunt> B = FOREACH A GENERATE col1, com.codelast.MyUDF(col2), col3;

 

这样的话,对于我们经常使用的操作,岂不是很方便?
pigUDFuser-defined function)就是拿来做这个的。
文章来源:http://www.codelast.com/
下面,就以IntelliJ这个IDE为例(其实用什么IDE倒无所谓,大同小异吧),说明我们如何实现这样一个功能。
新建一个新工程,在工程下创建“lib”目录,然后把pig安装包中的“pig-0.8.1-core.jar”文件放置到此lib目录下,然后在“Project StructureLibraries”下添加(点击“+”号)一个库,就命名为“lib”,然后点击右侧的“Attach Classes”按钮,选择pig-0.8.1-core.jar文件,再点击下方的“Apply”按钮应用此更改。这样做之后,你就可以在IDE的编辑器中实现输入代码时看到智能提示了。
此外,你还需要用同样的方法,将一堆Hadoopjar包添加到工程中,包括以下文件:

1

2

3

4

5

hadoop-XXX-ant.jar

hadoop-XXX-core.jar

hadoop-XXX-examples.jar

hadoop-XXX-test.jar

hadoop-XXX-tools.jar

 

其中,XXX是版本号。
如果没有这些文件,你在编译jar包的时候会报错。
文章来源:http://www.codelast.com/
跟我一起,在工程目录下的 src/com/coldelast/ 目录下创建Java源代码文件 MyUDF.java,其内容如下:

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

packagecom.codelast;

 

importjava.io.IOException;

 

importorg.apache.pig.EvalFunc;

importorg.apache.pig.data.Tuple;

 

/**

 * Author: Darran Zhang @ codelast.com

 * Date: 2011-09-29

 */

 

publicclassMyUDFextendsEvalFunc<Double> {

 

  @Override

  publicDouble exec(Tuple input)throwsIOException {

    if(input ==null|| input.size() ==0) {

      returnnull;

    }

 

    try{

      Double val = (Double) input.get(0);

      val = (val +500-300) /2.6;

      returnval;

    }catch(Exception e) {

      thrownewIOException(e.getMessage());

    }

  }

}

 

在上面的代码中,input.get(0)是获取UDF的第一个参数(可以向UDF传入多个参数);同理,如果你的UDF接受两个参数(例如一个求和的UDF),那么input.get(1)可以取到第二个参数。
然后编写build.xml(相当于C++里面的Makefile),用ant来编译、打包此工程——这里就不把冗长的build.xml写上来了,而且这也不是关键,没有太多意义。
文章来源:http://www.codelast.com/
假定编译、打包得到的jar包名为cl.jar,我们到这里几乎已经完成了大部分工作。下面就看看如何在pig中调用我们刚编写的自定义函数了。

1

2

3

4

5

6

7

8

grunt> REGISTER cl.jar;

grunt> A =LOAD'a.txt'AS(col1:chararray, col2:double, col3:int);   

grunt> B = FOREACH A GENERATE col1, com.codelast.MyUDF(col2), col3;

grunt> DUMP B;

(uidk,81.53846153846153,3)

(hfd,127.6923076923077,99)

(bbN,255.0,231)

(UFD,81.92307692307692,10)

 

注:第一句是注册你编写的UDF,使用前必须先注册。
从结果可见,我们实现了预定的效果。
UDF大有用途!
注意:对如果你的UDF返回一个标量类型(类似于我上面的例子),那么pig就可以使用反射(reflection)来识别出返回类型。如果你的UDF返回的是一个包(bag)或一个元组(tuple),并且你希望pig能理解包(bag)或元组(tuple)的内容的话,那么你就要实现outputSchema方法,否则后果很不好。具体可看这个链接的说明。

六十一、怎样自己写一个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

publicinterfaceLoadFunc {

publicvoidbindTo(String fileName, BufferedPositionedInputStream

 is,longoffset,longend)throwsIOException;

    publicTuple getNext()throwsIOException;

    // conversion functions

    publicInteger bytesToInteger(byte[] b)throwsIOException;

    publicLong bytesToLong(byte[] b)throwsIOException;

    ......

    publicvoidfieldsToRead(Schema schema);

publicSchema determineSchema(String fileName, ExecType execType, 

DataStorage storage)throwsIOException;

}

 

其中:

 bindTo函数在pig任务开始处理数据之前被调用一次,它试图将函数与输入数据关联起来。

 getNext函数读取输入的数据流并构造下一个元组(tuple)。当完成数据处理时该函数会返回null,当该函数无法处理输入的元组(tuple)时它会抛出一个IOException异常。

 接下来就是一批转换函数,例如bytesToIntegerbytesToLong等。这些函数的作用是将数据从bytearray转换成要求的类型。

 fieldsToRead函数被保留作未来使用,应被留空。

 determineSchema函数对不同的loader应有不同的实现:对返回真实数据类型(而不是返回bytearray字段)的loader,必须要实现该函数;其他类型的loader只要将determineSchema函数返回null就可以了。

但是,如果你在IDEimportpig 0.8.1jar包“pig-0.8.1-core.jar”,会发现 LoadFunc 根本不是一个接口(interface),而是一个抽象类(abstract class),并且要实现的函数也与该文档中所说的不一致。因此,只能说是文档过时了。
所以,要看文档的话,还是要看这个Pig UDF Manual,这里面的内容才是对的。
同时,我也推荐另外一个关于Load/Store Function的链接:Programming PigChapter 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

packagecom.codelast.udf.pig;

 

importorg.apache.hadoop.io.Text;

importorg.apache.hadoop.mapreduce.*;

importorg.apache.hadoop.mapreduce.lib.input.FileInputFormat;

importorg.apache.pig.*;

importorg.apache.pig.backend.executionengine.ExecException;

importorg.apache.pig.backend.hadoop.executionengine.mapReduceLayer.*;

importorg.apache.pig.data.*;

 

importjava.io.IOException;

importjava.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.

 */

 

publicclassMyLoaderextendsLoadFunc {

  protectedRecordReader recordReader =null;

 

  @Override

  publicvoidsetLocation(String s, Job job)throwsIOException {

    FileInputFormat.setInputPaths(job, s);

  }

 

  @Override

  publicInputFormat getInputFormat()throwsIOException {

    returnnewPigTextInputFormat();

  }

 

  @Override

  publicvoidprepareToRead(RecordReader recordReader, PigSplit pigSplit)throwsIOException {

    this.recordReader = recordReader;

  }

 

  @Override

  publicTuple getNext()throwsIOException {

    try{

      booleanflag = recordReader.nextKeyValue();

      if(!flag) {

        returnnull;

      }

      Text value = (Text) recordReader.getCurrentValue();

      String[] strArray = value.toString().split(",");

      List lst =newArrayList<String>();

      inti =0;

      for(String singleItem : strArray) {

        lst.add(i++, singleItem);

      }

      returnTupleFactory.getInstance().newTuple(lst);

    }catch(InterruptedException e) {

      thrownewExecException("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

publicclassMyLoadFuncextendsLoadFuncimplementsLoadMetadata {

 

  publicResourceSchema getSchema(String filename, Job job)throwsIOException {

    //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 BINTO'output';

 

看清楚了,在 LOAD 的时候,我们并没有写 AS 语句来指定字段名,但是在后面的 FOREACH 中,我们却可以使用 col1 这样的字段名,这正是因为 getSchema 方法的实现为我们做到了这一点。在数据文件的结构特别复杂的时候,这个功能几乎是不可或缺的,否则难以想像会给分析数据的人带来多大的不便。

六十二、重载(overloading)一个UDF
类似于C++的函数重载,pig中也可以重载UDF,例如一个函数ADD可以对两个int进行操作,也可以对两个double进行操作,那么我们可以为该函数实现 getArgToFuncMapping 方法,该函数返回一个 List<FuncSpec> 对象,这个对象中包含了参数的类型信息。具体怎么实现,可以看这个链接(搜索“Overloading UDFs”定位到所需章节)。

六十三、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 |grepjava |grepNameNode

 

应该有两个进程启动起来了:

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的正确配置,但是最好搞定它,以后大有用处啊。

六十四、用Pig加载HBase数据时遇到的错误“ERROR 2999: Unexpected internal error. could not instantiate 'com.twitter.elephantbird.pig.load.HBaseLoader' with arguments XXX”的原因之一
你也许早就知道了:Pig可以加载HBase数据,从而更方便地进行数据处理。但是在使用HBaseloader的时候,可能会遇到这样那样的问题,我这里就遇到了一例,给大家分析一下原因。
使用 org.apache.pig.backend.hadoop.hbase.HBaseStorage() 可以加载HBase数据,例如:

1

A =LOAD'hbase://table_name'USING org.apache.pig.backend.hadoop.hbase.HBaseStorage('column_family_name:qualifier_name','-loadKey true -limit 100')AS(col1: chararray, col2:chararray);

其中,table_name 是你要加载数据的HBase表名,column_family_name:qualifier_name 是表的column family:qualifier(当然,可以有多个column family:qualifier,以空格隔开即可),-loadKey true -limit 100 是加载数据时指定的参数,支持的参数如下:

-loadKey=(true|false)  Load the row key as the first column

-gt=minKeyVal

-lt=maxKeyVal 

-gte=minKeyVal

-lte=maxKeyVal

-limit=numRowsPerRegion max number of rows to retrieve per region

-delim=char delimiter to use when parsing column names (default is space or comma)

-ignoreWhitespace=(true|false) ignore spaces when parsing column names (default true)

-caching=numRows  number of rows to cache (faster scans, more memory).

-noWAL=(true|false) Sets the write ahead to false for faster loading.

    To be used with extreme caution, since this could result in data loss

    (see http://hbase.apache.org/book.html#perf.hbase.client.putwal).

由这些参数的解释,可知我上面的 -loadKey true 使得加载出来的数据的第一列是HBase表的row key-limit 100 使得从每一个region加载的最大数据行数为100(当你有Nregion时,总共加载的数据是不是 N*region总数 条,我没有试验)。
org.apache.pig.backend.hadoop.hbase.HBaseStorage() 包含在Pigjar包中,所以你不需要REGISTER额外的jar包。
我遇到的问题是:在按上面的代码加载HBase数据之后,在grunt中一回车,马上报错:

ERROR 2999: Unexpected internal error. could not instantiate 'com.twitter.elephantbird.pig.load.HBaseLoader' with arguments XXX
Details at logfile: XXX

这个时候,你当然应该去查看logfile,以确定具体问题是什么。
logfile内容较多,在其尾部,有下面的内容:

Caused by: java.lang.NoSuchMethodError: org.apache.hadoop.hbase.HBaseConfiguration.create()Lorg/apache/hadoop/conf/Configuration;

        at org.apache.pig.backend.hadoop.hbase.HBaseStorage.<init>(HBaseStorage.java:185)

文章来源:http://www.codelast.com/
好吧,到了这里,只能去看看pig的源码了。打开 HBaseStorage.java 文件,找到提示的185行,看到如下代码:

1

m_conf = HBaseConfiguration.create();

 

可见它调用了HBase代码中的一个类HBaseConfigurationcreate方法。按上面的提示,它是找不到这个方法,于是我们再看看使用的HBase的 HBaseConfiguration.java 里的代码,找遍全文,都找不到create方法!那么,我们再看看更新一点的版本的HBase的相同文件中是否有这个方法呢?下载0.90.4版本的HBase,果然在 HBaseConfiguration.java 中找到了create方法:

1

2

3

4

5

6

7

8

/**                                                                                                                                                      

 * Creates a Configuration with HBase resources                                                                                                      

 * @return a Configuration with HBase resources                                                                                                      

 */

publicstaticConfiguration create() {

  Configuration conf =newConfiguration();

  returnaddHbaseResources(conf);

}

 

所以,问题就在这里了:PigHBase loader不能使用某些版本的HBase,升级HBase吧!
另外,就算HBase版本适用了,你也得让Pig知道HBase的参数配置(要不然怎么可以指定一个HBase表名就可以加载其数据了呢),具体你可以看这个链接的说明。

六十五、JOIN的优化:
如果你对N个关系(relation)的某些字段进行JOIN,也就是所谓的“多路的”(multi-way)JOIN——我不知道用中文这样描述是否正确——在这种情况下,请遵循这样的原则来写JOIN语句:
JOIN用到的key所对应的记录最多的那个关系(relation)应该被排在最后。例如:

1

D =JOINABYcol1, BBYcol1, CBYcol1;

 

在这里,假设C这个relation就是上面所说的那种情况,所以把它排在最后。
文章来源:http://www.codelast.com/
为什么要遵循这样的原则?这是由Pig处理JOIN的方式来决定的。在JOINn个关系中,前面的n-1个关系的处理结果会被cache在内存中,然后才会轮到第n个关系,因此,把最占内存的放在最后,有时候是能起到优化作用的。

六十六、错误“Backend error : org.apache.pig.data.BinSedesTuple cannot be cast to org.apache.pig.data.DataBag”的原因
如果你正在使用Pig 0.8,那么要注意了:出现这个错误,可能是Pigbug导致的,详见这个链接。
说得简单点就是:此bug会导致无法解引用一个tuple中的bag。通常我们取一个tuple中的bag,是为了FLATTEN它,将记录展开,但是此bug使得你根本连tuple中的bag都输出不了。
bug并不会影响你的Pig脚本语法解析,也就是说,你的Pig脚本只要写对了,就能运行起来,但是它执行到后面会报错。

六十七、如何加载LZO压缩的纯文本数据
如果你的数据是纯文本经由LZO压缩而成,那么你可以用elephant-bird的 com.twitter.elephantbird.pig.store.LzoPigStorage 来加载它:

1

A =LOAD'/user/codelast/text-lzo-file'USING com.twitter.elephantbird.pig.store.LzoPigStorage()AS(col1: chararray, col2:int);

 

注意,这里没有写REGISTER jar包的命令,你需要自己补上。

六十八、如何用Pig设置map端的并行度(map数)
这个链接中的第(9)条,我们知道,无法通过PARALLEL来设置Pig job map端的并行度,但是,有没有什么办法可以间接实现这一点呢?
Java编写的MapReduce程序中,你可以像这个链接中的第(25)点所说的一样,通过FileInputFormat.setMinInputSplitSize()来间接更改map的数量,其实它就与设置 mapred.min.split.size 参数值的效果是一样的。
Pig中,我们是可以通过set命令来设置job参数的,所以,我们如果在Pig脚本的开头写上:

1

setmapred.min.split.size2147483648;

 

将使得对map来说,小于2G的文件将被作为一个split输入,从而一个小于2G的文件将只有一个map。假设我们的Pig job是一个纯mapjob,那么,map数的减少将使得输出文件的数量减少,在某些情况下,这种功能还是很有用的。
注意:上面的命令中,set的参数的单位是字节,所以2G=2*1024*1024*1024=2147483648

六十九、Pig调用现存的静态Java方法
不是每个人都会开发UDF,或者每个人都愿意去写一个UDF来完成一件极其简单的操作的,例如,把一个编码过的URL解码,如果我只想临时用一下这个功能,那么我还要去写一个UDF,累不累啊?
我们知道,java.net.URLDecoder.decode 这个静态方法已经实现了URL解码功能:

static String decode(String s, String enc) 

使用指定的编码机制对 application/x-www-form-urlencoded 字符串解码

那么,如何在Pig中使用这个现成的静态方法呢?为了展示这个使用过程,我造了一个数据文件:

1

2

3

[root@localhost ~]$cata.txt

 

http://zh.wikipedia.org/zh/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E

 

就一行,这个URL解码之后应该是:
http://zh.wikipedia.org/wiki/搜索引擎
因为里面含中文,所以被编码了。
处理此文件的Pig脚本url.pig如下:

1

2

3

4

DEFINE DecodeURL InvokeForString('java.net.URLDecoder.decode','String String');

A =LOAD'a.txt'AS(url: chararray);

B = FOREACH A GENERATE DecodeURL(url,'UTF-8');

STORE BINTO'url';

 

文章来源:http://www.codelast.com/
用 pig -x local url.pig 执行这个脚本,完成后我们查看输出目录下的 part-m-00000 文件内容,可见它确实被解码成了正确的字符串。
这样,我们就利用了现存的静态Java方法来偷了个懒,很方便。
需要注意的是:只能调用静态方法,并且此调用比同样的UDF实现速度要慢,这是因为调用器没有使用AccumulatorAlgebraic接口。根据这位兄台的测试,百万条的记录规模下,调用Java静态方法比UDF大约要慢一倍。至于这样的cost能不能接受,就看你自己的判断了。

(未完待续)

 

 

 

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值