5.6 KiB
Agent-Panel 通信优化:提升稳定性与效率
背景
Agent(go-gost/x/socket/websocket_reporter.go)与 Panel(go-backend/internal/ws/server.go)之间通过 WebSocket 进行实时通信,包括指标上报(每 5s)、命令下发/响应、和流量上报(HTTP)。经过代码审查,以下是发现的问题和优化建议。
发现的问题
1. Keepalive 时序不匹配 —— 导致误断连
| 参数 | Agent 侧 | Panel 侧 |
|---|---|---|
| Read deadline | reporterReadWait = 60s |
wsPongWait = 45s |
| Ping 发送间隔 | 无主动 ping(靠指标数据 5s 续命) | wsPingPeriod = 15s |
| Write timeout | reporterWriteWait = 5s |
wsWriteWait = 5s |
问题:Panel 每 15s 发 ping,Agent read deadline 60s,但 Panel pong deadline 只有 45s。如果 Agent 的指标消息被延迟(网络抖动),Panel 可能因 pong 超时而关闭连接。两侧的超时参数缺乏协调设计。
2. 固定重连间隔 —— 无退避策略
Agent 断线后以固定 5s 间隔重试(reconnectTime = 5 * time.Second),在 Panel 长时间不可用(升级、网络故障)的情况下,会产生大量无用连接尝试。
3. Panel 侧每次解密都重建 AES 加密器
ws/server.go 的 decryptIfNeeded() 和 SendCommand() 每次调用都 security.NewAESCrypto(secret) 重新创建 cipher(SHA256 + AES-GCM 初始化),对于高频指标消息(5s/次 × N 节点),有不必要的 CPU 开销。
4. 指标消息使用 JSON Text 格式传输
每 5s 发送一次包含 13 个字段的 SystemInfo JSON,加密后还需 base64 编码,一条消息约 300-500 bytes(加密后约 700 bytes)。对于大量节点场景,存在优化空间。
5. receiveMessages 紧循环中有频繁锁竞争
receiveMessages() 在每次 ReadMessage() 前都要 Lock/Unlock connMutex 检查连接状态,但 ReadMessage 本身是阻塞的,实际不需要在循环外检查。
6. 状态变更命令阻塞读消息循环
routeCommand 中的 Service/Chain/Limiter CRUD 命令是同步执行的,包括 saveConfig() 文件写入。执行期间会阻塞 receiveMessages 的读取循环。
推荐的优化方案(按优先级排列)
P0 — 高收益、低风险
优化 1:协调 Keepalive 参数
文件:websocket_reporter.go
- Agent 增加独立的 WebSocket ping 发送(每 20s),不依赖指标数据来维持连接
- 统一 read deadline 设置,确保两侧 read timeout > 2×ping interval
优化 2:指数退避重连
文件:websocket_reporter.go
- 初始间隔 2s,按指数退避增长至最大 2 分钟
- 连接成功后立即重置退避
- 增加随机抖动(jitter)避免大量 Agent 同时重连
优化 3:Panel 侧缓存 AES 加密器
文件:ws/server.go
- 将
AESCrypto实例缓存在nodeSession中,避免每条消息重建 SendCommand复用缓存实例
P1 — 中等收益
优化 4:减少 receiveMessages 锁竞争
文件:websocket_reporter.go
- 将连接状态检查移到循环外,只在出错/关闭时通过 channel 通知退出
- 用
context.WithCancel代替锁检查connectedflag 来控制生命周期
优化 5:异步化状态变更命令处理
文件:websocket_reporter.go
- 所有命令统一异步执行(通过 goroutine + response channel),避免阻塞 readLoop
- 当前只有 TcpPing/ServiceMonitorCheck/UpgradeAgent/RollbackAgent 是异步的
具体代码变更
Agent 侧 (go-gost/x/socket)
[MODIFY] websocket_reporter.go
- 指数退避重连:将
reconnectTime从固定5s改为动态退避字段,增加curBackoff/maxBackoff字段 - 独立 Ping 发送:在
handleConnection()中增加 WebSocket ping ticker(20s),独立于指标上报 - 减少锁竞争:
receiveMessages中只在循环入口检查一次连接,此后靠ReadMessage的 error 退出 - 统一命令异步化:所有
routeCommand调用统一使用 goroutine
Panel 侧 (go-backend/internal/ws)
[MODIFY] server.go
- 缓存 AES 加密器:在
nodeSession中增加crypto *security.AESCrypto字段,节点连接时初始化 decryptIfNeeded接收 crypto 参数而非 secret 字符串SendCommand使用缓存 crypto 实例
Verification Plan
Automated Tests
# 运行现有 agent 侧单元测试(验证不回归)
(cd go-gost/x && go test ./socket/... -v -count=1)
# 运行现有流量上报测试
(cd go-gost/x && go test ./service/... -v -count=1)
# 运行 panel 侧全部测试
(cd go-backend && go test ./... -count=1)
Manual Verification
Important
本次改动涉及实时通信核心路径,建议在 staging 环境部署后观察至少 30 分钟:
- 检查节点在面板中状态是否正常显示为在线
- 手动停止面板后观察 Agent 日志,确认重连间隔呈指数增长
- 恢复面板后确认 Agent 能自动恢复连接并恢复指标上报
- 通过面板下发命令(如添加/删除 service),确认命令执行成功
任务清单
- 优化 1:Agent 增加独立 WebSocket ping 发送
- 优化 2:Agent 指数退避重连
- 优化 3:Panel 缓存 AES 加密器
- 优化 4:Agent 减少 receiveMessages 锁竞争
- 优化 5:Agent 命令处理统一异步化
- 运行现有测试验证不回归