SparkSQL /DataFrame /Spark RDD誰快?

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

如題所示,SparkSQL /DataFrame /Spark RDD誰快?

按照官方宣傳以及大部分人的理解,SparkSQL和DataFrame雖然基於RDD,但是由於對RDD做了優化,所以效能會優於RDD。

之前一直也是這麼理解和操作的,直到最近遇到了一個場景,打破了這種不太準確的認識。

某些場景下,RDD要比DataFrame快,效能有天壤之別。

 

需求如下

以下兩份資料求交集,結果輸出url。

資料一,json格式,地址我們用path_json表示,大小10T,每一行資料格式:{"id":"md5字串", "url":"https://www.thesaurus.com/","title":"sysnonyms and antonyms",xxx},大概20來個欄位;

資料二,csv格式,地址我們用path_csv表示,大小50G,每一行資料格式:name url,2個欄位,用\t隔開。

 

拿到需求後,迅速瞟了一眼資料,爽快答應需求方分分鐘搞定。

此時此刻,必須得祭出宇宙Top N的IDE,結合我30多年的人生閱歷和程式碼經驗,瞬間雷光電閃,驚雷驟起,一頓操作猛如虎,天空飄過以下幾行程式碼:

(老鐵們,請自行安裝python,pyspark,pycharm)

方案一


from pyspark.sql import SparkSession
def join_it():
    path_json = 'hdfs://i/love/you/'  # 資料大小10T, 5萬分割槽
    path_csv  = 'hdfs://you/love/me'  # 資料大小50G
    path_save = 'hdfs://we/are/together'

    df1 = spark.read.json(path_json).select('url')
    df2 = spark.read.option('sep', '\t').schema('name string, url string').csv(path_csv)
    df1.join(df2) \
        .select(df1.url)\
        .coalesce(10000) \
        .write \
        .mode('overwrite') \
        .option('sep', '\t') \
        .csv(path_save)


if __name__ == '__main__':
    spark = SparkSession.builder.appName('pyspark').getOrCreate()
    sc = spark.sparkContext
    sc.setLogLevel("ERROR")
    #
    join_it()
    #
    spark.stop()

spark-submit提交任務到spark叢集,引數根據自己的實際情況自行修改。

spark-submit \
    --master yarn \
    --deploy-mode client \
    --name 'i-live-you' \
    --queue 'you-love-me' \
    --driver-cores 10 \
    --driver-memory 30g \
    --num-executors 3000 \
    --executor-memory 30g \
    --executor-cores 4 \
    --archives 'hdfs://your-python-path-on-hdfs#pkg'
    --conf spark.yarn.appMasterEnv.PYSPARK_PYTHON='叢集裡面的python地址' \
    --conf spark.sql.shuffle.partitions=50000 \
    --conf spark.default.parallelism=50000 \
    --conf spark.task.maxFailures=20 \
    your-spark-script.py

如果需要在本機除錯程式碼,spark的生成需要替換成如下,然後直接執行。除錯通過後,仍然需要按照上述方式spark-submit提交任務到叢集執行,由於資料量很大,需要在叢集執行才能看出效能差異。

spark = SparkSession.builder.appName('pyspark').master('local[*]').getOrCreate()

又是一頓猛操作,提交任務後,嗡嗡叫的肚子提醒我,要去廁所燒一根香,拜拜佛,佛祖保佑無bug。

深圳的夏天,依舊不負眾望的燥熱。熱情似火的太陽,伴著她最愛的紫外線和電磁波,循著外太空固定的軌道,邁著30萬公里/秒的矯健步伐,到達這顆承載70億人的藍色星球,穿透層層藍天白雲,無私的照亮著廣袤的深圳大地。

酷暑讓人思想活躍,思緒萬千。扯得有點遠了,重來不重來了,接著寫bug。

 

作為一個謹記廁所文化的人,蹲坑5分鐘,方便你我他;蹲坑半小時,痔瘡等著你。我選擇了後者。

半小時過去了,時間隨著大A股的大跌,瞬間來到了下午的收盤時間。果不其然,又一個下跌如期而至,就在這一刻,體內的混濁之氣伴著伴隨著收跌的股市排出體外。收拾乾淨後,我帶著滿身廁所的芬芳,回到了座位上。

再次開啟電腦螢幕,spark任務還在慢悠悠的讀取json檔案,半小時才讀取300G左右,10T的json檔案按照這個速度,全部讀完的好幾天。此方案不可用。

 

方案二

果斷改成RDD,然後用intersection求交集,果然快很多,10T跟50G求交集,12000cores,5分鐘出結果。Spark任務提交同方案一,不再贅述。此方案可行

屁顛屁顛的把結果交付給需求方,大佬甚是滿意地流出了開心的淚水

import json
from pyspark.sql import SparkSession

def join_it():
    path_json = 'hdfs://i/love/you/'  # 資料大小10T, 5萬分割槽
    path_csv  = 'hdfs://you/love/me'  # 資料大小50G
    path_save = 'hdfs://we/are/together'
    #
    rdd1 = sc.textFile(path_json).map(lambda v: json.loads(v).get('url', '')).coalesce(50000)
    rdd2 = sc.textFile(path_csv).map(lambda v: v.split('\t')[1])
    rdd1.intersection(rdd2).coalesce(20000).saveAsTextFile(path_save)


if __name__ == '__main__':
    spark = SparkSession.builder.appName('pyspark').getOrCreate()
    sc = spark.sparkContext
    sc.setLogLevel("ERROR")
    #
    join_it()
    #
    spark.stop()

 

方案三

離下班實際還有30分鐘,作為一個對技術有追求的資深碼農,嘗試用SparkSQL實現該功能。祭出程式碼,以資各位看官共享,新能跟RDD不相上下。此方案也可取。

import json
from pyspark.sql import SparkSession

def join_it():
    path_json = 'hdfs://i/love/you/'  # 資料大小10T, 5萬分割槽
    path_csv  = 'hdfs://you/love/me'  # 資料大小50G
    path_save = 'hdfs://we/are/together'
    #
    sc.textFile(path_json).map(lambda v: (json.loads(v).get('url', ''),)).toDF(['url']).createOrReplaceTempView('a')
    spark.read.option('sep', '\t').schema('name string, url string').select('url').csv(path_csv).createOrReplaceTempView('b')

    sql = '''
    SELECT
        a.url
    FROM
        a
    JOIN
        b
    ON
        a.url=b.url
    '''
    spark.sql(sql).coalesce(20000).write.mode('overwrite').option('sep', '\t').csv(path_save)


if __name__ == '__main__':
    spark = SparkSession.builder.appName('pyspark').getOrCreate()
    sc = spark.sparkContext
    sc.setLogLevel("ERROR")
    #
    join_it()
    #
    spark.stop()

 

總結:

當遇到源資料是體量比較大的json或其他格式的時候,不要用spark.read的形式直接匯入到DataFrame。

那要咋弄?可以先用RDD把源資料載入進來,然後再轉化成DataFrame,後面用SparkSQL進行操作,如此可達到較好的效能效果。

 

相關文章