一、现状与问题
典型的视频平台后端通常采用微服务架构,但普遍存在一个问题:业务代码与基础设施深度耦合。
| 组件 | 常见实现 | 问题 |
|---|---|---|
| 消息队列 | RocketMQ / RabbitMQ(直接注入客户端) | 换 MQ 必须改业务代码 |
| 服务调用 | OpenFeign / Dubbo | 语言锁定,只能 Java 调 Java |
| 服务发现 | Nacos / Eureka | 弱设备无法运行 |
| 缓存 | Redis(直接注入客户端) | 换缓存要改业务代码 |
业务代码和基础设施强耦合,导致无法在弱设备部署、无法跨语言、无法切换实现。
1.1 代码质量问题
-
Service 类臃肿(部分超过 800 行),Controller 包含事务逻辑
-
DTO 与 Entity 混用
-
异常处理不统一
-
硬编码较多(Redis key、超时时间)
-
测试覆盖不足
1.2 已有亮点:存储层已抽象
部分项目中存储层已经做了抽象:
text
StorageService(接口)
├── MinioStorageService(MinIO 实现)
├── LocalStorageService(本地实现)
这说明抽象层思路已被验证可行,可以推广到其他基础设施组件。
二、重构思路:可插拔基础设施
2.1 设计目标
-
业务代码只依赖接口:不依赖具体实现
-
配置驱动:改配置就能切换实现
-
多实现:每个接口有轻量/企业级/测试多种实现
-
渐进迁移:一个模块一个模块切,可回滚
2.2 核心接口定义
ServiceDiscovery(服务发现)
go
type ServiceDiscovery interface {
Register(ctx context.Context, instance ServiceInstance) error
Deregister(ctx context.Context) error
Discover(ctx context.Context, serviceName string) ([]ServiceInstance, error)
Watch(ctx context.Context, serviceName string) (<-chan []ServiceInstance, error)
}
实现:FileDiscovery(嵌入式单机)、EtcdDiscovery(K8s 标准)、NacosDiscovery(兼容现有)、MemoryDiscovery(单元测试)
MessageQueue(消息队列)
go
type MessageQueue interface {
Publish(ctx context.Context, topic string, msg Message) error
Subscribe(ctx context.Context, topic string, group string) (<-chan Message, error)
Ack(ctx context.Context, topic string, msg Message) error
}
实现:MemoryQueue(测试)、RedisStreamQueue(轻量)、RocketMQQueue(企业级)、NATSQueue(高性能)
CacheStore(缓存)
go
type CacheStore interface {
Get(ctx context.Context, key string) ([]byte, error)
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
Lock(ctx context.Context, key string, ttl time.Duration) (bool, error)
Incr(ctx context.Context, key string, ttl time.Duration) (int64, error)
}
实现:MemoryCache(测试)、SQLiteCache(嵌入式持久化)、RedisCache(分布式)、BoltDBCache(嵌入式 KV)
ServiceCaller(服务间调用)
go
type ServiceCaller interface {
Call(ctx context.Context, target string, method string, req []byte) ([]byte, error)
CallStream(ctx context.Context, target string, method string, req []byte) (<-chan []byte, error)
}
实现:MemoryCaller(测试)、HTTPClient(简单服务)、GRPCClient(跨语言标准)
2.3 配置驱动
yaml
infrastructure:
servicediscovery:
type: etcd # etcd / nacos / file / memory
endpoints: ["http://localhost:2379"]
messagequeue:
type: redis-stream # redis-stream / rocketmq / nats / memory
redisAddr: localhost:6379
cachestore:
type: redis # redis / sqlite / boltdb / memory
addr: localhost:6379
servicecaller:
type: grpc # grpc / http / memory
timeout: 3s
切换环境只需改配置:
| 场景 | 配置组合 |
|---|---|
| 嵌入式设备 | sqlite + memory + redis-stream |
| 老旧服务器 | sqlite + redis + file |
| 云上生产环境 | mysql + redis + etcd + rocketmq |
| 单元测试 | memory + memory + memory |
三、无状态与高并发
3.1 无状态原则
服务不保存任何与请求绑定的状态,全部外置:
| 状态类型 | 存储位置 |
|---|---|
| 用户会话 | Redis(JWT + 会话) |
| WebSocket 连接 | Redis Pub/Sub(跨实例广播) |
| 任务进度 | Redis Stream |
| 计数器 | Redis |
| 缓存 | Redis |
| 数据库 | MySQL |
3.2 WebSocket 跨实例广播
text
客户端A ──WS──▶ realtime-1 ──┐
├── Redis Pub/Sub ──▶ realtime-2 ──WS──▶ 客户端B
└──(弹幕/通知广播)
每个 realtime 实例订阅 Redis 主题,发消息只往 Redis Publish,所有实例收到后推给本地连接。实例增减不影响广播。
3.3 Redis Stream 作为消息队列
使用 Redis Stream 替代传统消息中间件,适合弱设备场景:
| 指标 | Redis Stream | RocketMQ |
|---|---|---|
| 内存 | 复用 Redis | 独立几百 MB |
| 部署 | 已在跑 Redis | 额外服务器 |
| 弱设备 | 可跑 | 不现实 |
核心用法:
-
XADD:入队
-
XREADGROUP:消费
-
XACK:确认
-
XCLAIM:认领超时任务
-
XTRIM:清理已 ACK 消息
四、API 规范
4.1 统一响应结构
json
{
"code": 0,
"message": "ok",
"data": {},
"requestId": "req-xxxx"
}
4.2 错误码体系
全局错误码(0-999):
| code | 说明 |
|---|---|
| 0 | 成功 |
| 400 | 参数错误 |
| 401 | 未认证 |
| 403 | 无权限 |
| 404 | 资源不存在 |
| 429 | 限流 |
| 500 | 服务器错误 |
业务错误码(1000+):
| 模块 | 范围 |
|---|---|
| 用户 | 1000-1099 |
| 内容 | 1100-1199 |
| 互动 | 1200-1299 |
| 订单 | 1300-1399 |
4.3 分页与认证
-
分页 :
page(默认 1)、pageSize(默认 20,上限 100) -
认证 :
Authorization: Bearer <jwt_token> -
幂等 :
Idempotency-Key: <client_uuid>(写操作)
五、重构优先级
| 优先级 | 项 | 工作量 | 风险 |
|---|---|---|---|
| P0 | 抽象 MessageQueue | 中 | 高 |
| P0 | 抽象 ServiceCaller | 中 | 高 |
| P0 | 抽象 ServiceDiscovery | 低 | 中 |
| P1 | 统一错误码/异常处理 | 低 | 低 |
| P1 | DTO/Entity 分层 | 中 | 低 |
| P2 | 长类拆分 | 中 | 低 |
| P2 | 硬编码配置化 | 低 | 低 |
策略: 先抽接口(改动集中在注入点),确认编译通过且功能不变后再逐个替换实现。每个服务独立迁移,先支持配置切换(保持原实现),再逐步替换。
六、总结
-
抽象层思路已被存储层验证可行,推广到 MessageQueue/ServiceDiscovery/CacheStore
-
配置驱动让同一套代码适应不同环境(嵌入式→企业级)
-
无状态设计让集群可水平扩展
-
Redis 一鱼多吃:缓存 + MQ(Stream)+ 锁 + 计数器
-
渐进迁移保证可回滚,风险可控