hadoop組件---spark實戰-----airflow----調度工具airflow的介紹和使用示例

Airflow是什麼

Airflow是一個可編程,調度和監控的工作流平臺,基於有向無環圖(DAG),airflow可以定義一組有依賴的任務,按照依賴依次執行。airflow提供了豐富的命令行工具用於系統管控,而其web管理界面同樣也可以方便的管控調度任務,並且對任務運行狀態進行實時監控,方便了系統的運維和管理,可視化方面和易用性都是很好的。

2019年airflow 已經成長爲apache的頂級項目了,跟spark搭配是很常用的場景。

只要符合定時任務流的工作,都可以用Airflow來實現。我們主要用Airflow來實現定時的ETL處理。

我們經常會使用spark來做etl,但是etl的流程和步驟是很多的,比如先清洗什麼,再清洗什麼,有一個步驟需要上一個步驟清洗完了才能啓動。最原始的肯定是人工監控 和 手動調度執行。但是這樣太累了。

airflow能很好的實現自動化這部分的邏輯。

只需要我們編寫好 相關 執行順序以及依賴的DAG流程。 airflow就能按照定好的流程自動進行調度運行。

airflow官網

airflow官網文檔

github

相關概念和原理

1、DAG
DAG 意爲有向無循環圖,在 Airflow 中則定義了整個完整的作業。同一個 DAG 中的所有 Task 擁有相同的調度時間。

2、Task
Task 爲 DAG 中具體的作業任務,它必須存在於某一個 DAG 之中。Task 在 DAG 中配置依賴關係,跨 DAG 的依賴是可行的,但是並不推薦。跨 DAG 依賴會導致 DAG 圖的直觀性降低,並給依賴管理帶來麻煩。

3、DAG Run
當一個 DAG 滿足它的調度時間,或者被外部觸發時,就會產生一個 DAG Run。可以理解爲由 DAG 實例化的實例。

4、Task Instance
當一個 Task 被調度啓動時,就會產生一個 Task Instance。可以理解爲由 Task 實例化的實例

5、執行器(Executor)
Airflow本身是一個綜合平臺,它兼容多種組件,所以在使用的時候有多種方案可以選擇。比如最關鍵的執行器就有四種選擇:

SequentialExecutor:單進程順序執行任務,默認執行器,通常只用於測試

LocalExecutor:多進程本地執行任務

DaskExecutor :動態任務調度,主要用於數據分析

CeleryExecutor:分佈式調度,生產常用

celery是一個分佈式調度框架,其本身無隊列功能,需要使用第三方組件,比如redis或者rabbitmq,以rabbitmq爲例,系統整體結構如下所示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GVHRtzsb-1582787774334)(http://image.525.life/FrmIX62pUY-wp5tUIX9Ubin97_50)]

其中:

turing爲外部系統

GDags服務幫助拼接成dag

master節點webui管理dags、日誌等信息

scheduler負責調度,只支持單節點

worker負責執行具體dag中的task, worker支持多節點

在整個調度系統中,節點之間的傳遞介質是消息,而消息的本質內容是執行腳本的命令,也就是說,工作節點的dag文件必須和master節點的dag文件保持一致,不然任務的執行會出問題。

6、任務處理器

operator描述了工作流中的一個task,是一個抽象的概念,相當於抽象task定義

airflow內置了豐富的任務處理器,用於實現不同類型的任務:

BashOperator : 執行bash命令

PythonOperator : 調用python代碼

EmailOperator : 發送郵件

HTTPOperator : 發送 HTTP 請求

SqlOperator : 執行 SQL 命令

除了這些基本的構建塊之外,還有更多的特定處理器:DockerOperator,HiveOperator,S3FileTransferOperator,PrestoToMysqlOperator,SlackOperator

7、角色

webserver : 提供web端服務,以及會定時生成子進程去掃描對應的目錄下的dags,並更新數據庫

scheduler : 任務調度服務,根據dags生成任務,並提交到消息中間件隊列中 (redis或rabbitMq)

celery worker : 分佈在不同的機器上,作爲任務真正的的執行節點。通過監聽消息中間件: redis或rabbitMq 領取任務

flower : 監控worker進程的存活性,啓動或關閉worker進程,查看運行的task

airflow特點

分佈式任務調度:允許一個工作流的task在多臺worker上同時執行

可構建任務依賴:以有向無環圖的方式構建任務依賴關係

task原子性:工作流上每個task都是原子可重試的,一個工作流某個環節的task失敗可自動或手動進行重試,不必從頭開始任務

安裝

# airflow needs a home, ~/airflow is the default,
# but you can lay foundation somewhere else if you prefer
# (optional)
export AIRFLOW_HOME=~/airflow

# install from pypi using pip
pip install apache-airflow

# initialize the database
airflow initdb

# start the web server, default port is 8080
airflow webserver -p 8080
# nohup airflow webserver -p 8080 > ~/airflow/active.log 2>&1 &

# start the scheduler
airflow scheduler

# 刪除dag(需要先去~/airflow/dags中刪除py文件)
airflow delete_dag -y {dag_id}

使用pip安裝的安裝路徑一般在 python路徑的site-packages中。

當在~/airflow/dags文件下添加py文件後,需要等待一會,纔會在web中顯示。
重新運行airflow scheduler可以立刻在web中顯示py文件。

顯示了py文件之後 我們就可以運行DAG了。

修改配置

基礎配置airflow.cfg

安裝好Airflow,第一次運行 airflow initdb 之後,會在Airflow文件夾下面產生一個airflow.cfg文件,這個就是基礎配置文件。我們以這個基礎文件作爲模板來修改成爲我們需要的配置文件。以下的操作都是找到對應的配置字段,修改其字段內容。

修改默認時區:default_timezone = Asia/Shanghai,說明:修改時區之後,Airflow前端頁面仍舊會使用UTC時區顯示,但是配合主機/容器的時區,這樣我們在寫dag任務執行時間的時候就不需要轉換時區了。

修改執行器類型:executor = CeleryExecutor

不加載範例dag:load_example = False

不讓同個dag並行操作:max_active_runs_per_dag = 1,說明:在ETL過程中,還是線性執行會比較好控制,如果裏面需要批量操作,可以在ETL的具體處理過程中加入多線程或者多進程方式執行,不要在dag中體現

最高的dag併發數量:dag_concurrency = 16,說明:一般配置成服務器的CPU核數,默認16也沒問題。

最高的任務併發數量:worker_concurrency = 16,說明:CeleryExecutor在Airflow的worker線程中執行的,這裏配置的是啓動多少個worker

數據庫配置:sql_alchemy_conn = mysql://airflow:[email protected]:3306/airflow?charset=utf8,說明:我們一般是用MySQL來配合Airflow的運行

Celery Broker:broker_url = redis://:[email protected]:6379/0,說明:默認配置中兩個redis配置被分到兩個redis區,我們也照做吧。

Celery Result backend:result_backend = redis://:[email protected]:6379/1,說明:默認配置中兩個redis配置被分到兩個redis區,我們也照做吧。

五、MySQL需要注意的地方

mysql的配置中需要加入以下內容,不然執行會報錯。需要在initdb之前加入並重啓。

[mysqld]innodb_large_prefix = onexplicit_defaults_for_timestamp = 1

六、運行

由於使用的是CeleryExecutor,需要順序執行三個進程:airflow webserver -Dairflow scheduler -Dairflow worker -D

常用管理airflow命令

airflow test dag_id task_id execution_date   測試task

示例: airflow test example_hello_world_dag hello_task 20200226

airflow run dag_id task_id execution_date 運行task

airflow run -A dag_id task_id execution_date 忽略依賴task運行task

airflow trigger_dag dag_id -r RUN_ID -e EXEC_DATE  運行整個dag文件

airflow webserver -D  守護進程運行webserver

airflow scheduler -D  守護進程運行調度

airflow worker -D 守護進程運行celery worker

airflow worker -c 1 -D 守護進程運行celery worker並指定任務併發數爲1

airflow pause dag_id  暫停任務

airflow unpause dag_id 取消暫停,等同於在管理界面打開off按鈕

airflow list_tasks dag_id 查看task列表

airflow clear dag_id 清空任務實例

web界面使用

啓動web管控界面需要執行airflow webserver -D命令,默認訪問端口是8080

假設部署在 192.168.30.11這臺服務器上

在瀏覽器中訪問 http://192.168.30.11:8080/admin/

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bSMBTaSD-1582787774405)(http://image.525.life/FvwJF_jf2iopVX5C4l5Ypo0Bcrh2)]

(1) 任務啓動暫停開關

(2) 任務運行狀態

(3) 待執行,未分發的任務

(4) 手動觸發執行任務

(5) 任務管控界面

選擇對應dag欄目,點擊(5)中的 Graph View即可進入任務管控界面

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-M51OYGYk-1582787774408)(http://image.525.life/FlPPVOmca11wJsaAAIeJhqQS-Fep)]

點擊對應的任務,會彈出一個任務管控臺,主要幾個功能如下:

View Log : 查看任務日誌

Run : 運行選中任務

Clear:清空任務隊列

Mark Success : 標記任務爲成功狀態

在界面中配置參數

Menu -> Admin -> Variables

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bLSA3vpt-1582787774411)(http://image.525.life/FoQKNh6BU93XP5XA6lwlhs8UfhJp)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-To5CU79k-1582787774414)(http://image.525.life/Fqi2qnb3w2LXPiZpUcWTI7PkUc2W)]

使用airflow

運行一個dag的流程

在~/airflow/dags文件下添加py文件,(需要等待一會,纔會在web中顯示,如果未開啓webserver,也是可以運行的)

airflow unpause dag_id(取消暫停任務,任務會按照設定時間週期執行)
airflow trigger_dag dag_id(立刻運行整個dag)

重啓一個dag的流程

rm -rf ~/airflow/dags/aml_sl_with_config.py
airflow delete_dag -y aml_sl_with_config
ps -ef |grep "airflow scheduler" |awk '{print $2}'|xargs kill -9
vi ~/airflow/dags/aml_sl_with_config.py
nohup airflow scheduler &

通過DAG文件實現定時任務

crontab語法

crontab格式如下所示:

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                       7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * *  command to execute
是否必須 取值範圍 可用特殊符號 備註
Minutes Yes 0–59 * , -
Hours Yes 0–23 * , -
Day of month Yes 1–31 * , - ? L W ? L W部分實現可用
Month Yes 1–12 or JAN–DEC * , -
Day of week Yes 0–6 or SUN–SAT * , - ? L # ? L W 部分實現可用
Year No 1970–2099 * , - 標準實現裏無這一項

特殊符號功能說明:

逗號(,)
逗號用於分隔一個列表裏的元素,比如 "MON,WED,FRI" 在第五域(day of week)表示Mondays, Wednesdays and Fridays。

連字符(-)
連字符用於表示範圍,比如2000–2010表示2000到2010之間的每年,包括這兩年(閉區間)。

百分號(%)
用於命令(command)中的格式化

L
表示last,最後一個,比如第五域,5L表示當月最後一個星期五

W
W表示weekday(Monday-Friday),指離指定日期附近的工作日,比如第三域設置爲15L ,這表示臨近當月15附近的工作日,假如15號是星期六,那麼定時器會在14號執行,如果15號是星期天,那麼定時器會在16號執行,也就是說只會在離指定日期最近的那天執行。

井號#
#用於第五域(day of week),#後面跟着一個1~5之間的數字,這個用於表示第幾個星期,比如5#3表示第三個星期五

?
在有些實現裏面,?與*的功能相同,還有一些實現裏面?表示cron的啓動時間,比如 當cron服務在8:25am啓動,則? ? * * * *會更新爲25 8 * * * *, 直到下一次cron服務重新啓動,定時器會再次更新。

/
/一般與*組合使用,後面跟着一個數字,表示頻率,比如在第一域(Minutes)中*/5表示每5分鐘,是普通列表表示5,10,15,20,25,30,35,40,45,50,55,00的縮寫

普通任務–helloWorld

from datetime import timedelta, datetime
import airflow
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.operators.dummy_operator import DummyOperator

default_args = { #默認參數
    'owner': 'zzq', #dag擁有者,用於權限管控
    'depends_on_past': False,  #是否依賴上游任務
    'start_date': datetime(2020, 2, 26), #任務開始時間,默認utc時間
    'email': ['[email protected]'], #告警通知郵箱地址
    'email_on_failure': False,
    'email_on_retry': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}

dag = DAG(
    'example_hello_world_dag',  #dag的id
    default_args=default_args,
    description='my first DAG', #描述
    schedule_interval='*/20 * * * *', # crontab
    start_date=datetime(2020, 2, 26) #開始時間,覆蓋默認參數
)

def print_hello():
    return 'Hello world!'

dummy_operator = DummyOperator(task_id='dummy_task', dag=dag)

hello_operator = BashOperator(   #通過BashOperator定義執行bash命令的任務
    task_id='sleep_task',
    depends_on_past=False,
    bash_command='echo `date` >> /home/py/test.txt',
    dag=dag
)

dummy_operator >> hello_operator #設置任務依賴關係
#dummy_operator.set_downstream(hello_operator)

定義http任務並使用本地時間

import os
from datetime import timedelta, datetime
import pytz
from airflow.operators.http_operator import SimpleHttpOperator
from airflow.models import DAG

default_args = {
    'owner': 'zzq',
    # 'depends_on_past': False,
    'depends_on_past': True,
    'wait_for_downstream': True,
    'execution_timeout': timedelta(minutes=3),
    'email': ['[email protected]'],
    'email_on_failure': False,
    'email_on_retry': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}

#將本地時間轉換爲utc時間,再設置爲start_date
tz = pytz.timezone('Asia/Shanghai')
dt = datetime(2020, 2, 26, 12, 20, tzinfo=tz)
utc_dt = dt.astimezone(pytz.utc).replace(tzinfo=None)

os.environ['AIRFLOW_CONN_HTTP_TEST']='http://localhost:9090'

dag = DAG(
    'testtag01',
    default_args=default_args,
    description='my DAG',
    schedule_interval='*/2 * * * *',
    start_date=utc_dt
)

#通過SimpleHttpOperator定義http任務
task1 = SimpleHttpOperator(
    task_id='get_op1',
    http_conn_id='http_test',
    method='GET',
    endpoint='test1',
    data={},
    headers={},
    dag=dag)

task2 = SimpleHttpOperator(
    task_id='get_op2',
    http_conn_id='http_test',
    method='GET',
    endpoint='test2',
    data={},
    headers={},
    dag=dag)

task1 >> task2

參數細節

這裏我們要特別注意一個關於調度執行時間的問題。在談這個問題前,我們先確定幾個名詞:

start date: 在配置中,它是作業開始調度時間。而在談論執行狀況時,它是調度開始時間。
schedule interval: 調度執行週期。
execution date: 執行時間,在 Airflow 中稱之爲執行時間,但其實它並不是真實的執行時間。
那麼現在,讓我們看一下當一個新配置的 DAG 生效後第一次調度會在什麼時候。很多人會很自然的認爲,第一次的調度時間當然是在作業中配置的 start date,但其實並不是。

第一次調度時間是在作業中配置的 start date 的第二個滿足 schedule interval 的時間點

並且記錄的 execution date 爲作業中配置的 start date 的第一個滿足 schedule interval 的時間點。

另外,當作業已經執行過之後,start date 的配置將不會再生效,這個作業的調度開始時間將直接按照上次調度所對應的 execution date 來計算。

這個例子只是簡要的介紹了一下 DAG 的配置,也只介紹了非常少量的配置參數。Airflow 爲 DAG 和作業提供了大量的可配置參數,詳情可以參考 Airflow 官方文檔

跳過非最新 DAG Run

假如有一個每小時調度的 DAG 出錯了,我們把它的調度暫停,之後花了3個小時修復了它,修復完成後重新啓動這個作業的調度。於是 Airflow 一下子創建了 3 個 DAG Run 並同時執行,這顯然不是我們希望的,我們希望它只執行最新的 DAG Run。

我們可以創建一個 Short Circuit Operator,並且讓 DAG 中所有沒有依賴的作業都依賴這個作業,然後在這個作業中進行判斷,檢測當前 DAG Run 是否爲最新,不是最新的直接跳過整個 DAG。

def skip_dag_not_latest_worker(ds, **context):
    if context['dag_run'] and context['dag_run'].external_trigger:
        logging.info('Externally triggered DAG_Run: allowing execution to proceed.')
        return True
 
    skip = False
    now = datetime.now()
    left_window = context['dag'].following_schedule(context['execution_date'])
    right_window = context['dag'].following_schedule(left_window)
    logging.info('Checking latest only with left_window: %s right_window: %s now: %s', left_window, right_window, now)
 
    if not left_window < now <= right_window:
        skip = True
    return not skip
 
ShortCircuitOperator(
    task_id='skip_dag_not_latest',
    provide_context=True,
    python_callable=skip_dag_not_latest_worker,
    dag=dag
)

當存在正在執行的 DAG Run 時跳過當前 DAG Run

依舊是之前提到的每小時調度的 DAG,假設它這次沒有出錯而是由於資源、網絡或者其他問題導致執行時間變長,當下一個調度時間開始時 Airflow 依舊會啓動一次新的 DAG Run,這樣就會同時出現 2 個 DAG Run。如果我們想要避免這種情況,一個簡單的方法是直接將 DAG 的 max_active_runs 設置爲 1。但這樣會導致 DAG Run 堆積的問題,如果你配置的調度是早上 9 點至晚上 9 點,直至晚上 9 點之後 Airflow 可能依舊在處理堆積的 DAG Run。這樣就可能影響到我們原本安排在晚上 9 點之後的任務。

我們可以創建一個 Short Circuit Operator,並且讓 DAG 中所有沒有依賴的作業都依賴這個作業,然後在這個作業中進行判斷,檢測當前是否存在正在執行的 DAG Run,存在時則直接跳過整個 DAG。

def skip_dag_when_previous_running_worker(ds, **context):
    if context['dag_run'] and context['dag_run'].external_trigger:
        logging.info('Externally triggered DAG_Run: allowing execution to proceed.')
        return True
 
    skip = False
    session = settings.Session()
    count = session.query(DagRun).filter(
        DagRun.dag_id == context['dag'].dag_id,
        DagRun.state.in_(['running']),
    ).count()
    session.close()
    logging.info('Checking running DAG count: %s' % count)
    skip = count > 1
    return not skip
 
ShortCircuitOperator(
    task_id='skip_dag_when_previous_running',
    provide_context=True,
    python_callable=skip_dag_when_previous_running_worker,
    dag=dag
)

使用的最佳實踐

1、利用provide_context和XCOM在任務間傳遞信息

在default_args裏面配置’provide_context’: True,這樣在每個任務執行完之後都可以返回一個信息(當你需要的時候),可以使用xcom在不同的operator間傳遞變量。

這樣每個任務都可以獲取到之前任務執行返回的信息,以進行自身的處理操作。

以下是一個簡單的例子:



# 任務1,獲得數據並保存到文件中,返回文件名

def job_get_datas(**kwargs): 
    filename = get_datas() # 數據獲取的函數,返回的是存儲數據的文件名
    return    filename
	
operator_get_datas = PythonOperator(
    task_id='task_get_datas',
	python_callable=job_get_datas,
	dag=dag
	) 


# 把存儲文件的數據導入數據庫

def job_data_2_mysql(**kwargs): 
    filename = kwargs['task_instance'].xcom_pull(task_ids='task_get_datas') # 獲取task_get_datas任務返回的數據 
    result = data_2_mysql(filename) # 數據入庫的函數 
    return result 
	
operator_data_2_mysql = PythonOperator( 
    task_id='task_data_2_mysql',
	python_callable=job_data_2_mysql, 
	dag=dag)




# 或者 先push到xcom中再pull


def processing_data(**kwargs):
    kwargs['ti'].xcom_push(key='X', value=X)
    kwargs['ti'].xcom_push(key='str_with_trx_with_retail_with_corporate_with_account', value=str_with_trx_with_retail_with_corporate_with_account)


processing_data_operator = PythonOperator(
    task_id='processing_data_operator',
    provide_context=True,
    python_callable=processing_data,
    dag=dag,
)


def predict(**kwargs):
    ti = kwargs['ti']
    X = ti.xcom_pull(key='X', task_ids='processing_data_operator')
    
predict_operator = PythonOperator(
    task_id='predict_operator',
    provide_context=True,
    python_callable=predict,
    dag=dag,
)

注意:由於這裏的上下文信息(任務返回的數據)是存到Airflow的MySQL中,字段長度有限,所以不推薦返回具體數據,而是通過其他途徑存儲臨時數據(例如臨時文件形式),返回關鍵信息(例如臨時文件的文件名),這樣既不會因爲異常斷開導致整個任務流需要重跑,也不會因爲數據量過大導致Airflow存儲MySQL的時候報錯。

2、處理邏輯與任務流執行分離

雖然在dag裏面可以直接寫python代碼(Airflow本身也是用python實現的),但是不推薦將處理邏輯寫在dag上面。這裏有兩方面的考慮:

在Airflow的前端界面中,是可以看到dag的代碼的,將處理邏輯、特別是數據庫或其他服務的用戶密碼暴露出來未必是好事;

如果將邏輯寫在dag裏面,那麼在測試邏輯的時候,就太依賴Airflow了。這與解耦的開發邏輯思路相違背了,我們是需要一個鬆耦合的代碼世界。

那麼推薦在項目下面添加一個etl_utils目錄(或者你喜歡的名稱),用於存放處理邏輯。這個目錄下一般分成三個子目錄config、etl、system,分別是配置信息(數據庫密碼等)、邏輯代碼、通用工具(如封裝好的es操作類)。那麼一般項目的目錄結構如下:

-/dag_xxx.py-/test_xxx.py-/etl_utls/-/etl_utls/config/...-/etl_utls/etl/...-/etl_utls/system/...

所有的文件之間的調用層級以根目錄爲起點。我們在實現邏輯之後,就可以在根目錄下編寫測試代碼,按順序執行我們需要實現的流程。按這種方式測試完流程之後再組織dag。

3、關於中間數據

在處理邏輯中,我們儘量將每個處理過程細分出來,每個處理完成之後都將數據保存到臨時文件中(中間處理過程,一般不要存數據庫了,加大數據庫的存取壓力不是一件好事情,而且這些都是臨時的信息),這些文件可以是同一個文件進行反覆覆蓋(每個任務流都取一個相對唯一的文件名,例如使用uuid,或者第一次處理的時間戳,加上任務流名字作爲唯一辨識)。千萬不要將這些信息放在內存裏,萬一掛了,就找不回來了,又要整個流程重新跑過。

4、臨時文件

臨時文件,注意同個任務流中保持一致,但是在不同任務流中需要能區分,有時候上一個任務流失敗了,下一個任務流繼續執行,那麼如果沒有區分能力,就會把上一個任務流的數據給覆蓋掉了。注意在最後加上一個刪除文件的處理,減少系統空間壓力。

5、關於處理頻率

機器的處理能力總是有限的,所以我們在條件允許的情況下,每次處理的數據量儘量減小。一般減小每次處理的數據量的方法,就是增加處理頻率。但是加大處理頻率,又會加大Airflow自身運行需要佔用的資源。所以需要在數據量和頻率之間找到一個平衡,這裏每個項目可能有自己的特點,需要在每個項目的實際情況中找到適合項目的處理頻率。

高可用airflow集羣安裝步驟

airflow 單節點部署

將以所有上守護進程運行在同一臺機器上即可完成 airflow 的單結點部署,架構如下圖所示

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pQhrUv8B-1582787774416)(http://image.525.life/FsSLDWrWGVmH-zos4T3_cgkVcVwE)]

airflow 多節點(集羣)部署

在穩定性要求較高的場景,如金融交易系統中,一般採用集羣、高可用的方式來部署。Apache Airflow 同樣支持集羣、高可用的部署,airflow 的守護進程可分佈在多臺機器上運行,架構如下圖所示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4nBdjlvq-1582787774419)(http://image.525.life/FmN9VvS3FtDFtYMJYGiQ5bEMNUGl)]

這樣做有以下好處
1)高可用
如果一個 worker 節點崩潰或離線時,集羣仍可以被控制的,其他 worker 節點的任務仍會被執行。

2)分佈式處理
如果你的工作流中有一些內存密集型的任務,任務最好是分佈在多臺機器上運行以便得到更快的執行。

擴展 worker 節點

水平擴展
你可以通過向集羣中添加更多 worker 節點來水平地擴展集羣,並使這些新節點指向同一個元數據庫,從而分發處理過程。由於 worker 不需要在任何守護進程註冊即可執行任務,因此所以 worker 節點可以在不停機,不重啓服務下的情況進行擴展,也就是說可以隨時擴展。

垂直擴展
你可以通過增加單個 worker 節點的守護進程數來垂直擴展集羣。可以通過修改 airflow 的配置文件-{AIRFLOW_HOME}/airflow.cfg 中 celeryd_concurrency 的值來實現,例如:

celeryd_concurrency = 30

您可以根據實際情況,如集羣上運行的任務性質,CPU 的內核數量等,增加併發進程的數量以滿足實際需求。

擴展 Master 節點

您還可以向集羣中添加更多主節點,以擴展主節點上運行的服務。您可以擴展 webserver 守護進程,以防止太多的 HTTP 請求出現在一臺機器上,或者您想爲 webserver 的服務提供更高的可用性。需要注意的一點是,每次只能運行一個 scheduler 守護進程。如果您有多個 scheduler 運行,那麼就有可能一個任務被執行多次。這可能會導致您的工作流因重複運行而出現一些問題。
下圖爲擴展 Master 節點的架構圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-b9dLMjvE-1582787774421)(http://image.525.life/FvM8A9QzSRvjL2g04SmOsK6ILnAy)]

看到這裏,可能有人會問,scheduler 不能同時運行兩個,那麼運行 scheduler 的節點一旦出了問題,任務不就完全不運行了嗎?

答案: 這是個非常好的問題,不過已經有解決方案了,我們可以在兩臺機器上部署 scheduler ,只運行一臺機器上的 scheduler 守護進程 ,一旦運行 scheduler 守護進程的機器出現故障,立刻啓動另一臺機器上的 scheduler 即可。我們可以藉助第三方組件 airflow-scheduler-failover-controller 實現 scheduler 的高可用。

具體步驟如下所示:

下載 failover

git clone https://github.com/teamclairvoyant/airflow-scheduler-failover-controller

使用 pip 進行安裝

cd{AIRFLOW_FAILOVER_CONTROLLER_HOME}
pip install -e .

初始化 failover

scheduler_failover_controller init

注:初始化時,會向airflow.cfg中追加內容,因此需要先安裝 airflow 並初始化。

更改 failover 配置

scheduler_nodes_in_cluster= host1,host2

注:host name 可以通過scheduler_failover_controller get_current_host命令獲得

配置安裝 failover 的機器之間的免密登錄,配置完成後,可以使用如下命令進行驗證:

scheduler_failover_controller test_connection

啓動 failover

scheduler_failover_controller start

因此更健壯的架構圖如下所示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8O8SOkne-1582787774423)(http://image.525.life/FvI18g3vnSYi4bMtIBAC5PWO3Sap)]

隊列服務及元數據庫(Metestore)的高可用。

隊列服務取決於使用的消息隊列是否可以高用可部署,如 RabbitMQ 和 Redis。

RabbitMQ 集羣並配置Mirrored模式見:http://blog.csdn.net/u010353408/article/details/77964190

元數據庫(Metestore) 取決於所使用的數據庫,如 Mysql 等。

Mysql 做主從備份見:http://blog.csdn.net/u010353408/article/details/77964157

airflow 集羣部署的具體步驟

前提條件
節點運行的守護進程如下:

master1
運行: webserver, scheduler

master2
運行:webserver

worker1
運行:worker

worker2
運行:worker

隊列服務處於運行中. (RabbitMQ, Redis, etc)

安裝 RabbitMQ 方法參見: http://site.clairvoyantsoft.com/installing-rabbitmq/

如果正在使用 RabbitMQ, 推薦 RabbitMQ 也做成高可用的集羣部署,併爲 RabbitMQ 實例配置負載均衡。
步驟

在所有需要運行守護進程的機器上安裝 Apache Airflow。具體安裝方法可參考 上面的簡單安裝。

修改 {AIRFLOW_HOME}/airflow.cfg 文件,確保所有機器使用同一份配置文件。

修改 Executor 爲 CeleryExecutor

executor = CeleryExecutor

指定元數據庫(metestore)

sql_alchemy_conn = mysql://{USERNAME}:{PASSWORD}@{MYSQL_HOST}:3306/airflow

設置中間人(broker)
如果使用 RabbitMQ

broker_url = amqp://guest:guest@{RABBITMQ_HOST}:5672/

如果使用 Redis

broker_url = redis://{REDIS_HOST}:6379/0  #使用數據庫 0 

設定結果存儲後端 backend

celery_result_backend = db+mysql://{USERNAME}:{PASSWORD}@{MYSQL_HOST}:3306/airflow

#當然您也可以使用 Redis :celery_result_backend =redis://{REDIS_HOST}:6379/1

在 master1 和 master2 上部署您的工作流(DAGs)。
在 master 1,初始 airflow 的元數據庫

$ airflow initdb

在 master1, 啓動相應的守護進程

$ airflow webserver
$ airflow scheduler

在 master2,啓動 Web Server

$ airflow webserver

在 worker1 和 worker2 啓動 worker

$ airflow worker

使用負載均衡處理 webserver

可以使用 nginx,AWS 等服務器處理 webserver 的負載均衡,不在此詳述
至此,所有均已集羣或高可用部署,apache-airflow 系統已堅不可摧。

官方文檔如下:
Documentation: https://airflow.incubator.apache.org/
Install Documentation: https://airflow.incubator.apache.org/installation.html
GitHub Repo: https://github.com/apache/incubator-airflow

參考鏈接:

如何部署一個健壯的 apache-airflow 調度系統

優化–架構和高可用集羣

airflow中web server和worker都可以啓動多個,但是scheduler只能啓動一個,這樣造成了airflow的單點,目前已經有第三方開源方案來解決這個問題:

Airflow Scheduler Failover Controller

地址:https://github.com/teamclairvoyant/airflow-scheduler-failover-controller

實現原理

The Airflow Scheduler Failover Controller (ASFC) 是一種保證機制,保證至少有一共scheduler在運行。

首先需要啓動ASFC 在每個我們規劃的用於運行scheduler的實例中。 當我們啓動多個 ASFC時, 有一個會是啓用狀態,其他是備用狀態。ASFC之間通過心跳機制跟蹤確認scheduler可用,如果 心跳丟失,則啓用備用ASFC。

活動狀態的ASFC 每10秒會檢查 scheduler的狀態。 如果沒有找到scheduler,會嘗試重啓schduler的daemon進程。
如果還是無法啓動,則會在其他節點啓用scheduler的進程。

安裝

# git clone https://github.com/teamclairvoyant/airflow-scheduler-failover-controller
# cd airflow-scheduler-failover-controller
# pip install -e .

報錯

Collecting airflow>=1.7.0 (from scheduler-failover-controller==1.0.1)
Could not find a version that satisfies the requirement airflow>=1.7.0 (from scheduler-failover-controller==1.0.1) (from versions: 0.6)
No matching distribution found for airflow>=1.7.0 (from scheduler-failover-controller==1.0.1)

查看

# vi setup.py
install_requires=[
'airflow>=1.7.0',
'kazoo>=2.2.1',
'coverage>=4.2',
'eventlet>=0.9.7',
],

# pip list|grep airflow
apache-airflow 1.10.0

需要將setup.py中airflow改爲apache-airflow,安裝之後啓動

# scheduler_failover_controller -h

會報錯

pkg_resources.ContextualVersionConflict: (Flask-Login 0.2.11 (/usr/lib64/python2.7/site-packages), Requirement.parse('Flask-Login<0.5,>=0.3'), set(['flask-appbuilder']))

重裝Flask-Login

# pip uninstall Flask-Login
# pip install Flask-Login

重裝之後是Flask-Login 0.4.1,滿足要求,但是又會報錯

apache-airflow 1.10.0 has requirement flask-login==0.2.11, but you'll have flask-login 0.4.1 which is incompatible.

Airflow Scheduler Failover Controller和airflow1.10.0不兼容;需要選用兼容的版本

優化–Sensor 的替代方案

Airflow 中有一類 Operator 被稱爲 Sensor,Sensor 可以感應預先設定的條件是否滿足(如:某個時間點是否達到、某條 MySQL 記錄是否被更新、某個 DAG 是否完成),當滿足條件後 Sensor 作業變爲 Success 使得下游的作業能夠執行。Sensor 的功能很強大但卻帶來一個問題,假如我們有一個 Sensor 用於檢測某個 MySQL 記錄是否被更新,在 Sensor 作業啓動後 3 個小時這個 MySQL 記錄才被更新。於是我們的這個 Sensor 佔用了一個 Worker 整整 3 小時,這顯然是一個極大的浪費。

因此我們需要一個 Sensor 的替代方案,既能滿足 Sensor 原來的功能,又能節省 Worker 資源。有一個辦法是不使用 Sensor,直接使用 Python Operator 判斷預先設定的條件是否滿足,如果不滿足直接 raise Exception,然後將這個作業的 retry_delay(重試間隔時間) 設爲每次檢測的間隔時間,retries(重試次數) 設爲最長檢測時間除以 retry_delay,即滿足:最長檢測時間 = retries * retry_delay。這樣既不會長時間佔用 Worker 資源,又可以滿足 Sensor 原來的功能。

優化–Airflow DAG Creation Manager Plugin

Airflow 雖然具有強大的功能,但是配置 DAG 並不是簡單的工作,也有一些較爲繁瑣的概念,對於業務人員來說可能略顯複雜。因此,筆者編寫了 Airflow DAG Creation Manager Plugin(https://github.com/lattebank/airflow-dag-creation-manager-plugin) 以提供一個 Web界面來讓業務人員可視化的編寫及管理 DAG。具體的安裝及使用方法請查看插件的README。

插件的 Web 界面中可以直接所見即所得的編寫 DAG 圖。

插件中儘量簡化了一些繁瑣的諸如上文所述的作業開始調度時間等一系列的概念,並提供了一些在實際工作中常常會用到的一些額外的功能(如上文提到的跳過非最新 DAG Run、當存在正在執行的 DAG Run 時跳過當前 DAG Run 等),以及版本控制和權限管理。如果大家在使用 Airflow 的過程中也有類似的問題,歡迎嘗試使用 Airflow DAG Creation Manager Plugin。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章