件2 genconfig 接热源:get() 取值链最前端插进程级热源单例(attach/detach),优先级 Nacos 热源>env>YAML>内置默认>调用方 default;复合键 area.key 无歧义扁平化 (area,key) 二维;缺键/未 attach/异常一律回落原链,绝不引入第二默认值(坑④·tech-decisions §13);热源命中即返回不读 YAML,mtime 缓存无从盖住热值;middleware 等所有 genconfig.get 调用面一行不改。 件1 worker/genconfig_nacos.py 工厂:启用旗 TIER2_GENCONFIG_NACOS 默认关(=行为字节不变,兼作 §3.6/§6 回退开关);按档隔离 dataId(tier2=gen-hot-params-tier2 / cheap=gen-hot-params-cheap,env 可覆盖);建 client 前 NO_PROXY 内网旁路;真实 namespaceId 默认空串(勿写 public,沿前置任务口径);注入 NacosHotConfig 的 defaults 刻意空;全程 best-effort 失败不 attach 不抛,genconfig 回落纯本地。 cheap-worker 共享 worker.genconfig(from worker import genconfig),两档隔离靠各进程 attach 各自 dataId 的热源;缝在 cheap_service_app.py:main 对称调 build_and_attach_hot_source(tier=cheap),本单不动只说明。 验证:单测 19 条锁死四条回落路径(未 attach/Nacos 不可达/namespace 配错/YAML 回落)+ 两档各读各 dataId + Nacos>env 优先级 + 门阈值热读 + 工厂 best-effort/按档/NO_PROXY/namespace 空串;worktree 全量 126 passed 无回归;真 Nacos(mini-infra 100.64.0.8)演示脚本改演示 dataId → genconfig 0.5s 热读到新值不重启、清理不碰生产 dataId。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
157 lines
8.4 KiB
Python
157 lines
8.4 KiB
Python
"""genconfig_nacos.py —— 把 genconfig 的读取后端提升到 Nacos 的启动期工厂(配置控制面阶段二·步骤4)。
|
|
|
|
【这份解决什么问题】
|
|
阶段〇建好了 nacos_hotconfig.NacosHotConfig(只读视图:订阅一个 Nacos dataId、长轮询热更、线程安全读),
|
|
但它「只消费已建好的 v1 NacosClient、自己不建 client」。阶段二步骤4 把这最后一段接起来:本模块是
|
|
**worker 启动期**的单例工厂,负责把 v1 client 构造齐(server / 鉴权 / NO_PROXY 旁路 / 真实 namespaceId /
|
|
group / 按档 dataId),注入 NacosHotConfig,start() 订阅,再 attach 进 genconfig 作「热源」。此后进程内
|
|
所有 genconfig.get(area,key) 调用最先查 Nacos 热源——middleware 的软预算目标、门判阈值都自动吃上配置
|
|
中心的激活值,调用面一行不改。
|
|
|
|
【显式启用开关(默认关 = 现状字节不变)】
|
|
env TIER2_GENCONFIG_NACOS ∈ {1,true,yes,on} 才启用;不设(默认)时本工厂 no-op、不 attach、不牵 nacos
|
|
依赖,genconfig 完全走原链(env→YAML→内置默认),行为与阶段二前逐字节一致。这个旗同时是设计 §3.6/§6
|
|
的【回退开关】:线上出问题就关旗重启,genconfig 立刻切回纯本地 YAML(运行时也可 genconfig.detach_hot_source())。
|
|
|
|
【按档隔离的 dataId(设计 §3.3)】
|
|
cheap-worker 与 tier2 gen-worker 是两个独立进程、各起一个本工厂:tier="tier2" 订阅 gen-hot-params-tier2、
|
|
tier="cheap" 订阅 gen-hot-params-cheap。两档各 attach 各自 dataId 的热源,publish 一档不冲掉另一档的键。
|
|
genconfig 本身不认档(见 genconfig._hot_source 注释),隔离全靠「哪个进程订阅哪个 dataId」。
|
|
|
|
【NO_PROXY 内网铁律(阶段〇同款坑)】
|
|
本机系统代理是 fake-ip 198.18.x;nacos client 走 HTTP 直连内网 100.64.0.8:8848 必须绕过它,且必须在
|
|
【建 client 前】把 Nacos host 塞进 NO_PROXY/no_proxy(urllib 建连时读环境,晚设不生效,否则栽 502)。与
|
|
service/nacos_registry._bypass_proxy_for 同款;此处 worker 包内自带一份(worker 是底层,不反向依赖 service 包)。
|
|
|
|
【真实 namespaceId = 空串(前置任务口径)】
|
|
默认公共空间的 namespaceId 是空串,不是字面量 "public"(显示名≠id)。写侧(yudao publish)与读侧(本工厂)
|
|
必须对齐同一真实 namespaceId,否则激活写进一个空间、worker 在另一个空间读、配置永远推不到(前置任务 056433d5
|
|
已把 nacos_registry / Java / 凭据档统一到空串,本工厂沿同口径:NACOS_NAMESPACE 默认空串)。
|
|
|
|
【绝不引入第二默认值(坑④·tech-decisions §13)】
|
|
注入 NacosHotConfig 的 defaults 刻意为空 {}:热源缺某键时 NacosHotConfig.get 返回 genconfig 传入的
|
|
sentinel,由 genconfig 回落链(env→YAML→内置默认)接管——热源侧登记默认值 = 第二默认源,会静默盖住单源
|
|
(双源默认值影蔽刚在收敛环栽过),故这里 defaults 必须空。
|
|
|
|
【best-effort 铁律】
|
|
构造 / 订阅 全程 try/except:启用了但 Nacos 不可达 / 鉴权失败 / 缺 nacos 库 → 只告警、不抛、不 attach,
|
|
genconfig 回落纯本地链,生成主链不被配置中心连累。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
# genconfig 顶层零重依赖(不牵 agentscope),安全直接 import。包内相对导入优先,PYTHONPATH=gen-worker 直跑兜底。
|
|
try:
|
|
from . import genconfig # 包内相对导入(worker.genconfig_nacos)
|
|
except ImportError: # pragma: no cover —— PYTHONPATH=gen-worker 直跑时的绝对导入兜底
|
|
from worker import genconfig # type: ignore
|
|
|
|
if TYPE_CHECKING:
|
|
from worker.nacos_hotconfig import NacosHotConfig
|
|
|
|
|
|
# 启用旗(默认关 = 现状不变;同时是设计 §3.6/§6 回退开关)。与 genconfig 的 TIER2_GEN__ 前缀区分:
|
|
# TIER2_GEN__ 是「压某个旋钮值」,本旗是「是否把读取后端接到 Nacos」的总开关。
|
|
_ENABLE_FLAG = "TIER2_GENCONFIG_NACOS"
|
|
|
|
# 按档默认 dataId(设计 §3.3 按档隔离)。env TIER2_GENCONFIG_NACOS_DATAID 显式指定则不分档、直接用它
|
|
# (演示脚本用它指向演示专用 dataId,不碰生产 dataId)。
|
|
_DEFAULT_DATAIDS = {
|
|
"tier2": "gen-hot-params-tier2",
|
|
"cheap": "gen-hot-params-cheap",
|
|
}
|
|
|
|
_TRUE = ("1", "true", "yes", "on")
|
|
|
|
|
|
def is_enabled() -> bool:
|
|
"""启用旗是否打开(默认关)。调用方可先查再决定是否 build;也是回退开关的判据。"""
|
|
return os.environ.get(_ENABLE_FLAG, "").strip().lower() in _TRUE
|
|
|
|
|
|
def _bypass_proxy_for(host: str) -> None:
|
|
"""把 host 并进 NO_PROXY/no_proxy(内网直连绕系统 fake-ip 代理;必须在建 nacos client 前调)。
|
|
|
|
与 service/nacos_registry._bypass_proxy_for 同款逻辑(worker 是底层,不反向 import service 包,故各存
|
|
一份):两个大小写变体都写;逗号分隔精确比对各段,避免 "100.64.0.8" 与 "100.64.0.80" 之类子串误判;
|
|
已在列表就不重复追加。
|
|
"""
|
|
for key in ("NO_PROXY", "no_proxy"):
|
|
cur = os.environ.get(key, "")
|
|
segments = [seg.strip() for seg in cur.split(",") if seg.strip()]
|
|
if host not in segments:
|
|
segments.append(host)
|
|
os.environ[key] = ",".join(segments)
|
|
|
|
|
|
def _resolve_data_id(tier: str) -> str:
|
|
"""按档解析 dataId:env TIER2_GENCONFIG_NACOS_DATAID 显式覆盖 > 档默认 > 兜底 tier2 档名(未知档不炸)。"""
|
|
override = os.environ.get("TIER2_GENCONFIG_NACOS_DATAID", "").strip()
|
|
if override:
|
|
return override
|
|
return _DEFAULT_DATAIDS.get(tier, _DEFAULT_DATAIDS["tier2"])
|
|
|
|
|
|
def build_and_attach_hot_source(*, tier: str = "tier2") -> "NacosHotConfig | None":
|
|
"""worker 启动期调:构造 v1 NacosClient → 注入 NacosHotConfig → start() → attach 进 genconfig。
|
|
|
|
Args:
|
|
tier: 档位("tier2" / "cheap"),决定订阅哪个 dataId(设计 §3.3 按档隔离)。
|
|
|
|
Returns:
|
|
已 attach 的 NacosHotConfig(供调用方持有 / 关停 / 测试);启用旗未开或构造失败 → 返回 None
|
|
(genconfig 走原链)。全程 best-effort:任何异常只告警、不抛、不 attach。
|
|
|
|
注入 NacosHotConfig 的 defaults 刻意为空 {}:热源缺某键时 get 返回 sentinel,由 genconfig 回落链
|
|
(env→YAML→内置默认)接管——热源侧【绝不】登记第二套默认值(坑④双源默认值影蔽,tech-decisions §13)。
|
|
"""
|
|
if not is_enabled():
|
|
# 默认关:不 attach、不牵 nacos 依赖,genconfig 走原链(现状字节不变)。
|
|
return None
|
|
|
|
nacos_addr = os.environ.get("NACOS_SERVER_ADDR", "100.64.0.8:8848")
|
|
namespace = os.environ.get("NACOS_NAMESPACE", "") # 默认公共空间 = 空串(勿写 public;前置任务口径)
|
|
username = os.environ.get("NACOS_USERNAME", "nacos")
|
|
password = os.environ.get("NACOS_PASSWORD", "")
|
|
group = os.environ.get("TIER2_GENCONFIG_NACOS_GROUP", "DEFAULT_GROUP")
|
|
data_id = _resolve_data_id(tier)
|
|
|
|
# 内网铁律:必须在【建 client 前】把 Nacos host 塞进 NO_PROXY,否则 HTTP 直连被 fake-ip 代理拦(502)。
|
|
_bypass_proxy_for(nacos_addr.split(":")[0])
|
|
|
|
try:
|
|
import nacos # noqa: PLC0415 —— 惰性 import:默认关时不牵 nacos 依赖(本地/CLI/6c6g 免装)
|
|
from worker.nacos_hotconfig import NacosHotConfig # noqa: PLC0415
|
|
|
|
client = nacos.NacosClient(
|
|
nacos_addr,
|
|
namespace=namespace,
|
|
username=username,
|
|
password=password,
|
|
)
|
|
# defaults={} 是硬约束:热源不登记默认值,缺键交 genconfig 回落链(杜绝双源默认值影蔽,坑④)。
|
|
hot = NacosHotConfig(client=client, data_id=data_id, group=group, defaults={})
|
|
hot.start() # 初读 + 订阅长轮询推送(best-effort:Nacos 不可达也不抛,留空 store 让 genconfig 回落)
|
|
genconfig.attach_hot_source(hot)
|
|
print(
|
|
f"[genconfig-nacos] 已接 Nacos 热源:tier={tier} dataId={data_id} group={group} "
|
|
f"namespace={namespace!r} nacos={nacos_addr}",
|
|
flush=True,
|
|
)
|
|
return hot
|
|
except Exception as e: # noqa: BLE001 —— best-effort:接线失败不 attach、不抛,genconfig 回落纯本地链
|
|
print(
|
|
f"[genconfig-nacos] 接 Nacos 热源失败(genconfig 回落纯本地 YAML,best-effort):"
|
|
f"{type(e).__name__}: {e}",
|
|
flush=True,
|
|
)
|
|
return None
|
|
|
|
|
|
def detach() -> None:
|
|
"""卸载热源,genconfig 回落纯本地链(设计 §3.6/§6 回退开关的显式入口;转调 genconfig.detach_hot_source)。"""
|
|
genconfig.detach_hot_source()
|