Spark客户端Java程序开发Step By Step

来源:互联网 发布:飞机票生成器软件 编辑:程序博客网 时间:2024/05/10 01:13

STEP 0 搭建Spark集群

开发Spark客户端Java程序前,需要搭建一个可用的Spark集群。具体搭建过程参见 http://spark.apache.org/docs/0.8.0/spark-standalone.html 。
下面给出一个示例Spark集群的环境信息,后面的开发步骤都针对该示例Spark集群。
Java版本:Sun JDK 1.6.0
Scala版本: Scala 2.9.3
Spark:版本为 Spark 0.8.0 ,1个Master结点,4个Worker结点,每个Worker结点可用24 Cores和32GB Memory
另外,开发Spark程序通常需要从外部存储系统输入待处理的数据,而HDFS往往是首选。因此,这里再给出示例Spark集群使用的HDFS环境信息。
Hadoop版本为 CDH 4.2.0,1个NameNode(运行于Spark Master结点),4个DataNode(分别运行于4个Spark Worker结点)。

STEP 1 创建Maven Project

创建Maven Project可以使用Maven工具,也可以手工创建,创建的结果是生成一个初始的Project目录结构,其中包含若干特定用途的目录和文件,这一步重点关注Project根目录下的pom.xml文件。

/                                      Project根目录
----src/
   ----main/
       ----java/                 Java代码存储位置
       ----resources/      资源文件存储位置
   ----test/
       ----java/                 测试用途的Java代码存储位置
       ----resources/      测试用途的资源文件存储位置
----pom.xml                  Project配置文件

在pom.xml文件中可以设定Project的基本信息,指定代码库和对第三方代码的依赖等。在本例中,将对Spark代码的依赖和对Hadoop代码的依赖(访问HDFS需要)添加进pom.xml文件,再配置必要的代码库。下面为示例配置。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><!--配置Project的基本信息--><groupId>audaque.app</groupId><artifactId>SparkApp</artifactId><version>1.0</version><!--配置代码库:从akka.releases.repo库可以获取对Spark代码的依赖,从cdh.releases.repo库可以获取对Hadoop代码的依赖,从central库可以获取对其他第三方代码的依赖--><repositories><repository><id>central</id><name>Central Repository</name><url>http://repo.maven.apache.org/maven2</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>akka.releases.repo</id><name>Akka repository</name><url>http://repo.akka.io/releases</url></repository><repository><id>cdh.releases.repo</id><url>https://repository.cloudera.com/content/groups/cdh-releases-rcs</url><name>CDH Releases Repository</name><snapshots><enabled>false</enabled></snapshots></repository></repositories><!--配置对第三方代码的依赖:对Spark的依赖和对Hadoop的依赖--><dependencies><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_2.9.3</artifactId><version>0.8.0-incubating</version></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>2.0.0-mr1-cdh4.2.0</version></dependency></dependencies><!--配置Java源码基于Java 1.6编译器--><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.6</source><target>1.6</target></configuration></plugin></plugins></build></project>

STEP 2 编写Java源程序

编写Java源程序访问Spark集群,首先是获取表示Spark上下文的JavaSparkContext对象,然后利用JavaSparkContext对象生成初始RDD,取得相应的JavaRDD对象,之后就可以调用JavaRDD和JavaPairRDD的各个方法在集群上对RDD实施各种Transformation和Action操作了。作为示例,以下给出一个完整的词频统计程序,其中需要重点关注的是main方法中各条语句的作用。
import java.io.File;import java.io.IOException;import java.util.HashMap;import java.util.LinkedList;import java.util.List;import java.util.Map;import org.apache.spark.api.java.JavaPairRDD;import org.apache.spark.api.java.JavaRDD;import org.apache.spark.api.java.JavaSparkContext;import org.apache.spark.api.java.function.Function2;import org.apache.spark.api.java.function.PairFlatMapFunction;import scala.Tuple2;public class App {static final String SPARK_MASTER_ADDRESS = "spark://hadoop01:7077";static final String SPARK_HOME = "/home/ARCH/spark";static final String APP_LIB_PATH = "lib";public static void main(String[] args) throws Exception {/************************ 以下代码片段可被所有App共用 ****************************/// 设置App访问Spark使用的用户名:ARCHSystem.setProperty("user.name", "ARCH");// 设置App访问Hadoop使用的用户名:ARCHSystem.setProperty("HADOOP_USER_NAME", "ARCH");// 在将要传递给Executor的环境中设置Executor访问Hadoop使用的用户名:ARCHMap<String, String> envs = new HashMap<String, String>();envs.put("HADOOP_USER_NAME", "ARCH");// 为App的每个Executor配置最多可以使用的内存量:2GBSystem.setProperty("spark.executor.memory", "2g");// 为App的所有Executor配置共计最多可以使用的Core数量(最大并行任务数):20System.setProperty("spark.cores.max", "20");// 获取要分发到集群各结点的Jar文件// 此例策略:若指定路径为文件,则返回该文件;若指定路径为目录,则列举目录下所有文件String[] jars = getApplicationLibrary();// 获取Spark上下文对象——访问Spark的起点。构造方法各参数的意义分别为:// 1 Spark Master结点的地址;2 App的名称;// 3 Spark各Worker结点的Spark部署目录,各结点相同;4 待分发到集群各结点的Jar文件;// 5 待传递给Executor环境(仅Map中的部分Key有效)JavaSparkContext context = new JavaSparkContext(SPARK_MASTER_ADDRESS,"Spark App 0", SPARK_HOME, jars, envs);/************************ 以上代码片段可被所有App共用 ****************************/// Spark上的词频统计countWords(context);}private static String[] getApplicationLibrary()throws IOException {List<String> list = new LinkedList<String>();File lib = new File(APP_LIB_PATH);if (lib.exists()) {if (lib.isFile() && lib.getName().endsWith(".jar")) {list.add(lib.getCanonicalPath());} else {for (File file : lib.listFiles()) {if (file.isFile()&& file.getName().endsWith(".jar"))list.add(file.getCanonicalPath());}}}String[] ret = new String[list.size()];int i = 0;for (String s : list)ret[i++] = s;return ret;}private static void countWords(JavaSparkContext context)throws Exception {String input  =   "hdfs://hadoop01:8020/user/ARCH/a.txt";JavaRDD<String> data =   context.textFile(input).cache();JavaPairRDD<String, Integer> pairs;pairs = data.flatMap(new SplitFunction());pairs = pairs.reduceByKey(new ReduceFunction());String output =  "hdfs://hadoop01:8020/user/ARCH/output";pairs.saveAsTextFile(output);}private static class SplitFunction extendsPairFlatMapFunction<String, String, Integer> {private static final long serialVersionUID = 41959375063L;public Iterable<Tuple2<String, Integer>> call(String line)throws Exception {List<Tuple2<String, Integer>> list;list = new LinkedList<Tuple2<String, Integer>>();for (String word : line.split(" "))list.add(new Tuple2<String, Integer>(word, 1));return list;}}private static class ReduceFunction extendsFunction2<Integer, Integer, Integer> {private static final long serialVersionUID = 5446148657508L;public Integer call(Integer a, Integer b) throws Exception {return a + b;}}}

STEP 3 运行

将STEP 2的示例源程序文件加入STEP 1创建的Maven Project(Java源码目录为src/main/java),然后使用Maven工具组建Project(在Project根目录下执行Maven命令 mvn package),如果是第一次组建,Maven需要从配置的代码库下载程序对第三方的依赖,过程的持续时间可能较长。组建成功后,在Project根目录下的target目录下会生成程序的Jar文件,对于本例Jar文件的名称是SparkApp-1.0.jar。
在HDFS上为示例程序准备好待统计词频的输入文件(hdfs://hadoop01:8020/user/ARCH/a.txt),然后在Project根目录下执行以下Maven命令尝试运行示例程序。
mvn exec:java -Dexec.mainClass=App
以上命令实际发生的过程是将示例程序的Jar文件及示例程序对第三方的依赖添加到类路径,然后运行指定的Main类,即App。这与以Standalone方式运行普通Java程序的方式无异。结果,程序运行了,但是马上又出错退出了。
回过头再看看示例程序的源码,在countWords方法中调用JavaRDD的flatMap方法和JavaPairRDD的reduceByKey方法时使用了两个自定义类型SplitFunction和ReduceFunction的实例作为调用参数,这两个参数将以序列化的方式传递到在Worker结点运行的各个Executor,但是现在Executor在反序列化这两个参数时加载不到相应的自定义类型,于是出错了。
对此,Spark允许客户端程序指定要分发到集群各结点的Jar文件,示例程序被设计为将lib目录中的所有Jar文件指定为要分发到集群各结点的Jar文件。Executor需要的自定义类型SplitFunction和ReduceFunction包含在程序的Jar文件(SparkApp-1.0.jar)中,因此将该Jar文件拷贝到lib目录,然后再次执行上面同样的命令。结果,程序运行了,运行完成后正常退出。可以到输出目录(hdfs://hadoop01:8020/user/ARCH/output)检查词频统计的结果。
0 0
原创粉丝点击