什麼是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))) } }