TeraSort是Hadoop的测试中很有用的一个工具,但以前只是粗略的知道它的功能和用法,简单的用它做了几个测试用例。实际上,对于这种比较通用的工具,如果能够了解它更多一些的话,对于理解Hadoop是很有帮助的,同时也可以更好的利用它来帮助测试。最近有点时间,就了解了一些它的背景,代码实现原理等等,就先记录下来吧。
SortBenchmark(http://sortbenchmark.org/ )是JimGray自98年建立的一项排序竞技活动,它制定了不同类别的排序项目和场景,每年一次,决出各项排序算法实现的第一名(看介绍是在每年的ACM SIGMOD颁发奖牌哦)。
Hadoop在2008年以209秒的成绩获得年度TeraSort项(Dotona类)的第一名;而此前这一项排序的记录是297秒。
从SortBenchmark网站上可以了解到,Hadoop到今天仍然保持了Minute项Daytona类型排序的冠军。Minute项排序是通过评判在60秒或小于60秒内能够排序的最大数据量来决定胜负的;其实等同于之前的TeraSort(TeraSort的评判标准是对1T数据排序的时间)。
Hadoop源代码中包含了TeraSort,打包在examples包(如:hadoop-0.20.2-examples.jar)。
SortBenchmark对排序的输入数据制定了详细规则,要求使用其提供的gensort工具(http://www.ordinal.com/gensort.html )生成输入数据。Hadoop的TeraSort也用Java实现了一个生成数据工具TeraGen,算法与gensort一致。
对输入数据的基础要求是:输入文件是由一行行100字节的记录组成,每行记录包括一个10字节的Key;以Key来对记录排序。
Minute项排序允许输入文件可以是多个文件,但Key的每个字节要求是binary编码而不是ASCII编码,也就是每个字符可能有256种可能,也就是说每条记录,有2的80次方种可能的Key;
同时Daytona类别则要求排序程序不仅是为10字节长Key、100字节长记录排序设计的,还可以支持对其他长度的Key或行记录进行排序;也就是说这个排序程序是通用的。
在hadoop里,利用TeraGen生成排序输入数据的命令格式是这样的:
$ bin/hadoop jar hadoop-0.19.2-examples.jar teragen 10000000000 /terasort/input1TB
注意,teragen后的数值单位是行数;因为每行100个字节,所以如果要产生1T的数据量,则这个数值应为1T/100=10000000000(10个0)。
生成的数据是这样的:
!x'-n[Pp+l1049085170QQQQQQQQQQRRRRRRRRRRSSSSSSSSSSTTTTTTTTTTUUUUUUUUUUVVVVVVVVVVWWWWWWWWWWXXXXXXXX
r0JZ8-|o\)1049085171YYYYYYYYYYZZZZZZZZZZAAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFF
@Jp9XC#d/J1049085172GGGGGGGGGGHHHHHHHHHHIIIIIIIIIIJJJJJJJJJJKKKKKKKKKKLLLLLLLLLLMMMMMMMMMMNNNNNNNN
N)eM''3/,!~@@1049085176MMMMMMMMMMNNNNNNNNNNOOOOOOOOOOPPPPPPPPPPQQQQQQQQQQRRRRRRRRRRSSSSSSSSSSTTTTTTTT
#g{6{0Z;%\1049085177UUUUUUUUUUVVVVVVVVVVWWWWWWWWWWXXXXXXXXXXYYYYYYYYYYZZZZZZZZZZAAAAAAAAAABBBBBBBB
7
每行记录由3段组成:
在这我其实有个小问题,因为排序是针对Key的,岂不是后面的Value只需要保证长度就可以,至于内容是什么都没关系么?有人可能说随机字母组成是为了避免压缩时不均衡。但SortBenchmark是要求所有输入文件、输出文件、甚至中间传输过程的文件都不允许使用压缩的。不管怎么说,对内容制定个规则,应该还是为了保证比赛公平性吧。
TeraGen作业没有Reduce Task,产生文件的个数取决于设定Map的个数。
运行TeraSort的命令是这样的:
$ bin/hadoop jar hadoop-0.19.2-examples.jar terasort /terasort/input1TB /terasort/output1TB
运行后,我们可以看到会起m个mapper(取决于输入文件个数)和r个reducer(取决于设置项:mapred.reduce.tasks),排好序的结果存放在/terasort/output1TB目录。
(这个图非常经典,截自:Hadoop:The Definitive Guide)
恰好趁这个机会,把自己对MapReduce过程的理解简要的整理一下:
Hadoop默认的partitioner是HashPartitioner,它的实现是这样的:
public classHashPartitioner implements Partitioner {
public void configure(JobConf job) {}
public int getPartition(K2 key, V2 value,
int numPartitions) {
return (key.hashCode() &Integer.MAX_VALUE) % numPartitions;
}
}
TotalOrderPartitioner首先要解决的问题是,partitioner发生在map里,而每个mapper只处理它自己的一份split数据,它如何知道它所处理的数据在全局所有输入数据里的位置?
回溯到源头,InputFormat有数据的全局观。TeraSort定义的TeraInputFormat有一个重要的功能,就是把对全部数据形成一个摘要文件,以提供给之后的partitioner使用。为了保证保证效率,TeraSort采用抽样来实现摘要。
运行TeraSort后,它做的第一件事情是对输入数据进行抽样,抽样频率由设置项抽样总条数terasort.partitions.sample决定,默认值为100000条。对输入记录分10个区间(或更小)来分批采样,直到采到足够条数;采样完成后对这些抽样点进行排序,然后对排序后记录均分成partitions个区间,最终将这些区间分割点写到文件,文件名为_partition.lst。这个文件会被加到distributed cache里,目的是为了能被hadoop分发到将来运行mapper的每一个TT上去。
有了所有数据的摘要信息,后面的partitioner做起来就有依据了。当它处理一条mapper的输出记录时,它可以按照一种映射算法,依据每条记录的Key与_partition.lst记录的对应信息做比较,将它划分到某一个partition,从而保证partition之间的有序性。
假设我们从_partition.lst得到的Key组合为sample[];某一条记录的Key值为key,如果查找到sample[i-1] <= key < sample[i] , 那么这条记录会被分配到第i个partition,也就是第i个reducer来处理。这样,partition之间也就有序了。
在TeraSort中,构建了一个trie来实现对Key的查找归类(Partitioner的过程其实就是归类,然后把每一类交给一个reducer处理)。TeraSort默认使用2层的Trie,意味着它只用Key的前两个字节与与分割点比较; Trie的非叶子节点有256个子节点(对应着Key的每一个字节的binary code,有256种可能)。
需要提到的是,TeraSort输出的replica数设置是1份,而不是Hadoop默认使用的3份。为什么?因为SortBenchmark没有规定结果要存多份副本,而设置成1份,Hadoop会就近存在本地(如果这个reducer的TT上也同时有DN)。这可节省了不少网络和磁盘消耗,间接的提高了TeraSort的执行效率。
bin/hadoop jar hadoop-0.19.2-examples.jar teravalidate /terasort/output1TB /terasort/validate1TB
如果有错误,log记录会放在输出目录里。
job.setLong("mapred.min.split.size", Long.MAX_VALUE);
我稍微总结下,可以用TeraSort来测试的场景: