Hadoop 中使用DistributedCache遇到的问题

本文详细介绍了在Hive中使用MAPJOIN技术优化大数据文件与小数据文件匹配计算的方法,并解决了分布式缓存(distributedcache)在实际应用中遇到的FileNotFoundException和OutOfMemoryError:GCoverheadlimitexceeded等问题。

 

自己在写MAR/REDUCE代码时,遇到了一个问题,一个大数据文件和一个小数据文件匹配计算,但是小数据文件太小,所以想采用HIVE的MAP JOIN的方式,把小数据文件放到直接大数据文件map的datanode的内存中,这样少了MR代码的1对N的数据文件关联。

实现这个的最佳方案就是利用distributed cache。HIVE的MAP JOIN也是利用这个技术。

首先简要介绍一下distributed cache是如何使用的,然后总结下自己在使用distributed cache遇到的问题,这些问题网上也有人遇到,但是没有给出明确的解释。希望能够帮助同样遇到此类问题的朋友。

distributed cache至少有如下的两类类应用:
1.MAP、REDUCE本身和之间共享的较大数据量的数据
2.布置第三方JAR包,可以避免集群的删减导致部分依赖的机器的JAR包的丢失

distributed cache使用的流程总结如下:
1.在HDFS上准备好要共享的数据(text、archive、jar)
2.在distributed cache中添加文件
3.在mapper或者reducer类中获取数据
4.在map或者reduce函数中使用数据


1.数据本来就在HDFS上,所以省去了流程中的第一步
可以使用hadoop fs -copyFromLocal把本地文件cp到HDFS上
2.在distributed cache中添加文件
public static void disCache(String dimDir, JobConf conf) throws IOException {
           FileSystem fs = FileSystem.get(URI.create(dimDir), conf);
           FileStatus[] fileDir = fs.listStatus(new Path(dimDir));
           for (FileStatus file : fileDir) {
                DistributedCache.addCacheFile(URI.create(file.getPath().toString()), conf);
           }
}
因为我利用的数据是HIVE脚本生成的,所以无法指定具体的文件路径,采用这种方式把一张HIVE表的所有数据都加载到cache中。如果能直接明确知道文件名称就简单很多了,例如:
DistributedCache.addCacheFile(URI.create(“/mytestfile/file.txt”), conf);
3.在mapper或者reducer中获取数据
public void configure(JobConf job) {
          try {
               urlFile = DistributedCache.getLocalCacheFiles(job);
               seoUrlFile = "file://" + urlFile[0].toString();
               allUrlFile = "file://" + urlFile[1].toString();
               getData(seoUrlFile, seoUrlDim);
               getData(allUrlFile, allUrlDim);
          } catch (IOException e) {
                     // TODO Auto-generated catch block
               e.printStackTrace();
           }
}
      我在流程2中添加了两个文件,通过DistributedCache.getLocalCacheFiles(job)获取Path[],针对不同文件调用getData,把文件数据放在不同的List中seoUrlDim,allUrlDim
     如此一来,distributed cache的过程就结束了,接下来就在map()或者reduce()中使用这些数据就OK了。
4.在map或者reduce函数中使用数据
     一定要区分流程3和4,3是获取数据,4是使用数据。我就是在前期没弄明白这个的差异,导致内存溢出。


虽说好像挺简单的,但是在实现这个代码的过程中有如下几个问题困扰了我好久,网上也没找到很好的解决方案,后来在兄弟的帮助下搞定
1.FileNotFoundException
2.java.lang.OutOfMemoryError: GC overhead limit exceeded
1.FileNotFoundException
     这个问题涉及到DistributedCache.getLocalCacheFiles(job) 这个函数,此函数返回的Path[]是执行map或者reduce的datanode的本地文件系统中路径,但是我在getData中利用的SequenceFile.Reader的默认filesystem是hdfs,这就导致获取数据时是从hdfs上找文件,但是这个文件是在本地文件系统中,所以就会报出这个错误FileNotFoundException。
     例如,DistributedCache.getLocalCacheFiles(job)返回的PATH路径是:/home/dwapp/hugh.wangp/mytestfile/file.txt,在默认文件系统是hdfs时,获取数据时会读hdfs://hdpnn:9000/home/dwapp/hugh.wangp/mytestfile/file.txt,但是我们是在本地文件系统,为了避免数据获取函数选择"错误"的文件系统,我们在/home/dwapp/hugh.wangp/mytestfile/file.txt前加上"file://",这样就会从本地文件系统中读取数据了。就像我例子中的seoUrlFile = "file://" + urlFile[0].toString();

2.java.lang.OutOfMemoryError: GC overhead limit exceeded
     这个问题是我在没搞明白如何使用distributed cache时犯下的错误,也就是我没弄明白流程3和4的区别。我把流程3中说的获取数据的过程放在map函数中,而在map函数中其实是使用数据的过程。这个错误因为使每用一个map就要获取一下数据,也就是初始化一个list容器,使一个datanode上起N个map,就要获取N个list容器,内容溢出也就是自然而然的事情了。
     我们一定要把获取数据的过程放在mapper或reducer类的configure()函数中,这样对应一个datanode就只有一份数据,N个map可以共享着一份数据。


内容单薄,望志同道合之士互相学习。
<think>我们正在处理一个关于Hadoop减法操作的问题。在Hadoop中,并没有直接的"减法"操作符,但我们可以通过MapReduce编程模型来实现类似集合减法的操作。例如,我们可能想要实现两个数据集之间的差集(A - B),即找出在A中出现但不在B中出现的记录。 参考HadoopMapReduce模型,我们可以通过以下步骤实现: 1. 将两个数据集(A和B)都作为输入。 2. 在Map阶段,为每条记录标记它来自哪个数据集(例如,给来自A的记录标记为"A",来自B的记录标记为"B")。 3. 在Reduce阶段,对于相同的键(即需要比较的记录),如果该键对应的值列表中有来自A的标记而没有来自B的标记,则输出该记录(表示它在A中但不在B中)。 另外,也可以使用Hive或Pig等高层语言来实现,它们可能提供更简洁的语法。 但是,用户明确提到了“减法操作”,所以我们主要关注MapReduce的实现。 步骤详细描述: 1. 输入:两个数据集A和B,存储在HDFS上的两个目录中。 2. Map阶段: - 对于数据集A中的每条记录,输出键值对:键为记录内容(或者用于比较的字段),值为标记(如"A")。 - 对于数据集B中的每条记录,输出键值对:键为记录内容(同样用于比较的字段),值为标记(如"B")。 3. Reduce阶段: - 对于同一个键,会收到一个值的列表,包含所有标记(可能包含多个"A"和"B",但因为我们只关心存在性,所以重复也没关系,但要注意去重)。 - 我们检查这个列表:如果其中包含至少一个"A"标记,且不包含任何"B"标记,则输出这个键(即记录)。 注意:这里我们假设记录是唯一的,或者我们只关心记录是否出现,而不关心次数。如果记录有重复,那么我们需要根据需求决定输出次数(例如,如果A中有重复而B中没有,那么应该输出多次?)。但通常减法操作是集合操作,所以我们一般会去重。 另外,我们可以通过设置多个Reduce任务来并行处理。 代码示例(简化版): Mapper类(处理两个数据集): ```java public class MinusMapper extends Mapper<LongWritable, Text, Text, Text> { private Text tag = new Text(); private Text word = new Text(); public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); // 获取当前输入分片来自哪个文件(A或B) FileSplit fileSplit = (FileSplit)context.getInputSplit(); String filename = fileSplit.getPath().getName(); if (filename.equals("A.txt")) { // 假设A数据集的文件名为A.txt tag.set("A"); } else if (filename.equals("B.txt")) { // 假设B数据集的文件名为B.txt tag.set("B"); } word.set(line); context.write(word, tag); } } ``` Reducer类: ```java public class MinusReducer extends Reducer<Text, Text, Text, NullWritable> { public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException { boolean inA = false; boolean inB = false; for (Text val : values) { if (val.toString().equals("A")) { inA = true; } else if (val.toString().equals("B")) { inB = true; } } // 如果记录在A中且不在B中,则输出 if (inA && !inB) { context.write(key, NullWritable.get()); } } } ``` 注意:这里我们假设整个记录作为键,所以每条记录都是唯一的。如果记录有多个字段,我们可能需要根据某个字段作为键,但这里简化处理。 另外,这种方法可能遇到性能问题,因为所有记录都要通过网络传输到Reducer。对于大型数据集,我们可以考虑使用Bloom Filter等优化方法,但这里不展开。 此外,用户可能是在Hive或Pig中操作,那么可以使用更简单的SQL-like语句。 例如在Hive中: ```sql SELECT * FROM A LEFT JOIN B ON A.key = B.key WHERE B.key IS NULL; ``` 或者 ```sql SELECT * FROM A WHERE A.key NOT IN (SELECT key FROM B); ``` 在Pig中: ```pig A = LOAD 'A' AS (line:chararray); B = LOAD 'B' AS (line:chararray); Result = MINUS(A, B); -- 注意:Pig有MINUS操作符,但要求两个关系必须模式兼容,且去重。 -- 或者使用JOIN和FILTER ``` 但是,用户的问题是“Hadoop中减法的实现方法”,所以我们重点放在MapReduce上。 生成相关问题: 1. 如何优化Hadoop减法操作的性能? 2. 在Hadoop中,除了减法操作,还有哪些集合操作(如并集、交集)的实现方法? 3. 如何处理减法操作中数据倾斜的问题?</think>### Hadoop中减法操作的实现方法 在Hadoop中,没有直接的"减法操作符",但可以通过多种方式实现类似功能,主要取决于具体场景和数据结构。以下是三种常见实现方法: #### 1. **MapReduce实现集合差集(A-B)** 这是最基础的实现方式,通过两阶段MapReduce找出存在于A但不存在于B的记录: ```java // Mapper标记数据来源 public void map(LongWritable key, Text value, Context context) { String sourceTag = ((FileSplit)context.getInputSplit()).getPath().getName(); context.write(value, new Text(sourceTag)); // 值标记为"A"或"B" } // Reducer执行差集逻辑 public void reduce(Text key, Iterable<Text> values, Context context) { boolean inA = false, inB = false; for (Text val : values) { if (val.toString().equals("A")) inA = true; if (val.toString().equals("B")) inB = true; } if (inA && !inB) context.write(key, NullWritable.get()); // 输出A-B结果 } ``` **适用场景**:大规模数据集求差集,如用户行为分析中的差异检测[^1]。 #### 2. **Hive SQL实现(推荐)** 通过HiveQL可更简洁地实现: ```sql SELECT * FROM table_A WHERE id NOT IN (SELECT id FROM table_B); ``` 或使用`LEFT JOIN`: ```sql SELECT A.* FROM table_A A LEFT JOIN table_B B ON A.id = B.id WHERE B.id IS NULL; ``` **优势**:开发效率高,自动优化执行计划,支持复杂条件过滤[^2]。 #### 3. **Spark实现(高性能方案)** 使用Spark RDD或DataFrame API: ```scala val rddA = sc.textFile("hdfs:///data/A") val rddB = sc.textFile("hdfs:///data/B") val result = rddA.subtract(rddB) // 直接差集操作 result.saveAsTextFile("hdfs:///result") ``` **性能优势**:基于内存计算,比MapReduce快10-100倍,支持迭代计算[^3]。 ### 关键注意事项 1. **数据倾斜处理**:当Key分布不均时,使用`salting`技术(添加随机前缀) 2. **去重要求**:如果结果需要去重,在Reduce阶段添加`HashSet`去重逻辑 3. **成本控制**:大规模操作应启用YARN的公平调度器,避免资源抢占[^3] 4. **优化技巧**: - 将小数据集放入DistributedCache - 使用Bloom Filter预过滤 - 设置合理分区数(建议为集群核数2-3倍) ### 典型应用场景 - **用户行为分析**:找出今日新增用户(今日活跃用户 - 历史用户) - **异常检测**:正常数据全集 - 白名单 = 可疑数据 - **数据清洗**:原始数据集 - 无效记录集 = 洁净数据
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值