एयरफ्लो में गतिशील वर्कफ़्लोज़ बनाने का उचित तरीका


98

मुसीबत

क्या Airflow में कोई वर्कफ़्लो बनाने का कोई तरीका है जैसे टास्क ए के पूरा होने तक बी। के कार्यों की संख्या अज्ञात है? मैंने सबडैग्स को देखा है, लेकिन ऐसा लग रहा है कि यह केवल स्थिर कार्यों के साथ काम कर सकता है जिन्हें डेग निर्माण में निर्धारित किया जाना है।

डेग ट्रिगर काम करेगा? और यदि ऐसा है तो आप एक उदाहरण प्रदान कर सकते हैं।

मेरे पास एक मुद्दा है जहां टास्क सी की गणना टास्क ए पूरी होने तक करने के लिए कार्य बी की संख्या जानना असंभव है। प्रत्येक टास्क B. * को गणना करने में कई घंटे लगेंगे और इसे संयोजित नहीं किया जा सकता है।

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

विचार # 1

मुझे यह समाधान पसंद नहीं है क्योंकि मुझे एक ब्लॉकिंग एक्सट्रास्कैन्सर सेंसर बनाना है और सभी टास्क बी * को पूरा करने में 2-24 घंटे लगेंगे। इसलिए मैं इसे एक व्यवहार्य समाधान नहीं मानता। निश्चित रूप से एक आसान तरीका है? या एयरफ्लो इसके लिए डिज़ाइन नहीं किया गया था?

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 --|

1 संपादित करें:

अभी तक इस सवाल का जवाब अभी भी नहीं है । मुझे कई लोगों द्वारा एक समाधान की तलाश में संपर्क किया गया है।


क्या सभी कार्य B * समान हैं, जिसमें वे एक लूप में बनाए जा सकते हैं?
डेनियल ली

हाँ सभी बी। कार्य टास्क ए पूरा होने के बाद एक लूप में जल्दी से बनाया जा सकता है। टास्क ए को पूरा होने में लगभग 2 घंटे लगते हैं।
costrouc

क्या आपको समस्या का हल मिला? क्या आप इसे पोस्ट करना चाहेंगे?
डैनियल डुबोव्स्की

3
आइडिया # 1 के लिए एक उपयोगी संसाधन: लिंक्डइन.
com/pulse/…

1
यहाँ एक लेख है जो मैंने यह समझाते हुए लिखा है कि इस लिंक्डइन
।pulse/dynamic-workflows-airflow

जवाबों:


33

यहाँ है कि मैंने इसे बिना किसी सबडैग्स के समान अनुरोध के साथ कैसे किया:

पहले एक ऐसा तरीका बनाएं जो आपको जो भी मान चाहे वापस लौटाए

def values_function():
     return values

अगला वह तरीका बनाएं जो नौकरियों को गतिशील रूप से उत्पन्न करेगा:

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)

और फिर उन्हें संयोजित करें:

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

मूल्यों को कहां परिभाषित किया जाता है?
भिक्षु

11
इसके बजाय for i in values_function()मुझे कुछ उम्मीद होगी for i in push_func_output। समस्या यह है कि मुझे उस आउटपुट को गतिशील रूप से प्राप्त करने का कोई तरीका नहीं मिल रहा है। पायथनऑपरेटर का आउटपुट निष्पादन के बाद एक्सकॉम में होगा लेकिन मुझे नहीं पता कि क्या मैं इसे डीएजी परिभाषा से संदर्भित कर सकता हूं।
Ena

@ ईना क्या आपको इसे हासिल करने का कोई तरीका मिला?
eldos

1
@eldos नीचे मेरा जवाब देखें
Ena

1
क्या होगा अगर हमें पाश के भीतर कई चरणों पर निर्भर चरणों की एक श्रृंखला करनी पड़े? क्या groupफ़ंक्शन के भीतर दूसरी निर्भरता श्रृंखला होगी ?
कोडिंगइन्कल्स

12

मैंने पिछले कार्यों के परिणाम के आधार पर वर्कफ़्लो बनाने का एक तरीका निकाला है।
मूल रूप से आप जो करना चाहते हैं, उसमें निम्न के साथ दो उप-खंड हैं:

  1. XCOM एक सूची धक्का (test1.py देख subdag कि पहले निष्पादित हो जाता है में (या कभी क्या आप गतिशील कार्यप्रवाह बाद में बनाने की जरूरत) def return_list())
  2. अपने दूसरे सबडैग के पैरामीटर के रूप में मुख्य डैग ऑब्जेक्ट को पास करें
  3. अब यदि आपके पास मुख्य डैग ऑब्जेक्ट है, तो आप इसका उपयोग इसके कार्य उदाहरणों की सूची प्राप्त करने के लिए कर सकते हैं। कार्य इंस्टेंसेस की उस सूची से, आप वर्तमान रन के किसी कार्य को उपयोग करके फ़िल्टर कर सकते हैं parent_dag.get_task_instances(settings.Session, start_date=parent_dag.get_active_runs()[-1])[-1]), कोई शायद यहाँ और अधिक फ़िल्टर जोड़ सकता है।
  4. उस कार्य के उदाहरण के साथ, आप पहले subdag में से एक में dag_id निर्दिष्ट करके मूल्य की आवश्यकता के लिए xcom पुल का उपयोग कर सकते हैं: dag_id='%s.%s' % (parent_dag_name, 'test1')
  5. अपने कार्यों को गतिशील बनाने के लिए सूची / मूल्य का उपयोग करें

अब मैंने अपने स्थानीय एयरफ़्लो इंस्टॉलेशन में इसका परीक्षण किया है और यह ठीक काम करता है। मुझे नहीं पता कि अगर xcom पुल के हिस्से में कोई समस्या है, अगर एक ही समय में चल रहे डेग के एक से अधिक उदाहरण हैं, लेकिन तब आप शायद या तो एक अनोखी कुंजी का उपयोग करेंगे या कुछ ऐसा होगा जो विशिष्ट रूप से xcom की पहचान करेगा मूल्य आप चाहते हैं वर्तमान मुख्य डाग का एक विशिष्ट कार्य प्राप्त करने के लिए 100% सुनिश्चित होने के लिए कोई भी 3. कदम का अनुकूलन कर सकता है, लेकिन मेरे उपयोग के लिए यह पर्याप्त रूप से अच्छा प्रदर्शन करता है, मुझे लगता है कि एक को xcom_pull का उपयोग करने के लिए केवल एक कार्य_बिनेंस ऑब्जेक्ट की आवश्यकता है।

इसके अलावा मैं हर निष्पादन से पहले पहले सबडैग के लिए एक्सकॉम को साफ करता हूं, बस यह सुनिश्चित करने के लिए कि मुझे गलती से कोई गलत मूल्य नहीं मिलता है।

मैं समझाने में बहुत बुरा हूँ, इसलिए मुझे आशा है कि निम्नलिखित कोड सब कुछ स्पष्ट कर देगा:

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

और मुख्य कार्य:

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

Airflow 1.9 पर ये DAG फ़ोल्डर में जोड़े जाने पर लोड नहीं हुआ, क्या मुझे कुछ याद आ रहा है?
एंथनी कीन

@AnthonyKeane आपने अपने dag फ़ोल्डर में subdags नामक फ़ोल्डर में test1.py और test2.py रखा था?
क्रिस्टोफर बेक

मैंने हाँ कर दी। दोनों फाइलों को सबडैग में कॉपी किया और टेस्टाचैट को डैग फोल्डर में रखा, फिर भी यह त्रुटि मिलती है। टूटा हुआ DAG: [/ home/airflow/gcs/dags/test.py] जिसका कोई भी उप नाम नहीं है subdags.test1 नोट: मैं Google क्लाउड कंपोज़र (Google का प्रबंधित Airflow 1.9.0) का उपयोग कर रहा हूं
एंथनी कीन

@AnthonyKeane क्या यह एकमात्र त्रुटि है जिसे आप लॉग में देखते हैं? टूटी हुई डीएजी उप-संकलित त्रुटि के कारण हो सकती है।
क्रिस्टोफर बेक

3
हाय _ _init_ _.py@ क्रिसटन बेक मुझे मेरी गलती मिली जो मुझे सबडैग्स फ़ोल्डर में जोड़ने की आवश्यकता थी । धोखेबाज़ त्रुटि
एंथनी कीन

9

हां यह संभव है कि मैंने एक उदाहरण DAG बनाया है जो इसे प्रदर्शित करता है।

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)

डीएजी चलाने से पहले आप इन तीन एयरफ्लो वेरिएबल्स का निर्माण करें

airflow variables --set DynamicWorkflow_Group1 1

airflow variables --set DynamicWorkflow_Group2 0

airflow variables --set DynamicWorkflow_Group3 0

आप देखेंगे कि DAG इसी से जाता है

यहाँ छवि विवरण दर्ज करें

इसके बाद यह भाग गया

यहाँ छवि विवरण दर्ज करें

डायनामिक वर्कफ़्लोज़ ऑन एयरफ़्लो बनाने के बारे में मेरे लेख में आप इस डीएजी पर अधिक जानकारी देख सकते हैं ।


1
लेकिन क्या होता है अगर आपके पास इस DAGR के कई DagRun हैं। क्या वे सभी एक ही चर को साझा करते हैं?
मार्च-

1
हाँ वे एक ही चर का उपयोग करेंगे; मैं इसे अपने लेख में बहुत अंत में संबोधित करता हूं। आपको डायनामिक रूप से वेरिएबल बनाने और वैग रन आईडी में वैरिएबल नाम का उपयोग करने की आवश्यकता होगी। मेरा उदाहरण केवल गतिशील संभावना को प्रदर्शित करने के लिए सरल है, लेकिन आपको इसे उत्पादन की गुणवत्ता बनाने की आवश्यकता होगी :)
काइल ब्रिडेनस्टाइन

क्या गतिशील कार्य बनाते समय पुल आवश्यक हैं? आपके लेख को पूरी तरह से क्षण भर में पढ़ लेंगे, लेकिन पूछना चाहते थे। मैं अभी एक अपस्ट्रीम कार्य के आधार पर एक डायनेमिक कार्य बनाने के साथ संघर्ष कर रहा हूं, और यह पता लगाने के लिए शुरू कर रहा हूं कि मैंने कहां गलत किया है। मेरा वर्तमान मुद्दा यह है कि किसी कारण से मुझे डीएजी-बैग में सिंक करने के लिए डीएजी नहीं मिला। जब मैं मॉड्यूल में स्थिर सूची का उपयोग कर रहा था, तब मेरा DAG सिंक हुआ, लेकिन जब मैंने उस स्थिर सूची को एक अपस्ट्रीम कार्य से बनाया जाना बंद कर दिया।
ल्यूसिड_गोसे

यह बहुत चालाक है
jvans

1
@jvans धन्यवाद यह चतुर है, लेकिन उत्पादन की गुणवत्ता की संभावना नहीं है
काइल ब्रिडेनस्टाइन

6

OA: "क्या एयरफ्लो में कोई तरीका है जिससे वर्कफ़्लो बनाया जा सके कि टास्क ए पूरा होने तक बी। की संख्या अज्ञात है?"

लघु उत्तर नहीं है। Airflow इसे चलाने के लिए शुरू करने से पहले DAG प्रवाह का निर्माण करेगा।

हमने कहा कि हम एक सरल निष्कर्ष पर आए हैं, कि हमें ऐसी आवश्यकता नहीं है। जब आप कुछ काम को समानांतर करना चाहते हैं, तो आपको उन संसाधनों का मूल्यांकन करना चाहिए जो आपके पास उपलब्ध हैं और संसाधित करने के लिए मदों की संख्या नहीं है।

हमने इसे इस तरह किया था: हम गतिशील रूप से कार्यों की एक निश्चित संख्या उत्पन्न करते हैं, कहते हैं कि 10, काम को विभाजित करेगा। उदाहरण के लिए यदि हमें 100 फाइलों को प्रोसेस करने की आवश्यकता है तो प्रत्येक कार्य उनमें से 10 को प्रोसेस करेगा। मैं आज बाद में कोड पोस्ट करूंगा।

अपडेट करें

यहाँ कोड है, देरी के लिए खेद है।

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

कोड स्पष्टीकरण:

यहां हमारे पास एक एकल कार्य और एक एकल कार्य (दोनों डमी) हैं।

फिर लूप के लिए प्रारंभ कार्य से हम एक ही अजगर के साथ 10 कार्य बनाते हैं। फ़ंक्शन create_dynamic_task में कार्य बनाए जाते हैं।

प्रत्येक अजगर को कॉल करने योग्य के लिए हम तर्क के रूप में समानांतर कार्यों की कुल संख्या और वर्तमान कार्य सूचकांक को पास करते हैं।

मान लें कि आपके पास विस्तृत करने के लिए 1000 वस्तुएं हैं: पहला कार्य इनपुट में प्राप्त होगा कि इसे 10 खंडों में से पहला हिस्सा विस्तृत करना चाहिए। यह 1000 वस्तुओं को 10 विखंडू में विभाजित करेगा और पहले वाले को विस्तृत करेगा।


1
यह एक अच्छा समाधान है, जब तक आपको प्रति आइटम एक विशिष्ट कार्य की आवश्यकता नहीं होती है (जैसे प्रगति, परिणाम, सफलता / असफलता,
रिट्रीट

@ ईना parallelTaskपरिभाषित नहीं है: क्या मुझे कुछ याद आ रहा है?
एंथनी कीन

2
@AnthonyKeane यह अजगर का कार्य है जिसे आपको वास्तव में कुछ करने के लिए कॉल करना चाहिए। जैसा कि कोड में टिप्पणी की गई है, यह कुल संख्या और वर्तमान संख्या को इनपुट के रूप में लेगा ताकि कुल तत्वों का एक हिस्सा विस्तृत हो सके।
एना

4

मुझे लगता है कि आपके लिए देख रहे हैं DAG गतिशील बना रहा है मैं इस तरह की स्थिति का सामना कुछ दिनों पहले किया था कुछ खोज के बाद मुझे यह ब्लॉग मिला ।

डायनेमिक टास्क जनरेशन

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

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

कोड डालने के बाद हमारा DAG कैसा दिखता है यहाँ छवि विवरण दर्ज करें

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

यह पूरी उम्मीद थी कि यह किसी और की भी मदद करेगा


क्या आपने इसे खुद से हासिल किया है? मैं थक गया। लेकिन मैं असफल रहा।
न्यूटन

हां, इसने मेरे लिए काम किया। आप किस मुद्दे का सामना कर रहे हैं?
मुहम्मद बिन अली

1
मैं समझ गया। मेरी समस्या हल हो गई है। धन्यवाद। मुझे डॉकटर छवियों में पर्यावरण चर को पढ़ने का सही तरीका नहीं मिला।
न्यूटन

1
क्या होगा यदि तालिका आइटम बदल सकते हैं, इस प्रकार हम उन्हें एक स्थिर यमल फ़ाइल में नहीं डाल सकते हैं?
फ्रैंकजु

यह वास्तव में निर्भर करता है कि आप इसका उपयोग कहां कर रहे हैं। हालाँकि मैं आपको क्या सुझाव देना चाहूंगा। @FrankZhu यह कैसे ठीक से किया जाना चाहिए?
मुहम्मद बिन अली

3

मुझे लगता है कि मैं इस के लिए एक अच्छा समाधान पाया है https://github.com/mastak/airflow_multi_dagrun , जो कई dagruns, के लिए इसी तरह ट्रिगर द्वारा DagRuns के सरल enqueuing का उपयोग करता TriggerDagRuns । अधिकांश क्रेडिट https://github.com/mastak पर जाते हैं , हालांकि मुझे सबसे हाल के एयरफ्लो के साथ काम करने के लिए कुछ विवरणों को पैच करना पड़ा ।

समाधान एक कस्टम ऑपरेटर का उपयोग करता है जो कई 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()

आप उदाहरण के लिए, अपने पायथनऑपरेटर में कॉल करने योग्य फ़ंक्शन से कई डगरुन सबमिट कर सकते हैं:

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
)

मैंने https://github.com/flinz/airflow_multi_dagrun पर कोड के साथ एक कांटा बनाया


3

नौकरियों का ग्राफ रन टाइम पर उत्पन्न नहीं होता है। बल्कि ग्राफ़ तब बनाया जाता है जब वह आपके डैग्स फ़ोल्डर से एयरफ्लो द्वारा उठाया जाता है। इसलिए यह वास्तव में हर बार जब यह चलता है तो नौकरी के लिए एक अलग ग्राफ होना संभव नहीं है। आप लोड समय पर क्वेरी के आधार पर ग्राफ़ बनाने के लिए किसी कार्य को कॉन्फ़िगर कर सकते हैं । यह ग्राफ उसके बाद के प्रत्येक रन के लिए समान रहेगा, जो शायद बहुत उपयोगी नहीं है।

आप एक ग्राफ़ डिज़ाइन कर सकते हैं जो शाखा ऑपरेटर के उपयोग से क्वेरी परिणामों के आधार पर प्रत्येक रन पर विभिन्न कार्यों को निष्पादित करता है।

मैंने जो किया है वह कार्यों के एक सेट को पूर्व-कॉन्फ़िगर करने के लिए है और फिर क्वेरी परिणामों को लें और उन्हें कार्यों में वितरित करें। यह शायद किसी भी तरह बेहतर है क्योंकि यदि आपकी क्वेरी बहुत अधिक परिणाम देती है, तो आप किसी भी तरह से समवर्ती कार्यों के साथ शेड्यूलर को बाढ़ नहीं करना चाहते हैं। और भी सुरक्षित होने के लिए, मैंने यह सुनिश्चित करने के लिए एक पूल का उपयोग किया कि मेरी संगति अप्रत्याशित रूप से बड़ी क्वेरी के साथ हाथ से बाहर न निकले।

"""
 - 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)


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

ध्यान दें कि ऐसा लगता है कि कार्य के परिणामस्वरूप मक्खी पर सबडैग बनाना संभव हो सकता है, हालांकि, सबडैग्स पर अधिकांश प्रलेखन जो मैंने पाया है कि उस सुविधा से दूर रहने की सलाह देते हैं क्योंकि यह हल करने की तुलना में अधिक समस्याओं का कारण बनता है। ज्यादातर मामलों में। मैंने ऐसे सुझाव देखे हैं कि कुछ ही समय में सबडैग्स को एक अंतर्निहित फीचर के रूप में हटाया जा सकता है।
सड़ा हुआ

यह भी ध्यान दें कि for tasks in tasksमेरे उदाहरण में लूप में, मैं उस ऑब्जेक्ट को हटा देता हूं जिसे मैं देख रहा हूं। यह एक बुरा विचार है। इसके बजाय कुंजियों की एक सूची प्राप्त करें और उस पर पुनरावृति करें - या हटाए गए को छोड़ दें। इसी तरह, अगर xcom_pull कोई नहीं लौटाता है (एक सूची या खाली सूची के बजाय) जो लूप के लिए भी विफल रहता है। कोई 'के लिए' से पहले xcom_pull चलाना चाह सकता है, और फिर जांचें कि क्या यह कोई नहीं है - या सुनिश्चित करें कि वहां कम से कम एक खाली सूची है। YMMV। शुभ लाभ!
सड़ा हुआ

1
क्या है open_order_task?
alltej

आप सही हैं, यह मेरे उदाहरण में एक टाइपो है। यह get_orders_task.set_upstream () होना चाहिए। मैं इसे ठीक कर दूंगा।
सड़ा हुआ

0

समझ में नहीं आता कि समस्या क्या है?

यहाँ एक मानक उदाहरण है। अब यदि फ़ंक्शन सबडैग केfor i in range(5): साथ बदल जाता है for i in range(random.randint(0, 10)):तो सब कुछ काम करेगा। अब कल्पना करें कि ऑपरेटर 'स्टार्ट' डेटा को एक फ़ाइल में रखता है, और एक यादृच्छिक मूल्य के बजाय, फ़ंक्शन इस डेटा को पढ़ेगा। तब ऑपरेटर 'स्टार्ट' कार्यों की संख्या को प्रभावित करेगा।

समस्या केवल यूआई में डिस्प्ले में होगी क्योंकि सबडैग में प्रवेश करने के बाद, कार्यों की संख्या इस समय फ़ाइल / डेटाबेस / एक्सकॉम से अंतिम रीड के बराबर होगी। जो स्वचालित रूप से एक समय में एक डेग के कई लॉन्च पर प्रतिबंध देता है।


-1

मुझे यह मीडियम पोस्ट मिला जो इस सवाल से काफी मिलता-जुलता है। हालांकि यह टाइपो से भरा है, और जब मैंने इसे लागू करने की कोशिश की तो काम नहीं करता।

उपरोक्त के लिए मेरा उत्तर इस प्रकार है:

यदि आप गतिशील रूप से कार्यों का निर्माण कर रहे हैं, तो आपको ऐसा कुछ करना होगा जो एक अपस्ट्रीम कार्य द्वारा नहीं बनाया गया हो, या उस कार्य से स्वतंत्र रूप से परिभाषित किया जा सकता है। मैंने सीखा है कि आप किसी टेम्पलेट (जैसे, एक कार्य) के बाहर निष्पादन की तारीखों या अन्य एयरफ़्लो चर को पास नहीं कर सकते हैं, जैसा कि कई अन्य लोगों ने पहले भी बताया है। इस पोस्ट को भी देखें ।


यदि आप मेरी टिप्पणी पर एक नज़र डालते हैं, तो आप देखेंगे कि वास्तव में अपस्ट्रीम कार्यों के परिणाम के आधार पर कार्य बनाना संभव है।
क्रिस्टोफर बेक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.