2026-10-11 23:08:44 +02:00
|
|
|
|
# MQTT 可靠性修复 — 2026-10-11
|
|
|
|
|
|
|
|
|
|
|
|
本次范围为 MQTT-FIX-T01 → MQTT-FIX-T02,依赖顺序执行,两张卡自动继续。
|
|
|
|
|
|
按 [AGENTS.md](../../AGENTS.md) 与 [协作契约](README.md) 实现、独立审查及返工。
|
|
|
|
|
|
`done` 仅代表源码自动化技术验收完成;**源码修复完成后仍须人工部署与生产验证**。
|
|
|
|
|
|
中文实现、审查及返工简报存放于已忽略的 `review-notes/`,不入库。
|
|
|
|
|
|
|
|
|
|
|
|
## 证据与边界
|
|
|
|
|
|
|
|
|
|
|
|
排查日期 2026-10-11,Europe/Amsterdam。以下生产观察由用户提供,未在本轮访问生产确认:
|
|
|
|
|
|
|
|
|
|
|
|
1. 应用当天约 01:00 启动,故障前未自动重启、未发生 OOM。
|
|
|
|
|
|
2. 16:50:43 起出现 `MQTT disconnected unexpectedly (reason_code=Unspecified error).`。
|
|
|
|
|
|
3. 截至约 22:34 累计约 270 万条断线日志;22:34–22:35 一分钟内 15,283 次断线、101 次 publish rejected。
|
|
|
|
|
|
4. 一个 uvicorn 进程、284 个总线程;并非已经确认 284 个 MQTT 客户端。
|
|
|
|
|
|
5. 主 client ID 为 `home-automation`;配置最后更新时间为 2026-08-28,当天未观察到变更。
|
|
|
|
|
|
6. Modbus、WarmteLink 串口及独立 DSMR MQTT source 仍有新数据,不能描述为 broker 整体停止服务。
|
|
|
|
|
|
7. 生产镜像的 `app/main.py`、`app/integrations/mqtt.py` SHA256 与源码一致。
|
|
|
|
|
|
8. 用户在 broker 日志中看到大量新连接及 `session taken over`。
|
|
|
|
|
|
9. 22:37 用户授权重启应用后,观察超过一分钟未见新异常断线或发布失败,各采集源恢复;仅属临时恢复。
|
|
|
|
|
|
10. 首次断线触发原因未知,不能将网络故障或 broker 重启写成事实。
|
|
|
|
|
|
|
|
|
|
|
|
待独立核实的链路:60 秒 HA 发布任务在未连接时调用 `connect()`;它无条件创建同 ID Paho
|
|
|
|
|
|
client/线程、覆盖引用但未停旧 client;旧回调能覆盖共享连接状态。已有离线 fake 复现只是
|
|
|
|
|
|
排查线索,不能代替本轮回归测试。
|
|
|
|
|
|
|
|
|
|
|
|
部署背景:生产日志配置为 `{"Type":"json-file","Config":{}}`;约 276 万行日志,按内容
|
|
|
|
|
|
估算 JSON 文件约 353 MiB(非 stat 精确大小),当时约 666 GB 空闲,无即时容量危机。
|
|
|
|
|
|
生产 Compose 在 `/home/tianyu/.local/share/homeautomation`,与源码 Compose 是两个文件。
|
|
|
|
|
|
|
|
|
|
|
|
**本轮禁止**:push、tag、改写已推送历史、修改生产配置、重启/重建生产容器、连接真实
|
|
|
|
|
|
MQTT broker(包括 `10.238.75.82:1883`)、访问真实串口、改动生产数据库或日志;禁止
|
|
|
|
|
|
清空历史日志、删除 Docker 文件、prune、变更全局 Docker daemon。仅提供部署步骤。
|
|
|
|
|
|
允许源码/测试/部署文档修改、本地 commit、fixup 及整理本轮尚未推送提交。保留已有无关改动。
|
|
|
|
|
|
|
|
|
|
|
|
### MQTT-FIX-T01 — 修复主 MQTT 客户端生命周期
|
|
|
|
|
|
|
|
|
|
|
|
- **Status**: `done`
|
|
|
|
|
|
- **Depends**: none
|
|
|
|
|
|
- **Context**: 防止短暂断线后定时任务创建同 ID 客户端累积,避免旧回调污染新连接。
|
|
|
|
|
|
|
|
|
|
|
|
**Files**
|
|
|
|
|
|
- `modify app/integrations/mqtt.py`
|
|
|
|
|
|
- `modify app/main.py`
|
|
|
|
|
|
- `modify tests/test_mqtt_client.py`
|
|
|
|
|
|
- `modify tests/test_mqtt_subscribe.py`
|
|
|
|
|
|
- `create tests/test_mqtt_scheduler.py`(必要的调度任务回归)
|
|
|
|
|
|
- `modify README.md`(MQTT 生命周期说明)
|
|
|
|
|
|
- `modify .gitignore`(编排过程报告目录保护)
|
|
|
|
|
|
- `create docs/design/mqtt-reliability-fixes.md`(主线维护任务与 Status)
|
|
|
|
|
|
|
|
|
|
|
|
**Steps**
|
|
|
|
|
|
1. 独立核实当前生命周期、回调及调度代码,结合安装的 Paho 2.1.0 源码核对启动顺序和失败清理。
|
|
|
|
|
|
2. 使同配置 `connect()` 幂等,区分正在连接、等待自动重连、无客户端;现有 client 能自动重连时不再创建额外 client。
|
|
|
|
|
|
3. 初始连接失败、初始化失败、后台循环失效均须可恢复,失败对象/循环清理,不能永久跳过重试。
|
|
|
|
|
|
4. 配置变更安全停止、清理旧 client 后替换,受管理的主 client 只有一个。
|
|
|
|
|
|
5. connect/disconnect/message 回调核验身份或 generation,拒绝旧状态及过期消息。
|
|
|
|
|
|
6. 处理创建、替换、关闭与回调并发;不能持有回调需要的锁调用等待网络线程退出的 `loop_stop()`。
|
|
|
|
|
|
7. 保持独立 DSMR source、订阅恢复、MQTT 测试身份及现有 publish 行为兼容。
|
|
|
|
|
|
8. 增加离线故障与并发回归,更新生命周期说明,运行适用闸门并保存中文实现简报。
|
|
|
|
|
|
|
|
|
|
|
|
**Out of scope / 不要碰**
|
|
|
|
|
|
- 不使用随机 ID、单纯换 ID 或降低日志级别掩盖问题。
|
|
|
|
|
|
- 不改变 HTTP 路由/schema/docstring;如确有必要变更,须同步 OpenAPI 和前端类型。
|
|
|
|
|
|
- 不改变旧 milestone 完成状态;不访问生产或真实设备;不处理已有 Compose 串口改动。
|
|
|
|
|
|
|
|
|
|
|
|
**Acceptance criteria**
|
|
|
|
|
|
- [x] 同配置重复 connect 及断线后多轮定时任务不累积 client 或后台循环。
|
|
|
|
|
|
- [x] 初次连接/初始化失败后能重试,失败对象和循环被清理,循环失效也有恢复路径。
|
|
|
|
|
|
- [x] 配置重连后旧 client 停止,旧 connect/disconnect/message 回调不能影响新状态或处理过期消息。
|
|
|
|
|
|
- [x] 当前 client 重连成功后恢复订阅与发布。
|
|
|
|
|
|
- [x] shutdown、reconnect 与回调交错时能完成、不死锁。
|
|
|
|
|
|
- [x] 新测试使用 mock/fake 及合成配置,不连接生产 broker 或真实串口;覆盖行为与并发边界。
|
|
|
|
|
|
- [x] DSMR source、测试 client ID 及现有 publish 行为兼容。
|
|
|
|
|
|
- [x] pytest、ruff 全绿,独立 Reviewer 重跑;如适用同步契约/检查构建上下文。
|
|
|
|
|
|
|
|
|
|
|
|
**Reviewer checklist**
|
|
|
|
|
|
- 独立核查 Paho 2.1.0 生命周期顺序、线程泄漏、状态污染、死锁及失败恢复,不仅断言内部实现细节。
|
|
|
|
|
|
- 核查调度触发路径、多轮断线自动重连、配置替换与关闭交错,以及旧消息拒绝。
|
|
|
|
|
|
- 核查 DSMR 独立客户端及订阅/publish/test identity 回归风险。
|
|
|
|
|
|
- 先读对应实现/返工简报,独立重跑闸门;报告 PASS 或编号返工清单,不改实现或 Git。
|
|
|
|
|
|
|
|
|
|
|
|
### MQTT-FIX-T02 — 为 Docker 日志配置轮转
|
|
|
|
|
|
|
2026-10-11 23:42:08 +02:00
|
|
|
|
- **Status**: `done`
|
2026-10-11 23:08:44 +02:00
|
|
|
|
- **Depends**: MQTT-FIX-T01
|
|
|
|
|
|
- **Context**: 为本项目容器限定日志保留,明确源码修改与生产容器应用配置是两个操作。
|
|
|
|
|
|
|
|
|
|
|
|
**Files**
|
|
|
|
|
|
- `modify docker-compose.yml`
|
|
|
|
|
|
- `modify docker-compose.dev.yml`(仅必要时)
|
|
|
|
|
|
- `modify README.md`(部署说明)
|
|
|
|
|
|
- `modify tests/test_deployment.py`(必要的静态检查)
|
|
|
|
|
|
- `modify docs/design/mqtt-reliability-fixes.md`(主线维护 Status)
|
|
|
|
|
|
|
|
|
|
|
|
**Steps**
|
|
|
|
|
|
1. 为 app 与 migration 均配置 `logging.driver: json-file`,字符串 options `max-size: "10m"`、`max-file: "3"`。
|
|
|
|
|
|
2. 验证开发 override 合并后保留相同合理的轮转配置,不修改全局 daemon 或其他项目。
|
|
|
|
|
|
3. 用 `docker compose config --quiet` 或等效静态检查验证基础与合并配置;不输出含凭据的完整配置。
|
|
|
|
|
|
4. 说明宿主机日志路径 `/var/lib/docker/containers/<完整ID>/<完整ID>-json.log`、`docker logs`、保留策略。
|
|
|
|
|
|
5. 明确仅 `docker restart` 不应用 logging 配置,需要重建容器。
|
|
|
|
|
|
6. 给出生产目录 `/home/tianyu/.local/share/homeautomation` 中的最小配置同步、重建、验证与回退步骤。
|
|
|
|
|
|
7. 写清发布源码修复镜像与仅应用日志配置各自前提,以及短暂采集中断;不覆盖生产设备/volume/env 等配置。
|
|
|
|
|
|
8. 保存中文简报,跑 pytest / ruff 和配置验证,独立 Reviewer 核查可执行性。
|
|
|
|
|
|
|
|
|
|
|
|
**Out of scope / 不要碰**
|
|
|
|
|
|
- 不自动部署,不清空或删除日志,不 prune,不触碰设备历史数据。
|
|
|
|
|
|
- 不修改全局 Docker daemon;不把源码配置变更描述为生产已生效。
|
|
|
|
|
|
- 不纳入用户已有的串口映射/group_add 改动。
|
|
|
|
|
|
|
|
|
|
|
|
**Acceptance criteria**
|
2026-10-11 23:42:08 +02:00
|
|
|
|
- [x] app、migration 最终 Compose 均含指定字符串 logging options。
|
|
|
|
|
|
- [x] 开发 override 合并后日志配置正确。
|
|
|
|
|
|
- [x] Compose 经 config --quiet 或等效静态检查,不输出凭据。
|
|
|
|
|
|
- [x] 文档说明日志路径、docker logs、保留策略、restart 无效而须重建。
|
|
|
|
|
|
- [x] 文档明确生产路径、最小同步、镜像更新前提、采集中断、验证与回退;区分镜像发布和日志配置应用。
|
|
|
|
|
|
- [x] 无生产操作、数据/日志删除,已有无关改动保留。
|
|
|
|
|
|
- [x] pytest、ruff 全绿,独立 Reviewer 重跑并核查合并及部署可执行性。
|
2026-10-11 23:08:44 +02:00
|
|
|
|
|
|
|
|
|
|
**Reviewer checklist**
|
|
|
|
|
|
- 核查 logging options 类型及基础/开发配置合并;静态配置检查的范围和凭据保护。
|
|
|
|
|
|
- 核查生产/source Compose 分离,镜像构建发布前提与 logging 重建步骤是否明确。
|
|
|
|
|
|
- 核查只涉及 app/migration,不变更串口、volume、生产 DB、全局 daemon;说明旧日志在重建时的 Docker 生命周期影响及可回退方式。
|
|
|
|
|
|
- 先读对应实现/返工简报,独立重跑闸门,给 PASS 或编号返工清单。
|