Spark
A. 小文件過多
- 解決方法:
使用 SparkContext下newAPIHadoopFile完成數據輸入,指定org.apache.hadoop.mapreduce.lib.input.CombineTextInputFormat
B. 編程優化
Spark程序幾點優化
-
repartition和coalesce
這兩個方法都可以用在對數據的重新分區中,其中repartition是一個代價很大的操作,它會將所有的數據進行一次shuffle,然后重新分區。
如果你僅僅只是想減少分區數,從而達到減少碎片任務或者碎片數據的目的。使用coalesce就可以實現,該操作默認不會進行shuffle。其實repartition只是coalesce的shuffle版本。
一般我們會在filter算子過濾了大量數據后使用它。比如將 partition 數從1000減少到100。這可以減少碎片任務,降低啟動task的開銷。
note1: 如果想查看當前rdd的分區數,在java/scala中可以使用rdd.partitions.size(),在python中使用rdd.getNumPartitions()。
note2: 如果要增加分區數,只能使用repartition,或者把partition縮減為一個非常小的值,比如說“1”,也建議使用repartition。
-
mapPartitions和foreachPartitions
適當使用mapPartitions和foreachPartitions代替map和foreach可以提高程序運行速度。這類操作一次會處理一個partition中的所有數據,而不是一條數據。
mapPartition - 因為每次操作是針對partition的,那么操作中的很多對象和變量都將可以復用,比如說在方法中使用廣播變量等。
foreachPartition - 在和外部數據庫交互操作時使用,比如 redis , mysql 等。通過該方法可以避免頻繁的創建和銷毀鏈接,每個partition使用一個數據庫鏈接,對效率的提升還是非常明顯的。
note: 此類方法也存在缺陷,因為一次處理一個partition中的所有數據,在內存不足的時候,將會遇到OOM的問題。
-
reduceByKey和aggregateByKey
使用reduceByKey/aggregateByKey代替groupByKey。
reduceByKey/aggregateByKey會先在map端對本地數據按照用戶定義的規則進行一次聚合,之后再將計算的結果進行shuffle,而groupByKey則會將所以的計算放在reduce階段進行(全量數據在各個節點中進行了分發和傳輸)。所以前者的操作大量的減少shuffle的數據,減少了網絡IO,提高運行效率。
-
mapValues
針對k,v結構的rdd,mapValues直接對value進行操作,不對Key造成影響,可以減少不必要的分區操作。
-
broadcast
Spark中廣播變量有幾個常見的用法。
- 實現map-side join
在需要join操作時,將較小的那份數據轉化為普通的集合(數組)進行廣播,然后在大數據集中使用小數據進行相應的查詢操作,就可以實現map-side join的功能,避免了join操作的shuffle過程。在我之前的文章中對此用法有詳細說明和過程圖解。
- 使用較大的外部變量
如果存在較大的外部變量(外部變量可以理解為在driver中定義的變量),比如說字典數據等。在運算過程中,會將這個變量復制出多個副本,傳輸到每個task之中進行執行。如果這個變量的大小有100M或者更大,將會浪費大量的網絡IO,同時,executor也會因此被占用大量的內存,造成頻繁GC,甚至引發OOM。
因此在這種情況下,我最好提前對該變量進行廣播,它會被事先將副本分發到每個executor中,同一executor中的task則在執行時共享該變量。很大程度的減少了網絡IO開銷以及executor的內存使用。
-
復用RDD
避免創建一些用處不大的中間RDD(比如從父RDD抽取了某幾個字段形成新的RDD),這樣可以減少一些算子操作。
對多次使用的RDD進行緩存操作,減少重復計算,在下文有說明。
-
cache和persist
cache方法等價于persist(StorageLevel.MEMORY_ONLY)
不要濫用緩存操作。緩存操作非常消耗內存,緩存前考慮好是否還可以對一些無關數據進行過濾。如果你的數據在接下來的操作中只使用一次,則不要進行緩存。
如果需要復用RDD,則可以考慮使用緩存操作,將大幅度提高運行效率。緩存也分幾個級別。
- MEMORY_ONLY
如果緩存的數據量不大或是內存足夠,可以使用這種方式,該策略效率是最高的。但是如果內存不夠,之前緩存的數據則會被清出內存。在spark1.6中,則會直接提示OOM。
- MEMORY_AND_DISK
優先將數據寫入內存,如果內存不夠則寫入硬盤。較為穩妥的策略,但是如果不是很復雜的計算,可能重算的速度比從磁盤中讀取還要快。
- MEMORY_ONLY_SER
會將RDD中的數據序列化后存入內存,占用更小的內存空間,減少GC頻率,當然,取出數據時需要反序列化,同樣會消耗資源。
- MEMORY_AND_DISK_SER
不再贅述。
- DISK_ONLY
該策略類似于checkPoint方法,把所有的數據存入了硬盤,再使用的時候從中讀出。適用于數據量很大,重算代價也非常高的操作。
- 各種_2結尾的存儲策略
實際上是對緩存的數據做了一個備份,代價非常高,一般不建議使用。
-
結語
spark的優化方法還有很多,這篇文章主要從使用的角度講解了常用的優化方法,具體的使用方法可以參考博主的其他優化文章。
C. SparkSql優化配置
- Caching Data In Memory
Spark SQL can cache tables using an in-memory columnar format by calling
spark.cacheTable("tableName")
ordataFrame.cache()
. Then Spark SQL will scan only required columns and will automatically tune compression to minimize memory usage and GC pressure. You can callspark.uncacheTable("tableName")
to remove the table from memory.
Configuration of in-memory caching can be done using the setConf method on SparkSession or by running SET key=value commands using SQL.
- spark.sql.inMemoryColumnarStorage.compressed
default: true
> When set to true Spark SQL will automatically select a compression codec for each column based on statistics of the data.
- spark.sql.inMemoryColumnarStorage.batchSize
default: 10000
> Controls the size of batches for columnar caching. Larger batch sizes can improve memory utilization and compression, but risk OOMs when caching data.
- spark.sql.files.maxPartitionBytes
default: 134217728 (128 MB)
The maximum number of bytes to pack into a single partition when reading files.
- spark.sql.files.openCostInBytes
default: 4194304 (4 MB)
The estimated cost to open a file, measured by the number of bytes could be scanned in the same time. This is used when putting multiple files into a partition. It is better to over estimated, then the partitions with small files will be faster than partitions with bigger files (which is scheduled first).
- spark.sql.autoBroadcastJoinThreshold
default: 10485760 (10 MB)
Configures the maximum size in bytes for a table that will be broadcast to all worker nodes when performing a join. By setting this value to -1 broadcasting can be disabled. Note that currently statistics are only supported for Hive Metastore tables where the command ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan
has been run.
- spark.sql.shuffle.partitions
default: 200
Configures the number of partitions to use when shuffling data for joins or aggregations.
- 自動決定join和groupby時reducer的數量。 如果使用默認200配置,可能出現,sparksql的reduce task總是200個的情況,導致insert into hive的文件數量也是200個,容易造成小文件過多
- 如果partitions數目過少,容易出現shuffle內存超限。org.apache.spark.shuffle.MetadataFetchFailedException: Missing an output location for shuffle
D. Spark Sql unsupported (Spark 1.6.2)
- Tables with buckets: bucket is the hash partitioning within a Hive table partition. Spark SQL doesn’t support buckets yet.
- UNION type
- Unique join
- Column statistics collecting: Spark SQL does not piggyback scans to collect column statistics at the moment and only supports populating the sizeInBytes field of the hive metastore
- File format for CLI: For results showing back to the CLI, Spark SQL only supports TextOutputFormat.
- Hadoop archive
- Block level bitmap indexes and virtual columns (used to build indexes)
- Automatically determine the number of reducers for joins and groupbys: Currently in Spark SQL, you need to control the degree of parallelism post-shuffle using “SET spark.sql.shuffle.partitions=[num_tasks];”.
- Meta-data only query: For queries that can be answered by using only meta data, Spark SQL still launches tasks to compute the result.
- Skew data flag: Spark SQL does not follow the skew data flags in Hive.
- STREAMTABLE hint in join: Spark SQL does not follow the STREAMTABLE hint.
- Merge multiple small files for query results: if the result output contains multiple small files, Hive can optionally merge the small files into fewer large files to avoid overflowing the HDFS metadata. Spark SQL does not support that.
E.spark executor memory
spark.storage.memoryFraction
- this would allow cache use more of allocated memory
大多數情況下,cache memory可以松動,在保證運行內存和shuffle內存的情況下,滿足cache的要求。否則,可以采用 內存+硬盤的 緩存方式,來解決內存不夠分配緩存的情況。
spark.shuffle.memoryFraction=0.5
- this would allow shuffle use more of allocated memory
spark.yarn.executor.memoryOverhead=1024
- this is set in MB. Yarn kills executors when its memory usage is larger then (executor-memory + executor.memoryOverhead)
Little more info
If you get shuffle not found exception.
In case of org.apache.spark.shuffle.MetadataFetchFailedException: Missing an output location for shuffle
you should increase spark.shuffle.memoryFraction, for example to 0.5.Most common reason for Yarn killing off my executors was memory usage beyond what it expected. To avoid that you increase spark.yarn.executor.memoryOverhead, I've set it to 1024, even if my executors use only 2-3G of memory.