利用SparkLauncher在代码中调用Spark作业

背景

项目需要处理很多文件,而一些文件很大有几十GB,因此考虑对于这种文件,专门编写Spark程序处理,为了程序的统一处理,需要在代码中调用Spark作业来处理大文件。

实现方案

经过调研,发现可以使用Spark提供的SparkLauncher类进行Spark作业的提交,这个类的使用有很多参数需要注意,经过项目验证后,本文给出相对完整的使用方式以及说明

首先项目中要添加pom依赖,注意加上自己的版本

<dependency>
	<groupId>org.apache.spark</groupId>
     <artifactId>spark-launcher_2.11</artifactId>
</dependency>

其次,可以把Spark作业本身的一些参数放在配置文件里,灵活修改,我这里是配置kerberos安全认证的CDH集群,Spark作业提交时使用的模式为yarn-client,主要使用到了一下配置,配置中的路径这里是作为例子随便填的,实际按照自己环境填写,另外,整个应用是在CDH客户端节点执行的。每个配置项都有说明:

#spark application use
#driver的日志输出
driverLogDir=/root/test/logs/
#kerberos认证keytab文件
keytab=/root/test/dw_hbkal.keytab
# keyberos认证主体
principal=dw_hbkal
# yarn集群上运行spark作业
master=yarn
# yarn-client模式
deployMode=client
# spark-executor个数和内存配置
minExecutors=16
maxExecutors=16
executorMemory=1g
# driver内存配置
driverMemory=256M
# spark-executor使用的core数量配置
executorCores=2
# spark作业的主类
mainClass=com.unionpay.css.fcmp.compare.cp.spark.nonprikey.FileCompare
# spark作业的jar包
jarPath=/root/test/my-spark-job-1.0-SNAPSHOT.jar
# spark作业依赖的第三方jar
extjars=/root/test/mysql-connector-java-8.0.27.jar,/root/test/jedis-2.8.1.jar
# CHD客户端上存放的集群配置文件,表明向哪个集群提交spark作业
HADOOP_CONF_DIR=/root/CDH/bjc/CDH/etc/conf/hadoop-conf
JAVA_HOME=/usr/java/jdk1.8.0_141
SPARK_HOME=/opt/cloudera/parcels/CDH/lib/spark
# spark作业执行的yarn队列
yarnQueue=mysparkqueue

上述配置可以在代码中读取,并结合SparkLauncher一起使用,可以参看以下例子代码:

//负责发起spark作业
public class SparkJobService{

    private static final Logger logger = LoggerFactory.getLogger(SparkJobService.class);
    static Config config;
    //spark任务参数
    static String keytabPath;
    static String principal ;
    static String master;
    static String deployMode;
    static String minExecutods;
    static String maxExecutors;
    static String executorMemory;
    static String driverMemory;
    static String executorCores;
    static String mainClass;
    static String jarPath;
    static String extjars;
    static String yarnQueue;
    static String HADOOP_CONF_DIR;
    static String JAVA_HOME;
    static String SPARK_HOME;
    static String driverLogDir;

    static {
        config = new Config("job.properties");
        keytabPath = config.getString("keytab");
        principal = config.getString("principal");
        master = config.getString("master");
        deployMode = config.getString("deployMode");
        minExecutods = config.getString("minExecutods");
        maxExecutors = config.getString("maxExecutors");
        executorMemory = config.getString("executorMemory");
        driverMemory = config.getString("driverMemory");
        executorCores = config.getString("executorCores");
        mainClass = config.getString("mainClass");
        jarPath = config.getString("jarPath");
        extjars = config.getString("extjars");
        yarnQueue = config.getString("yarnQueue");
        HADOOP_CONF_DIR=config.getString("HADOOP_CONF_DIR");
        JAVA_HOME = config.getString("JAVA_HOME");
        SPARK_HOME = config.getString("SPARK_HOME");
        driverLogDir = config.getString("driverLogDir");
    }

    public static void main(String[] args) {
        try{
            //spark任务设置
            //如果在系统环境变量中添加了,可以不加
            HashMap<String,String> env = new HashMap();
            env.put("HADOOP_CONF_DIR",HADOOP_CONF_DIR);
            env.put("JAVA_HOME",JAVA_HOME);
            env.put("SPARK_HOME",SPARK_HOME);
			
			String jobArgs1 = "test1";
			String jobArgs2 = "test2"
			//......

            SparkLauncher launcher = new SparkLauncher(env).addSparkArg("--keytab",keytabPath).addSparkArg("--principal",principal).setMaster(master).setDeployMode(deployMode)
                    .setConf("spark.dynamicAllocation.minExecutors",minExecutods).setConf("spark.dynamicAllocation.maxExecutors",maxExecutors).setConf("spark.driver.memory",driverMemory).setConf("spark.executor.memory",executorMemory).setConf("spark.executor.cores",executorCores)
                    .setConf("spark.yarn.queue",yarnQueue)
                    .setAppResource(jarPath).setMainClass(mainClass).addAppArgs(jobArgs1,jobArgs2);

            //spark job中依赖jar,如mysql-connector.jar...
            for(String jarName : extjars.split(",")){
                launcher.addJar(jarName);
            }
            launcher.setAppName("SparkJob");
            //spark本地driver日志
            launcher.redirectError(new File(driverLogDir + "spark_driver.log"));
            final String[] jobId = new String[]{""};
            //用来等待spark作业结束
            CountDownLatch latch = new CountDownLatch(1);
            SparkAppHandle sparkAppHandle = launcher.setVerbose(false).startApplication(new SparkAppHandle.Listener() {
                @Override
                public void stateChanged(SparkAppHandle sparkAppHandle) {
                    SparkAppHandle.State state = sparkAppHandle.getState();
                    switch (state){
                        case SUBMITTED:
                            logger.info("提交spark作业成功");
							//yarn上spark作业的jobId
                            jobId[0] = sparkAppHandle.getAppId();
                            break;
                        case FINISHED:
                            logger.info("spark job success");
                            break;
                        case FAILED:
                        case KILLED:
                        case LOST:
                            logger.info("spark job failed");
                    }
                    if (state.isFinal())
                        latch.countDown();
                }

                @Override
                public void infoChanged(SparkAppHandle sparkAppHandle) {
                }
            });
			//等待Spark作业执行结束
            latch.await();
        }catch (Exception e){
            logger.error("error",e);
        }finally {
			//...
        }
    }
}

上述代码中,尤其注意spark作业参数是怎么配置的,不同的参数使用的是不同的方法调用,一些参数使用addSparkArg方法添加,一些使用setConf添加。特别提示,如果是传给spark应用本身的参数,需要使用addAppArgs方法传递,该方法形参为变长参数。

另外,代码中设置了spark本地driver日志路径,这样可以方便产看日志。通过SparkAppHandle的stateChanged回调函数,获得spark作业的执行状态,本例子中需要等待spark作业执行结束,因此提交作业之后,通过CountDownLatch机制来等待,在stateChanged中,当发现spark作业为结束状态,计数器减一,整个程序结束。

以上便是一种代码中调用Spark作业的一种实现方案,有问题可以一起交流。

原文地址:https://www.cnblogs.com/darange/p/15750403.html