聲明:
本文轉自我的個人博客,有興趣的可以查看原文。
轉發請註明來源。
最近工作需要,使用airflow搭建了公司的ETL系統,順帶在公司分享了一次airflow,整理成文,Enjoy!
1. airflow 介紹
1.1 airflow 是什麼
Airflow is a platform to programmatically author, schedule and monitor workflows.
airflow 是一個編排、調度和監控workflow的平臺,由Airbnb開源,現在在Apache Software Foundation 孵化。airflow 將workflow編排爲tasks組成的DAGs,調度器在一組workers上按照指定的依賴關係執行tasks。同時,airflow 提供了豐富的命令行工具和簡單易用的用戶界面以便用戶查看和操作,並且airflow提供了監控和報警系統。
1.2 airflow 核心概念
- DAGs:即有向無環圖(Directed Acyclic Graph),將所有需要運行的tasks按照依賴關係組織起來,描述的是所有tasks執行的順序。
- Operators:可以簡單理解爲一個class,描述了DAG中一個具體的task具體要做的事。其中,airflow內置了很多operators,如
BashOperator
執行一個bash 命令,PythonOperator
調用任意的Python 函數,EmailOperator
用於發送郵件,HTTPOperator
用於發送HTTP請求,SqlOperator
用於執行SQL命令...同時,用戶可以自定義Operator,這給用戶提供了極大的便利性。 - Tasks:Task 是 Operator的一個實例,也就是DAGs中的一個node。
- Task Instance:task的一次運行。task instance 有自己的狀態,包括"running", "success", "failed", "skipped", "up for retry"等。
- Task Relationships:DAGs中的不同Tasks之間可以有依賴關係,如
TaskA >> TaskB
,表明TaskB依賴於TaskA。
通過將DAGs和Operators結合起來,用戶就可以創建各種複雜的 workflow了。
1.3 其它概念
- Connections: 管理外部系統的連接信息,如外部MySQL、HTTP服務等,連接信息包括
conn_id
/hostname
/login
/password
/schema
等,可以通過界面查看和管理,編排workflow時,使用conn_id
進行使用。 - Pools: 用來控制tasks執行的並行數。將一個task賦給一個指定的
pool
,並且指明priority_weight
,可以干涉tasks的執行順序。 - XComs:在airflow中,operator一般(not always)是原子的,也就是說,他們一般獨立執行,同時也不需要和其他operator共享信息,如果兩個operators需要共享信息,如filename之類的, 推薦將這兩個operators組合成一個operator。如果實在不能避免,則可以使用XComs (cross-communication)來實現。XComs用來在不同tasks之間交換信息。
- Trigger Rules:指task的觸發條件。默認情況下是task的直接上游執行成功後開始執行,airflow允許更復雜的依賴設置,包括
all_success
(所有的父節點執行成功),all_failed
(所有父節點處於failed或upstream_failed狀態),all_done
(所有父節點執行完成),one_failed
(一旦有一個父節點執行失敗就觸發,不必等所有父節點執行完成),one_success
(一旦有一個父節點執行成功就觸發,不必等所有父節點執行完成),dummy
(依賴關係只是用來查看的,可以任意觸發)。另外,airflow提供了depends_on_past
,設置爲True時,只有上一次調度成功了,纔可以觸發。
2. 示例
先來看一個簡單的DAG。圖中每個節點表示一個task,所有tasks組成一個DAG,各個tasks之間的依賴關係可以根據節點之間的線看出來。
2.1 實例化DAG
# -*- coding: UTF-8 -*-
## 導入airflow需要的modules
from airflow import DAG
from datetime import datetime, timedelta
default_args = {
'owner': 'lxwei',
'depends_on_past': False, # 如上文依賴關係所示
'start_date': datetime(2018, 1, 17), # DAGs都有個參數start_date,表示調度器調度的起始時間
'email': ['[email protected]'], # 用於alert
'email_on_failure': True,
'email_on_retry': False,
'retries': 3, # 重試策略
'retry_delay': timedelta(minutes=5)
}
dag = DAG('example-dag', default_args=default_args, schedule_interval='0 0 * * *')
在創建DAGs時,我們可以顯示的給每個Task傳遞參數,但通過default_args,我們可以定義一個默認參數用於創建tasks。
注意,schedule_interval 跟官方文檔不一致,官方文檔的方式已經被deprecated。
2.2 定義依賴關係
這個依賴關係是我自己定義的,key表示某個taskId,value裏的每個元素也表示一個taskId,其中,key依賴value裏的所有task。
"dependencies": {
"goods_sale_2": ["goods_sale_1"], # goods_sale_2 依賴 goods_sale1
"shop_sale_1_2": ["shop_sale_1_1"],
"shop_sale_2_2": ["shop_sale_2_1"],
"shop_sale_2_3": ["shop_sale_2_2"],
"etl_task": ["shop_info", "shop_sale_2_3", "shop_sale_realtime_1", "goods_sale_2", "shop_sale_1_2"],
"goods_sale_1": ["timelySalesCheck", "productDaySalesCheck"],
"shop_sale_1_1": ["timelySalesCheck", "productDaySalesCheck"],
"shop_sale_realtime_1": ["timelySalesCheck", "productDaySalesCheck"],
"shop_sale_2_1": ["timelySalesCheck", "productDaySalesCheck"],
"shop_info": ["timelySalesCheck", "productDaySalesCheck"]
}
2.3 定義tasks和依賴關係
首先,實例化operators,構造tasks。如代碼所示,其中,EtlTask
、MySQLToWebDataTransfer
、MySQLSelector
是自定義的三種Operator,根據taskType實例化operator,並存放到taskDict中,便於後期建立tasks之間的依賴關係。
for taskConf in tasksConfs:
taskType = taskConf.get("taskType")
if taskType == "etlTask":
task = EtlTask(
task_id=taskConf.get("taskId"),
httpConnId=httpConn,
etlId=taskConf.get("etlId"),
dag=dag)
taskDict[taskConf.get("taskId")] = task
elif taskType == "MySQLToWebDataTransfer":
task = MySqlToWebdataTransfer(
task_id = taskConf.get("taskId"),
sql= taskConf.get("sql"),
tableName=taskConf.get("tableName"),
mysqlConnId =mysqlConn,
httpConnId=httpConn,
dag=dag
)
taskDict[taskConf.get("taskId")] = task
elif taskType == "MySQLSelect":
task = StatusChecker(
task_id = taskConf.get("taskId"),
mysqlConnId = mysqlConn,
sql = taskConf.get("sql"),
dag = dag
)
taskDict[taskConf.get("taskId")] = task
else:
logging.error("error. TaskType is illegal.")
構建tasks之間的依賴關係,其中,dependencies中定義了上面的依賴關係,A >> B
表示A是B的父節點,相應的,A << B
表示A是B的子節點。
for sourceKey in dependencies:
destTask = taskDict.get(sourceKey)
sourceTaskKeys = dependencies.get(sourceKey)
for key in sourceTaskKeys:
sourceTask = taskDict.get(key)
if (sourceTask != None and destTask != None):
sourceTask >> destTask
3. 常用命令
命令行輸入airflow -h
,得到幫助文檔
backfill Run subsections of a DAG for a specified date range list_tasks List the tasks within a DAG clear Clear a set of task instance, as if they never ran pause Pause a DAG unpause Resume a paused DAG trigger_dag Trigger a DAG run pool CRUD operations on pools variables CRUD operations on variables kerberos Start a kerberos ticket renewer render Render a task instance's template(s) run Run a single task instance initdb Initialize the metadata database list_dags List all the DAGs dag_state Get the status of a dag run task_failed_deps Returns the unmet dependencies for a task instance from the perspective of the scheduler. In other words, why a task instance doesn't get scheduled and then queued by the scheduler, and then run by an executor). task_state Get the status of a task instance serve_logs Serve logs generate by worker test Test a task instance. This will run a task without checking for dependencies or recording it's state in the database. webserver Start a Airflow webserver instance resetdb Burn down and rebuild the metadata database upgradedb Upgrade the metadata database to latest version scheduler Start a scheduler instance worker Start a Celery worker node flower Start a Celery Flower version Show the version connections List/Add/Delete connections
其中,使用較多的是backfill、run、test、webserver、scheduler。其他操作在web界面操作更方便。另外,initdb 用於初始化metadata,使用一次即可;resetdb會重置metadata,清除掉數據(如connection數據), 需要慎用。
4. 問題
在使用airflow過程中,曾把DAGs裏的task拆分得很細,這樣的話,如果某個task失敗,重跑的代價會比較低。但是,在實踐中發現,tasks太多時,airflow在調度tasks會很低效,airflow一直處於選擇待執行的task的過程中,會長時間沒有具體task在執行,從而整體執行效率大幅降低。
5. 總結
airflow 很好很強大。如果只是簡單的ETL之類的工作,可以很容易的編排。調度靈活,而且監控和報警系統完備,可以很方便的投入生產環節。