[英]Databricks/python - what is a best practice approach to create a robust long running job
我找不到一個很好的概述如何創建一個失敗可能性中等的工作。
我是一位經驗豐富的開發人員,但我對 databricks/spark 比較陌生。 雖然我可以通過編程解決問題,但我正在尋找最佳實踐解決方案。
我的場景是從 Web API 中讀取大量行。 運行該作業大約需要 36 小時。 在這 36 小時內,我很有可能在與 API 交互時遇到致命錯誤(超時、讀取時斷開連接、無效/意外的返回值等)。 雖然我可以越來越多地使我的工作對這些錯誤具有魯棒性,但理想情況下,我不必再次運行整個工作來恢復。 理想情況下,我只需要運行失敗的案例。
我的基本流程是這樣的:
我評估過的方法:
我在腦海中想象的解決方案是這樣的:
# Split the source table into 100 equal buckets
# Run only buckets 10,20,21 (presumably, those are the failed buckets)
# For each bucket, run the udf get_details
# If the bucket succeeds, put it's rows into aggregate_df. Otherwise, into error_df
aggregate_df, error_df = df.split_table_evenly(bucket_count=100)
.options(continue_on_task_failure=true)
.filter(bucket=[10,20,21])
.run_task_on_bucket(udf=get_details)
解決方案是使用支持流式查詢檢查點的Spark Structured Streaming 。 Spark 指南非常詳盡地描述了如何在不同場景中使用結構化流。 它沒有明確涵蓋我的用例——按行分解數據集——我將在這里進行描述。
基本方法是將流分解為 Spark 所稱的“微批量”。 對於上面的場景,要理解的重要一點是,按時間觸發批處理,而我想按行批處理。 Spark 有一種支持數據批處理的提供程序——Kafka 提供程序,它可以基於偏移量進行批處理。 由於我不想通過 Kafka 運行我的數據,所以我選擇不使用這種方法。
文件源確實有一個我們可以使用的工具:它能夠使用maxFilesPerTrigger
選項設置批處理中文件的數量限制。 我也使用latestFirst
首先處理最舊的文件,但這不是必需的。
source_dataframe = self.sparksession.readStream.option('maxFilesPerTrigger', 1) \
.option('latestFirst', True) \
.format('delta') \
.load(path)
由於這僅適用於整個文件,因此我需要在生成時限制文件的大小。 為此,我只是使用會生成合適的存儲桶大小的密鑰對數據集進行分區。
因為對於大多數用途來說,擁有許多文件並不是超級高效,所以我選擇將此數據集寫入兩次,因此除非我在檢查點,否則不要支付讀取許多小文件的成本。 但是,這完全是可選的。
dataframe = # Some query
curated_output_dataframe = dataframe
generate_job_input_dataframe = dataframe.partitionBy('somecolumn')
curated_output_dataframe.write.format(my_format).save(path=my_curated_output_path)
generate_job_data_dataframe.write.format(my_format).save(path=my_job_data_path)
然后,您可以使用流式函數讀取和寫入數據集,幾乎可以開始了。
source_dataframe = self.sparksession.readStream.option('maxFilesPerTrigger', 1).format(input_path).load(input_path)
query = dataframe.writeStream.format(output_path).start('output_path')
query.awaitTermination()
但是,在您以穩健的方式執行此操作之前,還有一些事情需要處理。
如果您希望能夠恢復您的工作,則需要設置檢查點位置。
sparkSession.readStream.option('checkpointLocation','/_checkpoints/some_unique_directory')
如果您不更改某些設置,Spark 將變得貪婪並讀取您的所有輸入文件。
有關更多詳細信息,請參閱此答案。
您應該估計您的費率並將起始目標費率設置為該費率。 我一開始就把我的設置得很低——spark 會從這個速率調整到任何它是可持續的。
sparksession.conf.set('spark.streaming.backpressure.enabled', True)
sparksession.conf.set('spark.streaming.backpressure.initialRate', target_rate_per_second)
sparksession.conf.set('spark.streaming.backpressure.rateEstimator', 'pid')
sparksession.conf.set('spark.streaming.backpressure.pid.minRate', 1)
除非您監視作業,否則 Spark 將永遠運行。 Spark 假設流作業無限期地運行,並且在數據集耗盡之前沒有明確支持運行。 您必須自己編寫代碼,不幸的是代碼很脆弱,因為在某些情況下您必須檢查作業狀態消息。
有關更多詳細信息,請參閱此答案。 答案中的代碼對我來說並不是 100% 有效 - 我在消息表達式中發現了更多案例。 這是我目前正在使用的代碼(刪除了日志記錄和注釋):
while query.isActive:
msg = query.status['message']
data_avail = query.status['isDataAvailable']
trigger_active = query.status['isTriggerActive']
if not data_avail and not trigger_active:
if 'Initializing' not in msg:
query.stop()
time.sleep(poll_interval_seconds)
其他注意事項這些不是檢查點微批次所必需的,但在其他方面很有用。
Spark 生成有用的日志——我發現在流執行中建立信任時,這兩個日志很有用:
22/05/18 23:22:32 INFO MicroBatchExecution: Streaming query made progress:
22/05/18 23:24:44 WARN ProcessingTimeExecutor: Current batch is falling behind. The trigger interval is 500 milliseconds, but spent 5593 milliseconds
可以通過以下方式啟用 PID 估計器的日志記錄
sparksession.conf.set('log4j.logger.org.apache.spark.streaming.scheduler.rate.PIDRateEstimator', 'TRACE')
Spark 最近添加了 RocksDB 對流狀態管理的支持,您需要明確啟用。 它對我來說很順利。
sparksession.conf.set('spark.sql.streaming.stateStore.providerClass', \
'org.apache.spark.sql.execution.streaming.state.RocksDBStateStoreProvider')
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.