Spark 1.6升級(jí)2.x防踩坑指南
Spark 2.x自2.0.0發(fā)布到目前的2.2.0已經(jīng)有一年多的時(shí)間了,2.x宣稱有諸多的性能改進(jìn),相信不少使用Spark的同學(xué)還停留在1.6.x或者更低的版本上,沒(méi)有升級(jí)到2.x或許是由于1.6相對(duì)而言很穩(wěn)定,或許是升級(jí)后處處踩坑被迫放棄。
Spark SQL是Spark中最重要的模塊之一,基本上Spark每個(gè)版本發(fā)布SQL模塊都有不少的改動(dòng),而且官網(wǎng)還會(huì)附帶一個(gè)Migration Guide幫忙大家升級(jí)。問(wèn)題在于Migration Guide并沒(méi)有詳盡的列出所有變動(dòng),本文以SQL模塊為主,扒一扒Spark升級(jí)2.x過(guò)程中可能會(huì)踩到的坑。
計(jì)算準(zhǔn)確性
那些升級(jí)后,讓你感到心中有千萬(wàn)只草泥馬奔騰而過(guò)的問(wèn)題
- SELECT '0.1' = 0返回的是true!Spark 2.2中,0.1會(huì)被轉(zhuǎn)換為int,如果你的數(shù)據(jù)類型全部是文本類型,做數(shù)值計(jì)算時(shí),結(jié)果極有可能不正確。之前的版本中0.1會(huì)被轉(zhuǎn)換為double類型絕大多數(shù)場(chǎng)景下這樣的處理是正確的。目前為止,社區(qū)還沒(méi)有很好的處理這個(gè)問(wèn)題,針對(duì)這個(gè)問(wèn)題,我給社區(qū)提交過(guò)一個(gè)PR,想要自己解決這個(gè)問(wèn)題的同學(xué),可以手動(dòng)合并下:https://github.com/apache/spark/pull/18986
- 過(guò)于復(fù)雜的SQL語(yǔ)句執(zhí)行可能會(huì)出現(xiàn)64KB字節(jié)碼編譯限制的問(wèn)題,這算是個(gè)老問(wèn)題了,Spark自從上了Tungsten基本上一直存在這個(gè)問(wèn)題,也算是受到了JVM的限制,遇到此類問(wèn)題,建議大家找找PR:https://github.com/apache/spark/search?utf8=%E2%9C%93&q=64KB&type=Issues
- 數(shù)據(jù)計(jì)算精度有問(wèn)題,SELECT 1 > 0.0001會(huì)報(bào)錯(cuò),這個(gè)問(wèn)題已在2.1.2及2.2.0中修復(fù):https://issues.apache.org/jira/browse/SPARK-20211
- 2.1.0版本中INNER JOIN涉及到常量計(jì)算結(jié)果不正確,后續(xù)版本已修復(fù):https://issues.apache.org/jira/browse/SPARK-19766
- 2.1.0中,執(zhí)行GROUPING SET(col),如果col列數(shù)據(jù)為null,會(huì)報(bào)空指針異常,后續(xù)版本已修復(fù):https://issues.apache.org/jira/browse/SPARK-19509
- 2.1.0中,嵌套的CASE WHEN語(yǔ)句執(zhí)行有可能出錯(cuò),后續(xù)版本已修復(fù):https://issues.apache.org/jira/browse/SPARK-19472
行為變化
那些不算太致命,改改代碼或配置就可以兼容的問(wèn)題。
- Spark 2.2的UDAF實(shí)現(xiàn)有所變動(dòng),如果你的Hive UDAF沒(méi)有嚴(yán)格按照標(biāo)準(zhǔn)實(shí)現(xiàn),有可能會(huì)計(jì)算報(bào)錯(cuò)或數(shù)據(jù)不正確,建議將邏輯遷移到Spark AF,同時(shí)也能獲得更好的性能
- Spark 2.1開始全表讀取分區(qū)表采用FilePartition的方式,單個(gè)Partition內(nèi)可以讀取多個(gè)文件,如果對(duì)文件做了壓縮,這種方式有可能導(dǎo)致查詢性能變差,可以適當(dāng)降低spark.sql.files.maxPartitionBytes的值,默認(rèn)是128MB(對(duì)于大部分的Parquet壓縮表來(lái)說(shuō),這個(gè)默認(rèn)設(shè)置其實(shí)會(huì)導(dǎo)致性能問(wèn)題)
- Spark 2.x限制了Hive表中spark.sql.*相關(guān)屬性的操作,明明存在的屬性,使用SHOW TBLPROPERTIES tb("spark.sql.sources.schema.numParts")無(wú)法獲取到,同理也無(wú)法執(zhí)行ALTER TABLE tb SET TBLPROPERTIES ('spark.sql.test' = 'test')進(jìn)行修改
- 無(wú)法修改外部表的屬性ALTER TABLE tb SET TBLPROPERTIES ('test' = 'test')這里假設(shè)tb是EXTERNAL類型的表
- DROP VIEW IF EXISTS tb,如果這里的tb是個(gè)TABLE而非VIEW,執(zhí)行會(huì)報(bào)錯(cuò)AnalysisException: Cannot drop a table with DROP VIEW,在2.x以下不會(huì)報(bào)錯(cuò),由于我們指定了IF EXISTS關(guān)鍵字,這里的報(bào)錯(cuò)顯然不合理,需要做異常處理。
- 如果你訪問(wèn)的表不存在,異常信息在Spark2.x里由之前的Table not found變成了Table or view not found,如果你的代碼里依賴這個(gè)異常信息,就需要注意調(diào)整了。
- EXPLAIN語(yǔ)句的返回格式變掉了,在1.6里是多行文本,2.x中是一行,而且內(nèi)容格式也有稍微的變化,相比Spark1.6,少了Tungsten關(guān)鍵字;EXPLAIN中顯示的HDFS路徑過(guò)長(zhǎng)的話,在Spark 2.x中會(huì)被省略為...
2.x中默認(rèn)不支持笛卡爾積操作,需要通過(guò)參數(shù)spark.sql.crossJoin.enabled開啟
OLAP分析中常用的GROUPING__ID函數(shù)在2.x變成了GROUPING_ID()
如果你有一個(gè)基于Hive的UDF名為abc,有3個(gè)參數(shù),然后又基于Spark的UDF實(shí)現(xiàn)了一個(gè)2個(gè)參數(shù)的abc,在2.x中,2個(gè)參數(shù)的abc會(huì)覆蓋掉Hive中3個(gè)參數(shù)的abc函數(shù),1.6則不會(huì)有這個(gè)問(wèn)題
執(zhí)行類似SELECT 1 FROM tb GROUP BY 1的語(yǔ)句會(huì)報(bào)錯(cuò),需要單獨(dú)設(shè)置spark.sql.groupByOrdinal false類似的參數(shù)還有spark.sql.orderByOrdinal false
CREATE DATABASE默認(rèn)路徑發(fā)生了變化,不在從hive-site.xml讀取hive.metastore.warehouse.dir,需要通過(guò)Spark的spark.sql.warehouse.dir配置指定數(shù)據(jù)庫(kù)的默認(rèn)存儲(chǔ)路徑。
CAST一個(gè)不存在的日期返回null,如:year('2015-03-40'),在1.6中返回2015
- Spark 2.x不允許在VIEW中使用臨時(shí)函數(shù)(temp function)https://issues.apache.org/jira/browse/SPARK-18209
- Spark 2.1以后,窗口函數(shù)ROW_NUMBER()必須要在OVER內(nèi)添加ORDER BY,以前的ROW_NUMBER() OVER()執(zhí)行會(huì)報(bào)錯(cuò)
- Spark 2.1以后,SIZE(null)返回-1,之前的版本返回null
Parquet文件的默認(rèn)壓縮算法由gzip變成了snappy,據(jù)官方說(shuō)法是snappy有更好的查詢性能,大家需要自己驗(yàn)證性能的變化
DESC FORMATTED tb返回的內(nèi)容有所變化,1.6的格式和Hive比較貼近,2.x中分兩列顯示
異常信息的變化,未定義的函數(shù),Spark 2.x: org.apache.spark.sql.AnalysisException: Undefined function: 'xxx’., Spark 1.6: AnalysisException: undefined function xxx,參數(shù)格式錯(cuò)誤:Spark 2.x:Invalid number of arguments, Spark 1.6: No handler for Hive udf class org.apache.hadoop.hive.ql.udf.generic.GenericUDAFXXX because: Exactly one argument is expected..
Spark Standalone的WebUI中已經(jīng)沒(méi)有這個(gè)API了:/api/v1/applications:https://issues.apache.org/jira/browse/SPARK-12299,https://issues.apache.org/jira/browse/SPARK-18683
版本回退
那些升級(jí)到2.x后,發(fā)現(xiàn)有問(wèn)題回退后,讓你欲哭無(wú)淚的問(wèn)題。
Spark 2.0開始,SQL創(chuàng)建的分區(qū)表兼容Hive了,Spark會(huì)將分區(qū)信息保存到HiveMetastore中,也就是我們可以通過(guò)SHOW PARTITIONS查詢分區(qū),Hive也能正常查詢這些分區(qū)表了。如果將Spark切換到低版本,在更新分區(qū)表,HiveMetastore中的分區(qū)信息并不會(huì)更新,需要執(zhí)行MSCK REPAIR TABLE進(jìn)行修復(fù),否則再次升級(jí)會(huì)出現(xiàn)缺數(shù)據(jù)的現(xiàn)象。
Spark 2.0 ~ 2.1創(chuàng)建的VIEW并不會(huì)把創(chuàng)建VIEW的原始SQL更新到HiveMetastore,而是解析后的SQL,如果這個(gè)SQL包含復(fù)雜的子查詢,那么切換到1.6后,就有可能無(wú)法使用這個(gè)VIEW表了(1.6對(duì)SQL的支持不如2.x)
其他
從2.2.0開始,Spark不在支持Hadoop 2.5及更早的版本,同時(shí)也不支持Java 7 了,所以,如果你用的版本比較老,還是盡快升級(jí)的比較好。
2.x中對(duì)于ThriftServer或JobServer這樣的長(zhǎng)時(shí)間運(yùn)行的服務(wù),穩(wěn)定性不如1.6,如果您的計(jì)算業(yè)務(wù)復(fù)雜、SQL計(jì)算任務(wù)繁多、頻繁的更新數(shù)據(jù)、處理數(shù)據(jù)量較大,穩(wěn)定性的問(wèn)題更加凸顯。穩(wěn)定性問(wèn)題主要集中在內(nèi)存方面,Executor經(jīng)常出現(xiàn)堆外內(nèi)存嚴(yán)重超出、OOM導(dǎo)致進(jìn)程異常退出等問(wèn)題。Executor進(jìn)程OOM異常退出后相關(guān)的block-mgr目錄(也就是SPARK_LOCAL_DIRS)并不會(huì)被清理,這就導(dǎo)致Spark Application長(zhǎng)時(shí)間運(yùn)行很容易出現(xiàn)磁盤被寫滿的情況。
總結(jié)
Spark 2.x中為了性能,SQL模塊的改動(dòng)相當(dāng)大,這也導(dǎo)致Bug變多,穩(wěn)定性變差。當(dāng)然,隨著Spark的不斷改進(jìn)迭代,這些問(wèn)題也在逐步緩解。
對(duì)于一個(gè)計(jì)算服務(wù),相比性能,數(shù)據(jù)計(jì)算的正確性及穩(wěn)定性更加重要。建議尚未升級(jí)到2.x的同學(xué),最好使用最新的Spark版本做升級(jí);升級(jí)前,務(wù)必結(jié)合自己的業(yè)務(wù)場(chǎng)景做好充分的測(cè)試,避免踩坑。