Airflow 等待执行日期范围内的一组外部DAG
我有一个DAG,每5分钟运行一次(我们称之为Airflow 等待执行日期范围内的一组外部DAG,airflow,apache-airflow,Airflow,Apache Airflow,我有一个DAG,每5分钟运行一次(我们称之为5_-min\u-DAG),另一个DAG每天运行,使用一些5_-min\u-DAG一天运行的输出(我们称之为daily\u-DAG) 我如何确保day_dag等待完成当天的5_min_dag运行 一些简化的代码来说明这个问题: 这可能吗 可能使用ExternalTaskSensor和/或SubDagOperator 这并不漂亮,但我通过生成一长串ExternalTaskSensor来完成这项工作: for tdelta in range(0, 2
5_-min\u-DAG
),另一个DAG每天运行,使用一些5_-min\u-DAG
一天运行的输出(我们称之为daily\u-DAG
)
我如何确保day_dag
等待完成当天的5_min_dag
运行
一些简化的代码来说明这个问题:
这可能吗
可能使用ExternalTaskSensor和/或SubDagOperator 这并不漂亮,但我通过生成一长串ExternalTaskSensor来完成这项工作:
for tdelta in range(0, 288): # 288 5-minute dags per day (24*60/5=288)
ExternalTaskSensor(
task_id='wait_for_5_min_dag_'+str(tdelta),
external_dag_id='5_min_dag',
external_task_id='5_min_task',
execution_delta=timedelta(minutes=tdelta),
dag=daily_dag
)
但这看起来不太好,取决于5分钟dag内的任务,而不是dag本身。这意味着如果5_min_dag
有分支,它将不起作用
要使用分支,也可以允许skipped
状态
for tdelta in range(0, 288):
ExternalTaskSensor(
task_id='wait_for_5_min_dag_'+str(tdelta),
external_dag_id='5_min_dag',
external_task_id='5_min_task',
allowed_states=['success','skipped'], # skipped means this branch not followed
execution_delta=timedelta(minutes=tdelta),
dag=daily_dag
)
return daily_dag
但是,对于5\u min\u dag
的每个分支,您需要一组这样的传感器
为了防止用这些传感器填充GUI,可以将它们包装在子DAG中
这种方法的另一个问题是,它一次生成大量“等待”任务,这很容易导致死锁状态
希望有人能提供更好的解决方案。我发现解决此问题的更好方法是使用
SQLSensor
查询气流元数据数据库
首先,需要建立一个数据库的数据库。我使用web UI设置了名为mysql\u default
的连接
以下操作员被设置为每日任务中的第一个任务。只有在每日日期的执行日期的所有5分钟
具有状态==success
时,该操作才会成功
wait_for_5_min_dags = SqlSensor(
task_id='wait_for_all_5_min_dags',
conn_id='mysql_default',
sql="""
SELECT GREATEST(COUNT(state)-287, 0)
FROM dag_run WHERE
(execution_date BETWEEN
'{{execution_date.replace(hour=0,minute=0)}}' AND '{{execution_date.replace(hour=23,minute=59)}}')
AND dag_id='5_min_dag'
AND state='success';
"""
)
SQLSensor
仅在查询返回非空或非零结果时成功。因此,这个查询被写为返回0,直到我们准确地找到当天成功运行的dag(24*60/5=288
)。如果我们在等待一个每小时运行的dag,我们会减去23
,因为我们每天都在等待24
dag。我也面临着类似的问题。当所有每小时的实例都完成时,我尝试使用TriggerDagRunOperator。
对于所有每小时实例,将更新气流变量。当运行次数为23次时,它将触发另一个dag(每日dag,尽管其他dag现在将具有schedule_interval=None)。触发新dag时,需要再次将变量值设置为0。(如果触发的dag失败,问题就在这里?)
您可以采取的一种方法是在5分钟DAG中结合使用ShortCircuit运算符和TiggerDagRun运算符
A(您的实际任务)->B(短路运算符)->C(触发器运算符)
在ShortCircuitOperator中,您可以使用python函数检查执行日期,5分钟dag的一天的最后执行日期为2018-08-10 23:55:55。您可以使用python日期时间库来检查它是否在这个时间,然后才通过ShortCircuitOperator
现在,要检查一天中之前的所有5分钟运行是否在ShorticuitOperator的同一python函数中成功,您可以使用气流的DagRun类编写和运行,而不是设置到气流数据库的单独连接。所以它看起来像
DagRun.find(dag_id="5_min_task",
state=State.SUCCESS,
execution_date=[all 5 min execution dates])
您可以运行一个循环来检查上述函数返回的计数,并在不匹配时休眠,并继续检查,直到所需的dag数运行匹配。使用变量的方法很有趣,但正如您所说,它对故障和其他边缘情况很敏感。多时间尺度聚合是相当常见的,我很惊讶我还没有找到一个标准的方法来解决这个问题。
wait_for_5_min_dags = SqlSensor(
task_id='wait_for_all_5_min_dags',
conn_id='mysql_default',
sql="""
SELECT GREATEST(COUNT(state)-287, 0)
FROM dag_run WHERE
(execution_date BETWEEN
'{{execution_date.replace(hour=0,minute=0)}}' AND '{{execution_date.replace(hour=23,minute=59)}}')
AND dag_id='5_min_dag'
AND state='success';
"""
)
dag = DAG('airflow_examples.hourly_to_daily_dependency_controller', description='DAG which prints Hello World',
schedule_interval='0 * * * *',
start_date=datetime(2018, 4, 15), catchup=True, default_args=default_args)
echo_task = BashOperator(
task_id='echo_hello_world',
bash_command='echo "in Hourly Dag: Hello"',
dag=dag)
def update_variable(ts,**kwargs):
hourly_dag_runs = Variable.get('hourly_dag_runs')
hourly_dag_runs = int(hourly_dag_runs) + 1
Variable.set('hourly_dag_runs', hourly_dag_runs)
update_hourly_dag_runs = PythonOperator(
task_id='update_hourly_dag_runs',
provide_context=True,
python_callable=update_variable,
dag=dag)
def conditionally_trigger(context, dag_run_obj):
hourly_dag_runs = Variable.get('hourly_dag_runs')
if (hourly_dag_runs == "23"):
dag_run_obj.payload = {'message': hourly_dag_runs}
Variable.set('hourly_dag_runs', 0)
return dag_run_obj
trigger = TriggerDagRunOperator(task_id='test_trigger_dagrun',
trigger_dag_id="airflow_examples.trigger_dailydag_run",
python_callable=conditionally_trigger,
dag=dag)
echo_task >> update_hourly_dag_runs >> trigger
DagRun.find(dag_id="5_min_task",
state=State.SUCCESS,
execution_date=[all 5 min execution dates])