[英]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.