Cách thích hợp để tạo quy trình làm việc động trong Luồng không khí


98

Vấn đề

Có cách nào trong Luồng không khí để tạo luồng công việc sao cho số lượng nhiệm vụ B. * là không xác định cho đến khi hoàn thành Nhiệm vụ A? Tôi đã xem xét các thẻ phụ nhưng có vẻ như nó chỉ có thể hoạt động với một tập hợp nhiệm vụ tĩnh phải được xác định khi tạo Dag.

Liệu trình kích hoạt dag có hoạt động không? Và nếu vậy, bạn có thể vui lòng cung cấp một ví dụ.

Tôi có một vấn đề là không thể biết số lượng nhiệm vụ B sẽ cần thiết để tính toán Nhiệm vụ C cho đến khi Nhiệm vụ A đã hoàn thành. Mỗi Nhiệm vụ B. * sẽ mất vài giờ để tính toán và không thể kết hợp với nhau.

              |---> Task B.1 --|
              |---> Task B.2 --|
 Task A ------|---> Task B.3 --|-----> Task C
              |       ....     |
              |---> Task B.N --|

Ý tưởng số 1

Tôi không thích giải pháp này vì tôi phải tạo Bộ kiểm soát bên ngoài chặn và tất cả Nhiệm vụ B. * sẽ mất từ ​​2-24 giờ để hoàn thành. Vì vậy tôi không coi đây là một giải pháp khả thi. Chắc chắn có một cách dễ dàng hơn? Hay Airflow không được thiết kế cho việc này?

Dag 1
Task A -> TriggerDagRunOperator(Dag 2) -> ExternalTaskSensor(Dag 2, Task Dummy B) -> Task C

Dag 2 (Dynamically created DAG though python_callable in TriggerDagrunOperator)
               |-- Task B.1 --|
               |-- Task B.2 --|
Task Dummy A --|-- Task B.3 --|-----> Task Dummy B
               |     ....     |
               |-- Task B.N --|

Chỉnh sửa 1:

Cho đến nay câu hỏi này vẫn chưa có một câu trả lời tuyệt vời . Tôi đã được một số người liên hệ để tìm giải pháp.


Có phải tất cả các nhiệm vụ B * đều giống nhau, ở chỗ chúng có thể được tạo trong một vòng lặp?
Daniel Lee

Có tất cả các nhiệm vụ B. * có thể được tạo nhanh chóng trong một vòng lặp sau khi Nhiệm vụ A đã hoàn thành. Nhiệm vụ A mất khoảng 2 giờ để hoàn thành.
costrouc

Bạn đã tìm ra giải pháp cho vấn đề chưa? bạn có thể đăng nó không?
Daniel Dubovski

3
Một tài nguyên hữu ích cho Idea # 1: linkedin.com/pulse/…
Juan Riaza

1
Đây là một bài viết tôi đã viết giải thích cách thực hiện điều này linkedin.com/pulse/dynamic-workflows-airflow-kyle-bridenstine
Kyle Bridenstine

Câu trả lời:


33

Đây là cách tôi đã thực hiện với một yêu cầu tương tự mà không có bất kỳ thẻ phụ nào:

Đầu tiên, hãy tạo một phương thức trả về bất kỳ giá trị nào bạn muốn

def values_function():
     return values

Phương thức tạo tiếp theo sẽ tạo các công việc động:

def group(number, **kwargs):
        #load the values if needed in the command you plan to execute
        dyn_value = "{{ task_instance.xcom_pull(task_ids='push_func') }}"
        return BashOperator(
                task_id='JOB_NAME_{}'.format(number),
                bash_command='script.sh {} {}'.format(dyn_value, number),
                dag=dag)

Và sau đó kết hợp chúng:

push_func = PythonOperator(
        task_id='push_func',
        provide_context=True,
        python_callable=values_function,
        dag=dag)

complete = DummyOperator(
        task_id='All_jobs_completed',
        dag=dag)

for i in values_function():
        push_func >> group(i) >> complete

Các giá trị được xác định ở đâu?
tu

11
Thay vì for i in values_function()tôi sẽ mong đợi một cái gì đó như thế for i in push_func_output. Vấn đề là tôi không thể tìm ra cách để có được đầu ra đó một cách động. Đầu ra của PythonOperator sẽ ở trong Xcom sau khi thực thi nhưng tôi không biết liệu tôi có thể tham chiếu nó từ định nghĩa DAG hay không.
Ena

@Ena Bạn đã tìm ra cách để đạt được điều đó chưa?
domainsos

1
@eldos xem câu trả lời của tôi bên dưới
Ena

1
Điều gì sẽ xảy ra nếu chúng ta phải thực hiện một loạt các bước phụ thuộc vào các bước trong vòng lặp? Liệu có một chuỗi phụ thuộc thứ hai trong grouphàm không?
CodingInCircles

12

Tôi đã tìm ra cách tạo quy trình làm việc dựa trên kết quả của các nhiệm vụ trước đó.
Về cơ bản những gì bạn muốn làm là có hai thẻ phụ như sau:

  1. Xcom đẩy một danh sách (hoặc những gì bạn cần để tạo dòng công việc động sau này) trong thẻ phụ được thực thi trước (xem test1.py def return_list())
  2. Chuyển đối tượng dag chính làm tham số cho thẻ phụ thứ hai của bạn
  3. Bây giờ nếu bạn có đối tượng dag chính, bạn có thể sử dụng nó để lấy danh sách các thể hiện nhiệm vụ của nó. Từ danh sách các trường hợp tác vụ đó, bạn có thể lọc ra một tác vụ đang chạy hiện tại bằng cách sử dụng parent_dag.get_task_instances(settings.Session, start_date=parent_dag.get_active_runs()[-1])[-1]), người ta có thể thêm nhiều bộ lọc hơn ở đây.
  4. Với phiên bản tác vụ đó, bạn có thể sử dụng xcom pull để nhận giá trị bạn cần bằng cách chỉ định dag_id cho một trong những thẻ phụ đầu tiên: dag_id='%s.%s' % (parent_dag_name, 'test1')
  5. Sử dụng danh sách / giá trị để tạo động tác vụ của bạn

Bây giờ tôi đã thử nghiệm điều này trong cài đặt luồng không khí cục bộ của tôi và nó hoạt động tốt. Tôi không biết liệu phần kéo xcom có ​​gặp vấn đề gì không nếu có nhiều phiên bản dag chạy cùng một lúc, nhưng khi đó có thể bạn sẽ sử dụng một khóa duy nhất hoặc thứ gì đó tương tự để xác định duy nhất xcom giá trị bạn muốn. Người ta có thể tối ưu hóa bước 3. để chắc chắn 100% nhận được một tác vụ cụ thể của dag chính hiện tại, nhưng đối với việc sử dụng của tôi, điều này hoạt động đủ tốt, tôi nghĩ người ta chỉ cần một đối tượng task_instance để sử dụng xcom_pull.

Ngoài ra, tôi làm sạch các xcom cho thẻ phụ đầu tiên trước mỗi lần thực thi, chỉ để đảm bảo rằng tôi không vô tình nhận được bất kỳ giá trị sai nào.

Tôi khá tệ trong việc giải thích, vì vậy tôi hy vọng đoạn mã sau sẽ làm cho mọi thứ rõ ràng:

test1.py

from airflow.models import DAG
import logging
from airflow.operators.python_operator import PythonOperator
from airflow.operators.postgres_operator import PostgresOperator

log = logging.getLogger(__name__)


def test1(parent_dag_name, start_date, schedule_interval):
    dag = DAG(
        '%s.test1' % parent_dag_name,
        schedule_interval=schedule_interval,
        start_date=start_date,
    )

    def return_list():
        return ['test1', 'test2']

    list_extract_folder = PythonOperator(
        task_id='list',
        dag=dag,
        python_callable=return_list
    )

    clean_xcoms = PostgresOperator(
        task_id='clean_xcoms',
        postgres_conn_id='airflow_db',
        sql="delete from xcom where dag_id='{{ dag.dag_id }}'",
        dag=dag)

    clean_xcoms >> list_extract_folder

    return dag

test2.py

from airflow.models import DAG, settings
import logging
from airflow.operators.dummy_operator import DummyOperator

log = logging.getLogger(__name__)


def test2(parent_dag_name, start_date, schedule_interval, parent_dag=None):
    dag = DAG(
        '%s.test2' % parent_dag_name,
        schedule_interval=schedule_interval,
        start_date=start_date
    )

    if len(parent_dag.get_active_runs()) > 0:
        test_list = parent_dag.get_task_instances(settings.Session, start_date=parent_dag.get_active_runs()[-1])[-1].xcom_pull(
            dag_id='%s.%s' % (parent_dag_name, 'test1'),
            task_ids='list')
        if test_list:
            for i in test_list:
                test = DummyOperator(
                    task_id=i,
                    dag=dag
                )

    return dag

và quy trình làm việc chính:

test.py

from datetime import datetime
from airflow import DAG
from airflow.operators.subdag_operator import SubDagOperator
from subdags.test1 import test1
from subdags.test2 import test2

DAG_NAME = 'test-dag'

dag = DAG(DAG_NAME,
          description='Test workflow',
          catchup=False,
          schedule_interval='0 0 * * *',
          start_date=datetime(2018, 8, 24))

test1 = SubDagOperator(
    subdag=test1(DAG_NAME,
                 dag.start_date,
                 dag.schedule_interval),
    task_id='test1',
    dag=dag
)

test2 = SubDagOperator(
    subdag=test2(DAG_NAME,
                 dag.start_date,
                 dag.schedule_interval,
                 parent_dag=dag),
    task_id='test2',
    dag=dag
)

test1 >> test2

trên Airflow 1.9, chúng không tải khi được thêm vào thư mục DAG, tôi thiếu cái gì?
Anthony Keane

@AnthonyKeane Bạn đã đặt test1.py và test2.py vào một thư mục có tên là thẻ phụ trong thư mục dag của bạn chưa?
Christopher Beck

Tôi đã đồng ý. Đã sao chép cả hai tệp để gắn thẻ phụ và đặt test.py trong thư mục dag, vẫn gặp lỗi này. DAG bị hỏng: [/home/airflow/gcs/dags/test.py] Không có mô-đun nào có tên là subags.test1 Lưu ý rằng tôi đang sử dụng Google Cloud Composer (Airflow 1.9.0 được quản lý của Google)
Anthony Keane

@AnthonyKeane có phải đây là lỗi duy nhất bạn thấy trong nhật ký không? DAG bị hỏng có thể do thẻ phụ có lỗi biên dịch.
Christopher Beck

3
Xin chào @Christopher Beck Tôi đã tìm thấy sai lầm của mình mà tôi cần thêm _ _init_ _.pyvào thư mục thẻ phụ. lỗi tân binh
Anthony Keane

10

Có, điều này là có thể Tôi đã tạo một DAG ví dụ minh họa điều này.

import airflow
from airflow.operators.python_operator import PythonOperator
import os
from airflow.models import Variable
import logging
from airflow import configuration as conf
from airflow.models import DagBag, TaskInstance
from airflow import DAG, settings
from airflow.operators.bash_operator import BashOperator

main_dag_id = 'DynamicWorkflow2'

args = {
    'owner': 'airflow',
    'start_date': airflow.utils.dates.days_ago(2),
    'provide_context': True
}

dag = DAG(
    main_dag_id,
    schedule_interval="@once",
    default_args=args)


def start(*args, **kwargs):

    value = Variable.get("DynamicWorkflow_Group1")
    logging.info("Current DynamicWorkflow_Group1 value is " + str(value))


def resetTasksStatus(task_id, execution_date):
    logging.info("Resetting: " + task_id + " " + execution_date)

    dag_folder = conf.get('core', 'DAGS_FOLDER')
    dagbag = DagBag(dag_folder)
    check_dag = dagbag.dags[main_dag_id]
    session = settings.Session()

    my_task = check_dag.get_task(task_id)
    ti = TaskInstance(my_task, execution_date)
    state = ti.current_state()
    logging.info("Current state of " + task_id + " is " + str(state))
    ti.set_state(None, session)
    state = ti.current_state()
    logging.info("Updated state of " + task_id + " is " + str(state))


def bridge1(*args, **kwargs):

    # You can set this value dynamically e.g., from a database or a calculation
    dynamicValue = 2

    variableValue = Variable.get("DynamicWorkflow_Group2")
    logging.info("Current DynamicWorkflow_Group2 value is " + str(variableValue))

    logging.info("Setting the Airflow Variable DynamicWorkflow_Group2 to " + str(dynamicValue))
    os.system('airflow variables --set DynamicWorkflow_Group2 ' + str(dynamicValue))

    variableValue = Variable.get("DynamicWorkflow_Group2")
    logging.info("Current DynamicWorkflow_Group2 value is " + str(variableValue))

    # Below code prevents this bug: https://issues.apache.org/jira/browse/AIRFLOW-1460
    for i in range(dynamicValue):
        resetTasksStatus('secondGroup_' + str(i), str(kwargs['execution_date']))


def bridge2(*args, **kwargs):

    # You can set this value dynamically e.g., from a database or a calculation
    dynamicValue = 3

    variableValue = Variable.get("DynamicWorkflow_Group3")
    logging.info("Current DynamicWorkflow_Group3 value is " + str(variableValue))

    logging.info("Setting the Airflow Variable DynamicWorkflow_Group3 to " + str(dynamicValue))
    os.system('airflow variables --set DynamicWorkflow_Group3 ' + str(dynamicValue))

    variableValue = Variable.get("DynamicWorkflow_Group3")
    logging.info("Current DynamicWorkflow_Group3 value is " + str(variableValue))

    # Below code prevents this bug: https://issues.apache.org/jira/browse/AIRFLOW-1460
    for i in range(dynamicValue):
        resetTasksStatus('thirdGroup_' + str(i), str(kwargs['execution_date']))


def end(*args, **kwargs):
    logging.info("Ending")


def doSomeWork(name, index, *args, **kwargs):
    # Do whatever work you need to do
    # Here I will just create a new file
    os.system('touch /home/ec2-user/airflow/' + str(name) + str(index) + '.txt')


starting_task = PythonOperator(
    task_id='start',
    dag=dag,
    provide_context=True,
    python_callable=start,
    op_args=[])

# Used to connect the stream in the event that the range is zero
bridge1_task = PythonOperator(
    task_id='bridge1',
    dag=dag,
    provide_context=True,
    python_callable=bridge1,
    op_args=[])

DynamicWorkflow_Group1 = Variable.get("DynamicWorkflow_Group1")
logging.info("The current DynamicWorkflow_Group1 value is " + str(DynamicWorkflow_Group1))

for index in range(int(DynamicWorkflow_Group1)):
    dynamicTask = PythonOperator(
        task_id='firstGroup_' + str(index),
        dag=dag,
        provide_context=True,
        python_callable=doSomeWork,
        op_args=['firstGroup', index])

    starting_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(bridge1_task)

# Used to connect the stream in the event that the range is zero
bridge2_task = PythonOperator(
    task_id='bridge2',
    dag=dag,
    provide_context=True,
    python_callable=bridge2,
    op_args=[])

DynamicWorkflow_Group2 = Variable.get("DynamicWorkflow_Group2")
logging.info("The current DynamicWorkflow value is " + str(DynamicWorkflow_Group2))

for index in range(int(DynamicWorkflow_Group2)):
    dynamicTask = PythonOperator(
        task_id='secondGroup_' + str(index),
        dag=dag,
        provide_context=True,
        python_callable=doSomeWork,
        op_args=['secondGroup', index])

    bridge1_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(bridge2_task)

ending_task = PythonOperator(
    task_id='end',
    dag=dag,
    provide_context=True,
    python_callable=end,
    op_args=[])

DynamicWorkflow_Group3 = Variable.get("DynamicWorkflow_Group3")
logging.info("The current DynamicWorkflow value is " + str(DynamicWorkflow_Group3))

for index in range(int(DynamicWorkflow_Group3)):

    # You can make this logic anything you'd like
    # I chose to use the PythonOperator for all tasks
    # except the last task will use the BashOperator
    if index < (int(DynamicWorkflow_Group3) - 1):
        dynamicTask = PythonOperator(
            task_id='thirdGroup_' + str(index),
            dag=dag,
            provide_context=True,
            python_callable=doSomeWork,
            op_args=['thirdGroup', index])
    else:
        dynamicTask = BashOperator(
            task_id='thirdGroup_' + str(index),
            bash_command='touch /home/ec2-user/airflow/thirdGroup_' + str(index) + '.txt',
            dag=dag)

    bridge2_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(ending_task)

# If you do not connect these then in the event that your range is ever zero you will have a disconnection between your stream
# and your tasks will run simultaneously instead of in your desired stream order.
starting_task.set_downstream(bridge1_task)
bridge1_task.set_downstream(bridge2_task)
bridge2_task.set_downstream(ending_task)

Trước khi bạn chạy DAG, hãy tạo ba Biến luồng khí này

airflow variables --set DynamicWorkflow_Group1 1

airflow variables --set DynamicWorkflow_Group2 0

airflow variables --set DynamicWorkflow_Group3 0

Bạn sẽ thấy rằng DAG đi từ

nhập mô tả hình ảnh ở đây

Về điều này sau khi nó chạy

nhập mô tả hình ảnh ở đây

Bạn có thể xem thêm thông tin về DAG này trong bài viết của tôi về tạo Quy trình công việc động trên luồng không khí .


1
Nhưng điều gì sẽ xảy ra nếu bạn có nhiều DagRun của DAG này. Tất cả chúng có chia sẻ cùng một Biến không?
Mar-k

1
Có, họ sẽ sử dụng cùng một biến; Tôi giải quyết vấn đề này trong bài viết của tôi ở cuối cùng. Bạn sẽ cần tạo động biến và sử dụng id chạy dag trong tên biến. Ví dụ của tôi rất đơn giản chỉ để chứng minh khả năng năng động nhưng bạn sẽ cần phải làm cho nó có chất lượng sản xuất :)
Kyle Bridenstine

Các cầu nối có cần thiết khi tạo các tác vụ động không? Sẽ đọc đầy đủ bài viết của bạn trong giây lát, nhưng muốn hỏi. Tôi đang vật lộn với việc tạo một tác vụ động dựa trên một tác vụ ngược dòng ngay bây giờ và đang bắt đầu xem xét lại xem tôi đã sai ở đâu. Vấn đề hiện tại của tôi là vì một số lý do tôi không thể lấy DAG để đồng bộ hóa với DAG-Bag. DAG của tôi đã đồng bộ hóa khi tôi đang sử dụng danh sách tĩnh trong mô-đun, nhưng đã dừng lại khi tôi chuyển danh sách tĩnh đó ra để được tạo từ một tác vụ ngược dòng.
lucid_goose

Điều này rất thông minh
jvans

1
@jvans cảm ơn nó thông minh nhưng có thể không phải là chất lượng sản xuất
Kyle Bridenstine.

6

OA: "Có cách nào trong Luồng không khí để tạo luồng công việc sao cho số lượng nhiệm vụ B. * là không xác định cho đến khi hoàn thành nhiệm vụ A?"

Câu trả lời ngắn gọn là không. Luồng không khí sẽ xây dựng luồng DAG trước khi bắt đầu chạy nó.

Điều đó nói rằng chúng tôi đã đi đến một kết luận đơn giản, đó là chúng tôi không có nhu cầu như vậy. Khi bạn muốn thực hiện song song một số công việc, bạn nên đánh giá các nguồn lực bạn có sẵn chứ không phải số lượng các hạng mục cần xử lý.

Chúng tôi đã làm như thế này: chúng tôi tự động tạo ra một số tác vụ cố định, chẳng hạn như 10, sẽ chia nhỏ công việc. Ví dụ: nếu chúng ta cần xử lý 100 tệp, mỗi tác vụ sẽ xử lý 10 tệp trong số đó. Tôi sẽ đăng mã sau ngày hôm nay.

Cập nhật

Đây là mã, xin lỗi vì sự chậm trễ.

from datetime import datetime, timedelta

import airflow
from airflow.operators.dummy_operator import DummyOperator

args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2018, 1, 8),
    'email': ['myemail@gmail.com'],
    'email_on_failure': True,
    'email_on_retry': True,
    'retries': 1,
    'retry_delay': timedelta(seconds=5)
}

dag = airflow.DAG(
    'parallel_tasks_v1',
    schedule_interval="@daily",
    catchup=False,
    default_args=args)

# You can read this from variables
parallel_tasks_total_number = 10

start_task = DummyOperator(
    task_id='start_task',
    dag=dag
)


# Creates the tasks dynamically.
# Each one will elaborate one chunk of data.
def create_dynamic_task(current_task_number):
    return DummyOperator(
        provide_context=True,
        task_id='parallel_task_' + str(current_task_number),
        python_callable=parallelTask,
        # your task will take as input the total number and the current number to elaborate a chunk of total elements
        op_args=[current_task_number, int(parallel_tasks_total_number)],
        dag=dag)


end = DummyOperator(
    task_id='end',
    dag=dag)

for page in range(int(parallel_tasks_total_number)):
    created_task = create_dynamic_task(page)
    start_task >> created_task
    created_task >> end

Giải thích mã:

Ở đây chúng ta có một tác vụ bắt đầu duy nhất và một tác vụ kết thúc duy nhất (cả hai đều là giả).

Sau đó, từ tác vụ bắt đầu với vòng lặp for, chúng tôi tạo 10 tác vụ với cùng một python có thể gọi. Các tác vụ được tạo trong hàm create_dynamic_task.

Đối với mỗi python có thể gọi, chúng tôi chuyển dưới dạng đối số là tổng số tác vụ song song và chỉ mục tác vụ hiện tại.

Giả sử bạn có 1000 mục để xây dựng: nhiệm vụ đầu tiên sẽ nhận được đầu vào mà nó phải xây dựng phần đầu tiên trong số 10 phần. Nó sẽ chia 1000 mục thành 10 phần và xây dựng phần đầu tiên.


1
Đây là một giải pháp tốt, miễn là bạn không cần phải là một nhiệm vụ cụ thể cho một mặt hàng (như tiến độ, kết quả, thành công / thất bại, lần thử lại, vv)
Alonzzo2

@Ena parallelTaskkhông được xác định: tôi có thiếu cái gì đó không?
Anthony Keane

2
@AnthonyKeane Đây là hàm python bạn nên gọi để thực sự làm điều gì đó. Như đã nhận xét trong đoạn mã, nó sẽ lấy tổng số và số hiện tại để xây dựng một phần của tổng số phần tử.
Ena

4

Những gì tôi nghĩ bạn đang tìm kiếm là tạo DAG động. Tôi đã gặp loại tình huống này vài ngày trước sau một số tìm kiếm, tôi đã tìm thấy blog này .

Tạo tác vụ động

start = DummyOperator(
    task_id='start',
    dag=dag
)

end = DummyOperator(
    task_id='end',
    dag=dag)

def createDynamicETL(task_id, callableFunction, args):
    task = PythonOperator(
        task_id = task_id,
        provide_context=True,
        #Eval is used since the callableFunction var is of type string
        #while the python_callable argument for PythonOperators only receives objects of type callable not strings.
        python_callable = eval(callableFunction),
        op_kwargs = args,
        xcom_push = True,
        dag = dag,
    )
    return task

Đặt quy trình làm việc DAG

with open('/usr/local/airflow/dags/config_files/dynamicDagConfigFile.yaml') as f:
    # Use safe_load instead to load the YAML file
    configFile = yaml.safe_load(f)

    # Extract table names and fields to be processed
    tables = configFile['tables']

    # In this loop tasks are created for each table defined in the YAML file
    for table in tables:
        for table, fieldName in table.items():
            # In our example, first step in the workflow for each table is to get SQL data from db.
            # Remember task id is provided in order to exchange data among tasks generated in dynamic way.
            get_sql_data_task = createDynamicETL('{}-getSQLData'.format(table),
                                                 'getSQLData',
                                                 {'host': 'host', 'user': 'user', 'port': 'port', 'password': 'pass',
                                                  'dbname': configFile['dbname']})

            # Second step is upload data to s3
            upload_to_s3_task = createDynamicETL('{}-uploadDataToS3'.format(table),
                                                 'uploadDataToS3',
                                                 {'previous_task_id': '{}-getSQLData'.format(table),
                                                  'bucket_name': configFile['bucket_name'],
                                                  'prefix': configFile['prefix']})

            # This is where the magic lies. The idea is that
            # once tasks are generated they should linked with the
            # dummy operators generated in the start and end tasks. 
            # Then you are done!
            start >> get_sql_data_task
            get_sql_data_task >> upload_to_s3_task
            upload_to_s3_task >> end

Đây là cách DAG của chúng tôi trông như thế nào sau khi đặt mã lại với nhau nhập mô tả hình ảnh ở đây

import yaml
import airflow
from airflow import DAG
from datetime import datetime, timedelta, time
from airflow.operators.python_operator import PythonOperator
from airflow.operators.dummy_operator import DummyOperator

start = DummyOperator(
    task_id='start',
    dag=dag
)


def createDynamicETL(task_id, callableFunction, args):
    task = PythonOperator(
        task_id=task_id,
        provide_context=True,
        # Eval is used since the callableFunction var is of type string
        # while the python_callable argument for PythonOperators only receives objects of type callable not strings.
        python_callable=eval(callableFunction),
        op_kwargs=args,
        xcom_push=True,
        dag=dag,
    )
    return task


end = DummyOperator(
    task_id='end',
    dag=dag)

with open('/usr/local/airflow/dags/config_files/dynamicDagConfigFile.yaml') as f:
    # use safe_load instead to load the YAML file
    configFile = yaml.safe_load(f)

    # Extract table names and fields to be processed
    tables = configFile['tables']

    # In this loop tasks are created for each table defined in the YAML file
    for table in tables:
        for table, fieldName in table.items():
            # In our example, first step in the workflow for each table is to get SQL data from db.
            # Remember task id is provided in order to exchange data among tasks generated in dynamic way.
            get_sql_data_task = createDynamicETL('{}-getSQLData'.format(table),
                                                 'getSQLData',
                                                 {'host': 'host', 'user': 'user', 'port': 'port', 'password': 'pass',
                                                  'dbname': configFile['dbname']})

            # Second step is upload data to s3
            upload_to_s3_task = createDynamicETL('{}-uploadDataToS3'.format(table),
                                                 'uploadDataToS3',
                                                 {'previous_task_id': '{}-getSQLData'.format(table),
                                                  'bucket_name': configFile['bucket_name'],
                                                  'prefix': configFile['prefix']})

            # This is where the magic lies. The idea is that
            # once tasks are generated they should linked with the
            # dummy operators generated in the start and end tasks. 
            # Then you are done!
            start >> get_sql_data_task
            get_sql_data_task >> upload_to_s3_task
            upload_to_s3_task >> end

Nó rất hữu ích với hy vọng nó cũng sẽ giúp được một số người khác


Bạn đã đạt được nó một mình chưa? Tôi mệt mỏi. Nhưng tôi đã thất bại.
Newt

Vâng, nó đã làm việc cho tôi. Bạn đang phải đối mặt với vấn đề gì?
Muhammad Bin Ali

1
Tôi hiểu rồi. Vấn đề của tôi đã được giải quyết. Cảm ơn. Tôi chỉ không hiểu đúng cách để đọc các biến môi trường trong hình ảnh docker.
Newt

1
Điều gì sẽ xảy ra nếu các mục trong bảng có thể thay đổi, do đó chúng tôi không thể đưa chúng vào tệp yaml tĩnh?
FrankZhu

Nó thực sự phụ thuộc vào nơi bạn đang sử dụng nó. Mặc dù tôi sẽ quan tâm đến những gì bạn đề nghị. @FrankZhu nó phải được thực hiện như thế nào cho đúng?
Muhammad Bin Ali

3

Tôi nghĩ rằng tôi đã tìm thấy một giải pháp tốt hơn cho vấn đề này tại https://github.com/mastak/airflow_multi_dagrun , sử dụng cách sắp xếp các DagRuns đơn giản bằng cách kích hoạt nhiều dagruns, tương tự như TriggerDagRuns . Hầu hết các khoản tín dụng đều truy cập https://github.com/mastak , mặc dù tôi đã phải vá một số chi tiết để làm cho nó hoạt động với luồng không khí gần đây nhất.

Giải pháp sử dụng một toán tử tùy chỉnh để kích hoạt một số DagRuns :

from airflow import settings
from airflow.models import DagBag
from airflow.operators.dagrun_operator import DagRunOrder, TriggerDagRunOperator
from airflow.utils.decorators import apply_defaults
from airflow.utils.state import State
from airflow.utils import timezone


class TriggerMultiDagRunOperator(TriggerDagRunOperator):
    CREATED_DAGRUN_KEY = 'created_dagrun_key'

    @apply_defaults
    def __init__(self, op_args=None, op_kwargs=None,
                 *args, **kwargs):
        super(TriggerMultiDagRunOperator, self).__init__(*args, **kwargs)
        self.op_args = op_args or []
        self.op_kwargs = op_kwargs or {}

    def execute(self, context):

        context.update(self.op_kwargs)
        session = settings.Session()
        created_dr_ids = []
        for dro in self.python_callable(*self.op_args, **context):
            if not dro:
                break
            if not isinstance(dro, DagRunOrder):
                dro = DagRunOrder(payload=dro)

            now = timezone.utcnow()
            if dro.run_id is None:
                dro.run_id = 'trig__' + now.isoformat()

            dbag = DagBag(settings.DAGS_FOLDER)
            trigger_dag = dbag.get_dag(self.trigger_dag_id)
            dr = trigger_dag.create_dagrun(
                run_id=dro.run_id,
                execution_date=now,
                state=State.RUNNING,
                conf=dro.payload,
                external_trigger=True,
            )
            created_dr_ids.append(dr.id)
            self.log.info("Created DagRun %s, %s", dr, now)

        if created_dr_ids:
            session.commit()
            context['ti'].xcom_push(self.CREATED_DAGRUN_KEY, created_dr_ids)
        else:
            self.log.info("No DagRun created")
        session.close()

Sau đó, bạn có thể gửi một số dagruns từ hàm có thể gọi trong PythonOperator của mình, ví dụ:

from airflow.operators.dagrun_operator import DagRunOrder
from airflow.models import DAG
from airflow.operators import TriggerMultiDagRunOperator
from airflow.utils.dates import days_ago


def generate_dag_run(**kwargs):
    for i in range(10):
        order = DagRunOrder(payload={'my_variable': i})
        yield order

args = {
    'start_date': days_ago(1),
    'owner': 'airflow',
}

dag = DAG(
    dag_id='simple_trigger',
    max_active_runs=1,
    schedule_interval='@hourly',
    default_args=args,
)

gen_target_dag_run = TriggerMultiDagRunOperator(
    task_id='gen_target_dag_run',
    dag=dag,
    trigger_dag_id='common_target',
    python_callable=generate_dag_run
)

Tôi đã tạo một fork bằng mã tại https://github.com/flinz/airflow_multi_dagrun


3

Biểu đồ công việc không được tạo trong thời gian chạy. Thay vào đó, biểu đồ được tạo khi nó được Airflow chọn từ thư mục dags của bạn. Do đó, sẽ không thực sự có thể có một biểu đồ khác cho công việc mỗi khi nó chạy. Bạn có thể định cấu hình công việc để xây dựng biểu đồ dựa trên truy vấn tại thời điểm tải . Biểu đồ đó sẽ không thay đổi cho mỗi lần chạy sau đó, điều này có lẽ không hữu ích lắm.

Bạn có thể thiết kế một biểu đồ thực thi các tác vụ khác nhau trên mỗi lần chạy dựa trên kết quả truy vấn bằng cách sử dụng Toán tử nhánh.

Những gì tôi đã làm là định cấu hình trước một tập hợp các nhiệm vụ và sau đó lấy kết quả truy vấn và phân phối chúng trên các nhiệm vụ. Điều này có lẽ tốt hơn dù sao đi nữa vì nếu truy vấn của bạn trả về nhiều kết quả, bạn có thể không muốn làm ngập bộ lập lịch với nhiều tác vụ đồng thời. Để an toàn hơn nữa, tôi cũng đã sử dụng một nhóm để đảm bảo đồng thời của tôi không vượt ra khỏi tầm tay với một truy vấn lớn bất ngờ.

"""
 - This is an idea for how to invoke multiple tasks based on the query results
"""
import logging
from datetime import datetime

from airflow import DAG
from airflow.hooks.postgres_hook import PostgresHook
from airflow.operators.mysql_operator import MySqlOperator
from airflow.operators.python_operator import PythonOperator, BranchPythonOperator
from include.run_celery_task import runCeleryTask

########################################################################

default_args = {
    'owner': 'airflow',
    'catchup': False,
    'depends_on_past': False,
    'start_date': datetime(2019, 7, 2, 19, 50, 00),
    'email': ['rotten@stackoverflow'],
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 0,
    'max_active_runs': 1
}

dag = DAG('dynamic_tasks_example', default_args=default_args, schedule_interval=None)

totalBuckets = 5

get_orders_query = """
select 
    o.id,
    o.customer
from 
    orders o
where
    o.created_at >= current_timestamp at time zone 'UTC' - '2 days'::interval
    and
    o.is_test = false
    and
    o.is_processed = false
"""

###########################################################################################################

# Generate a set of tasks so we can parallelize the results
def createOrderProcessingTask(bucket_number):
    return PythonOperator( 
                           task_id=f'order_processing_task_{bucket_number}',
                           python_callable=runOrderProcessing,
                           pool='order_processing_pool',
                           op_kwargs={'task_bucket': f'order_processing_task_{bucket_number}'},
                           provide_context=True,
                           dag=dag
                          )


# Fetch the order arguments from xcom and doStuff() to them
def runOrderProcessing(task_bucket, **context):
    orderList = context['ti'].xcom_pull(task_ids='get_open_orders', key=task_bucket)

    if orderList is not None:
        for order in orderList:
            logging.info(f"Processing Order with Order ID {order[order_id]}, customer ID {order[customer_id]}")
            doStuff(**op_kwargs)


# Discover the orders we need to run and group them into buckets for processing
def getOpenOrders(**context):
    myDatabaseHook = PostgresHook(postgres_conn_id='my_database_conn_id')

    # initialize the task list buckets
    tasks = {}
    for task_number in range(0, totalBuckets):
        tasks[f'order_processing_task_{task_number}'] = []

    # populate the task list buckets
    # distribute them evenly across the set of buckets
    resultCounter = 0
    for record in myDatabaseHook.get_records(get_orders_query):

        resultCounter += 1
        bucket = (resultCounter % totalBuckets)

        tasks[f'order_processing_task_{bucket}'].append({'order_id': str(record[0]), 'customer_id': str(record[1])})

    # push the order lists into xcom
    for task in tasks:
        if len(tasks[task]) > 0:
            logging.info(f'Task {task} has {len(tasks[task])} orders.')
            context['ti'].xcom_push(key=task, value=tasks[task])
        else:
            # if we didn't have enough tasks for every bucket
            # don't bother running that task - remove it from the list
            logging.info(f"Task {task} doesn't have any orders.")
            del(tasks[task])

    return list(tasks.keys())

###################################################################################################


# this just makes sure that there aren't any dangling xcom values in the database from a crashed dag
clean_xcoms = MySqlOperator(
    task_id='clean_xcoms',
    mysql_conn_id='airflow_db',
    sql="delete from xcom where dag_id='{{ dag.dag_id }}'",
    dag=dag)


# Ideally we'd use BranchPythonOperator() here instead of PythonOperator so that if our
# query returns fewer results than we have buckets, we don't try to run them all.
# Unfortunately I couldn't get BranchPythonOperator to take a list of results like the
# documentation says it should (Airflow 1.10.2). So we call all the bucket tasks for now.
get_orders_task = PythonOperator(
                                 task_id='get_orders',
                                 python_callable=getOpenOrders,
                                 provide_context=True,
                                 dag=dag
                                )
get_orders_task.set_upstream(clean_xcoms)

# set up the parallel tasks -- these are configured at compile time, not at run time:
for bucketNumber in range(0, totalBuckets):
    taskBucket = createOrderProcessingTask(bucketNumber)
    taskBucket.set_upstream(get_orders_task)


###################################################################################################

Lưu ý rằng có vẻ như có thể tạo thẻ phụ ngay lập tức do kết quả của một tác vụ, tuy nhiên, hầu hết các tài liệu về thẻ phụ mà tôi đã tìm thấy đều khuyên bạn nên tránh xa tính năng đó vì nó gây ra nhiều vấn đề hơn là cách giải quyết trong hầu hết các trường hợp. Tôi đã thấy các đề xuất rằng thẻ phụ có thể sớm bị xóa như một tính năng tích hợp sẵn.
thối

Cũng lưu ý rằng trong for tasks in tasksvòng lặp trong ví dụ của tôi, tôi xóa đối tượng mà tôi đang lặp lại. Đó là một ý tưởng tồi. Thay vào đó, hãy lấy danh sách các khóa và lặp lại nó - hoặc bỏ qua các thao tác xóa. Tương tự, nếu xcom_pull trả về None (thay vì danh sách hoặc danh sách trống) thì vòng lặp for cũng không thành công. Người ta có thể muốn chạy xcom_pull trước 'for', sau đó kiểm tra xem nó có phải là Không - hoặc đảm bảo có ít nhất một danh sách trống ở đó. YMMV. Chúc may mắn!
thối

1
cái gì trong open_order_task?
alltej

Bạn nói đúng, đó là lỗi đánh máy trong ví dụ của tôi. Nó phải là get_orders_task.set_upstream (). Tôi sẽ sửa chữa nó.
thối

0

Không hiểu vấn đề là gì?

Đây là một ví dụ tiêu chuẩn. Bây giờ nếu trong hàm phụ thay thế for i in range(5):bằng for i in range(random.randint(0, 10)):thì mọi thứ sẽ hoạt động. Bây giờ, hãy tưởng tượng rằng toán tử 'start' đặt dữ liệu vào một tệp và thay vì một giá trị ngẫu nhiên, hàm sẽ đọc dữ liệu này. Sau đó, toán tử 'bắt đầu' sẽ ảnh hưởng đến số lượng nhiệm vụ.

Vấn đề sẽ chỉ nằm ở phần hiển thị trong giao diện người dùng vì khi nhập thẻ phụ, số tác vụ sẽ bằng với lần đọc cuối cùng từ tệp / cơ sở dữ liệu / XCom vào lúc này. Điều này tự động đưa ra hạn chế đối với nhiều lần khởi chạy một dag cùng một lúc.


-1

Tôi tìm thấy bài đăng này trên Phương tiện rất giống với câu hỏi này. Tuy nhiên, nó có đầy lỗi chính tả và không hoạt động khi tôi thử triển khai nó.

Câu trả lời của tôi cho vấn đề trên như sau:

Nếu bạn đang tạo nhiệm vụ động, bạn phải làm như vậy bằng cách lặp qua một cái gì đó không được tạo bởi tác vụ ngược dòng hoặc có thể được xác định độc lập với tác vụ đó. Tôi biết được rằng bạn không thể chuyển ngày thực thi hoặc các biến luồng không khí khác cho một thứ gì đó bên ngoài khuôn mẫu (ví dụ: một nhiệm vụ) như nhiều người khác đã chỉ ra trước đây. Xem thêm bài đăng này .


Nếu bạn nhìn vào nhận xét của tôi, bạn sẽ thấy rằng thực sự có thể tạo các tác vụ dựa trên kết quả của các tác vụ ngược dòng.
Christopher Beck,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.