Skip to content

NATS 架构迁移示例 ​

基于 livis-claw-hub 项目实际架构,验证 NATS 替代 WebSocket 的可行性

一、项目结构 ​

example/
├── cmd/
│   ├── device/main.go      # 设备端程序
│   ├── hub/main.go         # Hub 服务程序
│   ├── client/main.go      # HTTP 客户端
│   └── test/main.go        # 端到端测试
├── internal/
│   ├── protocol/           # 消息协议(对应项目 WebSocket 协议)
│   ├── device/             # 设备端实现(对应项目 ClientConnection)
│   ├── hub/                # Hub 服务实现(对应项目 RelayUseCase)
│   ├── client/             # 客户端实现
│   └── config/             # 配置
├── go.mod
└── README.md

二、架构对比 ​

2.1 组件映射 ​

当前组件NATS 替代文件
WebSocket.Connnats.Conndevice/device.go
sync.Map clientsKV Storehub/hub.go
map[string]chan PendingRequest/Reply + Inboxhub/hub.go
SSE 流式响应多消息响应hub/hub.go
Ping/Pong 心跳KV 更新device/device.go

2.2 Subject 设计 ​

设备端订阅:
  device.{device_id}.command    # 接收命令
  device.{device_id}.cancel     # 接收取消

设备端发布:
  device.{device_id}.response   # 响应(通过 Reply-To)

Hub 订阅:
  device.*.response             # 通配符订阅所有响应

设备状态(KV Store):
  Bucket: DEVICE_STATUS
  Key: {device_id}

2.3 消息协议映射 ​

WebSocket 协议:

json
{"type":"query","msg_id":"xxx","payload":{"query":"..."}}
{"type":"response_chunk","msg_id":"xxx","payload":"..."}
{"type":"response_done","msg_id":"xxx","payload":"..."}

NATS 协议:

json
// 请求
{"msg_id":"xxx","query":"...","timestamp":1234567890}

// 流式响应
{"msg_id":"xxx","type":"response_chunk","seq":1,"data":"..."}
{"msg_id":"xxx","type":"response_done","seq":5}

三、运行示例 ​

3.1 端到端测试 ​

bash
# 运行完整测试
go run ./cmd/test -mode=test

# 输出:
# === 端到端测试 ===
# --- 测试 1: 检查设备在线 ---
# 设备在线: true
# --- 测试 2: 获取设备状态 ---
# 设备状态: id=test-device-001, status=online
# --- 测试 3: 列出在线设备 ---
# 在线设备数: 3
# --- 测试 4: 执行命令 ---
# 响应: type=response_chunk, seq=1, data="正在处理..."
# 响应: type=response_done, seq=5
# === 测试完成 ===

3.2 独立运行 ​

bash
# 终端 1:启动 Hub
go run ./cmd/hub -nats=nats://localhost:4222 -http=:8080

# 终端 2:启动设备
go run ./cmd/device -id=device-001 -nats=nats://localhost:4222

# 终端 3:发送命令
curl -X POST "http://localhost:8080/api/v1/device/execute?device_id=device-001" \
  -H "Content-Type: application/json" \
  -d '{"query":"你好"}'

四、核心实现 ​

4.1 设备注册(替代 sync.Map) ​

go
// 当前:sync.Map
clients.Store(deviceID, client)

// NATS:KV Store
status := &DeviceStatus{DeviceID: deviceID, Status: "online"}
kv.Put(ctx, deviceID, json.Marshal(status))

4.2 命令执行(替代 WebSocket + Pending Map) ​

go
// 当前:WebSocket + Pending Map
waitCh := make(chan *WSMessage)
client.Pending[msgID] = waitCh
client.WriteJSON(queryMsg)

// NATS:Request/Reply + Inbox
inbox := nats.NewInbox()
nc.Subscribe(inbox, handleResponse)
nc.PublishMsg(&nats.Msg{
    Subject: "device." + deviceID + ".command",
    Reply:   inbox,
    Data:    requestData,
})

4.3 流式响应 ​

go
// 设备端:发送多个响应
for i, chunk := range chunks {
    resp := NewChunkResponse(msgID, i+1, chunk)
    nc.Publish(replyTo, json.Marshal(resp))
}
resp := NewDoneResponse(msgID)
nc.Publish(replyTo, json.Marshal(resp))

五、性能分析 ​

详见 PERFORMANCE.md

5.1 百万设备场景 ​

维度当前架构NATS 架构
连接数单节点 10 万级单节点 100 万级
设备注册sync.Map 内存KV Store 分布式
跨节点协调需自研 Redis内置 Cluster
延迟< 10ms< 1ms

5.2 Subject 数量 ​

百万设备 = 百万 Subject?

✅ 没问题!
- Subject 只是字符串,不占用内存
- 每个设备只创建 1 个订阅
- Hub 使用通配符订阅(1 个订阅覆盖百万设备)

六、迁移路径 ​

  1. 阶段一:并行运行,验证 NATS 方案
  2. 阶段二:灰度切换,逐步迁移设备
  3. 阶段三:完全迁移,下线 WebSocket

七、相关文档 ​