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

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使用的用户名:ARCH
		System.setProperty("user.name", "ARCH");

		// 设置App访问Hadoop使用的用户名:ARCH
		System.setProperty("HADOOP_USER_NAME", "ARCH");

		// 在将要传递给Executor的环境中设置Executor访问Hadoop使用的用户名:ARCH
		Map<String, String> envs = new HashMap<String, String>();
		envs.put("HADOOP_USER_NAME", "ARCH");

		// 为App的每个Executor配置最多可以使用的内存量:2GB
		System.setProperty("spark.executor.memory", "2g");

		// 为App的所有Executor配置共计最多可以使用的Core数量(最大并行任务数):20
		System.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 extends
			PairFlatMapFunction<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 extends
			Function2<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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值