视频平台架构重构:从微服务到可插拔基础设施

一、现状与问题

典型的视频平台后端通常采用微服务架构,但普遍存在一个问题:业务代码与基础设施深度耦合。

组件 常见实现 问题
消息队列 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 设计目标

  1. 业务代码只依赖接口:不依赖具体实现

  2. 配置驱动:改配置就能切换实现

  3. 多实现:每个接口有轻量/企业级/测试多种实现

  4. 渐进迁移:一个模块一个模块切,可回滚

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 硬编码配置化 低 低

策略: 先抽接口(改动集中在注入点),确认编译通过且功能不变后再逐个替换实现。每个服务独立迁移,先支持配置切换(保持原实现),再逐步替换。

六、总结

  1. 抽象层思路已被存储层验证可行,推广到 MessageQueue/ServiceDiscovery/CacheStore

  2. 配置驱动让同一套代码适应不同环境(嵌入式→企业级)

  3. 无状态设计让集群可水平扩展

  4. Redis 一鱼多吃:缓存 + MQ(Stream)+ 锁 + 计数器

  5. 渐进迁移保证可回滚,风险可控

相关推荐
AI你一生一世1 小时前
当“暂停“成为架构决策:从签证系统停摆看高可用设计的边界
架构·系统架构·流量控制·幂等性·高可用设计·优雅降级·状态一致性
Cosolar1 小时前
不收费、不加水印、数据归你:我做了个开源的在线简历工具
后端·面试·前端框架
EasyDSS1 小时前
讲座视频资源散存易丢失?视频直播点播EasyDSS校园/视频融媒体平台高效管理视频
音视频·媒体·easydss
小蒜学长2 小时前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
L0208243 小时前
Kubernetes Ingress|CNI 网络模型|Calico 网络策略|Pod 调度器完整实战教程
运维·网络·云原生·容器·kubernetes
陈随易3 小时前
下载神器!Motrix 停更三年后重写回归
前端·后端·程序员
每天一道题3 小时前
CAP、Raft 与多数派:分布式系统如何达成一致?
分布式·后端·微服务
前端的日常3 小时前
一个人+AI做情侣食谱小程序,0 费用的服务器
前端·后端
2601_965619694 小时前
AI视频制作:做历史战争视频,兵力部署的老地图难找,AI怎么自动配战争地图?
人工智能·音视频
IT_陈寒4 小时前
Vue的v-for里用了个临时变量,页面直接挂了
前端·人工智能·后端