From ca88d8bfe06b7e6963f7806c27c7b7a3e38cbb68 Mon Sep 17 00:00:00 2001 From: zlt Date: Wed, 29 Jul 2026 15:28:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E6=8C=82=E8=BD=BD=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ app/ws/runner.py | 19 +++++++++++++++++-- docker-compose.dev.yml | 39 +++++++++++++++++++++++++++++++++++++++ scripts/deploy.sh | 6 ++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 docker-compose.dev.yml diff --git a/.gitignore b/.gitignore index e23a8e0..8fc3f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ logs/ # 本地临时: Cowork 会话搬运产物, 可整目录删除 _to_delete/ + +# 开发机的挂载覆盖 (由 docker-compose.dev.yml 复制而来)。 +# 绝不入库: 它会让容器跑磁盘代码而非镜像代码, 生产机上一旦误带就再也说不清跑的是哪版。 +docker-compose.override.yml diff --git a/app/ws/runner.py b/app/ws/runner.py index a1fa5e0..1a1c930 100644 --- a/app/ws/runner.py +++ b/app/ws/runner.py @@ -477,12 +477,27 @@ class WsRunner: if r == qmt_repo.PUT_DUP_KEY: logger.warning("成交 %s 重复推送 (seq=%s), 已占位不入账", pl.get("trade_no"), seq) + had_pending = bool(self._pending_seq) self._last_seq, self._pending_seq = wsc.next_watermark( self._last_seq, self._pending_seq, seq) self._unacked += 1 if self._pending_seq: - logger.warning("上行乱序: 水位卡在 %s, 暂存 %s 条 (待缺口补齐)", - self._last_seq, len(self._pending_seq)) + # **每条都打会把正常的补发刷成告警海。** §6.1 补发期间必然是这个形状: + # hello_ack 自带的 seq 是对端"当前"序号, 跑在补发区间之上, 于是整段补发过程中 + # 它一直躺在 pending 里 —— 水位每收一条推进 1 格、暂存恒为 1 条。 + # 2026-07-29 实测回退 60 格, 刷了 60 行 WARNING, 而通道其实完全正常。 + # 真正的异常是**暂存越堆越多**或**长时间不收敛**, 所以按次数限流, 并在补齐时 + # 明确收尾 —— 有始有终才看得出它到底有没有卡住。 + self._stat["ooo"] = self._stat.get("ooo", 0) + 1 + n, k = self._stat["ooo"], len(self._pending_seq) + if n == 1 or n % 200 == 0: + logger.warning("水位未连续: 卡在 %s, 暂存 %s 条 (第 %s 次)。刚重连的话这是 " + "§6.1 补发的正常形态, 补完自会收敛; 若暂存条数持续增长, " + "才是真乱序", self._last_seq, k, n) + elif had_pending: + logger.info("水位缺口已补齐, 连续到 %s (期间未连续 %s 次)", + self._last_seq, self._stat.get("ooo", 0)) + self._stat["ooo"] = 0 if self._unacked >= self._p("ack_batch", 20): await self._flush_ack() diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..513adfa --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,39 @@ +# 开发/联调机专用: 把源码目录挂进容器, 改完代码不用重新 build。 +# +# 为什么不直接写进 docker-compose.yml: 挂载会让容器跑的是**磁盘上的代码**, 而不是镜像里的 +# 代码。开发机上这正是想要的 (git pull 完 restart 一下就生效); 生产机上则相反 —— 部署的 +# 东西必须等于构建出来的东西, 否则"这台跑的到底是哪版"永远说不清。 +# +# 用法 (只在开发/联调机上做一次): +# cp docker-compose.dev.yml docker-compose.override.yml +# +# docker compose 会自动加载 docker-compose.override.yml, 之后: +# git pull && docker compose restart # 改 Python 代码, 秒生效 +# ./scripts/deploy.sh # 改了 requirements.txt 才需要重建 +# +# override 文件已在 .gitignore 里, 不会被带到生产机。 +# +# 注意: +# * 依赖装在镜像的 site-packages 里, 不在 /app —— 挂载不会影响它。改 requirements.txt +# 仍然必须 build。 +# * Dockerfile 设了 PYTHONDONTWRITEBYTECODE=1, 不会往你的源码目录里拉 __pycache__。 +# * pms-ws 改动**仍需 restart** —— 挂载省掉的是 build, 不是进程重启, 它是常驻连接进程, +# 不会自己重载模块。 + +services: + pms-web: + volumes: + - .:/app + - ./logs:/app/logs + pms-beat: + volumes: + - .:/app + - ./logs:/app/logs + pms-worker: + volumes: + - .:/app + - ./logs:/app/logs + pms-ws: + volumes: + - .:/app + - ./logs:/app/logs diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 8823174..cc5eb8e 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -22,6 +22,12 @@ if [[ "${1:-}" != "--no-pull" ]]; then git pull --ff-only fi +if [[ -f docker-compose.override.yml ]]; then + echo "==> 检测到 docker-compose.override.yml (源码已挂载)" + echo " 改 Python 代码不用跑本脚本, 直接 'docker compose restart' 即可。" + echo " 仍然重建一次, 以防你改的是 requirements.txt。" +fi + echo "==> 重建镜像" "${DC[@]}" build