共计 1608 个字符,预计需要花费 5 分钟才能阅读完成。
背景与核心痛点
订阅系统作为现代互联网服务的核心组件,面临着诸多技术挑战。特别是在高并发场景下,以下几个问题尤为突出:

- 消息丢失:网络抖动或服务重启导致的消息丢失,直接影响业务可靠性
- 重复消费:消息重试机制可能导致同一条消息被多次消费
- 订阅关系同步延迟:用户订阅状态变更无法实时生效
- 高并发瓶颈:在 10 万 QPS 压力下,传统架构的 CPU 使用率可达 80% 以上,内存占用超过 16GB
架构设计思路
推拉模型对比
- 推模型:服务端主动推送,实时性好但服务端压力大
- 拉模型:客户端轮询,实现简单但存在延迟和空轮询问题
我们最终选择 事件驱动架构,结合推拉优势:
– 核心事件采用推送保证实时性
– 辅助数据通过拉取降低服务端压力
分层架构设计
接入层 -> 消息路由层 -> 持久化层
↘ 订阅管理 ↗
- 接入层:处理协议转换、鉴权和流量控制
- 消息路由层:基于订阅关系进行消息分发
- 持久化层:保证消息可靠存储和回溯
订阅关系存储
采用 etcd 作为主要存储方案,理由如下:
- 强一致性保证订阅状态准确
- Watch 机制实现变更实时通知
- 集群部署保障高可用性
核心实现代码
幂等消费示例
// 基于消息 ID 的去重处理
func HandleMessage(msg Message) error {
// 生成唯一处理标识:消息 ID+ 消费者组
dedupKey := fmt.Sprintf("%s:%s", msg.ID, msg.ConsumerGroup)
// 使用 Redis SETNX 实现原子判重
set, err := redisClient.SetNX(dedupKey, "1", 24*time.Hour).Result()
if err != nil {return fmt.Errorf("redis error: %v", err)
}
if !set {return nil // 已处理过直接返回}
// 实际业务处理逻辑
return process(msg)
}
批量推送并发控制
// 带重试的批量推送
func BatchPush(messages []Message) error {
const maxRetry = 3
batchSize := 200 // 经验值:单批 200 条平衡吞吐与延迟
for i := 0; i < len(messages); i += batchSize {
end := i + batchSize
if end > len(messages) {end = len(messages)
}
batch := messages[i:end]
// 指数退避重试
retry := 0
for {err := doPush(batch)
if err == nil || retry >= maxRetry {break}
time.Sleep(time.Second * (1 << retry))
retry++
}
}
return nil
}
生产环境考量
压测数据对比
| 消息大小 | 单机 QPS | CPU 使用率 | 内存占用 |
|---|---|---|---|
| 1KB | 85,000 | 65% | 12GB |
| 5KB | 32,000 | 78% | 18GB |
| 10KB | 15,000 | 85% | 22GB |
故障恢复方案
- 脑裂处理:
- 部署奇数节点集群
-
设置合理的心跳超时(建议 5 -10 秒)
-
消息回溯:
- 保留 7 天原始消息
- 支持按时间偏移量重新消费
安全防护
- JWT 鉴权:每个请求携带签名 token
- 流量控制:
- 单用户限流 500QPS
- 全局熔断阈值 80% 系统负载
避坑经验总结
订阅变更一致性
采用两阶段提交保证状态同步:
1. 先在 etcd 标记 ” 修改中 ” 状态
2. 同步所有节点后更新为最终状态
大流量预热
- 提前扩容至预期流量 120%
- 逐步放开限流阈值(每小时提升 20%)
监控指标体系
核心 Prometheus 指标:
subscription_count订阅总数message_delay_seconds消息处理延迟push_failure_rate推送失败率consumer_lag消费滞后量
结语
本文介绍的设计方案已在生产环境支撑日均百亿级消息推送。关键点在于:
1. 选择合适的基础组件(如 etcd)
2. 严控消息处理的幂等性
3. 建立完善的监控体系
实际落地时,建议先在小规模场景验证核心流程,再逐步扩展到全量业务。
正文完
