1. 这不是“又一个调度工具”——Airflow 的工程本质是什么Apache Airflow 在 GitHub 上突破 4.6 万 Star绝非偶然。它早已不是“写几个 Python 函数就能跑任务”的玩具级调度器而是一套以 DAG有向无环图为契约、以 Operator 为原子单元、以 Executor 为执行引擎、以元数据为状态中枢的分布式工作流操作系统。我从 2018 年开始在金融风控中落地 Airflow经历过从单机开发环境到支撑日均 30 万 任务、跨 7 个业务域、SLA 要求 99.95% 的生产集群演进。很多人一上来就问“怎么装 Airflow”但真正卡住团队的从来不是 pip install airflow 这一行命令而是当 DAG 文件从 3 个涨到 300 个当任务依赖从线性变成网状嵌套当某次上游数据延迟 2 分钟导致下游 17 个关键报表全部告警时你靠什么定位靠什么止损靠什么证明这不是你的锅这正是标题里“工程深度评测”四个字的分量所在——它不讲“Airflow 能做什么”而聚焦“在真实企业级场景下它必须承担什么、实际能扛住什么、哪些地方会悄无声息地崩掉”。比如Airflow 的 Web UI 显示“Success”但业务方反馈“昨天的用户画像表少了一半数据”这种问题根本不会在日志里报错它藏在 Task Instance 的queued_dttm和start_date之间那 47 秒的排队延迟里再比如你用BashOperator调用一个 shell 脚本脚本里echo done执行成功了但 Airflow 却标记为失败——因为脚本末尾少了一个exit 0而 Airflow 默认把任何非零退出码都判为失败。这些不是文档里写的“注意事项”而是我在给某头部电商做数仓迁移时连续三天守着 Grafana 看task_duration指标曲线比对scheduler_loop_count和dagbag_import_timeout参数后才摸清的底层逻辑。所以这篇内容面向的不是想“试试 Airflow”的新手而是已经或即将把 Airflow 接入核心业务链路的工程师、架构师、数据平台负责人。你需要知道它的调度器Scheduler不是“轮询数据库”而是基于事件驱动的异步状态机它的 Executor 不是“执行命令”而是资源调度策略的具象化它的元数据库Metadata DB一旦锁表超过 3 秒整个集群的 DAG 解析就会雪崩。这些细节决定了你是在用 Airflow 建造一座桥还是在搭一座随时可能塌陷的浮桥。2. 架构拆解为什么 Airflow 的“简单”全是表象2.1 四层架构从代码到生产的不可见链条Airflow 的官方架构图常被简化为“Web Server Scheduler Worker”但这严重掩盖了其真实复杂度。一个任务从 Python 文件定义到最终在 Kubernetes Pod 中运行要穿越四层关键抽象层每一层都存在隐性耦合与性能拐点DAG 层声明式契约层这是用户唯一接触的代码层。但DAG(dag_idetl_user, schedule_intervaldaily)这行代码背后Airflow 会在每次 Scheduler Loop 中解析整个 DAG 文件构建 DAG 对象树并序列化存入元数据库。当 DAG 文件包含大量动态生成逻辑如for i in range(100): task PythonOperator(...)解析耗时会从毫秒级飙升至秒级。我曾见过一个含 287 个动态任务的 DAG单次解析占用 Scheduler CPU 92%直接拖垮整个集群的 DAG 更新频率。TaskInstance 层状态原子层每个任务实例TaskInstance不是独立实体而是元数据库中的一条记录包含state、start_date、end_date、duration、try_number等 20 字段。它的状态变更如从scheduled→queued→running不是内存操作而是通过 SQLAlchemy 的UPDATE ... WHERE语句完成。这意味着每秒 1000 次任务状态更新等于每秒 1000 次数据库写入。PostgreSQL 在高并发 UPDATE 下极易出现行锁等待而 Airflow 的 Scheduler 正是靠轮询这些状态来驱动调度逻辑——锁表 1 秒调度器就停滞 1 秒。Executor 层执行策略层很多人以为CeleryExecutor就是“用 Celery 跑任务”但真相是Airflow 的 Executor 不负责任务执行本身只负责将 TaskInstance 的执行请求投递给下游执行器并监听其返回状态。CeleryExecutor把请求发给 Celery Broker如 RedisKubernetesExecutor则生成 YAML 提交到 K8s API Server。这里的关键陷阱在于Executor 与下游执行器之间的通信是异步且无 ACK 机制的。如果 Celery Worker 因 OOM 被 K8s 杀死而 Airflow Scheduler 并不知道它仍会持续重试该任务直到达到max_tries。更糟的是KubernetesExecutor提交的 Pod 如果因 namespace 配额不足被拒绝K8s 不会返回错误给 Airflow而是静默丢弃请求——Airflow 认为任务已提交实际却从未启动。Scheduler 层状态中枢层这是 Airflow 最脆弱也最被低估的核心。它不是一个“定时器”而是一个多线程状态协调器每 5 秒默认scheduler_idle_sleep_time执行一次完整循环包含DAG 解析、DAG 处理、TaskInstance 状态同步、Executor 任务分发、心跳上报。这个循环必须在scheduler_loop_count默认 30次内完成否则 Scheduler 会被判定为“失联”触发 Failover 机制。而 Failover 不是简单的主备切换而是所有 Scheduler 实例同时尝试获取数据库锁抢夺slot表的scheduler_lock记录——在高并发下这会导致数据库连接池瞬间打满形成恶性循环。提示不要迷信“水平扩展 Scheduler”。实测表明当 Scheduler 实例数超过 3 个数据库锁竞争带来的性能损耗会抵消所有扩展收益。真正的扩容路径是优化 DAG 解析效率如禁用pickle_dags、降低元数据库压力如用pgbouncer连接池、将长耗时任务移出 Scheduler 循环如用TriggerDagRunOperator异步触发。2.2 元数据设计那个被所有人忽略的性能瓶颈Airflow 的元数据库通常是 PostgreSQL 或 MySQL不是“存储配置的地方”而是整个调度系统的状态总线。它的表结构设计直接决定了集群的吞吐上限。我们来看最关键的三张表表名核心字段性能风险点实测影响task_instancedag_id,task_id,execution_date,state,start_date,end_date每个任务实例插入 1 行日均 30 万任务 每天新增 30 万行state字段频繁 UPDATEPostgreSQL 在state字段上无索引时SELECT ... WHERE staterunning查询耗时从 2ms 涨至 1.8sdag_rundag_id,execution_date,state,run_idDAG 每次触发生成 1 行execution_date是复合查询高频条件未对(dag_id, execution_date)建联合索引DAG 列表加载超时30sslot_poolpool,slots,descriptionpool表用于资源隔离但slots字段是全局计数器多 Scheduler 实例并发更新slots字段引发行锁等待阻塞任务分发我接手某银行项目时其 Airflow 集群已运行 2 年task_instance表达 1.2 亿行。DBA 告诉我“你们的查询慢是因为数据量大。” 但真实原因是他们从未给state字段加索引而 Scheduler 每次循环都要执行SELECT * FROM task_instance WHERE state IN (running, queued)。加完索引后该查询从平均 840ms 降至 3msScheduler 循环时间缩短 40%。这说明Airflow 的性能问题80% 出现在数据库层面而非 Python 代码。你花一周优化 DAG 逻辑不如花 2 小时检查元数据库索引。另一个致命设计是dag_run表的execution_date字段。它被定义为TIMESTAMP WITH TIME ZONE但 Airflow 内部用它作为 DAG 版本标识符。问题在于当你用hourly调度时execution_date精确到秒但业务方常要求“按自然日处理”于是有人写schedule_intervaltimedelta(days1)结果execution_date变成2023-01-01 00:00:0000。而 Airflow 的 DAG 解析逻辑会严格比对execution_date与当前时间微秒级偏差都会导致 DAG 被跳过。我们曾因此丢失整整 3 天的交易对账数据——不是代码 bug而是execution_date的时区处理与业务预期错位。2.3 Executor 选型没有银弹只有代价权衡Airflow 官方支持 5 种 Executor但生产环境真正可用的只有 3 种SequentialExecutor仅开发、CeleryExecutor传统主力、KubernetesExecutor云原生首选。它们的区别不是“功能多少”而是资源隔离粒度、故障传播半径、运维复杂度的三角权衡CeleryExecutor任务以 Celery Worker 进程形式运行Worker 间内存隔离但共享同一套 Celery Broker如 Redis。优势是成熟稳定社区方案丰富劣势是 Broker 成为单点瓶颈——当 Redis 内存打满所有 Worker 断连Scheduler 无法感知任务堆积在 Broker 队列中直到 Redis OOM crash。我们曾用redis-cli --bigkeys发现某个 DAG 的serialized_dag数据占 Redis 72% 内存根源是 DAG 中引用了未序列化的大型 Python 对象如 pandas DataFrame。KubernetesExecutor每个任务启动独立 Pod资源彻底隔离天然支持多租户。但代价是每次任务启动需调用 K8s API Server 创建 Pod耗时 3~8 秒。这意味着如果你有 1000 个短耗时任务1s用KubernetesExecutor反而比CeleryExecutor慢 10 倍。我们为此专门开发了PodTemplate缓存机制——将常用镜像、资源请求预编译为 ConfigMap使 Pod 创建耗时从均值 5.2s 降至 1.7s。CeleryKubernetesExecutor混合模式这是 Airflow 2.0 新增的实验性选项允许部分任务走 Celery部分走 K8s。看似灵活实则引入新风险两种 Executor 共享同一套元数据库但状态同步逻辑不同。当 Celery Worker 执行失败时它通过celery.backends.database写回元数据库而 K8s Pod 失败则由 Airflow 的KubernetesJobWatcher监听事件并更新。两者更新时机不同步导致task_instance.state出现短暂不一致触发误告警。注意永远不要在生产环境用LocalExecutor。它看似简单但所有任务在 Scheduler 进程内执行一个任务内存泄漏如 pandas.read_csv 加载超大文件未释放整个 Scheduler 进程崩溃。我们曾因一个PythonOperator中忘记del df导致 Scheduler 每 4 小时 OOM 重启一次。3. 落地风险全景那些让架构师失眠的 7 类真实故障3.1 DAG 解析风暴当“热更新”变成“热崩溃”Airflow 的 DAG 自动发现机制dags_folder扫描是双刃剑。默认每 30 秒扫描一次发现新文件或修改就重新解析。问题在于解析是全量的不是增量的。当你在dags_folder下放了 500 个 DAG 文件每次扫描都要逐个 import、执行、构建 DAG 对象。而 Python 的 import 机制会触发模块级代码执行——如果某个 DAG 文件里写了requests.get(http://api.example.com)这个 HTTP 请求会在每次解析时被执行API 服务瞬间被打爆。更隐蔽的风险来自DAG构造函数参数。例如# 危险写法每次解析都调用外部 API default_args { retries: get_retry_config_from_consul(), # 每次解析都访问 Consul email: get_alert_emails_for_team(data), # 每次解析都查 LDAP } dag DAG(etl_sales, default_argsdefault_args, ...)实测表明这类代码会使单次 DAG 解析从 120ms 延长至 2.3sScheduler 循环超时概率提升 67%。正确做法是将外部依赖移到 Operator 执行时即execute()方法内或用once装饰器缓存结果cache.memoize(timeout300) # 缓存 5 分钟 def get_retry_config(): return requests.get(http://consul:8500/v1/kv/retry_config).json()另一个常见陷阱是DAG的schedule_interval设置。很多人用cron表达式0 2 * * *表示“每天 2 点执行”但 Airflow 的 cron 解析器会将其转换为datetime对象而datetime在 Python 中是可变对象。当多个 DAG 共享同一个schedule_interval对象时修改其中一个会意外影响其他 DAG。我们曾因此导致 12 个核心 DAG 同步偏移 1 小时——根源是schedule_interval croniter(0 2 * * *)被重复引用。3.2 任务状态漂移为什么 UI 显示 Success数据却丢了这是 Airflow 最令人抓狂的问题Web UI 显示绿色 SUCCESS但业务方坚称“昨天的报表没更新”。根本原因在于Airflow 的 SUCCESS 状态只代表“Operator 执行函数返回了且 exit code 为 0”不代表业务逻辑正确。典型场景BashOperator中执行mysql -e INSERT INTO ...但 SQL 语法错误MySQL 返回ERROR 1064而 Bash 脚本未捕获该错误$?仍是 0PythonOperator中调用pandas.to_sql()但目标表字段类型不匹配pandas 静默截断数据不抛异常HttpOperator调用 API返回 HTTP 200但响应体是{code:500,msg:internal error}Operator 默认不校验code字段。解决方案不是“加更多日志”而是在 Operator 层强制注入业务校验。我们为所有关键 DAG 开发了DataIntegrityCheckOperatorclass DataIntegrityCheckOperator(BaseOperator): def execute(self, context): # 1. 查询目标表行数 count self.hook.get_records(SELECT COUNT(*) FROM {{ params.table }})[0][0] # 2. 查询上游依赖表行数 upstream_count self.hook.get_records( SELECT COUNT(*) FROM {{ params.upstream_table }} WHERE dt{{ ds }} )[0][0] # 3. 校验比例如要求 95% if count upstream_count * 0.95: raise AirflowException(fData loss detected: {count}/{upstream_count})这个 Operator 插入在每个 ETL DAG 的末端使“SUCCESS”真正意味着“数据可信”。3.3 资源争抢黑洞Pool、Queue、Concurrency 的三重幻觉Airflow 提供pool资源池、queue执行队列、concurrency并发数三套资源控制机制但它们作用层级不同混用极易失控pool控制同一 Pool 下所有任务的总并发数基于数据库计数器实现有锁竞争queue控制任务分发到哪个 Executor 队列如 Celery 的queuehigh_priorityconcurrency控制单个 DAG 的最大并发运行实例数防止历史 DAG 积压。问题在于pool的 slots 计数器是全局的而queue的负载均衡由 Executor 自行实现。当pool设为 10queue设为celery但 Celery Worker 只有 2 个结果是10 个任务全挤在 2 个 Worker 上CPU 100%而其他queuespark的 Worker 闲置。我们曾因此导致实时风控任务被批处理任务饿死。更危险的是concurrency与max_active_runs的混淆。concurrency3表示“最多 3 个 task instance 同时运行”而max_active_runs1表示“最多 1 个 dag run 同时存在”。如果schedule_intervalhourlymax_active_runs1意味着每小时只允许一个 run但若某次 run 耗时 2 小时后续 run 会被跳过——数据就丢了。正确做法是对关键 DAGmax_active_runs设为None不限制用pool控制资源用trigger_rule控制依赖。3.4 时间语义陷阱execution_date 不是你想的那个“时间”execution_date是 Airflow 最易误解的概念。它不是任务实际运行的时间而是 DAG 实例的逻辑时间戳代表“该实例应处理的数据周期”。例如daily调度的 DAGexecution_date为2023-01-01表示“处理 2023-01-01 的数据”但任务实际在2023-01-02 00:05运行。这导致两大坑数据延迟计算错误业务方说“数据延迟 2 小时”你查execution_date是2023-01-01以为没问题但实际start_date是2023-01-02 02:00延迟已达 26 小时跨时区任务失败当execution_date设为2023-01-01T00:00:0000而你的数据库时区是Asia/ShanghaiWHERE dt{{ ds }}生成的 SQL 是WHERE dt2023-01-01但数据库里存的是2023-01-01 16:00:00UTC8查询为空。解决方案是永远用{{ data_interval_start }}和{{ data_interval_end }}替代{{ ds }}。Airflow 2.2 引入了数据区间概念data_interval_start是逻辑周期起点data_interval_end是终点它们自动适配时区。例如-- 正确精确覆盖逻辑周期 SELECT * FROM sales WHERE event_time {{ data_interval_start }} AND event_time {{ data_interval_end }}; -- 错误依赖 ds时区错乱 SELECT * FROM sales WHERE dt {{ ds }};3.5 失败自愈幻觉retry 机制如何放大故障Airflow 的retries参数常被当作“容错保险”但实际是把问题延后。默认retries0设为3意味着任务失败后Airflow 会等retry_delay默认 300 秒再重试共 3 次。问题在于重试不改变失败根因。如果失败是因为上游 API 服务宕机重试 3 次只会让 API 服务雪上加霜如果失败是因为磁盘空间不足重试只会更快填满磁盘。我们曾遇到一个案例某 DAG 调用 Spark 作业retries3retry_delaytimedelta(minutes5)。Spark Driver 因内存不足 OOM每次重试都申请同样内存第 3 次重试时YARN 集群内存耗尽整个数仓作业瘫痪 2 小时。正确做法是用trigger_rule和depends_on_past构建真正的失败隔离depends_on_pastTrue确保当前 run 只依赖前一个 run 成功避免“雪崩式失败”trigger_ruleall_done即使上游任务失败也继续执行下游的“失败分析任务”生成诊断报告对关键任务用on_failure_callback发送钉钉告警并附上context[task_instance].log_url直达日志。3.6 权限与审计盲区谁动了生产 DAGAirflow Web UI 的权限模型RBAC常被低估。默认Admin角色可编辑所有 DAG但生产环境必须做到DAG 级别权限隔离财务部门只能看到finance_*DAG不能触碰marketing_*操作审计留痕谁在何时启停了哪个 DAG谁修改了schedule_interval代码变更受控DAG 文件不能直接在生产服务器上编辑必须走 GitOps 流程。我们实施的方案是用AirflowProvider集成 GitLab CIDAG 代码提交到prod分支后自动触发部署流水线Web UI 关闭DAG的edit权限只开放read和trigger所有Admin操作日志接入 ELK设置告警规则“1 小时内同一用户启停 5 个 DAG触发二级审批”。3.7 升级地狱从 1.x 到 2.x 的 5 大断裂点Airflow 2.0 是重大重构升级不是pip install apache-airflow2.0.0就完事。我们花了 6 周完成 32 个核心 DAG 的迁移踩过的坑包括Operator API 变更PythonOperator的python_callable参数不再接受 lambda必须是命名函数Jinja 模板安全加固{{ task_instance.xcom_pull(...) }}默认被禁用需显式配置jinja_context_behaviorv2Scheduler 架构重写1.x 的DagFileProcessorManager被 2.x 的DagFileProcessor替代dag_parsing_processes参数含义改变插件机制变更自定义 Hook 必须继承BaseHook且get_conn()方法签名变化元数据表重命名xcom表改为xcom但dag_pickle表被移除serialized_dag表成为新存储。最痛的教训是升级前必须做 full backup且备份要包含dags/目录、plugins/目录、airflow.cfg、以及元数据库的完整 dump。我们曾因漏备份plugins/导致升级后所有自定义 Operator 报ModuleNotFoundError回滚耗时 4 小时。4. 实操避坑指南从零搭建高可用 Airflow 集群的 12 个硬核步骤4.1 环境准备绕过官方文档的 3 个致命假设Airflow 官方 Quick Start 假设你用 SQLite、单机、默认配置。生产环境必须推翻这三大假设数据库绝不选 SQLiteSQLite 是单文件数据库不支持并发写入。Scheduler 多线程写入时会频繁报database is locked。必须用 PostgreSQL推荐或 MySQL。安装时注意PostgreSQL 的shared_buffers至少设为 2GBwork_mem设为 64MB否则task_instance表 JOIN 查询会极慢。Python 版本锁定为 3.8 或 3.9Airflow 2.3 已放弃对 3.7 的支持而 3.10 的asyncio变更导致某些旧 Hook如S3ListOperator异常。我们实测 3.8.10 最稳定pip install apache-airflow[postgres,celery,kubernetes]2.3.4无兼容性问题。禁用pickle_dags该功能允许 DAG 以 pickle 格式序列化存入数据库方便跨进程共享。但 pickle 存在反序列化 RCE 风险且大幅增加元数据库体积。在airflow.cfg中设donot_pickle True强制使用 JSON 序列化。4.2 DAG 开发规范让代码可维护、可审计、可回滚我们强制执行的 DAG 编码标准文件命名业务域_功能_版本.py如finance_etl_daily_v2.py禁止dag1.py、new_dag.pyDAG ID 命名业务域_功能_调度粒度如finance_etl_daily长度 ≤ 50 字符必需参数每个 DAG 必须定义doc_mdMarkdown 文档、tags数组如[finance, etl]、schedule_interval明确 cron 或 timedelta禁止硬编码所有连接信息host、port、user从Connection对象读取用airflow.models.Connection.get_connection_from_secrets()日志分级logging.info(Start processing %s, context[dag_run].run_id)记录关键节点logging.debug(Raw data: %s, df.head())仅在调试环境启用。4.3 Scheduler 优化把循环时间压到 2 秒内目标Scheduler Loop Time 2s默认 30s 超时。关键配置# airflow.cfg # 1. 减少扫描频率避免频繁解析 dag_dir_list_interval 300 # 从 30s 改为 300s5分钟 # 2. 禁用无用插件减少 import 开销 plugins_folder /dev/null # 3. 优化数据库连接 sql_alchemy_pool_size 20 sql_alchemy_max_overflow 10 sql_alchemy_pool_recycle 3600 # 4. 关键关闭 DAG 自动发现改用 Git 同步 dags_are_paused_at_creation True # 5. 日志精简避免 I/O 瓶颈 logging_level INFO fab_logging_level WARNING实测效果某集群 DAG 数从 120 增至 450Scheduler Loop Time 从 8.2s 降至 1.7s。4.4 Executor 部署Celery Redis 的生产级调优CeleryExecutor配置要点Redis 连接池broker_url redis://:passwordredis:6379/0?socket_connect_timeout5socket_timeout10max_connections100Worker 并发数celeryd_concurrency 8单核 CPU避免过多进程争抢 GIL任务序列化task_serializer json禁用pickle防 RCEBroker 队列监控用redis-cli llen celery查看队列长度1000 时触发告警。4.5 KubernetesExecutor避开 Pod 启动慢的 3 个坑镜像预拉取在 K8s Node 上提前docker pull apache/airflow:2.3.4避免首次启动拉镜像耗时Pod 模板最小化删除所有非必要 volumeMount如configmaps、secrets只挂载所需项资源请求精准化resources.requests.memory 2Gi而非4Gi避免 K8s 调度器因资源碎片拒绝调度。4.6 监控告警不只是看 Web UI 的绿色 SUCCESS必须监控的 5 个黄金指标指标采集方式告警阈值业务含义scheduler_loop_timePrometheus airflow-exporter3s 持续 5 分钟Scheduler 失联风险task_instance_duration_seconds{statesuccess}同上P95 300s任务性能退化dag_run_duration_seconds{statefailed}同上0 持续 1 分钟DAG 级别故障celery_queue_length{queuecelery}Redis exporter500任务积压airflow_db_query_duration_seconds{queryupdate_task_instance}PgSQL exporterP99 1s元数据库瓶颈告警策略对scheduler_loop_time采用“阶梯式告警”——3s 发企业微信5s 电话通知10s 自动触发airflow scheduler --daemon重启。4.7 故障排查从日志定位到根因的 4 层穿透法当任务失败按此顺序排查Web UI 层看Log标签页确认是否BrokenDAG 解析失败或Upstream Failed依赖失败Scheduler 日志层grep DAG xxx not found airflow-scheduler.log查 DAG 是否未加载Executor 日志层Celery Worker 日志中搜Task xxx raised exception定位 Python 异常元数据库层SELECT * FROM task_instance WHERE dag_idxxx AND execution_date2023-01-01 ORDER BY start_date DESC LIMIT 1;查state、end_date、duration。我们曾用此法3 分钟内定位到某任务失败是因为task_instance表end_date字段为NULL根源是KubernetesExecutor的job_watcher进程因内存不足被 OOM kill。4.8 安全加固生产环境的 7 项强制措施Web UI HTTPSNginx 反向代理强制 HTTPS禁用 HTTPRBAC 权限最小化Viewer角色只开放DAGs、DAG Runs、Task Instances的can_read连接密码加密airflow connections add my_db --conn-uri postgresql://user:passhost/dbURI 中密码自动 AES 加密禁用危险插件airflow.plugins目录下只保留白名单插件定期pip list --outdated升级日志脱敏airflow.cfg中设log_format %(asctime)s %(name)s %(levelname)s - %(message)s禁用%(funcName)s防止泄露函数名审计日志开启audit_log True所有 UI 操作写入audit.log定期凭证轮换用 HashiCorp Vault 动态生成数据库密码Airflow 通过VaultBackend获取。4.9 备份恢复RPO 5 分钟的实战方案元数据库PostgreSQLpg_dump每 5 分钟全量备份 WAL 归档RPO5minDAG 代码GitLab 仓库每日快照git bundle create生成离线包插件代码plugins/目录打包为.tar.gz上传至 S3恢复演练每月 1 次全链路恢复测试从备份还原元数据库、重装插件、验证 DAG 加载。4.10 性能压测用真实流量验证集群水位我们用airflow tasks trigger模拟峰值流量# 1. 创建 1000 个测试 DAG for i in {1..1000}; do airflow dags trigger test_dag_${i} --conf {test:true} done # 2. 监控 Scheduler Loop Time、DB CPU、Celery Queue Length # 3. 当 Queue Length 2000停止压测记录此时并发数结论