airflow源码分析之BashOperator

本文深入介绍了Airflow中的BashOperator,包括其基本功能、参数设置及执行流程。BashOperator主要用于执行shell命令或脚本,文章详细解释了如何通过配置实现任务调度。

BashOperator主要的功能是执行shell命令或者shell脚本。负责具体的执行过程的是BashOperator.execute()函数。
airflow的bash_operator.py文件:

from builtins import bytes
import os
import signal
from subprocess import Popen, STDOUT, PIPE
from tempfile import gettempdir, NamedTemporaryFile

from airflow.exceptions import AirflowException
from airflow.models import BaseOperator
from airflow.utils.decorators import apply_defaults
from airflow.utils.file import TemporaryDirectory


class BashOperator(BaseOperator):
    """
    :param xcom_push: If xcom_push is True, the last line written to stdout
        will also be pushed to an XCom when the bash command completes.
    :type xcom_push: bool
    :param env: If env is not None, it must be a mapping that defines the
        environment variables for the new process; these are used instead
        of inheriting the current process environment, which is the default
        behavior. (templated)
    :type env: dict
    :type output_encoding: output encoding of bash command
    """
    template_fields = ('bash_command', 'env')
    template_ext = ('.sh', '.bash',)
    ui_color = '#f0ede4'

    @apply_defaults    # 处理默认的参数
    def __init__(
            self,
            bash_command, # string 可以是单独的命令,或者是命令集,或者是.sh文件
            xcom_push=False,
            env=None,
            output_encoding='utf-8',
            *args, **kwargs):

        super(BashOperator, self).__init__(*args, **kwargs)
        self.bash_command = bash_command
        self.env = env
        self.xcom_push_flag = xcom_push
        self.output_encoding = output_encoding

    def execute(self, context):
        """
        Execute the bash command in a temporary directory
        which will be cleaned afterwards
        """
        bash_command = self.bash_command
        self.log.info("Tmp dir root location: \n %s", gettempdir())
        with TemporaryDirectory(prefix='airflowtmp') as tmp_dir:
            with NamedTemporaryFile(dir=tmp_dir, prefix=self.task_id) as f:

                f.write(bytes(bash_command, 'utf_8'))
                f.flush()
                fname = f.name
                script_location = tmp_dir + "/" + fname
                self.log.info(
                    "Temporary script location: %s",
                    script_location
                )
                def pre_exec():
                    # Restore default signal disposition and invoke setsid
                    for sig in ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ'):
                        if hasattr(signal, sig):
                            signal.signal(getattr(signal, sig), signal.SIG_DFL)
                    os.setsid()
                self.log.info("Running command: %s", bash_command)
                sp = Popen(
                    ['bash', fname],
                    stdout=PIPE, stderr=STDOUT,
                    cwd=tmp_dir, env=self.env,
                    preexec_fn=pre_exec)

                self.sp = sp

                self.log.info("Output:")
                line = ''
                for line in iter(sp.stdout.readline, b''):
                    line = line.decode(self.output_encoding).strip()
                    self.log.info(line)
                sp.wait()
                self.log.info(
                    "Command exited with return code %s",
                    sp.returncode
                )

                if sp.returncode:
                    raise AirflowException("Bash command failed")

        if self.xcom_push_flag:
            return line

    def on_kill(self):
        self.log.info('Sending SIGTERM signal to bash process group')
        os.killpg(os.getpgid(self.sp.pid), signal.SIGTERM)

TemporaryDirectory:创建一个临时的目录,它使用了@contextmanager,生成了一个上下文管理器,因此它能用在with环境里。用contextmanager装饰的函数,要返回一个生成器,并且只能返回一个值

@contextmanager  # 生成一个上下文管理器
def TemporaryDirectory(suffix='', prefix=None, dir=None):
    name = mkdtemp(suffix=suffix, prefix=prefix, dir=dir)  # suffix:后缀  prefix: 前缀
    try:
        yield name    # yield 生成器 仅返回一个值
    finally:
        try:
            shutil.rmtree(name)   # 当with结束之后,删除临时目录
        except OSError as e:
            # ENOENT - no such file or directory
            if e.errno != errno.ENOENT:
                raise e

NamedTemporaryFile: 创建一个临时的文件,它继承了一个类,这个类实现了__enter__, exit 方法,因此能用with
pre_exec: 捕捉信号,并进行信号处理
subprocess.Popen: 具体执行command或者shell脚本

<think>好的,用户想分析Apache Airflow源码结构和实现细节,需要一份解析其架构与实现的回答。首先,我需要回忆一下Airflow的基本架构。根据引用[4],Executor是负责运行任务的组件,每个任务分配给Executor执行,并且可以分布在不同的机器或容器中。这说明Airflow的架构是分布式的,可能需要了解其核心组件如Scheduler、Worker、Web Server等。 接下来,用户需要源码结构分析。我应该先查看官方文档和源码仓库,确定主要模块。通常,像Airflow这样的项目会有核心模块如airflow/models、airflow/executors等。需要解释每个模块的作用,比如DAG解析、任务调度、执行器等。 然后,关于实现细节,可能需要深入Scheduler的工作流程,比如如何解析DAG文件,生成DagRun和TaskInstance。这里要结合引用[1]提到的层次化设计理念,说明各个组件如何分层协作。此外,Executor的实现也很重要,比如常用的LocalExecutor、CeleryExecutor,以及他们如何与工作进程交互,这部分可以引用[4]的内容。 用户可能对扩展机制感兴趣,比如如何自定义Operator、Hook或Plugin。需要参考引用[2]中提到的Task类型,说明如何利用现有Operator或开发新的。同时,动态任务生成和DAG版本控制也是高级特性,可能需要结合源码中的示例进行说明。 另外,用户的问题涉及源码解析,可能需要指导如何开始阅读源码,比如从核心模型开始,逐步深入调度器和执行器。同时,建议查阅官方文档(引用[2])和社区资源,帮助理解实现细节。 最后,确保回答符合系统级指令:使用正确的中文,数学格式,生成相关问题。需要检查是否有遗漏的引用,比如引用[3]提到的Helm部署可能与源码结构关系不大,但如果有涉及部署的部分可能需要提及。不过用户主要关注源码,所以可能不需要涉及部署图表。 总结思路:先概述架构,再分模块解析源码结构,详细说明关键实现如Scheduler和Executor,讨论扩展机制,最后给出学习建议和相关问题。</think>### Apache Airflow 源码解析:架构与实现 #### 一、核心架构 Apache Airflow 采用**主从分布式架构**,核心组件包括: 1. **Web Server**:提供可视化界面,展示DAG状态和任务日志。 2. **Scheduler**:负责解析DAG文件、生成任务实例并调度执行[^1]。 3. **Executor**:根据配置类型(如`LocalExecutor`/`CeleryExecutor`)执行具体任务[^4]。 4. **Worker**:实际执行任务的进程(仅在分布式模式下存在)。 各组件通过**元数据库**(默认SQLite,生产环境常用MySQL/PostgreSQL)同步状态。 #### 二、源码结构分析 主要模块路径及功能: ``` airflow/ ├── models/ # 数据模型 │ ├── dag.py # DAG定义类 │ └── taskinstance.py # 任务实例状态管理 ├── executors/ # 执行器实现 │ ├── local_executor.py # 本地执行 │ └── celery_executor.py# 分布式执行[^4] ├── operators/ # 预定义任务类型 │ └── http_operator.py # HTTP请求Operator[^2] ├── hooks/ # 外部系统连接 ├── macros/ # 模板变量扩展 └── www/ # Web界面后端 ``` #### 三、关键实现细节 1. **DAG解析机制** - Scheduler 定期扫描`dags/`目录下的Python文件 - 通过`executed import`方式加载DAG对象,生成**DagRun**和**TaskInstance** - 使用`sqlalchemy`将任务状态持久化到数据库 2. **调度器工作流** ```python # 伪代码流程 while True: dags = load_dags_from_files() # 加载DAG定义 for dag in dags: create_dag_run_if_needed() # 生成DagRun task_instances = dag.get_task_instances() schedule_tasks(task_instances) # 状态检查与任务分发 sleep(scheduler_heartbeat_sec) # 心跳间隔 ``` 3. **Executor执行逻辑**(以CeleryExecutor为例): ```python class CeleryExecutor(BaseExecutor): def execute_async(self, task_instance): # 将任务发送到消息队列 celery_task = send_task_to_queue(task_instance) self.running_tasks[task_instance.key] = celery_task def sync(self): # 轮询任务状态并更新数据库 for task in poll_celery_tasks(): handle_task_state_change(task) ``` #### 四、扩展机制 1. **自定义Operator开发** 继承`BaseOperator`并实现`execute`方法: ```python class MyOperator(BaseOperator): def __init__(self, my_param, **kwargs): super().__init__(**kwargs) self.my_param = my_param def execute(self, context): logging.info(f"Processing with {self.my_param}") ``` 2. **插件系统** 在`plugins/`目录下创建插件模块,可扩展菜单、视图等Web界面功能。 #### 五、学习建议 1. **源码阅读路线**: - 从`airflow/models/base.py`理解基础模型 - 研究`airflow/jobs/scheduler_job.py`调度逻辑 - 分析`airflow/executors/sequential_executor.py`执行流程 2. **调试技巧**: ```bash # 启动带调试的Scheduler airflow scheduler --debug ``` 3. **官方资源**: - [Operator类型文档](http://airflow.apache.org/docs/apache-airflow/stable/concepts/operators.html) - [Executor实现原理](https://airflow.apache.org/docs/apache-airflow/stable/executor/index.html)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值