4.1.3 半连接(Semi-join)

假设一个场景,需要连接两个很大的数据集,例如,用户日志和OLTP的用户数据。任何一个数据集都不是足够小到可以缓存在map作业的内存中。这样看来,似乎就不能使用reduce端的连接了。尽管不是必须,可以思考以下问题:如果在数据集的连接操作中,一个数据集中有的记录由于因为无法连接到另一个数据集的记录,将会被移除。这样还需要将整个数据集放到内存中吗?在这个例子中,在用户日志中的用户仅仅是OLTP用户数据中的用户中的很小的一部分。那么就可以从OLTP用户数据中只取出存在于用户日志中的那部分用户的用户数据。然后就可以得到足够小到可以放在内存中的数据集。这种的解决方案就叫做半连接。

图4.6说明了在半连接中将要执行的三个MapReduce作业(Job)。

接下来介绍如何实现一个半连接。

技术20 实现半连接

当需要连接两个都很大的数据集时,很容易想到要用重分区连接(利用了整个MapReduce框架的reduce端的连接)。如果这么想了,又不能够将其中一个数据集过滤到一个较小的尺寸以便放到map端的内存中,那也就是想想而已。然而,如果能够将一个数据集减小到一个可管理的大小,也许就用不着使用重分区连接了。

问题

需要连接两个都很大的数据集,同时减少整理和排序阶段的消耗。

解决方案

在这个技术中,将会用到三个MapReduce作业来连接两个数据集,以此来减少reduce端连接的消耗。对于很大的数据集,这个技术非常有用。

讨论

在这个技术中,将会用到附录D.2中的复制连接(Replicated join)的代码来实现MapReduce作业中的最后两步(http://www.cnblogs.com/datacloud/p/3617078.html)。同时,在图4.6中的三个作业将会被分开来说明。

作业1

第一个MapReduce作业的功能是从日志文件中提取出用户名,用这些用户名生成一个用户名唯一的集合(Set)。这通过在map函数执行用户名的投影(projection)操作来实现。然后用reduce出用户名。为了减少在map阶段和reduce阶段之间传输的数据量,采用如下方法:在map任务中采用哈希集(HashSet)来保存用户名,在cleanup方法中输出哈希集的值。图4.7说明了这个作业的流程:

作业1的map和reduce的代码如下:

 public static class Map extends Mapper<Text, Text, Text, NullWritable> {

     private Set<String> keys = new HashSet<String>();

     @Override
protected void map(Text key, Text value, Context context)
throws IOException, InterruptedException {
keys.add(key.toString());
} @Override
protected void cleanup(Context context)
throws IOException, InterruptedException { Text outputKey = new Text(); for(String key: keys) {
outputKey.set(key);
context.write(outputKey, NullWritable.get());
} } } public static class Reduce extends Reducer<Text, NullWritable, Text, NullWritable> { @Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context)
throws IOException, InterruptedException {
context.write(key, NullWritable.get());
} }

作业1的结果就是来自于日志文件中的所有用户的集合。集合中的用户名是唯一的。

作业2

作业2包含了复杂的过滤过程。目的是从全体用户的用户数据集中移除不存在于日志文件中的用户。这是一个只包含map的作业。它用到了复制连接来缓存出现在日志文件中的用户名,并把他们和全体用户的数据集连接。由于来自于作业1的用户唯一的数据集要远远小于全体用户的数据集,就把来自作业1的用户集放到缓存中了。图4.8说明了这个作业的流程:

现在是个不错的时间去熟悉一下附录D中的复制连接框架。这个框架对KeyValueTextInputFormat和TextOutputFormat提供了内置支持,并假设 KeyValueTextInputFormat生成的键是连接键。同时,这也是数据被展开的过程。图4.9是这个框架的类图:

GenericReplicatedJoin类是执行连接的类。如图4.9中所示,在GenericReplicatedJoin的类列表中前三个类是可扩展的,相对应的复制连接的行为也是可定制的。readFromInputFormat方法可以用于任意的输入类型(InputFormat)。getDistributedCacheReader方法可以被重载来支持来自于分布式缓存(distributed cache)的任意文件类型。在这一步中的核心是join方法。join方法将会生成作业的输出键和输出值。在默认的实现中,两个数据集的值将会被合并以生成最终的输出值。这个join方法可以自定义,可以指定仅仅输出来自于OLTP的用户表的值,如下所示:

 public class ReplicatedFilterJob extends GenericReplicatedJoin {

     @Override
public Pair join(Pair inputSplitPair, Pair distCachePair) {
return inputSplitPair;
} }

还需要把来自于作业1的文件放到分布式缓存中:

 for(FileStatus f: fs.listStatus(uniqueUserStatus)) {
if(f.getPath().getName().startsWith("part")) {
DistributedCache.addCacheFile(f.getPath().toUri(), conf);
}
}

然后,在驱动(driver)代码中,调用GenericReplicatedJoin类:

 public class ReplicatedFilterJob extends GenericReplicatedJoin {

     public static void runJob(Path usersPath,
Path uniqueUsersPath,
Path outputPath)
throws Exception { Configuration conf = new Configuration(); for(FileStatus f: fs.listStatus(uniqueUsersPath)) {
if(f.getPath().getName().startsWith("part")) {
DistributedCache.addCacheFile(f.getPath().toUri(), conf);
}
} Job job = new Job(conf);
job.setJarByClass(ReplicatedFilterJob.class);
job.setMapperClass(ReplicatedFilterJob.class);
job.setNumReduceTasks(0);
job.setInputFormatClass(KeyValueTextInputFormat.class);
outputPath.getFileSystem(conf).delete(outputPath, true);
FileInputFormat.setInputPaths(job, usersPath);
FileOutputFormat.setOutputPath(job, outputPath); if(!job.waitForCompletion(true)) {
throw new Exception("Job failed");
} } @Override
public Pair join(Pair inputSplitPair, Pair distCachePair) {
return inputSplitPair;
} }

作业2的输出就是已被用户日志数据集的用户过滤过的用户集了。

作业3

在最后一步中,需要将作业2生成的已过滤的用户集和原始的用户日志合并了。表面上,已过滤的用户集是足够小到可以放到内存中,同样也可以放到分布式缓存中。图4.10说明了这个作业的流程:

 FileStatus usersStatus = fs.getFileStatus(usersPath);

 for(FileStatus f: fs.listStatus(usersPath)) {

     if(f.getPath().getName().startsWith("part")) {
DistributedCache.addCacheFile(f.getPath().toUri(), conf);
} ...

这里要再次用到复制连接框架来执行连接。但这次不用自定义join方法的行为,因为两个数据集中的数据都要出现在最后的输出中。

执行这个代码,观察前述步骤生成的输出。

$ bin/run.sh com.manning.hip.ch4.joins.semijoin.Main users.txt user-logs.txt output

$ hadoop fs -ls output
/user/aholmes/output/filtered
/user/aholmes/output/result
/user/aholmes/output/unique $ hadoop fs -cat output/unique/part*
bob
jim
marie
mike $ hadoop fs -cat output/filtered/part*
mike 69 VA
marie 27 OR
jim 21 OR
bob 71 CA $ hadoop fs -cat output/result/part*
jim logout 93.24.237.12 21 OR
mike new_tweet 87.124.79.252 69 VA
bob new_tweet 58.133.120.100 71 CA
mike logout 55.237.104.36 69 VA
jim new_tweet 93.24.237.12 21 OR
marie view_user 122.158.130.90 27 OR
jim login 198.184.237.49 21 OR
marie login 58.133.120.100 27 OR

这些输出说明了在半连接的作业中的逻辑进程和最终连接的输出。

小结

在这个技术中说明了如何使用半连接来合并两个数据集。半连接的创建包括了比其他连接类型更多的步骤。但它确实是一个处理大的数据集的map端连接的强大的工具。当然,这些很大的数据集要能够被减小到能够放到内存中。

[大牛翻译系列]Hadoop(3)MapReduce 连接:半连接(Semi-join)的更多相关文章

  1. [大牛翻译系列]Hadoop(2)MapReduce 连接:复制连接(Replication join)

    4.1.2 复制连接(Replication join) 复制连接是map端的连接.复制连接得名于它的具体实现:连接中最小的数据集将会被复制到所有的map主机节点.复制连接有一个假设前提:在被连接的数 ...

  2. [大牛翻译系列]Hadoop(1)MapReduce 连接:重分区连接(Repartition join)

    4.1 连接(Join) 连接是关系运算,可以用于合并关系(relation).对于数据库中的表连接操作,可能已经广为人知了.在MapReduce中,连接可以用于合并两个或多个数据集.例如,用户基本信 ...

  3. [大牛翻译系列]Hadoop(5)MapReduce 排序:次排序(Secondary sort)

    4.2 排序(SORT) 在MapReduce中,排序的目的有两个: MapReduce可以通过排序将Map输出的键分组.然后每组键调用一次reduce. 在某些需要排序的特定场景中,用户可以将作业( ...

  4. [大牛翻译系列]Hadoop 翻译文章索引

    原书章节 原书章节题目 翻译文章序号 翻译文章题目 链接 4.1 Joining Hadoop(1) MapReduce 连接:重分区连接(Repartition join) http://www.c ...

  5. [大牛翻译系列]Hadoop(4)MapReduce 连接:选择最佳连接策略

    4.1.4 为你的数据选择最佳连接策略 已介绍的每个连接策略都有不同的优点和缺点.那么,怎么来判断哪个最适合待处理的数据? 图4.11给出了一个决策树.这个决策树是于论文<A Compariso ...

  6. [大牛翻译系列]Hadoop(14)MapReduce 性能调优:减小数据倾斜的性能损失

    6.4.4 减小数据倾斜的性能损失 数据倾斜是数据中的常见情况.数据中不可避免地会出现离群值(outlier),并导致数据倾斜.这些离群值会显著地拖慢MapReduce的执行.常见的数据倾斜有以下几类 ...

  7. [大牛翻译系列]Hadoop(22)附录D.2 复制连接框架

    附录D.2 复制连接框架 复制连接是map端连接,得名于它的具体实现:连接中最小的数据集将会被复制到所有的map主机节点.复制连接的实现非常直接明了.更具体的内容可以参考Chunk Lam的<H ...

  8. [大牛翻译系列]Hadoop(19)MapReduce 文件处理:基于压缩的高效存储(二)

    5.2 基于压缩的高效存储(续) (仅包括技术27) 技术27 在MapReduce,Hive和Pig中使用可分块的LZOP 如果一个文本文件即使经过压缩后仍然比HDFS的块的大小要大,就需要考虑选择 ...

  9. [大牛翻译系列]Hadoop(18)MapReduce 文件处理:基于压缩的高效存储(一)

    5.2 基于压缩的高效存储 (仅包括技术25,和技术26) 数据压缩可以减小数据的大小,节约空间,提高数据传输的效率.在处理文件中,压缩很重要.在处理Hadoop的文件时,更是如此.为了让Hadoop ...

随机推荐

  1. Win Mingw-64获取

    首先得了解下三种异常不同实现: SJLJ: slower but available for every architecture. SEH: fastest but limited to 64-bi ...

  2. Android进阶笔记18:选用合适的IPC方式

    1. 相信大家都知道Android进程间通信方式很多,比如AIDL.Messenger等等,接下来我就总结一下这些IPC方式优缺点. 2. IPC方式的优缺点和适用场景 3. 附加:使用Intent实 ...

  3. C#中怎么在EXCEL中的单元格中画斜线啊 ??

    Code Snippet 做法: 1,先添加引用COM,找 Excel 2,using Excel = Microsoft.Office.Interop.Excel; 3, 代码 private Ex ...

  4. CSS/CSS3 如何实现元素水平居中

    更新:可直接访问 [CSS/CSS3 如何实现元素水平居中] 查看效果,右键查看源代码 -------------------------------------------------分割线---- ...

  5. 【Linux】gdb调试core文件

    编写服务器端程序,很容易遇到Crash问题,比较幸运的是Linux提供了core file,保留了Crash的现场.有时候,根据当前的调用栈,并且打印出当前栈的变量就可以分析出crash的原因,但是, ...

  6. Delphi单元文件之-防止程序重复执行

    //工程引用此单元就能防止同时出现多个实例unit MultInst;interfaceuses Windows ,Messages, SysUtils, Classes, Forms;impleme ...

  7. $(function(){})与$(document).ready(function(){})

    $(function(){ //jq ready()的简写 }); $(document).ready(function(){ // }); 或者: $().ready(function(){ //j ...

  8. 纯css3 transforms 3D文字翻开翻转3D开放式效果

    详细内容请点击 在线预览立即下载 在本教程中,将基于CSS3创建的一个实现一个有趣的3D开放式效果.教程的目的是展示我们如何能带来一些生活上使用CSS3 . html: <ul class=&q ...

  9. 【CSS3】---结构性伪类选择器-root+not+empty+target

    结构性伪类选择器—root :root选择器,从字面上我们就可以很清楚的理解是根选择器,他的意思就是匹配元素E所在文档的根元素.在HTML文档中,根元素始终是<html>. 示例演示: 通 ...

  10. 学习Slim Framework for PHP v3 (七)--route middleware怎么被add进来的?

    上两篇中分析了route是怎么被加进来的,以及如何被匹配的.这篇说一下route middleware是如何被加进来的,即add进来的.index.php的代码如下: $app->get('/f ...