跳转至

长跑 daemon 部署指南 (v1.4.94+)

本指南覆盖 daemon 在生产环境长期运行 (24h+ / 7 天 / 30 天) 时特有的配置项、 故障自愈、与失败 fallback 路径。

适用场景: - VPS / 云服务器 7×24 跑 daemon - 容器化部署 (Docker / Kubernetes) - 多账号同 daemon 多 session - 期权 / 期货策略需要长期保持订阅与 push 不中断

如果只是本地短时间手工跑 (CLI 调用 / 手动测试), 本指南不必读, 默认行为已足够.


v1.4.94 长跑加固开关 (env opt-in, default OFF)

v1.4.94 加了 2 个 env opt-in 开关, 让长跑 daemon 在 client_sig 失效前主动续期 / 持续失败时主动重新登录, 减少需要人工 admin reload / 重启 daemon 的频率.

默认 OFF 是因为这两个路径依赖 backend 的具体响应行为, 真机 verify 后下版会切 default ON. 当前生产环境若想提前启用, 按本节配置.

FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1 — 提前 1h 主动 refresh client_sig

FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1 ./futu-opend ...

何时有效: daemon 启动时从 /authority/ 响应抽出 client_sig_invalid_local_time_s (C++ auth_impl.cpp:3245-3247). 若这个时间戳 > 0 (服务端给了过期时间), daemon spawn 一个 timer 在 (invalid_time - 3600s) 时主动调 AuthRefresher::refresh_qot_login() 续期, 不等 backend 返报错.

默认 OFF 原因: backend 是否接受 multi-hour 提前 refresh 还是 "too early" reject 未真机 verify (pitfall #42). 真机 verify path:

  1. 启动 daemon: FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1 RUST_LOG=info futu-opend ...
  2. daemon log 应有 "v1.4.94 G1: client_sig invalidate scheduled" + ttl_secs
  3. daemon log 应有 "v1.4.94 G1: spawning client_sig proactive timer"
  4. 等到 timer 触发 (生产环境通常 24-48h 后), log 应有 "v1.4.94 G1: refresh completed"
  5. timer 触发后 daemon 仍能正常 query / push, 不掉线 = backend 接受提前 refresh

FUTU_CLIENT_SIG_REACTIVE_REFRESH=1 — tcp_login 持续失败时反应式 refresh

FUTU_CLIENT_SIG_REACTIVE_REFRESH=1 ./futu-opend ...

何时有效: daemon TCP 心跳失败 → reconnect monitor 启动. 若 reconnect 时 tcp_login 连续失败 ≥ 3 次, daemon 认为 client_sig 可能失效, 自动调 AuthRefresher::refresh_qot_login() + auth::reauth_via_remember_login() 拿 fresh AuthResult, 替换 reconnect monitor 的 client_sig 缓存, 下一轮 tcp_login 用新 sig.

默认 OFF 原因: tcp_login 失败的 ret_type 含义不止 client_sig 失效一种 — 还可能是服务端反刷限流 / 风控. 误 refresh 可能加重 limit (pitfall #42 + docs/protocol/auth.md "error_code=15 三大成因"). 真机 verify path:

  1. 在测试环境改本地系统时钟 +30 天 (跨过 client_sig 自然过期), 或等长跑 30 天
  2. 启动 daemon: FUTU_CLIENT_SIG_REACTIVE_REFRESH=1 ...
  3. heartbeat 必断 → reconnect monitor 启动
  4. log 应见 "reconnect login failed" (3 次)
  5. log 应见 "v1.4.94 G4: persistent tcp_login failures → trying reactive client_sig refresh"
  6. log 应见 "v1.4.94 G4: refresh_qot_login OK → reauth_via_remember_login"
  7. 后续 tcp_login 应成功, daemon 恢复 push

推荐部署组合

环境 G1 G4 备注
本地短跑 (CLI / 测试) OFF OFF 默认即可, 无 long-runner 需求
生产长跑 (24h+) early adopter ON OFF 先验 G1 不报副作用; G4 默认仍由 admin reload 处理
生产长跑 (7 天+) full opt-in ON ON 期望 zero admin reload; tester 已 verify pass
多账号集中 daemon (5+ accounts) ON OFF 启 G4 风险: 一账号反刷 → daemon 多账号都被影响

具体 verify 后真机抓包 (.githooks/pre-push 已含 redact 规则, 可安全 paste log 给开发者) → v1.4.95+ 评估切 default ON.


容器化部署示例

Docker

# docker-compose.yml 片段
services:
  futu-opend:
    image: ghcr.io/futuleaf/futu-opend-rs:v1.4.94
    restart: unless-stopped
    environment:
      FUTU_ACCOUNT: <account>
      FUTU_PWD: <password>
      FUTU_CLIENT_SIG_PROACTIVE_REFRESH: "1"   # 长跑加固
      # FUTU_CLIENT_SIG_REACTIVE_REFRESH: "1"  # 单账号场景才开
    volumes:
      - ./.futu-opend-rs:/root/.futu-opend-rs    # device_id + credentials 持久化
    ports:
      - "11111:11111"   # gRPC
      - "11112:11112"   # REST
      - "11113:11113"   # WebSocket

systemd 单元

[Unit]
Description=futu-opend long-running gateway
After=network-online.target

[Service]
Type=simple
User=futu
EnvironmentFile=/etc/futu-opend.env
Environment=FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1
ExecStart=/usr/local/bin/futu-opend --rest-port 11112 --grpc-port 11111
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=multi-user.target

/etc/futu-opend.envFUTU_ACCOUNT=... + FUTU_PWD=... (mode 0600 owner=futu).


自愈 vs 人工干预

场景 v1.4.94 行为 v1.4.94 行为 env required
client_sig 自然过期 (30 天) daemon 报错 → 需 admin reload G1 timer 提前 1h 自动续 FUTU_CLIENT_SIG_PROACTIVE_REFRESH=1
网络抖动后 tcp_login 1-2 次 fail 指数退避后续重试
网络抖动后 tcp_login ≥ 3 次 fail 持续 fail / 人工干预 G4 自动 refresh + retry FUTU_CLIENT_SIG_REACTIVE_REFRESH=1
服务端反刷 (error_code=15) 等 60s 重试 同 (G4 不 trigger 反刷场景)
账号风控 / 密码改 / 设备锁 必须人工 SMS / --reset-device
broker auth_code 30 天过期 v1.4.94 G2 RepullAuthCode 已自动 refresh (default ON 自 v1.4.94)

Troubleshooting

G1 启动后 timer 不 spawn

log 找 "v1.4.94 G1 skip" 看 reason: - "client_sig_invalid_local_time_s=0": backend 没返此字段 → 老 backend / 旧 credentials shell. --reset-device 重新 password auth 后 backend 会下发新字段. - "env FUTU_CLIENT_SIG_PROACTIVE_REFRESH not set to 1": env var 没传到 daemon. - "TooLate": invalid_time 已过期或不到 1h, 跳过. 重启 daemon 让首登拿新 invalid_time.

G4 启动后 refresh 不 trigger

log 找 "v1.4.94 G4 skip" 看 reason: - "consecutive failures below threshold": 失败次数还没到 3 - "auth_refresher not injected": 内部 wiring 问题 → 报 issue - "already attempted refresh this cycle": 防 refresh-fail-loop. 重启 daemon 重置.

G4 refresh 失败但 daemon 继续用 stale auth

设计如此. log 应有 "v1.4.94 G4: refresh_qot_login failed (continue with stale AuthResult)" 或 "v1.4.94 G4: reauth_via_remember_login failed (continue with stale AuthResult; user may need admin reload)". G4 是 best-effort, 失败不打断老 fallback 行为 (admin reload 路径完整保留).


与 v1.4.95+ 计划

  • v1.4.95+: G1/G4 真机 verify 充分后切 default ON, 本指南顶部的 "default OFF" 描述更新.
  • v1.5+: G6 实际 moomoo path broker channel routing (现 v1.4.94 只 lay infrastructure, routing 仍走主 client_sig 对齐 C++ 等价行为).

如有部署问题, 提 issue 或 contact via official site.