84 lines
3.8 KiB
Markdown
84 lines
3.8 KiB
Markdown
# 给 QMT 侧:补 `ack_seq` 处理(协议 §4.5)
|
||
|
||
## 现象
|
||
|
||
PMS 每 2 秒发一次 `ack_seq`,QMT 侧回:
|
||
|
||
```json
|
||
{"instruction_id": null, "code": "BAD_PARAM", "reason": "unknown type ack_seq", "retryable": false}
|
||
```
|
||
|
||
定位到 `handlers.py` 的 `handle()`:类型分发链里有 `hello` / `ping` / `place_order` /
|
||
`cancel_order` / `query_positions` / `query_funds` / `query_orders`,**唯独没有 `ack_seq`**,
|
||
所以落到了最后一行的兜底:
|
||
|
||
```python
|
||
self._reject_raw(envelope, 'BAD_PARAM', f'unknown type {msg_type}', False)
|
||
```
|
||
|
||
对过一遍:我方下行 8 个类型,贵方实现了 7 个,只差这一个。
|
||
|
||
## 为什么会漏
|
||
|
||
多半是协议文档的锅,不是实现的锅。`QMT_WS_PROTOCOL.md` 的版本历史表里,
|
||
**V1.0 定稿那行排在 V0.9.2 上面**,而 `ack_seq` 恰恰是 V0.9.2 才补进来的
|
||
(「新增下行消息 ack_seq 累积确认(§4.5)」)。从上往下读到「V1.0 定稿」就停的话,
|
||
底下引入 `ack_seq` 的那行会被跳过去。PMS 侧会把这两行顺序修正,条款不动。
|
||
|
||
## 后果(已定位,非猜测)
|
||
|
||
不处理会形成自激循环:
|
||
|
||
```
|
||
PMS 发 ack_seq → QMT 回 reject → reject 自身带 seq → PMS 水位推进
|
||
→ 2 秒后 PMS 又要 ack → 再被 reject → ...
|
||
```
|
||
|
||
实测 2 秒一条,十几分钟烧掉 800 多个 seq。通道显示 ONLINE,实际只在刷 reject。
|
||
|
||
更实质的影响在贵方:§6.1 规定「QMT 收到 `ack_seq{seq: N}` 后可清理 `seq ≤ N` 的消息」。
|
||
收不到 ack,**Redis 里的上行消息只涨不清**。联调期 Redis 不设过期(双方已确认),
|
||
这部分会一直堆着。
|
||
|
||
## 补法
|
||
|
||
`handlers.py` 的 `handle()` 里,在 `if msg_type == 'ping':` 附近加一个分支:
|
||
|
||
```python
|
||
if msg_type == 'ack_seq':
|
||
seq = int(payload.get('seq') or 0)
|
||
if seq > 0:
|
||
self.store.ack(seq) # 清理 seq <= N;方法名按贵方 store 的实际接口来
|
||
return # 不需要回任何消息
|
||
```
|
||
|
||
三点提醒:
|
||
|
||
1. **`ack_seq` 不需要回消息。** 它是单向通知,回 `ack` 或 `pong` 都会让 PMS 侧多一条
|
||
无主上行。
|
||
2. **累积语义。** 收到 `{seq: 10450}` 表示 `seq ≤ 10450` 全部已落库,可一次性清理,
|
||
不是只清这一条。
|
||
3. **§6.1 的 7 天下限仍要守。** 「已确认的消息也至少保留 7 天」——防 PMS 侧库回滚后
|
||
无从追溯。所以是「可清理」不是「立即删」,按贵方存储策略取舍。
|
||
|
||
## PMS 侧已做的临时处理
|
||
|
||
PMS 已加自动降级:收到 reason 里含 `ack_seq` 的协议级 reject 就**停发 `ack_seq`**,
|
||
打断循环。水位照常落库,重连补发靠 `hello.last_seq`(§4.5 原话:「断线重连时以
|
||
`hello.last_seq` 为准,`ack_seq` 只用于让 QMT 及时释放存储」),**不丢成交**。
|
||
|
||
标志位每次重连重置——贵方补上之后,PMS 重连即自动恢复发送,不需要通知我们改配置。
|
||
|
||
## 顺带确认的几件事
|
||
|
||
- **签名完全互通。** `crypto1.py` 的 `compact_payload_json` / `build_canonical` 与 PMS 侧
|
||
逐字一致,双向验签实测通过,这一层可以划掉了。
|
||
- **`new_nonce()` 已修。** 上一版是 `token_hex(8)`(8 字节),现在是 `token_hex(16)`,
|
||
符合 §2.2。
|
||
- **时间窗。** `handlers.py` 对我方每条消息做 ±30 秒检查(超了回 TS_SKEW)。这是对的,
|
||
但两机时钟漂开会让 `place_order` 直接被拒,现象是「下单没反应」,很难往时钟上想。
|
||
PMS 侧已把实测偏差显示出来,建议两边都确认 NTP 在跑。
|
||
- **`pong` 带 seq** 是符合 §5「所有上行消息带 seq」的,实现没问题。只是按 §6.1 它也会
|
||
进 Redis 并在重连时补发——5 秒一个,一天 1.7 万条纯 `{}`。要不要给 `pong` 免掉 seq,
|
||
是个可以聊的口径问题,不急。
|