设计说明

本文档详细描述 live-webrtc-go 的系统架构、模块拆分、数据流向与扩展点,便于二次开发或架构评审。

目录

系统架构

架构总览

                           ┌─────────────────────────────────────┐
                           │          HTTP Server :8080          │
                           │                                     │
   ┌──────────┐            │  ┌─────────┐    ┌─────────────┐    │
   │ Publisher│ ──WHIP──▶  │  │  Auth   │───▶│   SFU       │    │
   │ (OBS/Web)│            │  │Middleware│    │  Manager    │    │
   └──────────┘            │  └─────────┘    └──────┬──────┘    │
                           │                         │           │
   ┌──────────┐            │         ┌───────────────┤           │
   │  Viewer  │ ◀──WHEP──  │         │               │           │
   │(Browser) │ ───────▶   │         ▼               ▼           │
   └──────────┘            │  ┌─────────────┐ ┌───────────┐     │
                           │  │    Room     │ │ Recording │     │
                           │  │  (Fanout)   │ │  & Upload │     │
                           │  └──────┬──────┘ └─────┬─────┘     │
                           └─────────┼──────────────┼───────────┘
                                     │              │
                           ┌─────────▼──────────────▼───────────┐
                           │          Object Storage            │
                           │           (S3/MinIO)               │
                           └────────────────────────────────────┘

请求处理链

HTTP Request
    │
    ▼
┌─────────────┐
│    CORS     │ ← ALLOWED_ORIGIN
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ Rate Limiter│ ← RATE_LIMIT_RPS, RATE_LIMIT_BURST
└──────┬──────┘
       │
       ▼
┌─────────────┐
│   Auth      │ ← AUTH_TOKEN / ROOM_TOKENS / JWT_SECRET
└──────┬──────┘
       │
       ▼
┌─────────────┐
│   Handler   │ → Business Logic
└─────────────┘

核心概念

Room(房间)

房间是 SFU 的核心抽象,每个房间:

  • 最多一个 Publisher(发布者)
  • 可有多个 Subscriber(订阅者)
  • 拥有独立的 Track Fanout 逻辑
  • 可配置独立的认证 Token

Track Fanout(轨道分发)

当发布者推送媒体轨道时,系统创建 Track Fanout:

  • 从发布者 PeerConnection 读取 RTP 包
  • 复制并分发给所有订阅者
  • 可选写入录制文件

PeerConnection(对等连接)

每个 WebRTC 连接:

  • 发布者:接收媒体轨道
  • 订阅者:发送媒体轨道
  • ICE 协商通过 WHIP/WHEP 协议完成

模块详解

cmd/server

职责:程序入口,服务初始化

// main.go 主要流程
1. config.Load()           // 加载配置
2. uploader.Init()         // 初始化上传器
3. sfu.NewManager()        // 创建房间管理器
4. api.NewHTTPHandlers()   // 创建 HTTP 处理器
5. RegisterRoutes()        // 注册路由
6. otel.InitTracer()       // 初始化追踪
7. http.Server.Listen()    // 启动服务
8. Graceful Shutdown       // 优雅退出

internal/config

职责:环境变量解析与默认值

┌─────────────────────────────────────┐
│             Config                  │
├─────────────────────────────────────┤
│ HTTPAddr        string              │
│ AllowedOrigin   string              │
│ AuthToken       string              │
│ RoomTokens      map[string]string   │
│ JWTSecret       string              │
│ RecordEnabled   bool                │
│ RecordDir       string              │
│ S3Endpoint      string              │
│ RateLimitRPS    float64             │
│ STUN/TURN       []string            │
│ ...                                 │
└─────────────────────────────────────┘

internal/api

职责:HTTP 请求处理

文件 功能
handlers.go WHIP/WHEP/Rooms/Records/Admin 端点处理
middleware.go CORS、限流、Token/JWT 认证
routes.go URL 路由、参数提取、房间名校验

认证优先级

1. Room-specific Token (ROOM_TOKENS)
    ↓ (not found or failed)
2. Global Token (AUTH_TOKEN)
    ↓ (not found or failed)
3. JWT (JWT_SECRET)
    ↓ (not found or failed)
4. Allow (no auth configured)

internal/sfu

职责:WebRTC SFU 核心逻辑

┌─────────────────────────────────────────────────────┐
│                     Manager                          │
│  - 管理所有 Room 实例                                │
│  - 创建/删除 Room                                    │
│  - 统计房间数量                                      │
└──────────────────────┬──────────────────────────────┘
                       │ 1:N
                       ▼
┌─────────────────────────────────────────────────────┐
│                      Room                            │
│  - Publisher PeerConnection                          │
│  - Subscriber PeerConnections                        │
│  - TrackFeeds (TrackFanout map)                      │
└──────────────────────┬──────────────────────────────┘
                       │ 1:N
                       ▼
┌─────────────────────────────────────────────────────┐
│                  TrackFanout                         │
│  - Remote Track (from publisher)                     │
│  - Local Tracks (to subscribers)                     │
│  - readLoop: RTP distribution                        │
│  - Optional: Recorder (IVF/OGG writer)               │
└─────────────────────────────────────────────────────┘

关键方法

方法 作用
Manager.Publish() 创建房间,建立发布者连接
Manager.Subscribe() 创建订阅者连接,绑定现有轨道
Room.attachTrackFeed() 新轨道分发到所有订阅者
TrackFanout.readLoop() RTP 包读取与分发循环

internal/metrics

职责:Prometheus 指标暴露

指标 类型 说明
live_rooms Gauge 活跃房间数
live_subscribers GaugeVec 每房间订阅者数
rtp_bytes_total CounterVec RTP 字节数
rtp_packets_total CounterVec RTP 包数

internal/uploader

职责:S3/MinIO 文件上传

Upload Flow:
1. Check Enabled() → client != nil
2. Open local file
3. Build object key (prefix + filename)
4. client.PutObject()
5. (Optional) Delete local file

数据流

推流流程 (WHIP)

1. Publisher → POST /api/whip/publish/{room}
   │
2. HTTPHandlers.ServeWHIPPublish()
   │  ├─ CORS 检查
   │  ├─ 限流检查
   │  └─ 认证检查
   │
3. Manager.Publish(roomName, sdpOffer)
   │  ├─ getOrCreateRoom()
   │  └─ Room.Publish(sdpOffer)
   │
4. Room.Publish()
   │  ├─ 创建 MediaEngine + Interceptors
   │  ├─ NewPeerConnection(ICEConfig)
   │  ├─ SetRemoteDescription(offer)
   │  ├─ CreateAnswer()
   │  ├─ SetLocalDescription(answer)
   │  └─ OnTrack: attachTrackFeed()
   │
5. 返回 SDP Answer
   │
6. TrackFanout.readLoop() 持续运行
   │  ├─ 从 Remote Track 读取 RTP
   │  ├─ 写入录制器(如启用)
   │  └─ 分发到所有 Local Tracks

播放流程 (WHEP)

1. Viewer → POST /api/whep/play/{room}
   │
2. HTTPHandlers.ServeWHEPPlay()
   │  ├─ CORS/限流/认证检查
   │  └─ Manager.Subscribe()
   │
3. Manager.Subscribe(roomName, sdpOffer)
   │  └─ Room.Subscribe(sdpOffer)
   │
4. Room.Subscribe()
   │  ├─ 检查订阅者上限
   │  ├─ NewPeerConnection()
   │  ├─ 遍历现有 TrackFeeds
   │  │   └─ TrackFanout.attachToSubscriber()
   │  ├─ SetRemoteDescription/CreateAnswer
   │  └─ OnICEStateChange: removeSubscriber()
   │
5. 返回 SDP Answer

断开连接

ICE State Change (Failed/Disconnected/Closed)
    │
    ▼
┌─────────────────────────────────────┐
│ Publisher 断开                       │
├─────────────────────────────────────┤
│ 1. closePublisher()                  │
│ 2. 关闭所有 TrackFanout              │
│ 3. 上传录制文件                      │
│ 4. 清空订阅者列表                    │
│ 5. pruneIfEmpty()                    │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│ Subscriber 断开                      │
├─────────────────────────────────────┤
│ 1. removeSubscriber()                │
│ 2. 从所有 TrackFanout 移除绑定       │
│ 3. 关闭 PeerConnection              │
│ 4. pruneIfEmpty()                    │
└─────────────────────────────────────┘

认证体系

Token 认证

优先级 1: Room Token (ROOM_TOKENS)
┌─────────────────────────────────────┐
│ ROOM_TOKENS="room1:abc;room2:def"   │
│                                      │
│ 访问 room1 → 检查 token == "abc"    │
│ 访问 room2 → 检查 token == "def"    │
│ 访问 room3 → 回退到全局 Token       │
└─────────────────────────────────────┘

优先级 2: Global Token (AUTH_TOKEN)
┌─────────────────────────────────────┐
│ AUTH_TOKEN="secret123"              │
│                                      │
│ 所有房间使用相同 Token               │
└─────────────────────────────────────┘

JWT 认证

// JWT Claims 结构
type roomClaims struct {
    Room  string `json:"room,omitempty"`   // 限制房间
    Role  string `json:"role,omitempty"`   // "admin" 角色
    Admin any    `json:"admin,omitempty"`  // true/1 管理员
    jwt.RegisteredClaims
}

// 使用场景
1. 房间访问: claims.Room == room  claims.Room == ""
2. 管理接口: claims.Role == "admin"  claims.Admin == true

录制与上传

录制格式

编解码器 文件格式 写入器
Opus .ogg oggwriter (48kHz, 2ch)
VP8 .ivf ivfwriter
VP9 .ivf ivfwriter

文件命名

{room}_{trackID}_{unixTimestamp}.{ext}

示例: demo_video0_1710123456.ivf

上传流程

Room.closePublisher()
    │
    ▼
TrackFanout.close() → 返回录制文件路径
    │
    ▼
uploader.Enabled()?
    │ Yes
    ▼
go uploadRecording(path)
    │
    ▼
Upload(ctx, path)
    ├─ PutObject(S3Bucket, objectKey, file)
    └─ (Optional) os.Remove(localFile)

可观测性

Prometheus 指标

# 活跃房间数
live_rooms

# 每房间订阅者数
live_subscribers{room="demo"}

# RTP 字节数(累计)
live_rtp_bytes_total{room="demo"}

# RTP 包数(累计)
live_rtp_packets_total{room="demo"}

OpenTelemetry 追踪

环境变量:
OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_SERVICE_NAME=live-webrtc-go

追踪的 span:
- HTTP Handler: {method} {path}

健康检查

GET /healthz → "ok" (200 OK)

扩展点

1. 多实例部署

当前房间状态在内存中,多实例需要:

  • 外部存储(Redis/数据库)存储房间映射
  • 会话亲和(Sticky Session)
  • 或客户端重定向

2. 媒体处理

可在 TrackFanout.readLoop() 前插入:

  • 转码(FFmpeg 集成)
  • 多码率
  • 截图/水印

3. 认证扩展

middleware.go 中扩展:

  • OAuth2 集成
  • Webhook 回调验证
  • IP 白名单

4. 存储扩展

实现 rtpWriter 接口:

type rtpWriter interface {
    WriteRTP(*rtp.Packet) error
    Close() error
}

可支持:

  • 实时转封装(MP4)
  • 流式上传(不落地)
  • CDN 推送

性能考量

内存使用

  • 每个 TrackFanout: 约 1-2 MB(RTP 缓冲)
  • 每个订阅者: 约 1500 bytes (MTU buffer)
  • 录制缓冲: 取决于写入频率

CPU 使用

  • RTP 包处理: 主循环在 readLoop()
  • 编解码协商: 仅连接建立时
  • 指标更新: 每个 RTP 包

优化建议

  1. 零拷贝 RTP 转发(需修改 TrackFanout)
  2. 批量指标更新
  3. 连接池(多房间场景)
  4. SIMD 优化(大量订阅者)