Apache Spark: 核心概念,架構(gòu)和內(nèi)部原理

本文翻譯自
Apache Spark: core concepts, architecture and internals

本文覆蓋了Apache Spark的RDD、DAG、執(zhí)行工作流、tasks的stages的形成、shuffle的實(shí)現(xiàn)等核心概念,還描述了Spark的架構(gòu)和它的主要模塊Spark Driver。

介紹

Spark是分布式數(shù)據(jù)處理的通用框架,提供了用于大規(guī)模操作數(shù)據(jù)的功能API,內(nèi)存數(shù)據(jù)緩存和可重用計(jì)算。它將一組coarse-grained轉(zhuǎn)換作用于分區(qū)數(shù)據(jù),如果失敗則利用數(shù)據(jù)集的血統(tǒng)來(lái)重新計(jì)算tasks。值得一提的是,Spark支持大多數(shù)數(shù)據(jù)格式,與各種存儲(chǔ)系統(tǒng)集成,可以在Mesos或YARN上執(zhí)行。

功能強(qiáng)大且簡(jiǎn)潔的API與豐富的庫(kù)相結(jié)合,可以更輕松地大規(guī)模執(zhí)行數(shù)據(jù)操作。例如,以Parquet格式執(zhí)行Cassandra列族的備份和恢復(fù):

def backup(path: String, config: Config) {  
  sc.cassandraTable(config.keyspace, config.table)
    .map(_.toEvent).toDF()
    .write.parquet(path)
}

def restore(path: String, config: Config) {  
  sqlContext.read.parquet(path)
  .map(_.toEvent)
  .saveToCassandra(config.keyspace, config.table)
}

或運(yùn)行差異分析,比較不同數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù):

sqlContext.sql {  
  """
     SELECT count()
     FROM cassandra_event_rollups
     JOIN mongo_event_rollups
     ON cassandra_event_rollups.uuid = cassandra_event_rollups.uuid
     WHERE cassandra_event_rollups.value != cassandra_event_rollups.value
  """.stripMargin
}

概述

Spark圍繞彈性分布式數(shù)據(jù)集(RDD)和有向無(wú)環(huán)圖(DAG)的概念構(gòu)建,DAG表示它們之間的轉(zhuǎn)換和依賴。

Spark-Overview

Spark Application(通常也稱為Driver Program或者Application Master)在高級(jí)別由SparkContext和用戶代碼組成,用戶代碼與它交互創(chuàng)建RDD并執(zhí)行一系列轉(zhuǎn)換以實(shí)現(xiàn)最終結(jié)果。然后將這些RDD的轉(zhuǎn)換變?yōu)镈AG并提交給Scheduler以在一組工作節(jié)點(diǎn)上執(zhí)行。

RDD:Resilient Distributed Dataset
RDD可以被認(rèn)為是具有故障恢復(fù)可能性的不可變并行數(shù)據(jù)結(jié)構(gòu)。它提供了各種對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換和實(shí)體化(materializations ),以及控制元素的緩存和分區(qū)以優(yōu)化數(shù)據(jù)放置的API。RDD既可以從外部存儲(chǔ)創(chuàng)建,也可以從另一個(gè)RDD創(chuàng)建,并存儲(chǔ)有關(guān)其父節(jié)點(diǎn)的信息,以優(yōu)化執(zhí)行(通過(guò)流水線操作),并在發(fā)生故障時(shí)重新計(jì)算分區(qū)。

從開(kāi)發(fā)者的角度來(lái)看,RDD表示分布式的不可變數(shù)據(jù)(分區(qū)數(shù)據(jù)+iterator),且存在惰性機(jī)制(transformations)。RDD接口定義了五個(gè)主要的屬性:

//a list of partitions (e.g. splits in Hadoop)
def getPartitions: Array[Partition]

//a list of dependencies on other RDDs
def getDependencies: Seq[Dependency[_]]

//a function for computing each split
def compute(split: Partition, context: TaskContext): Iterator[T]

//(optional) a list of preferred locations to compute each split on
def getPreferredLocations(split: Partition): Seq[String] = Nil

//(optional) a partitioner for key-value RDDs
val partitioner: Option[Partitioner] = None  

這是一個(gè)調(diào)用sparkContext.textFile("hdfs://...")方法創(chuàng)建RDD的例子:
首先加載HDFS blocks到內(nèi)存,然后使用map()函數(shù)過(guò)濾keys,創(chuàng)建兩個(gè)RDDS:

DAG-logical-vs-partitions-view

  • HadoopRDD
    • getPartitions = HDFS blocks
    • getDependencies = None
    • compute = load block in memory
    • getPrefferedLocations = HDFS block locations
    • partitioner = None
  • MapPartitionsRDD
    • getPartitions = same as parent
    • getDependencies = parent RDD
    • compute = compute parent and apply map()
    • getPrefferedLocations = same as parent
    • partitioner = None

RDD操作

RDD的操作分為以下幾種:

  • Transformations
    • 將用戶函數(shù)應(yīng)用于分區(qū)中的每個(gè)元素(或整個(gè)分區(qū))
    • 將聚合函數(shù)應(yīng)用于整個(gè)數(shù)據(jù)集(groupBy,sortBy)
    • 引入RDD之間的依賴關(guān)系以形成DAG
    • 提供重新分區(qū)的功能(repartition,partitionBy)
  • Actions
    • 觸發(fā)job執(zhí)行
    • 用于實(shí)現(xiàn)計(jì)算結(jié)果
  • Extra: persistence
    • 顯式地將RDD存儲(chǔ)在內(nèi)存,磁盤(pán)或堆外(off-heap)(cache, persist)
    • 檢查點(diǎn)(checkpoint),截?cái)郣DD的血統(tǒng)

下面是一些代碼示例,其中匯總了來(lái)自Cassandra的lambda樣式的數(shù)據(jù),將先前匯總的數(shù)據(jù)與原始存儲(chǔ)中的數(shù)據(jù)相結(jié)合,并演示了RDD上可用的一些轉(zhuǎn)換和操作

//aggregate events after specific date for given campaign
val events =  
    sc.cassandraTable("demo", "event")
      .map(_.toEvent)                                
      .filter { e =>
        e.campaignId == campaignId && e.time.isAfter(watermark)
      }
      .keyBy(_.eventType)
      .reduceByKey(_ + _)                                        
      .cache()                                            

//aggregate campaigns by type
val campaigns =  
    sc.cassandraTable("demo", "campaign")
      .map(_.toCampaign)
      .filter { c => 
         c.id == campaignId && c.time.isBefore(watermark)
      }
      .keyBy(_.eventType)
      .reduceByKey(_ + _)
      .cache()

//joined rollups and raw events
val joinedTotals = campaigns.join(events)  
           .map { case (key, (campaign, event)) => 
             CampaignTotals(campaign, event) 
            }
           .collect()

//count totals separately
val eventTotals =  
    events.map{ case (t, e) => s"$t -> ${e.value}" }
    .collect()

val campaignTotals =  
    campaigns.map{ case (t, e) => s"$t -> ${e.value}" }
    .collect()

執(zhí)行工作流概述

Spark-Scheduling-Process

執(zhí)行工作流:將包含RDD轉(zhuǎn)換的用戶代碼變成有向無(wú)環(huán)圖,然后由DAGScheduler劃分stages。stagese組合了不需要shuffling/repartitioning數(shù)據(jù)的任務(wù)。tasks運(yùn)行在workers上,然后將結(jié)果返回客戶端。

DAG

Logical-view

這是上面示例代碼的DAG。因此,基本上任何數(shù)據(jù)處理工作流都可以定義為讀取數(shù)據(jù)源,應(yīng)用一組轉(zhuǎn)換并以不同方式實(shí)現(xiàn)結(jié)果。
轉(zhuǎn)換在RDD之間創(chuàng)建依賴關(guān)系,在這里我們可以看到它們的不同類型。

依賴關(guān)系通常分為“窄”和“寬”:


Dependency-Types
  • 具有“窄”依賴的RDD操作,如map()和filter()

Spark中有兩種類型的tasks:ShuffleMapTask將其輸入分區(qū),ResultTask將其輸出發(fā)送給driver。
兩種類型的stages:ShuffleMapStageResultStage

Shuffle

在shuffle期間,ShuffleMapTask將blocks寫(xiě)入本地文件,然后接下來(lái)的stages中的tasks通過(guò)網(wǎng)絡(luò)抓取這些blocks。

  • Shuffle Write

    • 在分區(qū)之間重新分配數(shù)據(jù)并將文件寫(xiě)入磁盤(pán)
    • 每個(gè)hash shuffle task為每個(gè)“reduce” task創(chuàng)建一個(gè)文件(total = MxR)
    • sort shuffle task創(chuàng)建一個(gè)文件,其中區(qū)域分配給reducer
    • sort shuffle使用內(nèi)存排序和溢出到磁盤(pán)以獲得最終結(jié)果
  • Shuffle Read

    • 獲取文件并應(yīng)用reduce()邏輯
    • 如果需要數(shù)據(jù)有序,則對(duì)于任何類型的shuffle,它在“reducer”側(cè)排序

在Spark Sort Shuffle是自1.2以來(lái)的默認(rèn)值,但Hash Shuffle也可用。

Sort Shuffle

Sort-Shuffle
  • 傳入記錄根據(jù)其目標(biāo)分區(qū)ID在內(nèi)存中累加和排序
  • 如果溢出,已排序的記錄將寫(xiě)入一個(gè)或多個(gè)文件,然后合并
  • 索引文件存儲(chǔ)數(shù)據(jù)文件中數(shù)據(jù)塊的偏移量
  • 在某些條件下,可以在不進(jìn)行反序列化的情況下進(jìn)行排序 SPARK-7081

Spark組件

Spark有三個(gè)主要的組件:


Spark-Cluster-Architecture
  • Spark Driver
    • 用來(lái)執(zhí)行用戶應(yīng)用程序的單獨(dú)進(jìn)程
    • 創(chuàng)建SparkContext以計(jì)劃jobs執(zhí)行并與集群管理器協(xié)商
  • Executors
    • 運(yùn)行driver安排的tasks
    • 將計(jì)算結(jié)果存儲(chǔ)在內(nèi)存,磁盤(pán)或堆外
    • 與存儲(chǔ)系統(tǒng)交互
  • Cluster Manager
    • Mesos
    • YARN
    • Spark Standalone

相關(guān)閱讀:

[1] Apache Spark: core concepts, architecture and internals
[2] SparkInternals 對(duì)Spark的深刻解讀,非常好的內(nèi)容!
[3] Spark shuffle introduction

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容