Airflow 等待执行日期范围内的一组外部DAG

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

我有一个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, 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])