Config/Bus 配置传播与回滚实验(U34)
仓库根目录的公开入口:
python3 labs/config-bus/run.py需要 Python 3.8+、JDK 21 和 Maven 3.9.x。可用 JAVA_HOME 指定 JDK、MAVEN_BIN 指定 Maven 可执行文件;macOS 未设置 JAVA_HOME 时通过 /usr/libexec/java_home -v 21 选择 JDK。首次解析 Maven 依赖需要网络。临时 Git 仓库由真实 JGit 库创建与提交,无需额外调用 Git CLI。无需手动启动 Config Server 或消息代理,不连接本机已有的配置服务、Kafka 或 RabbitMQ。
本目录是一个 Maven 模块:pom.xml、公开 runner、HTTP 测试、资源 fixture 与故障代理、Config Server、客户端与状态端点、HTTP 权限配置。对应 Issue 71 与 PRD L04。
组件与观察边界
测试通过真实 socket HTTP 读取 Config Server、客户端状态和 Actuator 端点。Config Server 与两个客户端分别运行在独立的 Spring ApplicationContext 中,使用随机 loopback HTTP 端口;三个应用 Context 都位于同一个测试 JVM,不是三个独立 OS 进程。Context 各自维护配置和刷新状态,但共享 JVM 的堆、线程、系统属性与进程生命周期,不能据此推断跨进程故障隔离。
| 组件 | 实际实现与用途 |
|---|---|
| 配置服务 | 真实 Spring Cloud Config Server,通过 HTTP 提供配置;客户端使用真实 Config Client 加载配置 |
| 配置仓库 | 每次 fixture 用真实 JGit 库新建本地临时 Git 仓库,以合成配置提交产生版本;不修改本仓库 Git 历史 |
| 两个客户端 | 独立 Spring Context 与随机 HTTP 端口;从各自公开 HTTP 状态观察最终生效值 |
| 传播通道 | spring-kafka-test 启动真实 embedded Kafka broker,使用本次独有 topic;Bus 使用真实 Kafka binder 传播消息,不使用内存 Test Binder |
| 故障注入代理 | 本地受控 HTTP 代理,用于阻断指定客户端到真实 Config Server 的请求;它是故障注入设施,其错误响应不代表 Config Server 自身的故障实现 |
通过的实验只支持测试输入与观察窗口内的局部结论。Bus 发布端收到 HTTP 成功响应不等于所有实例均已刷新;应分别核查两个客户端的配置值、版本及恢复结果。生产配置变更的全实例原子刷新与原子回滚不在本实验的证明范围内。
用例与预期观察
以下是用例要求的行为;本次是否通过以 runner 生成的运行证据为准。测试名称固定在 runner 的 EXPECTED_TESTS,观察行固定在 MARKERS,两者均与 Surefire/进程输出核对。
| 测试方法 | 公开 HTTP 操作与预期结果 | 观察行 |
|---|---|---|
clientLoadsGitProfileThroughRealHttpServer | 两个客户端均读取 catalog/prod/main,得到 profile-v1 | U34 initialRead=PASS |
gitCommitDoesNotRefreshObjectsUntilTheLocalEndpointRuns | 单独提交 Git 不改变当前对象;A 调用 /actuator/refresh 后刷新到 v2,B 保留 v1,普通单例仍持有初始值 | U34 localRefresh=PASS |
busDestinationUpdatesOnlyBAndBroadcastConvergesBoth | A 发布到 /actuator/busrefresh/catalog:b,只有 B 更新;再广播,经真实 Kafka 传播后两端均收敛到 v3 | U34 busPropagation=PASS |
oneFailedConfigPathLeavesMixedVersionsAndExplicitRefreshRepairsIt | 代理仅向 B 的 Config HTTP 返回 503,消费重试 max-attempts=1;广播返回 204,A 更新、B 保持旧值;解除阻断并显式本地刷新 B 后收敛 | U34 partialFailure=PASS |
invalidLazyTargetFailsAfterRefreshAndGitRevertRestoresWithANewVersion | 发布非法参数后刷新端点返回 200,但延迟创建的业务对象导致 /state 返回 500;Git revert 产生新提交,再刷新恢复值并核查新配置版本 | U34 rollback=PASS |
ciphertextAtRestDoesNotReplaceAuthenticationOrEnvironmentRedaction | 未授权访问 Config Server、/encrypt 与客户端 refresh 端点均 401;{cipher} 密文只进 Git,不进入无关应用的响应;授权 Config HTTP 按设计返回明文,客户端 /state 与 /actuator/env 均不含原始秘密;非法密文回退 invalid. 前缀并可用新密文恢复 | U34 secretProtection=PASS |
部分失败用例中 B 的 Bus 消费者仍运行,隔离的是它到 Config Server 的 HTTP 路径。恢复由显式 /actuator/refresh 触发;该用例不证明消费者离线恢复、Kafka 断线重放或自动修复混版。
版本与运行记录
固定组合为 Boot 4.0.9、Cloud release train 2025.1.4、Config Server/Client 5.0.6、Bus 5.0.4、spring-kafka/spring-kafka-test 4.0.8、embedded Kafka 4.1.2。runner 同时核对 POM 声明与 Maven 解析结果,版本不符即失败;JDK 发行商/完整版本、Maven、Python、OS、架构以及 JGit 等实际依赖版本写入本次记录。
runner 执行单模块 clean test,明确禁止跳过清理/测试和忽略失败;随后核对 study.configbus.ConfigBusHttpTest 的本轮 Surefire 报告、6 个测试名称/总数、失败/错误/跳过计数,以及 6 条登记的 U34 …=PASS 观察行。报告缺失/陈旧、测试数减少、测试跳过、观察行缺失/重复、依赖版本不符、命令超时、进程清理失败或运行期间源码改变,均不得产生成功状态。
证据固定写入 evidence/u34-2026-10-27.json,包含开始/结束时间、输入、每条命令与退出码、观察行、实际依赖版本、Surefire 名称/计数、实验文件与日志的 SHA-256。只有 status=passed 且 exitCode=0 的完整记录表示该次验证通过;README 和预期观察本身不代表已经实跑。
原始日志、命令状态、Surefire XML 副本及依赖清单保存在仓库外:
~/.cache/software-engineering-study/java-depth-impl/u34/run-<随机目录>/每次执行先将证据标为 running,完成后原子替换为 passed 或 failed;失败会覆盖上一轮成功状态,保留该轮日志路径。强杀 runner 时可能只留下 running,也不能视为成功。测试阶段和依赖解析各限时 600 秒,超时返回 124;SIGINT/SIGTERM 分别返回 130/143。runner 对自己创建的进程组先发 TERM,再在有界宽限后发 KILL,避免 Maven 已退出而测试 JVM 仍占用端口。
fixture 在 close 中关闭客户端/Config Server Context、HTTP 服务与故障代理、Kafka broker,并清理临时 Git/工作目录;进程组回收是 runner 的退出保障。runner 保留本次缓存日志、Maven 构建产物和证据用于检查,不按端口或进程名清理本机其他服务。
未运行范围
- TLS/mTLS、真实凭据或密钥管理系统、生产网络与身份提供方。
- RabbitMQ binder、Nacos 配置服务及其运行行为;教材中的 Nacos 对照需要单独证据。
- 多节点 Config Server/Kafka 集群、跨主机网络分区、生产吞吐/容量与消息持久性承诺。
- 独立 OS 客户端进程、OS 进程崩溃/强杀、断电恢复与滚动发布。
- 全实例原子刷新、生产原子回滚及任意失败下的一致配置保证。
配置与凭据只使用合成数据;秘密信息相关观察不能替代生产安全审计。上述项目均未在本实验运行,也不因单 JVM 内测试通过而获得验证。