原来程序是将所有jar打包到libs目录下,然后运行生成好的run.sh。现在要使用spark-submit将它提交到spark上运行。几经波折之后,终于圆满完成。
首先遇到的问题是如何使用gradle将工程打包成可执行的jar文件。这个问题网上已有答案,就是使用插件
"com.github.johnrengelman.shadow"。gradle的配置如下:
apply plugin: 'com.github.johnrengelman.shadow'
jar {
zip64 true
from {configurations.compile.collect{ it.isDirectory() ? it : zipTree(it)}}
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
manifest {attributes "Main-class":"com.sinosun.mstp.bigdata.UpsLauncher"}
}
打包的问题解决后,接着遇到了配置文件的问题。原来程序使用的是自己的配置文件,而且配置文件的目录固定为classpath下的conf。使用spark-submit命令后,它会定位到spark的conf目录下。
采用的解决方案是将配置文件打进jar包,运行时将jar包中的配置解压到spark的conf目录下,然后读取。但是在解压的遇到了问题,原因是不明白getResourceAsStream()的路径计算方式,推荐大家看这篇 博客,里面详细讲解了getResourceStream 和 getSystemResourceStream的路径问题。解决了第二个问题后gradle文件变成了。
apply plugin: 'com.github.johnrengelman.shadow'
jar {
zip64 true
from {configurations.compile.collect{ it.isDirectory() ? it : zipTree(it)}}
into('com/***/***') {from 'conf'}
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
manifest {attributes "Main-class":"com.sinosun.mstp.bigdata.UpsLauncher"}
}
上面两个问题解决后,程序已经可以运行了,但是不能从kafka中读取数据。原因是代码写错了,正确的代码如下:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.streaming.Duration;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaPairReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka.KafkaUtils;
import scala.Tuple2;
@SuppressWarnings("serial")
public class JavaKafkaWordCount {
private JavaKafkaWordCount() {
}
public static void main(String[] args) throws Exception {
args = new String[4];
args[0] = "192.168.80.4:2181,192.168.80.5:2181,192.168.80.8:2181";
args[1] ="group1";
args[2] ="testTopic";
args[3]="3";
if (args.length < 4) {
System.err.println("Usage: JavaKafkaWordCount ");
System.exit(1);
}
SparkConf sparkConf = new SparkConf().setAppName("JavaKafkaWordCount").setMaster("local[2]");
// Create the context with 2 seconds batch size
JavaStreamingContext jssc = new JavaStreamingContext(sparkConf, new Duration(2000));
int numThreads = Integer.parseInt(args[3]);
Map topicMap = new HashMap<>();
String[] topics = args[2].split(",");
for (String topic : topics) {
topicMap.put(topic, numThreads);
}
JavaPairReceiverInputDStream messages = KafkaUtils.createStream(jssc, args[0], args[1],
topicMap);
JavaDStream lines = messages.map(new Function, String>() {
@Override
public String call(Tuple2 tuple2) {
return tuple2._2();
}
});
lines.foreachRDD(new VoidFunction>() {
@Override
public void call(JavaRDD t) throws Exception {
t.collect().forEach(new Consumer() {
@Override
public void accept(String t) {
System.out.println(t);
}
});
}
});
jssc.start();
jssc.awaitTermination();
}
}
在解决第三个问题的时候,顺带着解决了另一个问题,就是如何使用eclipse远程调试spark-submit程序。可以看这篇 博客。
程序成功接收kafka的数据后,又遇到了一个新问题。shell退出后程序也退出了,这是linux的生命周期问题,具体原因很多地方都有介绍,大家可以自己寻找。解决方法是使用 nohup (启动命令) & 启动。
最后一个问题是想使用程序自己的日志系统,spark默认的日志系统是log4j,但我程序使用的是logback,解决方法很巧妙,使用如下命令运行。
./spark-submit --conf "spark.driver.userClassPathFirst=true" *.jar
参数
spark.driver.userClassPathFirst=true指定spark优先读取用户classpath下的文件,我的classpath下是logback的包,所以spark使用了我的logback.
总结一下,要完成一件事真的不容易,有好几次都想绕过这些问题,找其它办法,但最后还是坚持下来了。用一句诗来描述此时此刻的心情就是:山重水复疑无路,柳暗花明又一村。