通过spark-submit运行java程序

原来程序是将所有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.

总结一下,要完成一件事真的不容易,有好几次都想绕过这些问题,找其它办法,但最后还是坚持下来了。用一句诗来描述此时此刻的心情就是:山重水复疑无路,柳暗花明又一村。










你可能感兴趣的:(java)