Why Pig Latin
业界对于大数据的分析的需求越来越多,特别是那些依赖对TB级别的大数据进行分析,从而进行创新的互联网公司。TeraData之类的公司虽然提供了解决方案,但是如果数据量级达到TB水平,价格就太贵了。另一方面,很多从事数据分析的人员都是传统的“面向过程式编程”的程序员,对于他们来说利用SQL这种工具来进行数据分析会很不习惯。
但是说实话,现在最新的理念是人人都可以做数据分析,人人都要有数据分析的能力。因此相比过程式的语言,SQL这种语言反而接受程度更高。原作者(们)之所以会有这样的想法,跟这篇论文写作的时间有关,这篇论文是写于2009年,那个时候从事数据分析的确实主要是“面向过程式编程”的程序员。
不过现在如果让那些"面向过程式编程"的程序员来做数据分析,可能还是过程式的语言用起来更顺手一点。也就是说Pig应该是面向专业的数据分析用户的。但是SQL也有它的市场。
但是现在最新的趋势其实是让用户连SQL都不用会,就可以直接进行数据分析。只能说时代在进步,对于数据处理工具的要求在提高。当数据真的变成经济发展的基本要素的时候,确实不能要求使用的人非要会一个什么Pig, 什么SQL之类的。
另一种选择是直接使用类似map-reduce
这种面向过程的分布式编程框架(Hadoop)来进行数据分析。
这种方式的优点就是“过程式”的,非常的自由,可以表达各种计算逻辑。缺点也在于它太自由了,导致它的表达力太差,即使对于那种非常常见的分析需求比如"join", 要用map-reduce
这种底层的编程框架来做,也需要自己写很多的代码,非常的冗杂。
因此Yahoo!内部提出了Pig Latin
这种平衡了上述两种方式优点的这么一种新的大数据处理的语言: 它既提供高阶的、声明式的查询原语,同时又提供低阶的、过程式的编程风格。
Pig Latin长啥样
假设说我们有一个表urls
:
(url, category, pagerank)
我们想计算: 那些足够大的category,计算那些有足够高的pagerank的url的平均pagerank是多少
, 如果用SQL表达,大概是这样的:
SELECT category, AVG(pagerank)
FROM urls WHERE pagerank > 0.2
GROUP BY category HAVING COUNT(*) > 106
而如果用Pig Latin
来表达呢,是这样的:
good_urls = FILTER urls BY pagerank > 0.2;
groups = GROUP good_urls BY category;
big_groups = FILTER groups BY COUNT(good_urls)>106;
output = FOREACH big_groups GENERATE category, AVG(good_urls.pagerank);
从这个例子可以看出Pig Latin
的两个基本特点:
- 首先它是由一系列的步骤组成的 -- 低阶的、过程式的编程风格。
- 而这里面的每一步说用到的算子呢抽象级别都很高 -- 高阶的、声明式式的查询原语。
Pig Latin其实就是一种让程序员能够进行更细粒度控制的SQL。
这个例子没有体现的其他一些特点:
- 支持一个完全自由的、完全嵌套的数据模型
- 支持用户自定义函数
- 支持对文本的、没有schema信息的数据进行直接处理
- 提供了一个Debug环境 -- 这个对于处理大数据非常的有用。
特性和动机
Pig设计的主要设计目标是:成为有经验的程序员对大数据集进行ad-hoc分析的工具
。Pig的很多特性都是为这个设计目标服务的。
它是一门Dataflow语言
Pig的每一步都是一个(而且只有一个)高阶的数据转换。与此相对应的,SQL里面则通过指定一系列的条件来对一份数据进行过滤、聚合处理。因此看起来会比SQL的方式干净一点、简单一点, 看看Yahoo!内部的用户是怎么说的:
“I much prefer writing in Pig [Latin] versus SQL.
The step-by-step method of creating a program
in Pig [Latin] is much cleaner and simpler to use
than the single block method of SQL. It is easier
to keep track of what your variables are, and
where you are in the process of analyzing your
data.”
– Jasmine Novak, Engineer, Yahoo!
但是需要注意的是,虽然Pig Latin
通过让用户指定一些的数据转换的步骤来进行数据处理。但是这并不意味着,实际处理的逻辑必须按照用户指定的顺序来,只要编译器觉得调整一下执行的顺序可以获得更好的性能,同时又可以得到一样的结果,那么完全可以对用户的数据程序进行优化。
而之所以编译器可以进行优化就是因为Pig Latin
提供的原语足够高阶,使得编译器能够看到
这些数据处理的逻辑。而如果直接使用map-reduce
框架进行编程的话,由于具体的数据处理逻辑都被隐含在map
或者reduce
函数里面,使得编译器无法看到这些逻辑,优化也就无从谈起。
语言提供的原语越高阶,那么编译器可以进行的优化的可能也就越多。但是同时语言也就越受限,因为你一门语言越高阶,那么就越具体,越针对某个具体的领域了(DSL)。所以我们可以基于map-reduce来做一个图计算的任务,但是你要基于
Pig Latin
来做一个图计算的任务就不可能了,因为Pig Latin已经特化
了。
快速开始以及互操作性
Pig的设计目标是ad-hoc的数据分析,用户可以针对任意一个文本文件进行数据分析,而不需要先把它"导入"到Pig系统里面去,这样可以节省很多的数据导入时间。传统的数据库都要求用户把数据导入之后才能进行分析、查询,主要原因有三方面:
- 保证事务一致性
- 保证数据的快速查询(通过索引之类的机制)
- 对数据进行加工,然后用户以及其它别的用户可以基于这个数据进行进一步的处理。
而由于Pig只支持read-only的数据分析,而这种类型的分析基本都是要扫描所有数据的,因此事务啊,查询效率啊之类的都不重要。在论文的作者看来用户经常需要分析一个临时的数据,然后就丢掉了,因此他觉得没必要对数据加工以及管理。
而且由于Pig可以对一个外部的文件(而不需要导入)进行分析,使得Pig可以很好的跟大数据生态系统里面的其它工具进行协作。
对于这一部分其实不是很认同,从今天的观点来看大的公司基本都会把数据入库到一个大的数据仓库里面,比如开源的Hadoop, 或者是阿里巴巴的ODPS之类的。因此能够对外部的文本文件进行处理看起来很酷,但是可能实际用起来并没有那么有用。
事务一致性对于大数据来说确实没有太大的意义,但是对数据的快速查询还是需要的,还是前面说的原因,一份数据进入数仓之后,不同的用户会基于不同的查询条件对数据进行分析。如果每次都要扫描所有的数据,那还怎么进行大数据分析。
嵌套式的数据模型
其实程序员脑子里面的数据模型都是嵌套式的,比如我们要表达一个人的父亲的工作单位的电话号码
, 可能会是这样的:
person.father.company.phone
这是明显的嵌套式
的。
而我们主流的数据库提供的数据结构都是扁平式的,比如如果要存储上述例子里面的信息在MySQL里,至少要设计person
, company
两张表,把数据打平才能表达这个信息。但是这其实是不自然的,是强制让人类用计算机的思维来思考事情。编写起来自然也就不是那么舒服了。
Pig Latin支持set, map, tuple等非原子数据类型作为一个表的字段
, 它有如下的优点:
- 内嵌的数据模型更接近程序员的思维方式,因此更自然。
- 数据经常被以内嵌的数据模型的方式保存在磁盘上
这个我倒是没什么感觉。
- 内嵌的数据模型使得我们实现一个代数语言成为可能。
因为一个代数操作的结果就是一个内嵌的结构
- 一个内嵌的数据模型使得程序员可以非常方便的编写各种UDF。
作为头等公民的UDF
对大数据进行分析的很大一部分工作是对数据进行自定义的处理。为此Pig Latin为UDF提供了丰富的支持。
支持并行执行
因为Pig Latin是为了处理大规模数据而设计的,因此Pig Latin在设计的时候精心挑选了那些能够并行化的原语到语言里面来。而那些无法并行化的语言Pig Latin就不做语言方面的支持, 比如非等join
。
调试环境
在任何语言里面,要想把一个数据处理的程序写对都需要很多个迭代。所以写这类程序的方式基本是写一点,试跑一下,再改一改。但是如果处理大数据也这么干太费时间了。因此Pig Latin提供了调试的环境,让你可以方便地对Pig Latin大数据处理程序进行调试。
Pig Latin语法简介
数据类型
主要四种:
- 原子类型. 比如数字,字符串。
- Tuple. Tuple就是一系列的字段。每个字段可以是任意类型的数据。
- Bag. Bag是tuple的集合。而且Bag里面允许出现重复的元素。
- Map. map跟Java里面的map类似。但是key必须是原子类型,value可以是任意类型。
加载数据: LOAD
queries = LOAD `query_log.txt`
USING myLoad()
AS (userId, queryString, timestamp);
从query_log.txt
这个文件里面加载数据,利用myLoad
这个函数解析成三个字段。
对每个tuple进行处理: FOREACH
expanded_queries = FOREACH queries GENERATE userId, expandQuery(queryString)
通过FOREACH, 把原来的三个字段,处理成新的两个字段(其中expandQuery
是一个UDF)。
对数据进行过滤: FILTER
real_queries = FILTER queries BY NOT isBot(userId)
把相关的数据Group在一起: COGROUP
grouped_data = COGROUP results BY queryString,
revenue BY queryString;
COGROUP跟JOIN类似,这里不做展开了。
实现
Pig Latin被Pig完全实现了。Pig被设计成可以对接各种不同的执行平台,目前实现的平台是Hadoop. 一个Pig Latin的执行主要分两步:
- 把一段Pig Latin的脚本编译成逻辑执行计划。
- 把逻辑执行计划变异成实际的物理执行计划(Hadoop MapReduce)。
构造逻辑计划
对于客户端输入的Pig Latin指令,Pig的解析器首先对它进行解析,并且验证一下输入文件,输入的bag都被定义过。
Pig会对每个bag都编译对应的逻辑计划。而一个新的bag的逻辑计划是它自己的逻辑计划再加上它说引用的bag的逻辑计划。比如:
c = COGROUP a BY ..., b BY ...;
这里a, b, c都有自己的逻辑执行计划,而c的执行计划是它自己这个指令的执行计划再加上a和b的执行计划。
注意在执行完这条命令之后,并不会有真正的数据处理逻辑被执行,这些指令只是被记录下来,只有当有STORE
命令执行的时候,才会真正触发整个执行计划的执行。
这其实也就是所谓的延迟执行
。延迟执行使得内存内的流水线
,指令重排
等等成为可能。
Apache Beam也是类似的延迟执行的风格,先记录相应的执行指令,等真正提交的时候才执行。
编译成Map-Reduce的执行计划
把一个Pig Latin的逻辑执行计划编译层一个map-reduce任务非常的简单。map-reduce
语言本质上是提供了一个大规模的group-by的能力,其中ma
p生成要group的key。而reduce则对每个group进行处理。
一个Pig Latin的逻辑执行计划其实就是一个包含各种原语的图,而其中有一种特殊的原语是(CO)GROUP, 把一个逻辑计划编译成物理计划的过程其实就是以GROUP为边界把逻辑计划拆成多个MapReduce任务的过程。两个GROUP之间的原语可以作为上一个GROUP的reduce
的一部分,也可以作为下一个GROUP的map
的一部分。
目前Pig的实现里面把Pig Latin文本编译成逻辑计划的过程跟各种具体的执行引擎相隔离 -- 因为这个逻辑是通用的。只有编译成具体的物理计划的时候才会跟具体的执行引擎有关。
Pig Pen
Pig Pen是一个Pig Latin的所见即所得的编程环境,有点Reactive Development的意思。
总结
- Pig Latin这种设计思路很新颖,很有意思。把两种范式: 声明式和命令式结合到一起(也真是挺敢想的)。
- Pig Latin编译成物理计划的过程没想到这么简单。
- Pig Latin跟Beam看起来很像,Pig Latin一直提一个概念: Dataflow, 而Beam得前身就是Google Dataflow。
- Pig Latin想作为一种通用的数据处理语言,跟底层实际的引擎相分离,这一点跟Beam也很像。
- 稍微丰富一下Pig Latin的语言,是不是可以把它嫁接到Apache Beam上去,让它不但支持离线,还能支持实时。这样Pig Latin专门做他们语言层面的事情,Apache Beam专门做适配底层执行引擎的事情去,这样一下子Pig Latin就可以支持所有的数据处理引擎 -- 实现跨域式发展。
- 语言提供的原语越高阶,那么编译器可以进行的优化的可能也就越多。但是同时语言也就越受限,因为你一门语言越高阶,那么就越具体,越针对某个具体的领域了(DSL)。
- 延迟加载可以给优化带来更多的机会。
网友评论