airflow源码分析之BashOperator

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

### Airflow 和 XXL-Job 的特点与差异 #### 定义与起源 Airflow 是由 Airbnb 开源的一款基于 Python 实现的任务调度工具,最初于 2014 年启动并于次年春天正式对外公布其源码[^1]。而 XXL-JOB 则是一个分布式任务调度平台,采用 Java 进行开发,在国内拥有较高的知名度。 #### 架构设计 Airflow 使用有向无环图 (Directed Acyclic Graph, DAG) 来描述作业流程中的各个节点及其依赖关系,这使得它能够灵活处理复杂的工作流逻辑并提供良好的可扩展性和维护性。相比之下,XXL-JOB 更侧重于简单易用的任务管理和执行机制,提供了图形化的界面用于配置定时任务以及监控运行状态。 #### 功能特性 - **编程语言**: Airflow 主要面向熟悉 Python 编程环境的开发者;XXL-JOB 针对的是更广泛的Java生态系统的使用者群体。 - **社区支持与文档质量**: Airflow 加入了 Apache 孵化器项目,并且有着活跃的国际社区贡献者队伍为其持续改进功能特性和修复漏洞问题; 反观 XXL-JOB 虽然在国内有一定的用户基础和技术交流圈子,但在全球范围内的影响力相对较小一些。 - **部署方式灵活性**: Airflow 支持多种数据库作为元数据存储后端选项(如 MySQL、PostgreSQL),同时也兼容 Kubernetes 等容器编排框架下的弹性伸缩需求; 对于 XXL-JOB,则通常建议搭配 Zookeeper 或 Redis 实现高可用集群模式下的协调管理服务。 - **可视化操作体验**: 尽管两者都具备 Web UI 接口供管理员查看历史记录或调整参数设置等功能,不过 Airflow 的前端页面更加注重数据分析场景的应用展示效果,内置丰富的图表组件辅助理解业务指标变化趋势;XXL-JOB 的UI则更为简洁直观,适合快速上手入门级别的运维人员使用。 ```python from airflow import DAG from datetime import timedelta from airflow.operators.bash_operator import BashOperator default_args = { 'owner': 'airflow', 'depends_on_past': False, 'start_date': days_ago(2), } dag = DAG( dag_id='example_bash_operator', default_args=default_args, schedule_interval=timedelta(days=1)) t1 = BashOperator(task_id='print_date', bash_command='date', dag=dag) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值