处理联调
This commit is contained in:
parent
5505da5e15
commit
01b3efa684
|
|
@ -471,14 +471,33 @@ class WsRunner:
|
||||||
self._stat["last_reject"] = (
|
self._stat["last_reject"] = (
|
||||||
f"{code or '(无 code)'} · {str(pl.get('reason') or '(无 reason)')[:80]}"
|
f"{code or '(无 code)'} · {str(pl.get('reason') or '(无 reason)')[:80]}"
|
||||||
f" · 针对 {iid or '(消息未带 instruction_id)'}")
|
f" · 针对 {iid or '(消息未带 instruction_id)'}")
|
||||||
await _db(qmt_repo.update_order, iid, status=qmt_repo.OS_REJECTED,
|
# reject 按协议是**委托级**响应, 必然带 instruction_id。不带的话它拒的就不是
|
||||||
reject_code=code[:32], reject_reason=str(pl.get("reason") or "")[:300],
|
# 委托, 而是我们发的某条协议消息 (hello/ping/ack_seq) —— 这是通道故障, 不是
|
||||||
final_at=datetime.now())
|
# 交易结果, 不能拿去改委托状态 (iid 为空时 update_order 是空转, 更糟的是它把
|
||||||
# 设计 §13: 指令下发失败不自动重发。可重试码也只是记下来,
|
# 故障伪装成了"没事发生")。这里分开计数并原样打出整条 payload: 对端的 reject
|
||||||
# 由 executor 下一跳按最新行情重新决定 —— 换价重发的判断权在择时模块。
|
# 可能带我们没读的字段, 猜不如看。
|
||||||
lvl = logger.error if code == "INTERNAL" else logger.warning
|
if not iid:
|
||||||
lvl("[reject] %s %s: %s (retryable=%s, 不自动重发)", iid, code,
|
self._stat["proto_rejects"] = self._stat.get("proto_rejects", 0) + 1
|
||||||
pl.get("reason"), pl.get("retryable"))
|
n = self._stat["proto_rejects"]
|
||||||
|
if n <= 3 or n % 50 == 0:
|
||||||
|
logger.error("[协议级 reject #%s] 对端拒绝了我们发的一条协议消息 "
|
||||||
|
"(非委托, 无 instruction_id)。整条 payload: %s",
|
||||||
|
n, json.dumps(pl, ensure_ascii=False)[:500])
|
||||||
|
if n == 10:
|
||||||
|
logger.error("协议级 reject 已达 10 条且仍在增长: 很可能是"
|
||||||
|
"「我们 ack → 对端拒 → 拒绝本身带 seq → 我们又要 ack」"
|
||||||
|
"的自激循环, 通道看着 ONLINE 其实什么也没在做。"
|
||||||
|
"请拿上面的 payload 与 QMT 侧对齐消息格式")
|
||||||
|
else:
|
||||||
|
await _db(qmt_repo.update_order, iid, status=qmt_repo.OS_REJECTED,
|
||||||
|
reject_code=code[:32],
|
||||||
|
reject_reason=str(pl.get("reason") or "")[:300],
|
||||||
|
final_at=datetime.now())
|
||||||
|
# 设计 §13: 指令下发失败不自动重发。可重试码也只是记下来,
|
||||||
|
# 由 executor 下一跳按最新行情重新决定 —— 换价重发的判断权在择时模块。
|
||||||
|
lvl = logger.error if code == "INTERNAL" else logger.warning
|
||||||
|
lvl("[reject] %s %s: %s (retryable=%s, 不自动重发)", iid, code,
|
||||||
|
pl.get("reason"), pl.get("retryable"))
|
||||||
elif type_ == wsc.T_ORDER_UPDATE:
|
elif type_ == wsc.T_ORDER_UPDATE:
|
||||||
await self._on_order_update(iid, pl)
|
await self._on_order_update(iid, pl)
|
||||||
elif type_ == wsc.T_TRADE:
|
elif type_ == wsc.T_TRADE:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# tradingSystem (PMS) 一键部署
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 为什么需要它: 源码是**打进镜像**的 (docker-compose.yml 只挂了 ./logs), 所以
|
||||||
|
# `docker compose restart` 只是把老容器停了再起, 跑的还是旧镜像里的旧代码 ——
|
||||||
|
# 改完代码 restart 一下看不到变化, 而且**一点报错都没有**, 排查时极其误导。
|
||||||
|
# 代码改动要生效必须 build + force-recreate, 这个脚本就是干这个的。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# ./scripts/deploy.sh 拉代码 → 重建镜像 → 重建服务 → 报状态
|
||||||
|
# ./scripts/deploy.sh --no-pull 跳过 git pull (本地已改好, 不想拉远端)
|
||||||
|
# PROFILES="--profile ws" ./scripts/deploy.sh 只跑页面 + ws (默认 sched+ws)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
PROFILES="${PROFILES:---profile sched --profile ws}"
|
||||||
|
DC=(docker compose $PROFILES)
|
||||||
|
|
||||||
|
if [[ "${1:-}" != "--no-pull" ]]; then
|
||||||
|
echo "==> git pull"
|
||||||
|
git pull --ff-only
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> 重建镜像"
|
||||||
|
"${DC[@]}" build
|
||||||
|
|
||||||
|
echo "==> 重建容器 (force-recreate: 让新镜像真正生效)"
|
||||||
|
"${DC[@]}" up -d --force-recreate
|
||||||
|
|
||||||
|
echo
|
||||||
|
"${DC[@]}" ps
|
||||||
|
echo
|
||||||
|
echo "==> 建表/补表 (幂等)"
|
||||||
|
docker compose run --rm --no-deps pms-web python scripts/init_db.py --yes || {
|
||||||
|
echo "建表失败 —— 上面有原因, 服务已经起来了, 修完重跑本脚本即可"; exit 1; }
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "完成。ws 通道状态:"
|
||||||
|
echo " docker compose run --rm --no-deps pms-web python scripts/ws_smoke.py status"
|
||||||
|
|
@ -96,7 +96,13 @@ def cmd_status(args):
|
||||||
f"持续增长则说明序号语义对不上")
|
f"持续增长则说明序号语义对不上")
|
||||||
if int(s.get("reconnects") or 0) >= 5:
|
if int(s.get("reconnects") or 0) >= 5:
|
||||||
bad.append(f"重连 {s['reconnects']} 次: 连接不稳, 见上面「最后断开原因」")
|
bad.append(f"重连 {s['reconnects']} 次: 连接不稳, 见上面「最后断开原因」")
|
||||||
if int(s.get("rejects") or 0) and not qmt_repo.list_orders(limit=1):
|
if int(s.get("proto_rejects") or 0):
|
||||||
|
bad.append(f"协议级 reject {s['proto_rejects']} 条 (不带 instruction_id, 拒的不是委托"
|
||||||
|
f"而是我们发的协议消息)。若 rx≈tx 且两者同步增长, 基本可以断定是"
|
||||||
|
f"「我们 ack → 对端拒 → 拒绝本身带 seq → 我们又要 ack」的自激循环: "
|
||||||
|
f"通道显示 ONLINE, 实际什么也没在做。整条 payload 见 "
|
||||||
|
f"docker compose logs pms-ws | grep 协议级")
|
||||||
|
elif int(s.get("rejects") or 0) and not qmt_repo.list_orders(limit=1):
|
||||||
bad.append(f"出口表一张委托都没有, 却收到 {s['rejects']} 条 reject —— 对端在拒绝我们"
|
bad.append(f"出口表一张委托都没有, 却收到 {s['rejects']} 条 reject —— 对端在拒绝我们"
|
||||||
f"的非委托消息 (hello/ping/ack?), 见上面「最后一条拒绝」")
|
f"的非委托消息 (hello/ping/ack?), 见上面「最后一条拒绝」")
|
||||||
if ch.get("resync_required"):
|
if ch.get("resync_required"):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue