Windows平臺下單機Spark環境搭建

爲了在有限的資源上學習大數據處理與分析技術,借鑑Linux以及部分網上的教程,在Windows10平臺搭建Spark環境。本文將簡單記錄搭建流程以及其中遇到的坑。

Spark的部署模式主要有四種:

  • Local模式(單機模式)
  • Standalone模式(使用Spark自帶的簡單集羣管理器)
  • YARN模式(使用YARN作爲集羣管理器)
  • Mesos模式(使用Mesos作爲集羣管理器)

安裝Java

  • 到 Oracle Java 官網下載JDK並安裝,安裝路徑建議直接選擇C:\Java,不要安裝在Program Files中(路徑有空格會導致後面配置Hadoop比較麻煩)
    • 添加環境變量JAVA_HOME,值爲安裝路徑,如C:\Java\jdk1.8.0_121
    • 在環境變量Path中增加值:%JAVA_HOME%\bin
    • 打開命令行測試是否安裝成功,輸入java -version,應該出現如下信息
      命令行查看Java版本信息
      命令行查看Java版本信息

安裝Spark

  • 到 Apache Spark 官網下載預編譯的壓縮文件,解壓到某個路徑中不含空格的文件夾下,也就成爲Spark的安裝路徑,如D:\spark
    下載Spark

     

    • 添加環境變量SPARK_HOME,值爲安裝路徑,如D:\spark
    • 在環境變量Path中增加值:%SPARK_HOME%\bin%SPARK_HOME%\sbin
    • 如果下載的Spark版本>=2.3,建議進一步添加環境變量SPARK_LOCAL_HOSTNAME,值爲localhost
    • 進入Spark的配置目錄conf,複製一個log4j.properties.template文件並命名爲log4j.properties,打開log4j.properties文件,進行如下修改

       
      # log4j.rootCategory=INFO, console
      log4j.rootCategory=WARN, console
    • 同樣在Spark的配置目錄conf,複製一個spark-env.sh.template文件並命名爲spark-env.sh,打開並增加以下一行代碼

       
      SPARK_LOCAL_IP = 127.0.0.1

安裝Hadoop

  • 到 Apache Hadoop 官網下載預編譯的壓縮包(這裏爲了更好對應,選擇下載2.7版本),解壓到某個路徑中不含空格的文件夾下,也就稱爲Hadoop的安裝路徑,如D:\hadoop
    下載Hadoop(binary版本)

     

    • 添加環境變量HADOOP_HOME,值爲安裝路徑,如D:\hadoop
    • 在環境變量Path中增加值:%HADOOP_HOME%\bin%HADOOP_HOME%\sbin
    • 進入Hadoop的配置目錄etc\hadoop,打開文件hadoop-env.cmd,修改Java的安裝路徑,如果Java安裝在Program Files可以通過設置爲PROGRA~1解決空格報錯的問題

       
      set JAVA_HOME=C:\PROGRA~1\Java\jdk1.8.0_121
    • 下載對應版本的 winutils,把下載到的bin文件夾覆蓋到Hadoop安裝目錄的bin文件夾,確保其中含有winutils.exe文件

    • 新建tmp\hive文件夾,如C:\tmp\hive,命令行導航到Hadoop的bin目錄,執行以下授權操作

       
      winutils.exe chmod -R 777 C:\tmp\hive
    • 最後在命令行輸入hadoop version測試是否安裝成功
      驗證Hadoop安裝成功

       

驗證Spark安裝成功

  • 打開命令行,運行spark-shell,應該輸入如下內容
    驗證Spark安裝成功
    驗證Spark安裝成功
  • 此時進入localhost:4040可以看到Spark的Web界面

使用Spark開發第一個程序

Python

安裝PySpark

  • 把Spark安裝路徑下的python\pyspark文件夾複製到系統Python的包文件夾下,例如在Anaconda環境中,複製到D:\Anaconda3\Lib\site-packages目錄下
  • 安裝Python包py4j,在命令行運行pip install py4j
  • 驗證PySpark配置成功,在命令行輸入pyspark,應該輸出如下內容
    驗證PySpark環境可用
    驗證PySpark環境可用

在PyCharm中使用PySpark

下面以一個經典的詞頻統計(Word Count)程序爲例,學習PySpark的使用,詞頻統計是一個很經典的分佈式程序,這裏用到中文分詞庫jieba,去除停用詞再進行計數

 
# -*- coding: utf-8 -*-
# 從pyspark.context模塊導入SparkContext
from pyspark.context import SparkContext
import jieba

# 實例化一個SparkContext,用於連接Spark集羣
# 第一個參數“local”表示以本地模式加載集羣
# 第二個參數“WordCount”表示appName,不能有空格
spark = SparkContext("local", "WordCount")

# 讀取數據,創建彈性式分佈數據集(RDD)
data = spark.textFile(r"path/to/news.txt")

# 讀取中文停用詞
with open(r'path/to/stopwords-zh.txt', 'r', encoding='utf-8') as f:
s = f.readline()
stop = [i.replace('\n','') for i in s]

# 分詞並統計詞頻
data = data.flatMap(lambda line: jieba.cut(line,cut_all=False)).\
filter(lambda w: w not in stop).\
map(lambda w: (w,1)).\
reduceByKey(lambda w0, w1: w0 + w1).\
sortBy(lambda x: x[1], ascending=False)

# 輸出前100個高頻詞彙
print(data.take(100))
  • 設置程序運行配置,打開Run->Edit Configuration,按照如下圖所示內容新建一個配置,其中環境變量必須加入SPARK_HOMEHADOOP_HOME以及SPARK_LOCAL_HOSTNAME
    設置PyCharm運行配置

     

  • 運行程序,最後輸出前100個高頻詞語
    WordCount程序輸出

     

程序提交到Spark運行

上述詞頻統計代碼也可以直接提交到Spark運行,方法如下:

  • 打開命令行,導航到Spark的安裝目錄,執行提交任務命令:

     
    cd D:/spark
    ./bin/spark-submit /path/to/wordcount.py
  • 最後輸出類似的執行結果
    提交Spark任務,並輸出運行結果

     

Scala & Java

Java

  • 在 IntelliJ IDEA 新建一個Maven工程
  • 在項目的Maven配置文件pom.xml中加入Spark-core依賴,根據安裝的Spark版本到 Maven Repository 倉庫找到對應的Maven依賴文本,如:

     
    <!-- https://mvnrepository.com/artifact/org.apache.spark/spark-core -->
    <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-core_2.11</artifactId>
    <version>2.2.3</version>
    </dependency>
  • 打開工程目錄下的主程序文件,通常爲./src/main/java/App.java,編寫詞頻統計代碼

  • 下面將以兩種形式進行編寫,Java Lambda的代碼風格接近Python,易於閱讀;而Java原生模式則稍顯複雜

Java Lambda

 
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;

import java.util.Arrays;

/**
* Word count!
*
*/
public class App
{
public static void main( String[] args )
{
// 創建Spark實例
SparkConf conf = new SparkConf().setAppName("WordCount").setMaster("local");
JavaSparkContext jsc = new JavaSparkContext(conf);

// 讀取數據,這裏是一個關於Spark介紹的文本
String filename = "path/to/spark.txt";
JavaRDD<String> data = jsc.textFile(filename);

// 切割壓平
JavaRDD<String> dataMap = data.flatMap(t -> Arrays.asList(t.split(" ")).iterator());

// 組合成元組
JavaPairRDD<String, Integer> dataPair = dataMap.mapToPair(t -> new Tuple2<>(t,1));

// 分組聚合
JavaPairRDD<String, Integer> dataAgg = dataPair.reduceByKey((w1,w2) -> w1+w2);

// 交換key,再排序
JavaPairRDD<Integer, String> dataSwap = dataAgg.mapToPair(tp -> tp.swap());
JavaPairRDD<Integer, String> dataSort = dataSwap.sortByKey(false);
JavaPairRDD<String, Integer> result = dataSort.mapToPair(tp -> tp.swap());

// 保存結果,saveAsTextFile()方法是將RDD寫到本地,根據執行task的多少生成多少個文件
// 輸出目錄不能預先存在,否則報錯
result.saveAsTextFile("path/to/spark_count");

// 輸出第一個
List<Tuple2<String, Integer>> resList = result.collect();
for (Tuple2<String, Integer> tp: resList){
System.out.println(tp._1+"\t"+tp._2);
}

jsc.stop();
}
}
  • 最後打開輸出結果文件夾的part-00000文件,輸出各個單詞的統計數:
     
    (Spark,7)
    (and,7)
    (the,5)
    (Apache,5)
    (of,4)
    (for,3)
    ...

Java 原生模式

 
import org.apache.spark.SparkConf;
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.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import scala.Tuple2;

import java.util.Arrays;
import java.util.Iterator;

/**
* Word count!
*
*/
public class App
{
public static void main( String[] args )
{
// 創建Spark實例
SparkConf conf = new SparkConf().setAppName("WordCount").setMaster("local");
JavaSparkContext jsc = new JavaSparkContext(conf);

// 讀取數據,這裏是一個關於Spark介紹的文本
String filename = "path/to/spark.txt";
JavaRDD<String> data = jsc.textFile(filename);

// 切割壓平
JavaRDD<String> dataMap = data.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});

// 組合成元組
JavaPairRDD<String, Integer> dataPair = dataMap.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s,1);
}
});

// 分組聚合
JavaPairRDD<String, Integer> dataAgg = dataPair.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer w1, Integer w2) throws Exception {
return w1 + w2;
}
});

// 交換key,再排序
JavaPairRDD<Integer, String> dataSwap = dataAgg.mapToPair(new PairFunction<Tuple2<String, Integer>, Integer, String>() {
@Override
public Tuple2<Integer, String> call(Tuple2<String, Integer> tp) throws Exception {
return tp.swap();
}
});

JavaPairRDD<Integer, String> dataSort = dataSwap.sortByKey(false);

JavaPairRDD<String, Integer> result = dataSort.mapToPair(new PairFunction<Tuple2<Integer, String>, String, Integer>() {
@Override
public Tuple2<String, Integer> call(Tuple2<Integer, String> tp) throws Exception {
return tp.swap();
}
});

// 保存結果,saveAsTextFile()方法是將RDD寫到本地,根據執行task的多少生成多少個文件
// 輸出目錄不能預先存在,否則報錯
result.saveAsTextFile("path/to/spark_count");

// 輸出第一個
List<Tuple2<String, Integer>> resList = result.collect();
for (Tuple2<String, Integer> tp: resList){
System.out.println(tp._1+"\t"+tp._2);
}

jsc.stop();
}
}
  • 最後結果與上面情況類似
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章