DAG代表了完整的工作流,而Task代表了工作流中的具体任务,Task之间的依赖关系代表了任务之间的依赖关系。
1 Task的类型
Task的类型有3种:
- Operator:Operator是预先定义的Task模板,用Operator创建Task的过程就是编 Operator的构造器传参的过程。Airflow社区提供了大量的Operator,.方便用户实现台 种功能。当然,Airflow也支特自定义Operator。.
- Sensor:Sensor是一种特殊类型的Operator,被设计用来等待某种信号,比如等待一 段时间过去,等待一个文件生成。在得到信号后,Sensor Task完成。
- TaskFlow。TaskFlow是Airflow自2.0版本开始提供的全新功能。只要简单地在普通的 Python方法前加上@task装饰器,Airflow就会自动把这个方法转成Task。从功能上 来说,TaskFlow相当于PythonOperator。只不过,TaskFlow封装了很乡Airfow的功 能和特性,一方面降低了使用门槛,让用户可以在不了解这些功能和特性的情况下快 速上手编写DAG;另一方面使得DAG的代码更加简洁、优雅。
1.1 Operator
使用Operator创建Task很简单,调用Operator的构造器即可。Airflow社区有大量的 Operator,覆盖了各种各样的功能。一般情况下,用户需要做的仅仅是选择合适的Operator。 当然,如果社区提供的Operator不能满足要求,自定义Operator是最终的解决方案。下面 我们先介绍经典的Operator的用法,再介绍自定义Operator的编写方法。
1.1.1 BashOperator
BashOperator是用来执行Bash命今的Operator。代码清单4-I0提供了BashOperator的 个示例 代码清单4-10使用BashOperator创建Task
from airflow.operators.bash import Bashoperator
t1 = Bashoperator(task_id='print_date',bash_command='date')
代码清单4-10使用BashOperator构造了一个task_id为print_date的Task,这个Task在 运行时会执行Bash命令date来输出当前的时间。
1.1.2 PythonOperator
PythonOperator是用来执行Python代码的Operator。
from airflow.operators.python import PythonOperator
def print_context(**kwargs):
print(kwargs)
t1 = PythonOperator(task_id="print_context", python_callable=print_context)
1.1.3 EmailOperator
是用来发邮件的 Operator
from airflow.operators.email import Email0perator
t1 = Emailoperator(
task_id='send_email'
to="gomin@example.com",
subject="Some subject"
html_content="Some content'
)
1.1.4 自定义Operator
所有的 Operator 都继承 自 BaseOperator 类。BaseOperaor 是一个抽象类,它的execute方 法是抽象的,由子类负责提供具体的实现。
from airflow.models.baseoperator import BaseOperator
class HelloOperator(BaseOperator):
def __init__(self, name: str, **kwargs) -> None:
super().__init__(**kwargs)
self.name = name
def execute(self, context):
print("Hello, %s!" % self.name)
如何使用这个自动以的Operator呢?我们将代码保存到文件hello_operator.py中。下面代码就是调用方法:
from airflow import DAG
from airflow.utils.dates import days_ago
from hello_operator import HelloOperator
with DAG("how_to_use", start_date=days_ago(2)) as dag1:
hello_task = HelloOperator(task_id="hello_world", name="world")
2 Sensor
2.1 为什么需要 Sensor
Sensor 内部实现了check和sleep的逻辑、用户只需要关心两件事情:使用哪个Senser,以及配置 Sensor 的参数。
Sensor的参数主要有3个:poke_interval、timeout和mode。 poke_interval定义了两次check之间的间隔时间。timcout定义了一直check不成功情况下的运行时间上界、超时则必须退出,释放资源。mode有两种:poke mode和reschedule mode。
2.2 常用的 Sensor
Airfiow 预先实现了非常多的 Sensor, 比较常用的有如下这些。
- FileSensor: 等待文件系统上的一个文件/ 目录就绪。
- S3KeySensor: 等待 S3 存储中的一个 key 出现。
- SqlSensor: 重复运行一条 SQL 语句直到满足条件。
- HivePartitionSensor: 等待 Hive 中的一个分区出现。
- ExternalTaskSensor: 等待另一个 DAG Run 整体或者其中的某个 Task Instance 完成。
- DateTimeSensor: 等到某个指定的时间。
- TimeDeltaSensor: 一般情况下 Task 会在 (execution_date + schedule_interval) 之后运行、 TimeDeltaSensor 能够让 Task 再等待一段指定的时间。
2.3 Sensor 的工作模式
Sensor 有两种工作模式。第一种工作模式是 poke mode, 这也是默认的模式。在这种模式下, Sensor会一直运行,也就会一直占用资源,直到成功返回或者超时。在两次 check的间隙, 实际上 Sensor 是不做任何事情的,如果这时还占用资源,会造成一定程度的浪费。对于条件很快就能满足的场景, 因为 check 很快就会成功返回,所以浪费是可以被接受的。但是,对于可能很久都不能满足条件的场景、使用poke mode 会造成非常严重的资源浪费。
通过调整timeout到一个比较小的值、可以在一定程度上解决这个问题。在达到timeout指定的时间后,Sensor会退出,释放资源。但是,timeout调得很小就失去了使用Sensor的意义,针对可能需要长时间等待的条件、,更好的解决方案是他用第二种工作模式reschedule mode。
既然在两次check的间隙Sensor是不做任何事情的,那么不如把这段时间的资派释放,让别的Task使用,这正是reschedule mode的设计理念。如果一个Task是Sensor类型的并且Sensor被配置成以reschedule mode运行,那么这个Task在chceck失败后会进入up_for_reschedule状态。等待poke_interval时间后才会再次被调度运行。当Task处于up_for_reschedule状态时,它所占用的资源会被释放、从而被其他Task使用。
3 TaskFlow
使用Operator是创建Task的通用方法 ( Sensor本质上也是Operator), 但是在Task基本上都是 Python 函数的场景中、有一种更简洁和友好的方法:TaskFlow。接下来首先道过对比 PythonOperator 和 TaskFlow 在构建同样的工作流时的代码、 充分展示 TaskFlow 的优 勢。然后阐述 TaskFlow 中的两个重要概念:结果传递和依赖推断。最后介绍 TaskrFlow 对 Virtual Environment 的支持。
3.1 PythonOperator 与 TaskFlow
TaskFlow是Airflow 2.0 引人的新功能,旨在优化Task是Python函数的场景。在这之前,如果用 Python 函数作为Task, 通常都是用PythonOperator来包装的。TaskFlo 能够让 Python函数自动变成Airfiow的 Task, 只需要如下额外的两行代码。
- 在函数前使用 @task 装饰器。
- 调用函数生成 Task对象
通过 TaskFlow, 用户能够专注于Python函数的编写、而无须关心Airfow底层的细节。
执行下面的Python函数:
def extract():
data_string = '("1001": 301.27, "1002": 433,21, "1003": 502.22)'
order_data_dict = json.loads(data_string)
return order_data_dict
def transform(order_data_dict: dict) :
total order_value = 0
for value in order_data_dict.values():
total_order_value += value
return ("total order value": total order value)
def load(total_order_value: float) :
print(f"Total_order_value is: {total_order_value:.2f}")
基于 PythonOperator 创建DAG
import json
from airflow.decorators import dag
from airflow.operators.python import PythonOperator
from airflow.utils.dates import days_ago
default_args = {owner': 'airflow'}
@dag(default_args=default_args, schedule_interval=None, start_date=days_ago(2))
def tutorial_et1():
def extract(**kwargs) :
ti = kwargg['ti']
data_string = '{"1001": 301.27, "1002": 433.21, "1003": 502.22}'
ti.xcom_push('order_data', data_string)
def transform(**kwargs):
ti = kwargs['ti']
extract_data_string = ti.xcom_pull(task_ida='extract', key='order_data')
order_data = json.loads(extract_data_string)
total_order_value = 0
for value in order_data.values():
total_order_value += value
total_value = {"total_order_value": total_order_value}
total_value_json_string = json.dumps(total_value)
ti.xcom_push('total_order_value', total_value_json_string)
def load(**kwargs):
ti = kwargs['ti']
total_value_string = ti.xcom_pull(task_ids='transform', key='total_order_value')
total_order_value = json.loads(total_value_string)
print(total_order_value)
extract_task = PythonOperator(
task_id='extract',
python_callable=extract
)
transform_task = PythonOperator(
task_id='transform',
python_callable=transform
)
load_task = PythonOperator(
task_id='load',
python_callable=load
)
extract_task >> transform_task >> load_task
tutorial_etl_etl_dag = tutorial_etl()
基于TaskFlow创建DAG的代码如下:
import json
from airflow.decorators import dag, task
from airflow.utils.dates import days_ago
default_args = {
'owner': 'airflow'
}