在我们使用spark过程中经常会遇到需要调用Python、shell、C++、PHP等语言编写的脚本程序。这个时候比较笨重的做法就是将这些需要调用的其他语言编写的程序翻译成spark支持的形式,这样做不仅费时费力而且有极大的可能出现翻译错误的问题。那么有没有更好的方法处理这类问题呢?以java语言为例
一、Spark RDD API
1、使用Java提供的接口执行本地命令
步骤1:创建外部脚本
#print.py
import sys
print '--start---'
getui_data_path=sys.argv[1]
print getui_data_path
步骤2:spark调用
String filePath = "./print.py";
List<String> list = Arrays.asList("1","2","3","4","5","6");
List<String> collect = sc.parallelize(list).map(new Function<String, String>() {
private static final long serialVersionUID = 1L;
@Override
public String call(String v1) throws Exception {
String result = null;
String[] commands = {"python",filePath,v1};
Process process = Runtime.getRuntime().exec(commands);//调用Python脚本
if(process.waitFor() == 0){
System.out.println("success");
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuffer strbr = new StringBuffer();
String line;
while ((line = br.readLine())!= null ){
strbr.append(line);
}
result = strbr.toString();
}
return result;
}
}).filter(new Function<String, Boolean>() {
private static final long serialVersionUID = 1L;
@Override
public Boolean call(String v1) throws Exception {
return v1 != null;
}
}).collect();
2、使用spark pipe算子
步骤1:创建外部脚本
想要使用pipe要求外部脚本
1、以标准输入接收参数,标准输出返回结果,且所有输出都会被收集到spark的rdd里去,所以没有的数据不要输出,否则会引入脏数据
2、排除换行符,否则其也会进入到后面的计算
3、rdd每个分区,启动一次外部程序
#print_pipe.py
import sys
#print '--start--'
for line in sys.stdin:
print line.strip() #strip()主要为了排除换行符
步骤2:spark调用
List<String> list = Arrays.asList("1","2","3","4","5","6");
List<String> collect = sc.parallelize(list)
.pipe("python print_pipe.py")
.collect();
建议:使用spark pipe方式调用,因为使用pipe算子是每个分区启动一次外部程序,使用Java命令形式是每条数据启动一次外部程序。而每次外部程序调用都是新开一个进程,所以其启动、关闭相对来说耗时非常大的。
但是使用pipe算子注意如果每个分区数据过多会导致外部程序OOM,当一个分区数据量过多时可以考虑一下两种方案:
1、RDD重分区:增大分区数量
2、外部程序里做策略:如获取指定数据量时触发一次处理,不要等数据全过来在处理
二、hive/spark sql + transform
...