技术探索与实践:一次从零构建高并发推送系统的真实记录
开篇:为什么分享这个话题

在互联网行业摸爬滚打这些年,我参与过不少项目,但最让我印象深刻的是去年年底带领团队重构公司核心业务中的消息推送模块。这是一次典型的“小步快跑、不断试错”的技术实践过程。
我们不是一开始就有完整的方案,也踩过不少坑,经历过线上服务宕机、消息堆积严重、用户体验下降等一系列问题。这篇文章想跟大家分享的就是这次真实的项目经历——如何从一个需求不清晰、资源有限的起点出发,一步步搭建起一个相对稳定的推送系统。
项目背景:业务驱动的技术升级

我们是一家做在线教育的科技公司,产品矩阵中有一项功能叫“课程提醒”,用户可以选择在某个时间点收到上课通知。随着用户量的增长,原有的基于定时任务+HTTP轮询的方式已经无法支撑越来越频繁的消息推送请求。
特别是在高峰期,服务器CPU经常飙到90%以上,延迟也越来越不可控。当时产品经理提出了几个关键指标:
- 峰值每秒要能处理1万条推送请求;
- 消息必须尽可能实时地到达前端(目标:500ms以内);
- 不能丢失任何一条消息;
- 推送失败时要有重试机制,并支持离线同步。
这些要求对我们来说是个不小的挑战。旧系统的瓶颈在于“中心化调度”,每次推送都要通过HTTP接口触发,而前端又采用长轮询的方式拉取消息,不仅响应慢,还容易造成雪崩效应。
遇到的问题和挑战

第一阶段:架构选型的困惑
最初的讨论集中在两个方向上:
继续沿用现有方案,优化代码逻辑
这是最直接的思路,但我们评估之后发现很难突破单体架构的性能上限,扩展性也非常差。引入消息队列,做异步解耦
这个方向看起来更有前景,但也带来了新的不确定性——比如MQ的可靠性保障、消费端的幂等设计、以及前端连接管理等问题。
我们内部争论了一周多的时间,最终决定尝试后者。虽然风险更大,但从长远来看是必须迈出的一步。
第二阶段:技术实现的难题
开始动手以后,陆续暴露出一些具体问题:
- 如何确保消息不丢失?RabbitMQ?Kafka?还是自研?
- 前端连接怎么管理?WebSocket是否可靠?有没有其他替代方案?
- 消息积压怎么办?要不要做分片?
- 后台服务需要部署多少节点?是否有弹性伸缩能力?
这些问题没有标准答案,只能边摸索边调整。
我们选择的技术方案和实现思路

经过多次评估后,我们最终确定了以下架构图的核心组件:
[外部事件] -> [API网关] -> [Kafka] -> [推送服务消费者] -> [WebSocket集群] -> [前端]
以下是每个环节的设计考量和实践细节:
1. 异步通信中枢:Apache Kafka
我们选择了Kafka作为消息总线,主要因为其在大规模写入和持久化方面的优异表现。我们在AWS上搭建了一个3节点集群,副本数设置为2,分区数根据估算的数据量设置了64个。
from kafka import KafkaProducer
producer = KafkaProducer(bootstrap_servers='kafka-broker1:9092')
def send_push_event(topic, message):
future = producer.send(topic, value=message.encode('utf-8'))
try:
record_metadata = future.get(timeout=10)
print(f"Message sent to {record_metadata.topic} partition {record_metadata.partition}")
except Exception as e:
print("Failed to send message:", str(e))

2. 后端消费服务:Go + Redis
使用Go语言编写消费者程序,对接Kafka消息,同时借助Redis缓存连接状态。为了保证高可用,部署了多个实例,使用Consul做服务注册和发现。
消费者的主流程大致如下:
func consumeLoop() {
for {
msg, err := consumer.ReadMessage(-1)
if err != nil {
log.Printf("Error reading message: %v", err)
continue
}
var payload PushPayload
json.Unmarshal(msg.Value, &payload)
sendToUser(payload.UserID, payload.Content) // 核心推送逻辑
}
}
其中,sendToUser()会去查询当前用户的WebSocket连接是否存在,并发送消息。
3. WebSocket接入层:Gorilla + 负载均衡
我们使用Gorilla WebSocket库搭建了一个轻量级接入层,每个连接维护一个goroutine来监听消息。接入层前面挂了一个Nginx做负载均衡,配置如下:
upstream websocket_backend {
least_conn;
server ws-node1 weight=3;
server ws-node2 weight=3;
keepalive 32;
}
server {
listen 80;
location /ws/ {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
proxy_ssl_verify on;
}
}
这个配置让客户端可以稳定地维持长连接,避免因节点故障导致掉线。
踩过的那些坑和经验总结
坑一:Kafka分区热不均
上线初期遇到消息分布不均的问题,某些分区消息堆积严重,另一些则很空闲。后来我们做了两件事缓解:
在生产者端增加了自定义分区策略,将用户ID作为key传入:
def partitioner(key_bytes, all_partitions, available_partitions): return abs(hash(key_bytes)) % len(all_partitions) producer = KafkaProducer(bootstrap_servers='...', partitioner=partitioner)动态扩容,增加分区数量并重新平衡。
坑二:WebSocket连接中断无感知
最初前端没有对连接状态进行监控,一旦网络抖动或后端重启,用户就收不到消息。我们后来加了心跳机制:
- 客户端每隔30秒发一个ping;
- 服务端收到ping后回复pong;
- 如果连续三次未收到pong,标记该连接断开,下次登录重新建立连接。
坑三:日志混乱,难以追踪
多个微服务协同工作后,日志分散在不同机器上,排查问题非常困难。我们最终引入了ELK组合(Elasticsearch + Logstash + Kibana),并统一了每个请求的trace_id,大大提升了可观测性。
实施效果和收益
经过两个月的努力,新系统上线后的表现超出了预期:
| 指标 | 旧系统 | 新系统 | 提升幅度 |
|---|---|---|---|
| 平均推送延迟 | 1200ms | 380ms | ≈70% |
| 峰值TPS | ~1500 | ~12000 | 8倍 |
| 消息丢失率 | ≈0.5% | <0.01% | 大幅下降 |
| 线程占用 | 200+ threads | 40 goroutines | 下降80% |
除了性能上的提升,整个系统的扩展性和容灾能力也更强。在后续的一次大促活动中,系统轻松扛住了比平时高出5倍的流量,几乎没有出现异常情况。
经验分享:给技术同行们的建议
如果你也在考虑类似的推送系统建设或者异步架构改造,以下几点建议也许能帮你少走弯路:
1. 不要一开始就追求完美架构
我们早期的一个错误就是试图把所有问题一次性解决,结果浪费了不少时间。建议先聚焦于核心链路,快速验证可行性,再逐步完善其他模块。
2. 技术选型要结合自身团队能力
我们曾经考虑过自研消息中间件,后来发现运维成本太高,放弃转用Kafka。不要为了“炫技”而牺牲可维护性。工具适合才是最好的。
3. 监控和日志是系统健康的底线保障
如果没有完善的监控体系,在分布式环境下排查问题就像蒙眼走路。务必尽早集成Prometheus、Grafana等工具,对关键指标如消费延迟、连接数、QPS等做到心中有数。
4. 尽早设计容灾方案,而不是事后补救
我们的第一次全链路压力测试是在上线前一周做的,结果发现Kafka数据积压严重。如果早点压测,就能更早发现问题。
5. 别忽视前端体验层面的优化
后端再强,如果前端处理不当,一样会影响用户体验。建议配合前端同事一起做一些实验,比如:
- 使用Service Worker做本地缓存;
- 对于非关键消息允许一定延迟;
- 提供手动重连按钮或自动重拉取机制。
结语:技术探索没有终点,只有不断的迭代
回顾整个项目,我觉得最大的收获并不是某个具体的架构设计,而是意识到“解决问题的方法永远在实践中产生”。很多看似完美的设计,放在真实场景里就会暴露出各种意想不到的问题。
作为一名技术负责人,最重要的不是你掌握了多少前沿技术,而是面对复杂问题时能否冷静分析、合理拆解,并找到最适合团队节奏的解决方案。
最后想说的是:技术探索的路上,失败和挫折都是常态。重要的是保持好奇心、不断学习,并愿意在实战中打磨自己和团队的能力。共勉!

评论 0