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