瀏覽代碼

refactor(datax): datasource 目录扁平化,db_type 改按父目录段判定

tianyu.chu 2 周之前
父節點
當前提交
dc864c45cb

+ 2 - 1
conf/templates/datasource/hdfs-ha.template.ini

@@ -1,5 +1,6 @@
 # HDFS 数据源连接模板(HA,多 NameNode + 自动 failover)
-# 落位:datasource/hdfs/{env}/{instance}.ini
+# 落位:datasource/hdfs/{env}-{实例简称}.ini
+# sync ini 引用:dataSource = hdfs/{env}-{实例简称}
 # [hadoop_config] 节对应 hdfs-site.xml 的关键 HA 参数,DataX JVM 不读 classpath 下的 xml,必须在 ini 里显式注入
 
 [base]

+ 2 - 1
conf/templates/datasource/hdfs.template.ini

@@ -1,5 +1,6 @@
 # HDFS 数据源连接模板(单 NameNode,非 HA)
-# 落位:datasource/hdfs/{env}/{instance}.ini
+# 落位:datasource/hdfs/{env}-{实例简称}.ini
+# sync ini 引用:dataSource = hdfs/{env}-{实例简称}
 
 [base]
 defaultFS = hdfs://<namenode_host>:<rpc_port>

+ 2 - 1
conf/templates/datasource/postgresql.template.ini

@@ -1,5 +1,6 @@
 # PostgreSQL 数据源连接模板
-# 落位:datasource/postgresql/{env}/{instance}.ini(env = dev / test / prod)
+# 落位:datasource/postgresql/{env}-{实例简称}.ini(env ∈ dev/test/prod)
+# sync ini 引用:dataSource = postgresql/{env}-{实例简称}(必须带 db_type 前缀)
 
 jdbcUrl = jdbc:postgresql://<host>:<port>/<database>
 username = <username>

+ 3 - 2
dw_base/datax/plugins/plugin.py

@@ -33,9 +33,10 @@ class Plugin(object):
     def init(self):
         self.check()
         self.ds_file = self.config_parser.get(self.plugin_type, GEN_CONFIG_KEY_DATA_SOURCE)
-        self.ds_type = self.ds_file.split('/')[-1].split('-')[0]
+        # ds_file 形如 {db_type}/{env}-{实例简称},首段即 db_type(= 父目录名)
+        self.ds_type = self.ds_file.split('/')[0]
 
-        self.ds_file_path = f'{self.base_dir}/../datasource/{self.ds_type}/{self.ds_file}.ini'
+        self.ds_file_path = f'{self.base_dir}/../datasource/{self.ds_file}.ini'
 
         if not os.path.exists(self.ds_file_path) or not os.path.isfile(self.ds_file_path):
             raise FileNotFoundError(self.ds_file_path)

+ 2 - 1
dw_base/datax/plugins/plugin_factory.py

@@ -30,7 +30,8 @@ class PluginFactory:
     def get_plugin(plugin_type: str, base_dir: str, config_parser: ConfigParser, start_time: str,
                    stop_time: str) -> Plugin:
         ds_file = config_parser.get(plugin_type, GEN_CONFIG_KEY_DATA_SOURCE)
-        ds_type = ds_file.split('/')[-1].split('-')[0]
+        # ds_file 形如 {db_type}/{env}-{实例简称},首段即 db_type
+        ds_type = ds_file.split('/')[0]
         if plugin_type == JOB_CONTENT_N_READER:
             if ds_type == DS_TYPE_HDFS:
                 plugin = HDFSReader(base_dir, config_parser, start_time, stop_time)

+ 12 - 10
kb/00-项目架构.md

@@ -56,17 +56,17 @@ poyee-data-warehouse/              # 项目根目录(仓库名 = 部署名)
 /home/bigdata/release/
 ├── poyee-data-warehouse/          # 本项目部署目录
 └── datasource/                    # 数据源连接配置(含账密,由运维管理,不入仓库)
-    ├── postgresql/                #   按 {db_type}/{env}/{instance}.ini 三段式组织
-    │   ├── prod/
-    │   │   ├── hobby.ini          #     生产 PG 实例举例
-    │   │   └── crm.ini
-    │   ├── test/
-    │   │   └── hobby.ini
-    │   └── dev/
-    │       └── hobby.ini
-    ├── mysql/                     #   结构同上(prod / test / dev 三套)
+    ├── postgresql/                #   文件名约定 {env}-{实例简称}.ini;env ∈ {dev,test,prod}
+    │   ├── prod-hobby.ini         #     生产 PG hobby 业务库
+    │   ├── prod-hobby-ro.ini      #     同环境多实例加二次后缀(ro = 只读从库)
+    │   ├── test-hobby.ini
+    │   └── dev-hobby.ini
+    ├── mysql/
+    │   └── prod-order.ini
     ├── mongo/
     ├── hdfs/
+    │   ├── prod-ha.ini            #     HA 集群
+    │   └── test-single.ini        #     单 NN 集群
     ├── clickhouse/
     ├── elasticsearch/
     ├── kafka/
@@ -74,6 +74,8 @@ poyee-data-warehouse/              # 项目根目录(仓库名 = 部署名)
     └── hbase/
 ```
 
+sync ini 里 `[reader]` / `[writer]` 的 `dataSource` 字段必须带 `{db_type}/` 前缀,例如 `dataSource = postgresql/prod-hobby`、`dataSource = hdfs/prod-ha`。代码按首段斜杠判 db_type(= 父目录),裸名(`hobby`)会找不到文件。前期跨环境同步常态(test 业务库 → prod HDFS),不设全局 env 概念,每个 sync ini 显式指向各自 env 的 source ini。
+
 
 
 ## 3. 模块关系图
@@ -317,7 +319,7 @@ python3 bin/datax-gc-generator.py --from hdfs --to elasticsearch \
 
 | 配置类型 | 存放位置 | 是否入仓库 | 维护角色 |
 |----------|---------|-----------|------|
-| 数据源连接(含账密) | `../datasource/{db_type}/{env}/{instance}.ini` | 否 | 运维   |
+| 数据源连接(含账密) | `../datasource/{db_type}/{env}-{实例简称}.ini` | 否 | 运维   |
 | DataX 同步任务定义 | `jobs/raw/` (采集) 和 `jobs/ads/` (导出) | 是 | 开发   |
 | Spark 默认参数 | `conf/spark-defaults.conf`(行为/开关)+ `conf/spark-tuning.conf`(资源/调优) | 是 | 开发   |
 | Spark 单作业覆盖 | 对应 `jobs/*.sql` 文件内 `SET spark.x.y=z` | 是 | 开发   |

+ 2 - 2
kb/02-权限与账号.md

@@ -185,9 +185,9 @@ sequenceDiagram
 
 ## 4. 数据源账号(非 LDAP/Ranger 链路)
 
-DataX 采集需要访问外部数据源(PG / MySQL / MongoDB / ES 等),这些账号**不走 LDAP/Ranger**,而是以明文/加密形式存放在项目同级目录,按 `{db_type}/{env}/{instance}.ini` 三段式组织。完整目录结构见 `00-项目架构.md` §1。
+DataX 采集需要访问外部数据源(PG / MySQL / MongoDB / ES 等),这些账号**不走 LDAP/Ranger**,而是以明文/加密形式存放在项目同级目录,按 `{db_type}/{env}-{实例简称}.ini` 扁平组织(不再按 env 分子目录)。完整目录结构见 `00-项目架构.md` §1。
 
-由运维同学维护,**不纳入本仓库版本控制**。开发 DataX 作业时 ini 引用 `{ds_type}/{ds_file}`(两段式不带 env;env 由启动命令 `-env <name>` 或 `conf/env.sh` 默认值注入,见 `21-命名规范.md` §4.4)
+由运维同学维护,**不纳入本仓库版本控制**。开发 DataX 作业时 sync ini 里 `dataSource = {db_type}/{env}-{实例简称}`,代码按首段斜杠定位 source ini
 
 ## 5. 相关公共文档
 

+ 3 - 3
kb/21-命名规范.md

@@ -265,10 +265,10 @@ ads 是面向具体应用场景的输出表(报表、接口、导出),表
 
 **通用约定:**
 
-- **一套代码跑多环境**:不在 ini 文件名里加 `prod` / `dev` / `test` 前缀。环境差异体现在 `datasource/{db_type}/{env}/{instance}.ini`,由脚本根据 `-env` 参数注入
-- ini 内 reader/writer 的 `dataSource` 字段只写 `{db_type}/{instance}`(不含 env),例如 `dataSource = mongo/hobby`
+- **sync ini 文件名不加 env 前缀**:前期跨环境同步是常态(test 业务库 → prod HDFS),不存在"全局 env"概念,sync ini 由其 `[reader]` / `[writer]` 分别显式指向各自 env 的 source ini
+- ini 内 reader/writer 的 `dataSource` 字段写 `{db_type}/{env}-{实例简称}`,例如 `dataSource = postgresql/prod-hobby`、`dataSource = hdfs/prod-ha`;source ini 落位与引用规则见 `00-项目架构.md` §1
 - DataX 生成引擎(`dw_base/datax/job_config_generator.py`)对文件名**不做校验**,上述命名规则是开发者写作约定,靠 code review 保证
-- 命名唯一性保证 JSON 输出目录 `conf/datax-json/{env}/{ini_basename}.json` 不会互相覆盖
+- 命名唯一性保证 JSON 输出目录 `conf/datax-json/{ini_basename}.json` 不会互相覆盖
 
 **参考样板**:`conf/templates/datax/{raw,ads,manual}/*.template.ini` 提供各类同步场景的字段齐全样本,新开发者写新 ini 时抄这里。
 

+ 9 - 87
kb/90-重构路线.md

@@ -113,11 +113,9 @@ D 基础设施 ─────┘
 | 告警 Webhook(钉钉 / 企微 Key) | `dw_base/common/alerter_constants.py`(老告警模块已于 2026-04-20 删除,含 `dingtalk_notifier.py` / `ent_interface_dingtalk*` / `bin/dingtalk-work-alert.sh`) | 新告警模块重写时 Webhook Key 外移到 `conf/alerter.ini`(**入库**——部署靠 git pull,gitignore 会拉不到;webhook key 不算高敏感,最多被拿去发垃圾消息),Python 侧改 ConfigParser 加载;`alerter_constants.py` 整个删除;新项目不再使用钉钉 |
 | Spark 默认参数(executor/driver/shuffle/sql.*) | `dw_base/spark/spark_sql.py` 构造函数 + `.config(...)` 链 | 移入 `conf/spark-defaults.conf`,SQL 文件可用 `SET` 覆盖,见 §2.3 |
 | DataX ini 路径前缀剥离 `conf/datax/config/` | `bin/datax-single-job-starter.sh`(TEMP 处理)、`bin/datax-job-config-generator.py`(`replace('conf/datax/config/', '')`)、`bin/datax-multiple-job-starter.sh`(日志路径派生) | 原目录已整体挪到 `conf/bak/` 并 gitignore,脚本里 replace 现在是 no-op 死逻辑。去除前缀假设,改为靠 ini 文件名(= 任务唯一标识,见 `21-命名规范.md` §3.9)识别用途 |
-| DataX 生成 JSON 输出目录名 `conf/datax/generated` | `bin/datax-job-config-generator.py` 末尾 `default_output_dir`、`bin/datax-single-job-starter.sh` 第 89/118 行、`bin/datax-multiple-job-starter.sh` 第 187 行、`.gitignore` | 目录改名 `conf/datax-json/`;子路径扁平化为 `conf/datax-json/{env}/{ini_basename}.json`(仅按 env 分一级,去掉 src_dst / project_layer_env 等派生层级);`.gitignore` 同步改 |
+| DataX 生成 JSON 输出目录名 `conf/datax/generated` | `bin/datax-job-config-generator.py` 末尾 `default_output_dir`、`bin/datax-single-job-starter.sh` 第 89/118 行、`bin/datax-multiple-job-starter.sh` 第 187 行、`.gitignore` | 目录改名 `conf/datax-json/`;子路径扁平化为 `conf/datax-json/{ini_basename}.json`(去掉 src_dst / project_layer_env 等派生层级;`ini_basename` 由 `21-命名规范.md` §3.9 保证全局唯一);`.gitignore` 同步改 |
 | JOB_NAME / JSON 文件名的 `ini→json` 转换逻辑重复实现 | Python 侧 `bin/datax-job-config-generator.py:126`(`os.path.basename(gcf).replace('.ini', '.json')`)+ Bash 侧 `bin/datax-single-job-starter.sh:88`(`basename .ini`) | 合一到 `dw_base.datax.path_utils.job_name_from_ini()`(或类似工具);Bash 侧通过 `python3 -c` 调用或在 `bin/common/init.sh` 定义等价 shell 函数,单一来源 |
-| DataX 脚本不支持 `-env` 参数 | `bin/datax-*.sh` / `bin/datax-job-config-generator.py` 参数解析 | 新增 `-env` 参数,由 ini 内 `dataSource = {db_type}/{instance}` 拼接成 `datasource/{db_type}/{env}/{instance}.ini` 的完整路径;详见 §2.5 |
-| `datasource/` 单层组织(无环境子目录) | `datasource/{db_type}/{instance}.ini` | 改为 `datasource/{db_type}/{env}/{instance}.ini`(运维侧权限隔离 + 支持一套代码跑多环境) |
-| ini 里 `dataSource` 字段拼接环境后缀 | 老项目写法 `dataSource = pg-hobby-prod` | 改为 `dataSource = {db_type}/{instance}`(不含环境),env 由脚本注入 |
+| ini 里 `dataSource` 字段拼接环境后缀 | 老项目写法 `dataSource = pg-hobby-prod` | 改为 `dataSource = {db_type}/{env}-{实例简称}`,代码按首段斜杠判 db_type(即父目录名);source ini 落位 `datasource/{db_type}/{env}-{实例简称}.ini`,无 env 子目录。已于 2026-04-22 落地(`dw_base/datax/plugins/plugin.py:37`、`plugin_factory.py:34`) ✅ |
 | 导出类 ini 扇出撞名风险 | `jobs/ads/{域}/` 下 ini 若都以源 Hive 表名命名,同一张 ads 表扇出到多个目标库时会重名覆盖 | 命名规则改为 `{源 Hive 表名}__{目标 db_type}_{目标 instance}.ini`(双下划线分隔源/目标),见 `21-命名规范.md` §3.9 |
 | `dw_base/common/template_constants.py` 大量死代码 | 定义了 20+ 个 SQL 模板路径常量,实际只有 2 个(`MYSQL_HIVE_CREATE_TABLE_TEMPLATE` / `MYSQL_HIVE_HBASE_CREATE_TABLE_TEMPLATE`)被引用,其余 18 个零 import | 整个文件删除;连带废弃下一条 |
 | `MySQLReader.generate_hive_ddl()` / `generate_hive_over_hbase_ddl()` 自动建表 DDL 路径 | `dw_base/datax/plugins/reader/mysql_reader.py:195/222`,被 `bin/datax-gc-generator.py:616/728` 调用;且 `conf/template/` 目录在新项目根本不存在,真调用会 FileNotFoundError | 整段路径废弃——与 CLAUDE.md 约定的 `manual/ddl/` 是 DDL 唯一来源相冲突。`datax-gc-generator.py` 仅生成 ini 配置,不再输出 CREATE TABLE DDL;DDL 由开发者按 `21-命名规范.md` 手写到 `manual/ddl/` |
@@ -157,88 +155,13 @@ L3   SparkSQL(...) 显式传参  +  extra_spark_config  +  命令行 -sc
 
 **与仓库改名的联动** ✅ 2026-04-22:仓库改名 `tendata-warehouse-release` → `poyee-data-warehouse` 已完成(项目根由用户手动改名 + 路径正则两处字面量同步)。`.idea/*.iml` + `modules.xml` 因 `.idea` + `*.iml` 在 `.gitignore`,老 iml / modules.xml 引用 / workspace.xml module name 残留是本地 IDE 状态,不入库亦不影响运行,不处理。
 
-### 2.5 DataX 脚本多环境支持与路径解耦
+### 2.5 DataX 脚本路径解耦
 
-**现状(脚本残留老逻辑,新项目的业务 ini 未走这条路径):**
+**现状**:`bin/datax-single-job-starter.sh` 和 `bin/datax-job-config-generator.py` 仍通过剥离 `conf/datax/config/` 前缀从源 ini 路径里派生 `SRC_DST` / `PROJECT_LAYER_ENV`,用于生成 JSON 输出路径和 `datax-multiple-job-starter.sh` 的日志目录。该目录已整体挪到 `conf/bak/` 并 gitignore,新项目 ini 放在 `jobs/raw/{domain}/` / `jobs/ads/{domain}/` / `manual/`,前缀不匹配时剥离变成 no-op,输出会落到形如 `conf/datax/generated/jobs/raw/trd/xxx.json` 的位置——能跑但与新约定不符。
 
-1. **脚本里残留路径前缀剥离**:`bin/datax-single-job-starter.sh` 和 `bin/datax-job-config-generator.py` 仍通过剥离 `conf/datax/config/` 前缀从源 ini 路径里派生 `SRC_DST` / `PROJECT_LAYER_ENV`,用于生成 JSON 输出路径和 `datax-multiple-job-starter.sh` 的日志目录。该目录已整体挪到 `conf/bak/` 并 gitignore,新项目 ini 放在 `jobs/raw/{domain}/` / `jobs/ads/{domain}/` / `manual/`,前缀不匹配时剥离变成 no-op,输出会落到形如 `conf/datax/generated/jobs/raw/trd/xxx.json` 的位置——能跑但与新约定不符。代码里这段死逻辑要清理,同时输出根目录也一并改名为 `conf/datax-json/`。
-2. **没有 `-env` 参数**:所有脚本不认 `-env`。
-3. **数据源配置单层组织**:老约定 `datasource/{db_type}/{instance}.ini` 把环境和实例扁平混在一起,要么靠不同的 `{instance}` 名字(如 `hobby-prod` / `hobby-dev`)区分,要么靠部署时替换文件。第一种让 ini 里写 `dataSource = pg-hobby-prod` 这种绑死环境的字符串;第二种让开发侧不知道当前跑的是哪套。
+**目标态**:清理死路径派生逻辑,JSON 输出扁平化为 `conf/datax-json/{ini_basename}.json`(基名由 `21-命名规范.md` §3.9 保证全局唯一),日志目录简化为 `${LOG_ROOT_DIR}/datax/${START_DATE}/${JOB_NAME}.log`。
 
-**目标态:一套代码多环境运行**
-
-**三件事联动(顺序重要):**
-
-#### 阶段 1:datasource 改按环境分子目录
-
-- 改为 `datasource/{db_type}/{env}/{instance}.ini`
-- 运维在集群侧按 `prod` / `test` / `dev` 建子目录,各自放一套连接配置
-- 权限隔离更方便:prod 子目录只给生产账号可读
-
-#### 阶段 2:ini 里 `dataSource` 字段去掉环境后缀
-
-- 老写法:`dataSource = pg-hobby-prod`
-- 新写法:`dataSource = pg/hobby`(只写 `{db_type}/{instance}`,不含 env)
-- **变更项目下所有存量 ini**:当前 `conf/bak/` 下的老配置不处理,等业务新 ini 从零写起时就按新规范
-
-#### 阶段 3:DataX 脚本加 `-env` 参数 + env 注入逻辑
-
-**`bin/common/init.sh` 扩展:**
-
-```bash
-# 从命令行参数里挑出 -env,或 fall back 到 conf/env.sh
-# 不读环境变量(用户明确要求不污染 shell 环境)
-# 不做"按用户/部署目录自动派生"——env 必须来自 -env 或 conf/env.sh,二者都没给就退出
-resolve_env() {
-  for arg in "$@"; do
-    case $arg in
-      -env) NEXT_IS_ENV=1 ;;
-      -env=*) DW_ENV="${arg#*=}" ;;
-      *) [ -n "$NEXT_IS_ENV" ] && DW_ENV="$arg" && unset NEXT_IS_ENV ;;
-    esac
-  done
-  if [ -z "$DW_ENV" ]; then
-    # fall back 到 conf/env.sh(入仓库的默认配置)
-    [ -f "${BASE_DIR}/conf/env.sh" ] && . "${BASE_DIR}/conf/env.sh"
-  fi
-  if [ -z "$DW_ENV" ]; then
-    echo "[FATAL] DW_ENV not set: pass -env <dev|test|prod> or define DW_ENV in conf/env.sh" >&2
-    exit 1
-  fi
-  export DW_ENV
-}
-```
-
-**`conf/env.sh` 草案**(**入仓库**,开发者维护,服务本地调试):
-
-```bash
-# 全局环境变量默认值
-# env 判定优先级:命令行 -env > 本文件 DW_ENV
-# 默认锁定为 dev:本地调试开箱即用;DolphinScheduler / 生产脚本总是命令行显式 -env prod 覆盖
-DW_ENV=dev
-LOG_ROOT_DIR="${HOME}/log"
-```
-
-**`dw_base/datax/job_config_generator.py` 改造**:
-
-- `JobConfigGenerator.__init__` 接受新参数 `env`
-- 各 reader/writer 插件在解析 `dataSource = pg/hobby` 时,自动拼成 `datasource/pg/{env}/hobby.ini` 的完整路径再去读连接信息
-
-**`bin/datax-job-config-generator.py` 改造**:
-
-- 新增 `-env` 参数
-- **去掉** `temp = os.path.dirname(gcf).replace('conf/datax/config/', '').split('/')` 这段路径前缀剥离
-- JSON 输出目录从 `conf/datax/generated/` 改名为 `conf/datax-json/`,子路径简化为 `conf/datax-json/{env}/{ini_basename}.json`(扁平,按 env 分一级;`ini_basename` 由 `21-命名规范.md` §3.9 保证全局唯一——采集类 = 目标 Hive 表名、导出类 = `{源}__{db_type}_{instance}`、一次性 = 日期前缀)
-- `.gitignore` 同步把 `conf/datax/generated` 改成 `conf/datax-json`
-
-#### 阶段 4:启动脚本层串起来
-
-- `datax-single-job-starter.sh` 调用 generator 时把 `$DW_ENV` 透传过去
-- `datax-multiple-job-starter.sh` 日志目录改为 `${LOG_ROOT_DIR}/datax/${DW_ENV}/${START_DATE}/${JOB_NAME}.log`(把 `SRC_DST` / `PROJECT_LAYER_ENV` 彻底移除)
-
-**兼容性**:阶段 1-3 期间 `conf/bak/` 下的老 ini 不参与新流程,保留作为老项目历史资产;新业务 ini 全部从零按新规范写。
-
-**参考**:kb/00-项目架构.md §4.3(DataX 脚本详细使用)、§6.4(多环境机制)、kb/21-命名规范.md §3.9(DataX ini 文件命名)。
+**已作废的子项**(2026-04-22 方向反转):`-env` 参数 + datasource 按 env 分子目录 + 一套代码跑多环境。前期跨环境同步是常态(test 业务库 → prod HDFS),不存在"全局 env"概念,sync ini 里 `dataSource = {db_type}/{env}-{实例简称}` 显式指向具体 source ini;source ini 落位 `datasource/{db_type}/{env}-{实例简称}.ini`,扁平组织。代码侧 db_type 判定改按首段斜杠(= 父目录)在 `plugin.py:37` + `plugin_factory.py:34` 落地(✅ 2026-04-22)。
 
 ### 2.6 DataX 入口脚本收口为两条命令(中优先级)
 
@@ -375,7 +298,7 @@ dfs.client.failover.proxy.provider.nameservice1 = org.apache.hadoop.hdfs.server.
 | `dw_base/datax/plugins/reader/hdfs_reader.py` / `writer/hdfs_writer.py` | **不用改**,`defaultFS + hadoopConfig` 由 `load_data_source()` 自动注入到 `parameter` |
 | `dw_base/__init__.py` | **保留** `os.environ['HADOOP_CONF_DIR']`(Spark on YARN 启动强校验)+ `os.environ.setdefault('SPARK_CONF_DIR', '/etc/spark/conf')`(pip pyspark 默认指向自身空 conf/,需显式指到集群配置才能加载 hive-site.xml);DataX JVM 不读这些,HA 靠 ini `[hadoop_config]`。详见 `01-运行环境.md §4` |
 | `bin/datax-gc-generator.py` | §2.7 从零重写时一并处理,这里不单独列 |
-| `datasource/hdfs/{env}/*.ini` | 按新 schema 新建(prod 带 `[hadoop_config]`;dev/test 只写 `[base] defaultFS`) |
+| `datasource/hdfs/{env}-{实例简称}.ini` | 按新 schema 新建(HA 集群带 `[hadoop_config]`;单 NN 只写 `[base] defaultFS`) |
 
 **回归测试**:
 - prod HA 集群:新 ini 跑一次真实 PG→HDFS 任务,生成 json 含完整 `hadoopConfig`,任务成功(2026-04-18 已用手写 json 预验证)
@@ -659,9 +582,8 @@ else:
 | `conf/alerter.ini`(告警 Webhook,入库) | 待启动 | 旧告警代码删除(已 2026-04-20 完成) | §2.1 |
 | `conf/spark-defaults.conf`(底层 11 条)+ `conf/spark-tuning.conf`(调优 10 条)+ `spark_sql.py` 三级覆盖 | ✅ 2026-04-21 | — | §2.3 |
 | `conf/datax-speed.ini`(DataX 分时速率) | 待启动 | — | §2.9 |
-| `datasource/{db_type}/{env}/{instance}.ini` 多环境分层 | 待启动 | — | §2.5 |
-| DataX 脚本去前缀剥离 + 加 `-env` 参数 | 待启动 | datasource 多环境 | §2.5 |
-| JSON 输出目录改名 `conf/datax-json/` | 待启动 | — | §2.1 |
+| datasource 扁平化 `{db_type}/{env}-{实例简称}.ini` + db_type 按父目录段判定 | ✅ 2026-04-22 | — | §2.1 / §2.5 |
+| DataX 脚本去前缀剥离 + JSON 输出目录改名扁平化 `conf/datax-json/{ini_basename}.json` | 待启动 | — | §2.1 / §2.5 |
 
 ### B `dw_base/` 重组
 

+ 2 - 1
kb/92-重构进度.md

@@ -87,7 +87,7 @@
 - [x] **HDFS HA 自检(前置决策)**:2026-04-18 新 CDH 环境实测:`HADOOP_CONF_DIR` 未设 + `/opt/datax` 无预置 hdfs-site.xml;手动 `export HADOOP_CONF_DIR=/etc/hadoop/conf` 后跑 DataX 仍 `UnknownHostException: nameservice1`。结论:`HADOOP_CONF_DIR` 对 DataX 无效(`datax.py` 不把 conf 目录入 classpath),**锁定走 Path B**
 - [x] 改造 `HDFSDataSource`:覆写 `get_datasource_dict()` 把 `[hadoop_config]` 整节作为 dict 塞进 `ds_dict['hadoopConfig']`(2026-04-18)
 - [x] 清理 `dw_base/__init__.py:16` 死代码 `os.environ['HADOOP_CONF_DIR']`(2026-04-18,实测对 DataX JVM 无影响)
-- [ ] HDFS 数据源 ini 新建:按新 schema 运维补齐 `datasource/hdfs/{env}/*.ini`(prod 带 `[hadoop_config]`;dev/test 只写 `[base] defaultFS`)
+- [ ] HDFS 数据源 ini 新建:按新 schema 运维补齐 `datasource/hdfs/{env}-{实例简称}.ini`(HA 集群带 `[hadoop_config]`;单 NN 只写 `[base] defaultFS`)
 - [ ] HA 回归测试:真实 HA 集群 + 单 NN 集群 + 主备切换三场景
 - [ ] **DataX 速率配置外移**:`conf/datax-speed.ini` 定义分时速率档;`dw_base/datax/job_config_generator.py:60-67` 硬编码替换为读 conf(见 `90-重构路线.md` §2.9)
 - [ ] 新建 `manual/imports/` + `manual/exports/` 目录(按日期 `{yyyymmdd}/` 组织一次性任务)
@@ -181,3 +181,4 @@
 | 2026-04-22 | **勘误:移除 "SQL SET 资源类参数不生效" 的错误表述**:A.4 落地时误把 "`SparkSession.conf.set()` 对已启动 session 不改变资源" 这条 Spark 固有行为类推到"SQL 里的 SET"。实际 `spark_sql.py:query()` L433-440 对 SQL 内 `SET spark.*` 做预扫描,在 session 启动前塞进 `_final_spark_config`,`__init_spark_session` 通过 `builder.config()` 写入后才 `getOrCreate()`——**资源类 SET 同样生效**。清理 4 处错误:`dw_base/spark/spark_sql.py:155` 注释、`conf/spark-tuning.conf` L6-7 注意段、`kb/90 §2.3` "落地坑"段 + L2 行括号、本文 L74 基于错误前提的验证项 | — |
 | 2026-04-22 | **仓库改名 `tendata-warehouse-release` → `poyee-data-warehouse` 收尾**:项目根目录由用户手动改名完成;代码侧 `dw_base/utils/file_utils.py:9` + `dw_base/utils/hdfs_merge_small_file.py:7` 两处 `re.sub(r"tendata-warehouse.*", ...)` 字面量同步更新。`.idea/*.iml` / `modules.xml` / `workspace.xml` 因 `.idea` + `*.iml` 在 `.gitignore`,属本地 IDE 状态,不入库亦不影响运行(老 `tendata-warehouse-release.iml` + modules.xml / workspace.xml 里的 module name 残留不处理)。联动 kb/90 §1.1 L88 表格行打勾 + §2.3 末尾"与仓库改名的联动"段压缩为一行记录 | — |
 | 2026-04-21 | **聚簇 A.4 Spark 参数外配 + `spark_sql.py` 三级覆盖**:按业务调整频率拆两文件入库 —— `conf/spark-defaults.conf`(12 条底层行为/开关类,初始化后少改:`spark.sql.adaptive/broadcastTimeout/codegen/arrow*/files/statistics.*` + `spark.dynamicAllocation.enabled` + `spark.files.ignoreCorruptFiles` + `spark.debug.maxToStringFields` + `spark.port.maxRetries` + `hive.exec.orc.default.block.size`)+ `conf/spark-tuning.conf`(10 条资源/并行度/队列,业务早期常改:`spark.{driver,executor}.{memory,cores}` + `spark.executor.instances` + `spark.executor.memoryOverhead` + `spark.driver.maxResultSize` + `spark.default.parallelism` + `spark.sql.shuffle.partitions` + `spark.yarn.queue`)。`dw_base/spark/spark_sql.py` 改造:(a) 模块级新增 `_load_spark_conf_file(path)`,读 Spark 原生 `key value` 格式,支持 `#` 注释与空行,文件缺失返回 `{}` 容错单测;(b) `__init__` 10 个 tuning 相关构造参数默认值 `'2g' / 200 / ...` → `Optional[...] = None` sentinel,不破坏既有调用点显式传参;(c) `__init_spark_session` 原 22 条硬编码 `.config(...)` 链替换为三段:L1 先 `spark-defaults.conf` 后 `spark-tuning.conf`(相同 key tuning 覆盖 defaults)→ L2 `self._final_spark_config`(SQL 内 SET)→ L3 构造参数非 None 项 + `extra_spark_config`(L3 内 extra 覆盖 named),保持原"extra > SQL SET > named" 的向后兼容;日志分层打 `L1/L2/L3` 前缀便于排查。联动:`kb/90 §2.2` conf 结构加 `spark-tuning.conf` + `§2.3` 改写为两文件模型(去单文件草案)+ 删"坑 2"(B1 → A2 依赖边)+ 聚簇 L59 依赖边删 | — |
+| 2026-04-22 | **datasource 扁平化 + db_type 按父目录段判定(方向反转)**:反转 2026-04-15 起立项的"一套代码跑多环境"设计(kb/90 原 §2.5:`-env` 参数 + `datasource/{db_type}/{env}/{instance}.ini` 分层 + env 注入)。实际前期跨环境同步是常态(test 业务库 → prod HDFS),"全局 env"概念不成立。新方案:(a) source ini 落位 `datasource/{db_type}/{env}-{实例简称}.ini`,env 写进文件名(env ∈ dev/test/prod),扁平组织;(b) sync ini 内 `dataSource = {db_type}/{env}-{实例简称}`(强制带 db_type 前缀;旧裸名 `hdfs-ha` 不再支持);(c) 代码侧 `dw_base/datax/plugins/plugin.py:37` + `plugin_factory.py:34` 的 db_type 提取从"文件名按 `-` 切首词"改为"按 `/` 切首段"(父目录名)—— 旧算法在新文件名出现 `-` 时会把 `env` 误判为 db_type,必须改。联动:kb/00 §1 目录树重绘(去 env 子目录,加样例 `prod-hobby.ini` / `test-hobby.ini` / `prod-ha.ini` 等 + sync ini 引用形式说明段)+ §6.1 配置分类表落位描述、kb/02 §4 同步、kb/21 §3.9 过时 3 行修正(废 `-env` 注入表述;引用形式 `{db_type}/{env}-{实例简称}`;JSON 输出路径去 `{env}/` 层)、kb/90 §2.1 硬编码表合并 3 行为 1 行 + §2.5 大幅压缩(三阶段设计整段删,保留路径解耦部分)+ §2.8 末尾表行更新 + §八 状态表合并、kb/92 L90 checklist 落位改扁平。conf/templates/datasource/\*.template.ini × 3 头注释补"落位"+"dataSource 引用"两行。`bin/datax-job-config-generator.py` L5/L114/L115 的路径注释讲的是另一个 `conf/datax/config/` 作业分组层级,与本次 datasource 扁平化无关,不动;`dw_base/datax/plugins/reader/mysql_reader.py:178` `{group}/mysql-{database}` 生成逻辑是非活跃代码(批量采集未启动),留到 §2.7 重构时对齐 | — |