Airflow 的入门

355 阅读13分钟

能看到这篇文章的人一定对airflow 有或多或少的了解,至少应该知道airflow 是做什么的。我们这篇文章主要任务就是作为airflow 知识的入门和巩固。

1. airflow 简介

  1. 开源的
  2. 用python 编写的调度工具(任务管理、调度、联控工作流平台)
  3. 是基于DAG 的任务管理系统,可以解决任务依赖的问题
  4. 可以很方便地查看任务的执行情况(是否成功、执行时间、执行依赖等等)
  5. 可以追踪历史执行情况、查看日志等等

2. 使用场景

  1. 大数据场景下数据的导入导出,触发数据的多个子操作,子操作间有依赖关系
  2. 需要统一的管理平台
  3. 定时执行一些脚本

3. airflow 的优势

  1. 有管理界面,容易操作,容易上手
  2. 调度代码和业务代码完全解耦(很重要的特点)
  3. 开发简单,可定制化,很灵活,可以满足用户各种需求
  4. 通过python 代码,可自定义子任务,支持各种Operator
  5. Operator 支持扩展,可以自定义Operator,方便二次开发

4. airflow 的组成部分

image.png

元数据库

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 的生命周期

image.png

如图,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 有三类:

  1. Sensor,就是传感监控器,用来监控一个事件的发生。
  2. Trigger,也叫做Remote Excution,用来执行某个远端的动作。
  3. 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。

另外的知识:

  1. Task,task 节点是组成DAG 的元素,它是BaseOperator 的一个子类。
  2. Task instance,就是每个task 的运行实例。
  3. 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 的触发维度可以定义成:

  1. 维度一: 根据dag 中上次T3 的运行状态来确定本次T3 是否被调用。
    使用dag 的default_args.depends_on_past 参数进行控制。当参数为True时,只有上次T3运行成功, 这次T3才会被触发。
  2. 维度二: 根据前置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 系统表中往往存储着一些我们在运行系统时的一些必要的数据,现在我们来看一下这些表的作用。

  1. connection 表:
    对于Task 来说,它们往往需要通过一些方式来访问其他平台上的资源,如jdbc/http 等等。我们都知道,在访问资源的时候,一般都需要一些验证信息。airflow 让我们将这些connection 信息以及所必须的鉴证信息存放在connection 表中。
    我们可以现在WebUI 的中管理这些connection,然后在代码中使用这些连接。
    在connection 表中有两类id,一个是id,就是表的主键id;另一个是conn_id,它是connection 的id, 通过这个规则我们可以总结出,我们可以定义多个同名的conn_id。在使用系统运行的时候,airflow 就会从同名的conn_id 的列表中随机选出一个进行使用,偏向于一种基本的load balance 的作用。
  2. user 表:
    这个表比较简单,就是包含user 的username 和email 的信息。同样,我们可以在WebUI 上进行管理。
  3. variable 表:
    存储的内容就是定义variable。
  4. xcom 表:
    同上,存储需要使用的xcom的数据。
  5. dag 表:
    这个表存储的是dag 的定义信息,dag_id 就是主键pk(使用字符型进行存储)。
  6. 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中存放注册信息。)
  7. task_instance 表:
    物理PK:无.
    逻辑PK:dag_id + task_id + execution_date 的组合。
    execution_date:表示触发dag 的的精确时间,是datetime 类型字段的字段。    start_date/end_date:表示执行task的起始/终止时间,同上,也是datetime类型字段。\
  8. job 表:
    存储job(可以理解为批次)的运行状态和信息。

Airflow 开发的技巧

其实前面的文章我们针对airflow 的基本知识介绍的已经差不多了。现在我觉得应该结合实际应用场景说一下具体的开发步骤了。

其实我们在公司中,如果能接触到airflow,那么证明公司或多或少已经有了相关的项目的支持。同时一定有相关的开发人员在维护。我们需要做的就是针对我们自己的业务做定制化开发。

在我们初步接触到airflow 开发任务的时候,我们第一步要做的不是去系统地了解airflow 的原理,更不要想着我要锻炼自己的能力,从零学习然后实现开发任务;反而是我们应该参照之前已有的业务的实现代码,在了解了airflow 的基本知识之后,将其粗略读一遍,看看它们到底是做了什么操作,是如何实现的,然后照着他们的实现,来定制我们的业务。(当然了,初步为公司接入airflow 的除外)

举例来说,我最近在使用airflow 的时候,从不了解到完成开发任务的步骤:

  1. 不限是了解了airflow 的基本概念,因为如果不去了解这些,那么基本的代码的含义都是看不明白的。
  2. 参考之前已有的代码,找一个和自己要实现的需求比较相近的实现示例,照着它的代码步骤,对自己的开发内容做一个实际的对比。
  3. 进一步了解实现需求的各个细节,debug,调参数,进行实际的开发。

当然在这一过程中也会遇到一些困难,这里简单列举一下我遇到的问题以及解决办法。

  1. 对于connection 的理解:我们都知道,airflow 的connection 是用来存储连接另一个平台的账户名密码等信息的。我们公司是统一管理connection 的,在申请的时候,需要知道各个连接终端的具体信息,然后再去申请。我在实际开发的过程中却遇到了一些问题,比如说实际的终端的模糊性,模棱两可等等,但是这些要及时和负责人沟通,否则容易延期。
  2. 对于python operator 的使用:在使用它的时候,中间会嵌入一些实际的python 代码,在做开发的时候,python 语言如果掌握的比较多,那么开发起来一定会很容易。所以同学们要多多了解一些python 的语法等等。我们之后一定还会有很多使用到的时候。