Spark從入門到放棄---RDD

凝望遠處的天鵝發表於2020-08-17

什麼是Spark?

關於Spark具體的定義,大家可以去閱讀官網或者百度關於Spark的詞條,在此不再贅述。從一個野生程式猿的角度去理解,作為大資料時代的一個準王者,Spark是一款主流的高效能分散式計算大資料框架之一,和MapReduce,Hive,Flink等其他大資料框架一起支撐了大資料處理方案的一片天空。筆者所在的公司,叢集裡面有數千臺高配機器搭載了Spark(還有Hive和Flink),用來處理千億萬億級別的大資料。黑體字內容基本就是對Spark的一個概括。

什麼是RDD?

套用一段關於RDD的常規解釋,RDD 是 Spark 提供的最重要的抽象概念,它是一種有容錯機制的特殊資料集合,可以分佈在叢集的結點上,以函式式操作集合的方式進行各種並行操作。通俗點來講,可以將 RDD 理解為一個分散式物件集合,本質上是一個只讀的分割槽記錄集合。每個 RDD 可以分成多個分割槽,每個分割槽就是一個資料集片段。一個 RDD 的不同分割槽可以儲存到叢集中的不同結點上,從而可以在叢集中的不同結點上進行平行計算。大家聽懂了啵?

Again,用一個野生程式猿的話來說,RDD就是一個資料集,裡面包含著我們要處理的千億萬億資料,類似於Java裡面的ArrayList,Python裡面的list。不同的是,Spark基於RDD提供了一大堆很好用的函式(運算元),專門來處理大資料。

Next?

作為一個人狠話不多的野生程式猿,就喜歡生猛地直接上程式碼。No BB, show you the code.

  

Wait.

思維縝密的我,還是得BB一句,工欲善其事必先利其器。想玩起來Spark,請先做好一下準備,以下以Windows舉例說明,Linux雷同。環境已經搭好的同學們,請忽略這一步,直接往下看。

#1,備好IDE

Java/Scala,請安裝好宇宙 第二的IDE,IDEAL(全名 IntelliJ IDEA),社群版即可,無需破解。Scala需要在IDEAL的Plugins裡面,安裝Scala外掛。

Python,也請安裝好世界第三的IDE,PyCharm,社群版即可,無需破解。

IDEA和PyCharm都出自於一個很厲害的軟體公司,JetBrains,這家公司以一己之力,扛起了程式設計界的好幾門主流語言的IDE。

 

#2,Spark

不管大家吃飯的傢伙是Java,Scacla,還是Python,建議大家都去裝一個Python,宇宙第二的程式語言(宇宙第一的語言是PHP),太好用了。

---如果是Python,直接在命令列執行pip install pyspark,即可安裝Spark。裝好之後,Java/Scala也可以用來操作Spark。

---如果是Java/Scala,如果大家電腦上有安裝Python,直接按照上一步操作裝好pyspark之後,Java/Scala就可以共用。

如果老鐵們不願意安裝Python,就需要自行去Spark官網下載相應版本,解壓後,把spark的bin路徑新增到Windows環境變數。(Windows下可能會報一個找不到null的錯誤,莫慌,需要自行下載Hadoop,以及對應版本的winutils,然後用winutils bin裡面的內容新覆蓋hadoop bin資料夾)

走到這一步,準備各做就緒。

 祭出程式碼

Part I --- 測試資料

先準備點測試資料。資料包含2個欄位,結構:name  score,每列用\t分割。程式碼如下:

 Python版本測試資料,name長度可以修改get_random_string引數,資料條數請根據自己電腦的配置修改loops引數。

import string
import random

file_data = 'seed'
file_save = 'result'
def get_random_string(size: int) -> str: stack = string.digits + string.ascii_letters rs = [stack[random.randrange(len(stack))] for _ in range(size)] # return ''.join(rs) def produce_seed(): loops = 100000 rs = ['{}\t{}'.format(get_random_string(4), random.randint(0, loops)) for _ in range(loops)] with open(file_data, 'w') as f: f.write('\n'.join(rs)) if __name__ == '__main__': produce_seed()

Scala/Java版本測試資料,同樣的,請老鐵們自行修改name長度和資料總行數。

import java.io.File
import java.util
import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.RandomStringUtils
import scala.util.{Random}

object Course {

  val dataFile = "seed"
  val savePath = "result"

  def main(args: Array[String]): Unit = {
    produceSeed(100000)
  }

  def produceSeed(loops:Int):Unit = {
    val file = new File(dataFile)
    val data = new util.ArrayList[String]()
    var str = ""
    (1 to loops).foreach(_ => {
      str = RandomStringUtils.randomAlphanumeric(10)
      data.add(s"$str\t${Random.nextInt(loops)}")
      if (data.size()>0 && data.size() % 10000==0) {
        FileUtils.writeLines(file,"UTF-8",data,true)
        data.clear()
      }
    })
    if (data.size()>0) {
      FileUtils.writeLines(file,"UTF-8",data,true)
    }
  }
}

附上pom,

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <groupId>groupId</groupId>
    <artifactId>scala3</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <scala.version>2.11.12</scala.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.11</artifactId>
            <version>2.4.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.11</artifactId>
            <version>2.4.5</version>
        </dependency>
    </dependencies>
</project>

 

Part II -- 生成RDD

為了給大家儘可能多展示一些運算元,下面的示例部分運算元可能有些冗餘,大家可以根據需求自行修改。

#Python版本:

需求:從源資料中找出第二列大於等於500的資料,並儲存

#encoding=utf-8
import shutil
from pyspark.sql import SparkSession

file_data = 'seed'
file_save = 'result'


def remove(filename: str):
    try:
        shutil.rmtree(filename)
    except:
        pass


def rdd_sample_1():
    '''
    選出第二列 >= 500的資料,並輸出file_save
    '''
    remove(file_save)

    def map1(row):
        parts = row.split('\t')
        return parts[0], int(parts[1])

    # 如果資料很大,可以在textFile之後,用repartition進行重分割槽
    rdd = sc.textFile(file_data)

    rdd1 = rdd  \
        .map(map1) \
        .filter(lambda r: r[1] >= 500) \
        .map(lambda r: '{}\t{}'.format(r[0], r[1])) \
        .coalesce(1)

    # 或者直接在map partition階段就進行挑選,效率要高一些。但是需要調大對應的記憶體,否則容易造成記憶體溢位
    def map2(iter):
        for row in iter:
            try:
                parts = row.split('\t')
                if int(parts[1]) >= 500:
                    yield row
            except Exception as e:
                print(e)
    #
    rdd2 = rdd.mapPartitions(map2).coalesce(1)

    # 大家2種方式選其一即可。這裡選擇第1種
    rdd1.saveAsTextFile(file_save)


if __name__ == '__main__':
    spark = SparkSession.builder.appName('pyspark').master('local[*]').getOrCreate()
    sc = spark.sparkContext
    sc.setLogLevel("ERROR")
    #
    rdd_sample_1()
    #
    spark.stop()

 

Scala版本:

需求同Python版本

import java.io.File
import java.util

import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.RandomStringUtils
import org.apache.spark.sql.SparkSession

import scala.collection.mutable.ListBuffer
import scala.util.{Random, Try}

object Course {

  val dataFile = "seed"
  val savePath = "result"

  val spark = SparkSession
    .builder()
    .appName("scala-spark")
    .master("local")
    .config("spark.sql.shuffle.partitions", "1000")
    .config("mapreduce.job.reduces",5)
    .getOrCreate()
  val sc = spark.sparkContext

  def main(args: Array[String]): Unit = {
    rddSample1()
  }

  def rddSample1(): Unit = {
    delete(savePath)
    //
    val rdd = sc.textFile(dataFile)
    // 第一種方式
    rdd.filter(_.split("\t")(1).toInt >= 500) //.saveAsTextFile(savePath)

    // 第二種方式
    rdd.map(v => {
      val parts = v.split("\t")
      if (parts(1).toInt >= 500) {
        v
      } else {""}
    })
      .filter(!_.isEmpty)//.saveAsTextFile(savePath)

    // 第三種方式,如果你機器或者記憶體夠大,可以用以下方式,效率更高
    rdd.mapPartitions(iterator => {
      val rs = ListBuffer[String]()
      var parts = Array[String]()
      iterator.foreach(v => {
        parts = v.split("\t")
        if (parts(1).toInt >= 500) rs += v
      })
      //
      rs.iterator
    })
      .saveAsTextFile(savePath)

    // 以上3種方式任選其一,最後用saveAsTextFile儲存即可
  }

  def produceSeed(loops:Int):Unit = {
    val file = new File(dataFile)
    val data = new util.ArrayList[String]()
    var str = ""
    (1 to loops).foreach(_ => {
      str = RandomStringUtils.randomAlphanumeric(10)
      data.add(s"$str\t${Random.nextInt(loops)}")
      if (data.size()>0 && data.size() % 10000==0) {
        FileUtils.writeLines(file,"UTF-8",data,true)
        data.clear()
      }
    })
    if (data.size()>0) {
      FileUtils.writeLines(file,"UTF-8",data,true)
    }
  }

  def delete(path:String):Try[Unit] = {
    Try(FileUtils.deleteDirectory(new File(path)))
  }
}

 

相關文章