- 架构文档新增第八节:日志系统与链路追踪设计 - Trace ID 全链路追踪(HTTP/WebSocket/跨服务) - 日志级别规范(DEBUG/INFO/WARN/ERROR/FATAL) - 结构化日志格式(开发文本/生产JSON) - 函数级日志规范(入口/出口模式) - WebSocket 日志追踪 - Go ↔ mediasoup Node 跨服务链路关联 - 敏感信息脱敏规则 - 前端错误上报机制 - 实施计划 Task 2 补充日志系统详细实现步骤 Made-with: Cursor
19 KiB
19 KiB
EchoChat 系统架构设计
本文档从整体设计方案中提取并深化架构设计部分,便于独立查阅。 完整设计方案见
docs/plans/2026-02-27-echochat-system-design.md
一、架构概述
EchoChat 采用 「精简单体 + 媒体微服务」 架构,核心思想是 控制面与媒体面彻底分离、业务系统与实时系统解耦。
- Go 单体服务:处理所有业务逻辑(认证、IM、会议控制、好友、通知、后台管理),内部按模块化组织,保留后期拆分为微服务的能力
- mediasoup Node 服务:独立的媒体控制微服务,管理 SFU Worker,不涉及任何业务逻辑
- mediasoup Worker:C++ SFU 引擎,负责 RTP 转发、拥塞控制、带宽自适应
二、架构分层图
┌─────────────────────────────────────────────────────────────┐
│ 接入层 (Nginx) │
│ SSL 终止 · 反向代理 · WebSocket 升级 · 静态资源 · 负载均衡 │
└─────┬───────────────────────┬───────────────────────────────┘
│ │
│ HTTPS / WSS │ HTTPS
│ │
┌─────┴──────────┐ ┌──────┴──────────┐
│ 前台用户端 │ │ 后台管理端 │
│ uniapp │ │ Vue3+Element │
│ (H5/App/小程序)│ │ Plus (PC Web) │
└─────┬──────────┘ └──────┬──────────┘
│ │
│ WebSocket + HTTP │ HTTP (RESTful)
│ │
┌─────┴───────────────────────┴──────────────────────────────┐
│ Go 单体服务(模块化) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ auth │ │ im │ │ meeting │ │ admin │ │
│ │ 认证鉴权 │ │ 即时通讯 │ │ 会议控制 │ │ 后台管理 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │ contact │ │ notify │ 每个模块: Controller → │
│ │ 联系人 │ │ 通知 │ Service → DAO → Model │
│ └──────────┘ └──────────┘ │
│ │ │ │ │
│ ┌────┴──────────────┴──────────────┴──────┐ │
│ │ 公共基础设施层 (pkg/) │ │
│ │ db · redis · ws · middleware · utils │ │
│ └─────────────────────────────────────────┘ │
└────────┬──────────────────────┬─────────────────────────────┘
│ │
PostgreSQL Redis HTTP
(持久化数据) (实时状态) │
┌─────┴─────────────┐
│ mediasoup Node 服务 │
│ Router 管理 │
│ Transport 管理 │
│ Producer/Consumer │
└─────┬─────────────┘
│ IPC
┌─────┴─────────────┐
│ mediasoup Worker │
│ (C++ SFU 引擎) │
│ RTP 转发 │
│ 拥塞控制 │
│ 带宽自适应 │
└───────────────────┘
三、各层职责说明
3.1 接入层 (Nginx)
| 职责 | 说明 |
|---|---|
| SSL 终止 | 处理 HTTPS/WSS 加密,内部服务间通信使用 HTTP |
| 反向代理 | 将请求分发到 Go 服务或前端静态资源 |
| WebSocket 升级 | 处理 WebSocket 协议升级 |
| 负载均衡 | 后期多实例部署时进行请求分发 |
3.2 Go 单体服务
系统的 "大脑",处理所有业务逻辑。
| 模块 | 职责 |
|---|---|
| auth | 用户注册/登录、JWT Token 管理、RBAC 角色权限 |
| im | 即时消息收发、会话管理、消息存储 |
| contact | 好友关系管理、好友分组 |
| meeting | 会议创建/管理、信令转发、mediasoup 资源编排 |
| notify | 通知推送、会议邀请、好友申请通知 |
| admin | 后台管理(用户管理、会议监控、系统配置) |
不负责的事情: 不处理 RTP 媒体数据、不参与音视频转发、不做 WebRTC 协议协商。
3.3 mediasoup Node 服务
mediasoup C++ SFU 的 "遥控器",不懂业务、不懂用户、只懂媒体对象。
| 职责 | 说明 |
|---|---|
| Worker 管理 | 创建和管理 mediasoup C++ Worker 进程 |
| Router 管理 | 每个会议房间对应一个 Router |
| Transport 管理 | 为每个参与者创建 WebRTC Transport |
| Producer/Consumer | 管理音视频流的推送和消费 |
3.4 mediasoup Worker
真正的 "发动机",纯 C++ 实现的 SFU 引擎。
| 职责 | 说明 |
|---|---|
| RTP 转发 | 接收发送者的 RTP 包,转发给所有接收者 |
| 拥塞控制 | 根据网络状况动态调整 |
| 带宽自适应 | Simulcast/SVC 支持 |
四、数据流说明
4.1 即时消息流
客户端A Go 服务 客户端B
│ │ │
│── WS: 发送消息 ──→ │ │
│ │── 写入 PostgreSQL │
│ │── 更新 Redis 未读数 │
│ ←── WS: 发送确认 ──│ │
│ │── WS: 推送新消息 ────────→ │
│ │ │
4.2 音视频会议流
客户端 Go 服务 mediasoup Node Worker
│ │ │ │
│── HTTP: 加入会议 → │ │ │
│ │── HTTP: 创建Router→│ │
│ │ ←── RTP能力 ──────│ │
│ ←── WS: 房间信息 ──│ │ │
│ │ │ │
│── WS: 创建Transport→│ │ │
│ │── HTTP: 创建 ────→ │── IPC ──────→ │
│ ←── WS: Transport参数│ │ │
│ │ │ │
│── WS: 开始推流 ──→ │ │ │
│ │── HTTP: Producer → │── IPC ──────→ │
│ │ │ │
│════════════════ RTP/DTLS 媒体流直连 ═══════════════════→│
│ (音视频数据不经过 Go 服务,直连 Worker) │
五、微服务演进路径
当前架构从第一天起就为微服务拆分做了准备:
5.1 代码层面的预留
| 规则 | 说明 |
|---|---|
| 模块间零直接引用 | auth 不会 import im 内部代码,通过 interface 通信 |
| 独立路由注册 | 每个模块有自己的 router.go,注册独立的路由组 |
| 数据库表按模块前缀 | auth_users、im_messages、meeting_rooms,后期可分库 |
| Redis key 按命名空间 | echo:auth:*、echo:im:*、echo:meeting:* |
5.2 演进路径
第一阶段(当前) 第二阶段 第三阶段
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Go 单体服务 │ → │ Go 实时服务 │ → │ auth-service │
│ (模块化) │ │ (信令+会议+IM) │ │ im-service │
│ │ │ │ │ meeting-service │
│ │ │ Go 业务服务 │ │ contact-service │
│ │ │ (用户+好友+管理) │ │ admin-service │
└─────────────┘ └──────────────────┘ └─────────────────┘
+ API Gateway
+ 服务发现
+ 链路追踪
六、部署架构
6.1 开发环境 (Docker Compose)
services:
go-service: # Go 后端 → :8080
media-server: # mediasoup Node → :3000 + :40000-40100/udp
postgres: # PostgreSQL 17 → :5432
redis: # Redis 7 → :6379
nginx: # 反向代理 → :80/:443
6.2 生产环境 (预留 K8s)
┌─────────────────────────────────────────┐
│ Kubernetes 集群 │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Go Pod │ │ Go Pod │ (水平扩展) │
│ │ (副本1) │ │ (副本N) │ │
│ └────┬────┘ └────┬────┘ │
│ └──────┬─────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ Service (LB) │ │
│ └───────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ mediasoup │ │ mediasoup │ │
│ │ Pod (副本1) │ │ Pod (副本N) │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ PostgreSQL (StatefulSet / 外部 RDS) │
│ Redis (StatefulSet / 外部 ElastiCache) │
└─────────────────────────────────────────┘
七、技术选型依据
| 技术 | 选型理由 |
|---|---|
| Go (Gin) | 高并发、静态编译、内存占用小,适合实时系统 |
| GORM | Go 生态最成熟的 ORM,社区活跃 |
| Wire | 编译时依赖注入,零运行时开销 |
| zap | 高性能结构化日志,Uber 出品 |
| Viper | 配置管理标准库,支持 YAML + 环境变量覆盖 |
| mediasoup | 最高性能的开源 SFU,C++ 实现 |
| PostgreSQL 17 | 强一致性、JSONB 支持、性能优异 |
| Redis 7 | 实时状态存储、发布订阅、高速缓存 |
| uniapp (Vue 3) | 一套代码多端运行(H5/App/小程序) |
| Element Plus | Vue 3 生态最成熟的 PC 端组件库 |
| Docker Compose | 轻量级容器编排,适合初期和开发环境 |
八、日志系统与链路追踪设计
8.1 设计目标
- 每一个请求从进入系统到完成响应,全链路可追踪
- Go 服务内部的函数调用链清晰可见
- Go 服务与 mediasoup Node 服务之间的调用可关联
- WebSocket 消息的处理过程可追踪
- 日志分级明确,开发/生产环境日志策略不同
- 日志格式统一、结构化,便于后期接入 ELK/Loki 等日志平台
8.2 请求链路追踪(Trace ID / Request ID)
每个请求在进入系统时生成唯一的 trace_id,贯穿整个处理链路:
客户端请求
│
▼
Nginx(生成或转发 X-Request-ID)
│
▼
Go 中间件(提取或生成 trace_id,注入 context)
│
├── Controller 日志:[trace_id] 收到请求 POST /api/v1/auth/login
├── Service 日志: [trace_id] 开始处理登录,account=zhangsan
├── DAO 日志: [trace_id] SQL查询 auth_users WHERE username=zhangsan
├── Redis 日志: [trace_id] SET echo:auth:token:1
│
├── 如果涉及 mediasoup 调用:
│ Go 将 trace_id 放入 HTTP Header 传给 Node 服务
│ Node 服务日志也携带同一个 trace_id
│
▼
Go 中间件:[trace_id] 请求完成 200 OK 耗时 25ms
实现方式:
// 中间件:为每个请求生成 trace_id 并注入 context
func TraceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
traceID := c.GetHeader("X-Request-ID")
if traceID == "" {
traceID = generateTraceID() // UUID 或 雪花算法
}
ctx := context.WithValue(c.Request.Context(), "trace_id", traceID)
c.Request = c.Request.WithContext(ctx)
c.Header("X-Request-ID", traceID)
c.Next()
}
}
8.3 日志级别规范
| 级别 | 使用场景 | 示例 |
|---|---|---|
| DEBUG | 开发调试信息,生产环境不输出 | 函数参数详情、SQL 语句、Redis 操作 |
| INFO | 正常业务流程的关键节点 | 用户登录成功、会议创建、消息发送 |
| WARN | 异常但不影响主流程 | 参数格式警告、缓存未命中、重试操作 |
| ERROR | 业务错误,需要关注 | 数据库查询失败、外部服务调用失败 |
| FATAL | 系统级致命错误 | 数据库连接失败、配置加载失败(启动时) |
环境日志策略:
| 环境 | 最低级别 | 输出方式 | 格式 |
|---|---|---|---|
| 开发 | DEBUG | 控制台(彩色) | 可读文本 |
| 生产 | INFO | 文件 + 控制台 | JSON 结构化 |
8.4 日志格式规范
结构化日志字段(每条日志必含):
{
"level": "info",
"timestamp": "2026-02-27T10:30:00.123Z",
"trace_id": "abc-123-def-456",
"module": "auth",
"func": "service.auth_service.Login",
"msg": "用户登录成功",
"user_id": 1,
"ip": "192.168.1.100",
"latency_ms": 25
}
8.5 函数级日志规范
每个 Service 和 DAO 层的关键函数遵循统一的日志模式:
func (s *AuthService) Login(ctx context.Context, req *dto.LoginRequest) (*dto.LoginResponse, error) {
funcName := "service.auth_service.Login"
// 入口日志:记录关键入参(脱敏)
logs.Info(ctx, funcName, "开始处理登录",
zap.String("account", req.Account),
)
var err error
defer func() {
if err != nil {
// 出口日志(失败):记录错误信息
logs.Error(ctx, funcName, "登录处理失败",
zap.String("account", req.Account),
zap.Error(err),
)
} else {
// 出口日志(成功)
logs.Info(ctx, funcName, "登录处理完成",
zap.String("account", req.Account),
)
}
}()
// 业务逻辑...
return resp, err
}
8.6 请求日志中间件
HTTP 请求日志中间件自动记录每个请求的完整信息:
[INFO] trace_id=abc-123 | POST /api/v1/auth/login | status=200 | latency=25ms | ip=192.168.1.100 | user_agent=Mozilla/5.0
[ERROR] trace_id=def-456 | POST /api/v1/meetings/join | status=500 | latency=150ms | ip=10.0.0.5 | error="mediasoup service unavailable"
8.7 WebSocket 日志
WebSocket 连接和消息也纳入日志追踪体系:
[INFO] ws_conn=conn-789 | user_id=1 | WebSocket 连接建立
[INFO] ws_conn=conn-789 | user_id=1 | trace_id=ws-001 | 收到事件: im.message.send | conversation_id=5
[INFO] ws_conn=conn-789 | user_id=1 | trace_id=ws-001 | 消息处理完成 | msg_id=10086 | latency=8ms
[WARN] ws_conn=conn-789 | user_id=1 | 心跳超时,准备断开连接
[INFO] ws_conn=conn-789 | user_id=1 | WebSocket 连接断开 | 在线时长=3600s
8.8 跨服务链路追踪(Go ↔ mediasoup Node)
Go 服务调用 mediasoup Node 服务时,通过 HTTP Header 传递 trace_id:
Go 服务:
[INFO] trace_id=abc-123 | 调用 mediasoup: POST /media/transport/create | room=123-456
mediasoup Node 服务:
[INFO] trace_id=abc-123 | 收到请求: POST /media/transport/create | room=123-456
[INFO] trace_id=abc-123 | Transport 创建成功 | transport_id=xxx | latency=5ms
Go 服务:
[INFO] trace_id=abc-123 | mediasoup 调用完成 | transport_id=xxx | latency=12ms
8.9 敏感信息脱敏
日志中的敏感信息必须脱敏处理:
| 字段 | 脱敏规则 | 示例 |
|---|---|---|
| 密码 | 不记录 | password=*** |
| Token | 只记录前后各 4 位 | token=eyJh...5NiJ |
| 邮箱 | 部分隐藏 | email=zh***@example.com |
| 手机号 | 中间 4 位隐藏 | phone=138****8000 |
| IP | 完整记录(用于安全审计) | ip=192.168.1.100 |
8.10 前端日志与错误上报
前端通过 API 上报关键错误和操作日志:
POST /api/v1/client/log # 前端错误上报接口
{
"level": "error",
"module": "mediasoup-client",
"message": "Transport connection failed",
"stack": "Error: ICE connection failed...",
"page": "/meeting/room",
"user_agent": "...",
"timestamp": 1740700000
}
前端需要记录的关键场景:
- WebSocket 连接失败/断线
- mediasoup-client Transport 连接失败
- API 请求超时/5xx 错误
- 页面 JS 异常(通过
window.onerror/Vue.config.errorHandler)