最近开发分析工具中使用了mapreduce和spark两种模式,独立的分析工具app已经开发完成并且使用命令行提交到集群运行成功,在任务代理中采用Runtime.getRuntime().exec方式提交mr或者spark到集群运行。mr运行没有出现任何问题,但是spark运行时,初期正常,没有任何问题,后来不知道什么时候开始,突然出现spark程序运行卡住,err中报错全是org.apache.spark.rpc.RpcTimeoutException.同时将相同的命令直接在命令行提交,运行又可以顺利完成,初步怀疑是由于java启动process导致了内存受限,于是开始了漫长的排错之旅。由于任务代理是webservice形式部署在tomcat中,为了排除tomcat的问题,初步尝试直接写一个jar文件来启动spark,结果发现竟然成功了,于是开发了一完整的专用执行命令行的AppInvoker.jar,通过任务代理启动AppInvoker.jar,传入spark-submit命令,AppInvoker.jar再去启动spark-submit来执行,初期也是没有问题。可是两周后,同样的问题出现了,spark运行到中间卡住,换回任务代理直接启动也不行。于是又开始调整spark执行时候的各种参数,设置executor-number,executor-memory,各种timeout参数,结果都不能解决问题,无奈之下,经过网上各种搜索,发现spark本身提供了SparkSubmit和SparkLaucher类可以提交任务,结果还是不行。。。。。
后来无意中又一次,spark还是卡住状态,直接把tomcat进程杀掉,居然发现spark开始动了,难道是调用的部分有问题?于是开始搜索java 调用cmd命令都有哪些坑,http://blog.csdn.net/wohaqiyi/article/details/62891619中提到:
对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,
问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。
接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。因为程序的输入流是我需要使用的,所以一直都有读取,那么影响的应该就是错误流。直接在提交命令之后,开了一个线程去清空p的错误流,重新打包运行,好了。。。。大功告成,代码如下
InputStream is2 = p.getErrorStream();
BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
StringBuilder buf = new StringBuilder();
String line = null;
while((line = br2.readLine()) != null) buf.append(line);
System.out.println("result:" + buf);
while (br2.readLine() != null);
try {
p.waitFor();
}catch (InterruptedException e){
e.printStackTrace();
}