能看到这篇文章的人一定对airflow 有或多或少的了解,至少应该知道airflow 是做什么的。我们这篇文章主要任务就是作为airflow 知识的入门和巩固。
1. airflow 简介
- 开源的
- 用python 编写的调度工具(任务管理、调度、联控工作流平台)
- 是基于DAG 的任务管理系统,可以解决任务依赖的问题
- 可以很方便地查看任务的执行情况(是否成功、执行时间、执行依赖等等)
- 可以追踪历史执行情况、查看日志等等
2. 使用场景
- 大数据场景下数据的导入导出,触发数据的多个子操作,子操作间有依赖关系
- 需要统一的管理平台
- 定时执行一些脚本
3. airflow 的优势
- 有管理界面,容易操作,容易上手
- 调度代码和业务代码完全解耦(很重要的特点)
- 开发简单,可定制化,很灵活,可以满足用户各种需求
- 通过python 代码,可自定义子任务,支持各种Operator
- Operator 支持扩展,可以自定义Operator,方便二次开发
4. airflow 的组成部分
元数据库
MetaData 存储有关任务状态的信息
调度器
Scheduler,它使用DAG 定义,结合元数据中的任务状态,决定哪些任务需要被执行,决定执行任务优先级的过程。
执行器
Executor,是一个消息队列,作为一个进程,它和调度器绑定,用来确定实际执行每个任务计划的工作进程。
有不同类型的执行器,每个执行器都使用一个指定工作进程的类来执行任务。
对于不同类型的调度器,读者可以自行查阅。这里不做重点介绍。
工作者
Workers,它们是实际执行任务逻辑的进程,通过正在使用的执行器来确定。
5.airflow 的基本概念
DAG
DAG(Directed Acyclic Graph, 有向无环图),这是airflow 的核心概念。
DAG 使用Python 定义,包含了用户需要运行的一系列task(任务),是用户定义的工作流的体现。
在DAG 中也定义了这些task 的依赖关系。
DAG 可以理解为一个做调度的工作流,它的主要配置项有两个,包括owner 和schedule(调度计划)。
DAG 支持多种调度方式,可以指定时间点,也可以指定周期。例如每天的早上3点执行,或者每半小时执行一次。
DAG 是由task 组成的,不同的task 任务构成了一个DAG。比如说,一个包含了三个简单任务:A,B,C 的DAG,我们让B 依赖于A,同时设置只有在在A 成功执行之后B 才可以运行;对于任务C,它可以在DAG 开始之后任意时间运行。
这里我们要注意,对于DAG 来说,它本身并不关注任务的具体内容,它关注的是三个任务的执行顺序以及依赖条件。
DAG RUN
这是DAG 的实例,通过airflow 的scheduler 创建,并根据配置的信息,使其在特定的时间运行。由于DAG 是由一个个task 组成的,所以每个DAG RUN 中也包含了DAG 中定义的task 的实例。
execution date
它是DAG 指定的运行时间
Task
上文已经提到了,组成DAG 的成员就是一个个task。
Task 就是DAG 中定义工作的基本单位,相当于DAG 工作流中的一个节点。
同时还有另一个operator 的概念,对比与task 来说:task 代表的是工作的抽象概念,而operator 是用来定义task 要做的具体的任务。
同一个DAG 中的task 可以定义依赖关系及先后顺序,参考如下代码:
with DAG('test_dag', start_date=datetime(2016, 1, 1)) as dag: # test_dag 就是这个dag 的id
task_1 = DummyOperator('task_1')
task_2 = DummyOperator('task_2')
task_1 >> task_2 # 这句话定义了依赖关系
代码中定义了一个包含两个task 的DAG,task_2依赖task_1,在DAG RUN 被创建之后,有限运行的是task_1,只有在task_1 成功执行完成之后,task_2才会执行。
Task Instance
顾名思义,这个就是一个task 任务的实例,类比于DAG 和DAG RUN 的关系。
Task 的生命周期
如图,task 的生命周期一共有八种,对于task 的正常流程,包含这几个阶段:
No status (airflow 的scheduler 创建了一个空的task实例)
Scheduled (airflow 的scheduler 对某个创建的task 实例进行了调度)
Queued (scheduler 将被调度的task 的实例传给执行器executor,然后将其放入执行队列)
Running (task 开始执行,处于执行状态中)
Success (task 执行成功,结束任务)
Operators
相比于DAG 来说,DAG 定义的是工作流如何执行;而对于Operator 来说,它定义的是一个task 执行的具体任务。
Operator 加上一些运行时上下文就变成了一个任务(task)。Operator 有三类:
- Sensor,就是传感监控器,用来监控一个事件的发生。
- Trigger,也叫做Remote Excution,用来执行某个远端的动作。
- Data transfer 是数据转换器,用来做数据转换。
Operator 在Airflow 中,是用来编写具体任务的类。
Operator 有很多中,例如:BashOperator
用来执行Bash 命令;PythonOperator
用来执行Python函数,MySOperator
可以操作MySQL 数据库执行相关操作,当然你也可以从BaseOperator中继承并开发自己的Operator。
Scheduler
Scheduler 是用来监控所有的task 和DAG 的,并用来触发依赖已经被满足的task。
Scheduler 以后端子进程的方式,和DAG 文件夹同步,然后周期性地收集DAG 的解析结果,从而找到可以满足条件的task。
之后,Scheduler 将满足运行条件的task 交给我们配置好的executor 来执行。
Scheduler 在是Airflow 中是一个顶层的服务,可以在命令行工具中运行airflow scheduler 就可以开启。
Executor
组成DAG 的元素是各个task,而Executor 就是用来执行task 的执行器。
对于Executor,它有多种配置方式,如:串行运行的SequetialExecutor(默认的配置,在dev 环境适合使用);可以在本地并发运行task 的LocalExecutor;可以分布式地运行task 的CeleryExecutor。
Hooks
Hook 的作用是airflow 用来与外部平台(如数据库)进行交互的。可以记作它定义了一种交互方式。
一个Hook 类就好比是一个JDBC driver。
airflow 已经实现了很多类型的hook。比如要访问RDBMS 数据库,airflow 有两类Hook 可供选择:基于原生Python DBAPI的Hook;基于JDBC的Hook。
另外的知识:
- Task,task 节点是组成DAG 的元素,它是BaseOperator 的一个子类。
- Task instance,就是每个task 的运行实例。
- Job,就是一个代号。在airflow 中很少被提及,但在数据库中有个job 表。要注意,Job 和task 并不C一回事,job 可以认为是airflow 的批次,准确来说是同一批被调用的task 或DAG 的统一的代号。在airflow 中,有三类Job,分别SchedulerJob/BackfillJob/LocalTaskJob。对于SchedulerJob和BackfillJob,job指的是指定dag这次被调用的运行时代号, LocalTaskJob是指定task的运行时代号。
Connections
从上文我们知道,hook 其实是一种访问方式,也就仅仅是一种访问方式。如同JDBC Driver,在我们需要连接DB 的时候,我们还需要一些额外的基础信息,包括:DB 的IP / Port / User / Pwd 等等信息。
在做Java 开发的时候,这种信息我们是不会直接写死在Java 代码中的,一般的做法是将其配置到某个网站,然根据不同的环境,到不同的网站上拉取。
在airflow 中,我们将其定义成Connection
,然后airflow 会将这些connection 信息存放在后台的connection 中。
Variables
Variable 常被用来定义一些系统级别的常量或者变量,在variable 中没有task_id 或者dag_id 这种属性,我们可以在WebUI 或代码中对Variable 进行新建、更新或者删除操作。
对于Variable 的另一个重要作用就像Spring 中的配置文件一样,根据环境的不同做不同的设置。
XComs
XCom 的作用类似于Variable,用于在Task 之间共享一些信息。和Variable 不同的是,XCom 包含task_id 和dag_id属性,它适合在不同的Task 之间做数据的传递。
XCom 使用方法会比Variables 复杂一些,举例来说,比如有一个dag,是由T1 和T2 两个task 组成的,其中T2 的执行依赖于T1 的完成。如果两个Task 之间需要交互,那么就可以在T1 中使用xcom_push() 方法来推送数据,比如说一个kv,然后在T2中使用xcom_pull()来获取这个kv。
Trigger Rules
在开发过程中,我们可以根据不同的业务定义不同的触发规则。
airflow 的触发条件有两个维度,可以为dag 中的每个task 都指定触发条件。
触发条件有两个维度, 举例T1&T2->T3,这种dag 的触发维度可以定义成:
- 维度一: 根据dag 中上次T3 的运行状态来确定本次T3 是否被调用。
使用dag 的default_args.depends_on_past 参数进行控制。当参数为True时,只有上次T3运行成功, 这次T3才会被触发。 - 维度二: 根据前置T1 和T2 的状态,来确定本次T3 是否被调用。
这种方式使用T3.trigger_rule 参数进行控制。有下面6种情形。默认是all_success。
all_success: all parents have succeeded
all_failed: all parents are in a failed or upstream_failed state
all_done: all parents are done with their execution
one_failed: fires as soon as at least one parent has failed, it does not wait for all parents to be done
one_success: fires as soon as at least one parent succeeds, it does not wait for all parents to be done
dummy: dependencies are just for show, trigger at will\
分支的支持
airflow有两个基于PythonOperator 的Operator 来支持dag分支功能。\
- ShortCircuitOperator:用来判断实现流程。使用的时候,Task 需要基于ShortCircuitOperator,如果这个Task 返回结果为false,那么其下游的Task 就会被skip;反之,如果返回结果为True,那么其下游Task 就会正常执行。它适合用在下游都是单线节点的场景。
- BranchPythonOperator:是用来实现Case 分支的。对于Task 来说,它需要基于BranchPythonOperator,这样airflow 会根据本task 的返回值(某个下游task 的id)来确定哪个下游Task 被执行,同时其他下游的Task 将会被skip。
airflow系统表
airflow 系统表中往往存储着一些我们在运行系统时的一些必要的数据,现在我们来看一下这些表的作用。
- connection 表:
对于Task 来说,它们往往需要通过一些方式来访问其他平台上的资源,如jdbc/http 等等。我们都知道,在访问资源的时候,一般都需要一些验证信息。airflow 让我们将这些connection 信息以及所必须的鉴证信息存放在connection 表中。
我们可以现在WebUI 的中管理这些connection,然后在代码中使用这些连接。
在connection 表中有两类id,一个是id,就是表的主键id;另一个是conn_id,它是connection 的id, 通过这个规则我们可以总结出,我们可以定义多个同名的conn_id。在使用系统运行的时候,airflow 就会从同名的conn_id 的列表中随机选出一个进行使用,偏向于一种基本的load balance 的作用。 - user 表:
这个表比较简单,就是包含user 的username 和email 的信息。同样,我们可以在WebUI 上进行管理。 - variable 表:
存储的内容就是定义variable。 - xcom 表:
同上,存储需要使用的xcom的数据。 - dag 表:
这个表存储的是dag 的定义信息,dag_id 就是主键pk(使用字符型进行存储)。 - dag_run 表:
这个表的内容就是dag 的运行历史记录,类似connection,这张表也有两个id 栏位,一个是id(PK), 另一个是run_id,run_id 栏位存的是这次运行的一个名字(字符型)。
对于同一个dag,它所产生的不同运行记录的run_id 是不能重复也不允许重复的。
对于这张表来说,还有以下几个需要注意的内容:
物理PK:就是id 列。
逻辑PK:是dag_id + execution_date 的组合。
execution_date:表示触发dag 的准确时间。
(注意:没有 task 表,airflow的task 是定义在python 源码中的,不会在DB中存放注册信息。) - task_instance 表:
物理PK:无.
逻辑PK:dag_id + task_id + execution_date 的组合。
execution_date:表示触发dag 的的精确时间,是datetime 类型字段的字段。 start_date/end_date:表示执行task的起始/终止时间,同上,也是datetime类型字段。\ - job 表:
存储job(可以理解为批次)的运行状态和信息。
Airflow 开发的技巧
其实前面的文章我们针对airflow 的基本知识介绍的已经差不多了。现在我觉得应该结合实际应用场景说一下具体的开发步骤了。
其实我们在公司中,如果能接触到airflow,那么证明公司或多或少已经有了相关的项目的支持。同时一定有相关的开发人员在维护。我们需要做的就是针对我们自己的业务做定制化开发。
在我们初步接触到airflow 开发任务的时候,我们第一步要做的不是去系统地了解airflow 的原理,更不要想着我要锻炼自己的能力,从零学习然后实现开发任务;反而是我们应该参照之前已有的业务的实现代码,在了解了airflow 的基本知识之后,将其粗略读一遍,看看它们到底是做了什么操作,是如何实现的,然后照着他们的实现,来定制我们的业务。(当然了,初步为公司接入airflow 的除外)
举例来说,我最近在使用airflow 的时候,从不了解到完成开发任务的步骤:
- 不限是了解了airflow 的基本概念,因为如果不去了解这些,那么基本的代码的含义都是看不明白的。
- 参考之前已有的代码,找一个和自己要实现的需求比较相近的实现示例,照着它的代码步骤,对自己的开发内容做一个实际的对比。
- 进一步了解实现需求的各个细节,debug,调参数,进行实际的开发。
当然在这一过程中也会遇到一些困难,这里简单列举一下我遇到的问题以及解决办法。
- 对于connection 的理解:我们都知道,airflow 的connection 是用来存储连接另一个平台的账户名密码等信息的。我们公司是统一管理connection 的,在申请的时候,需要知道各个连接终端的具体信息,然后再去申请。我在实际开发的过程中却遇到了一些问题,比如说实际的终端的模糊性,模棱两可等等,但是这些要及时和负责人沟通,否则容易延期。
- 对于python operator 的使用:在使用它的时候,中间会嵌入一些实际的python 代码,在做开发的时候,python 语言如果掌握的比较多,那么开发起来一定会很容易。所以同学们要多多了解一些python 的语法等等。我们之后一定还会有很多使用到的时候。