我构建了一个基本的Web解析器,它使用hadoop将url交给多个线程。这很有效,直到我到达输入文件的末尾,Hadoop在仍有线程运行时声明自己完成。这会导致错误org.apache.hadoop.fs.FSError:java.io.IOException:Stream Closed。反正是否保持流打开足够长的时间让线程完成? (我可以合理准确地预测线程在单个URL上花费的最长时间)。
这是我如何执行线程
public static class Map extends MapReduceBase implements
Mapper<LongWritable, Text, Text, Text> {
private Text word = new Text();
private URLPile pile = new URLPile();
private MSLiteThread[] Threads = new MSLiteThread[16];
private boolean once = true;
@Override
public void map(LongWritable key, Text value,
OutputCollector<Text, Text> output, Reporter reporter) {
String url = value.toString();
StringTokenizer urls = new StringTokenizer(url);
Config.LoggerProvider = LoggerProvider.DISABLED;
System.out.println("In Mapper");
if (once) {
for (MSLiteThread thread : Threads) {
System.out.println("created thread");
thread = new MSLiteThread(pile);
thread.start();
}
once = false;
}
while (urls.hasMoreTokens()) {
try {
word.set(urls.nextToken());
String currenturl = word.toString();
pile.addUrl(currenturl, output);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
线程本身会得到像这样的网址
public void run(){
try {
sleep(3000);
while(!done()){
try {
System.out.println("in thread");
MSLiteURL tempURL = pile.getNextURL();
String currenturl = tempURL.getURL();
urlParser.parse(currenturl);
urlText.set("");
titleText.set(currenturl+urlParser.export());
System.out.println(urlText.toString()+titleText.toString());
tempURL.getOutput().collect(urlText, titleText);
pile.doneParsing();
sleep(30);
} catch (Exception e) {
pile.doneParsing();
e.printStackTrace();
continue;
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Thread done");
}
urlpile中的相关方法是
public synchronized void addUrl(String url,OutputCollector<Text, Text> output) throws InterruptedException {
while(queue.size()>16){
System.out.println("queue full");
wait();
}
finishedParcing--;
queue.add(new MSLiteURL(output,url));
notifyAll();
}
private Queue<MSLiteURL> queue = new LinkedList<MSLiteURL>();
private int sent = 0;
private int finishedParcing = 0;
public synchronized MSLiteURL getNextURL() throws InterruptedException {
notifyAll();
sent++;
//System.out.println(queue.peek());
return queue.remove();
}
答案 0 :(得分:1)
正如我可以从下面的评论中推断出的那样,你可以在每个map()函数中做到这一点,以简化操作。 我看到你做了以下事情,预先创建了一些空闲线程。 您可以将以下代码移至
if (once) {
for (MSLiteThread thread : Threads) {
System.out.println("created thread");
thread = new MSLiteThread(pile);
thread.start();
}
once = false;
}
要,
public static class Map extends MapReduceBase implements
Mapper<LongWritable, Text, Text, Text> {
@Override
public void configure(JobConf job) {
for (MSLiteThread thread : Threads) {
System.out.println("created thread");
thread = new MSLiteThread(pile);
thread.start();
}
}
@Override
public void map(LongWritable key, Text value,
OutputCollector<Text, Text> output, Reporter reporter) {
}
}
因此,这可以初始化一次,就此而言,不再需要'一次'条件检查。
此外,您不需要像上面那样制作空闲线程。 我不知道你将创造16个空闲线程的性能提升多少。
无论如何,这是一个解决方案(虽然可能不完美)
你可以使用像countdownlatch Read more here这样的东西来批量处理N个或更多的网址并阻止它们完成。这是因为,如果您将每个传入的url记录释放到一个线程,则会立即获取下一个url,并且当你以相同的方式处理最后一个url时,即使你有剩余的线程,map()函数也会返回在队列中进行处理。你将不可避免地得到你提到的例外。
这里有一个例子,说明你可以阻止使用倒计时器。
public static class Map extends MapReduceBase implements
Mapper<LongWritable, Text, Text, Text> {
@Override
public void map(LongWritable key, Text value,
OutputCollector<Text, Text> output, Reporter reporter) {
String url = value.toString();
StringTokenizer urls = new StringTokenizer(url);
Config.LoggerProvider = LoggerProvider.DISABLED;
//setting countdownlatch to urls.countTokens() to block off that many threads.
final CountDownLatch latch = new CountDownLatch(urls.countTokens());
while (urls.hasMoreTokens()) {
try {
word.set(urls.nextToken());
String currenturl = word.toString();
//create thread and fire for current URL here
thread = new URLProcessingThread(currentURL, latch);
thread.start();
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
latch.await();//wait for 16 threads to complete execution
//sleep here for sometime if you wish
}
}
最后,在处理URL时,在URLProcessingThread中减少锁存计数器
public class URLProcessingThread implments Runnable {
CountDownLatch latch;
URL url;
public URLProcessingThread(URL url, CountDownLatch latch){
this.latch = latch;
this.url = url;
}
void run() {
//process url here
//after everything finishes decrement the latch
latch.countDown();//reduce count of CountDownLatch by 1
}
}
您的代码可能出现问题:
在pile.addUrl(currenturl, output);
,当你添加一个新的url时,同时所有16个线程都将获得更新(我不是很确定),因为相同的桩对象被传递给16个线程。你的网址有可能被重新处理,或者你可能会得到一些其他的副作用(我不太确定)。
其他建议:
此外,您可能希望使用
增加地图任务超时mapred.task.timeout
(默认= 600000ms)= 10分钟
描述:如果任务既不读取输入,也不写入输出,也不会更新任务将被终止的毫秒数 它的状态字符串。
您可以在mapred-site.xml中添加/覆盖此属性