技术探索与实践:从零构建高并发消息推送系统
开篇:为什么选择这个方向?

我在一家互联网教育公司工作了五年,早期主要是做后台服务开发和一些基础架构的搭建。随着业务的发展,我们逐步面临一个核心痛点:如何在用户在线量快速增长的情况下,高效、稳定地将实时消息推送给终端用户?
这不仅是技术上的挑战,也是影响用户体验的核心因素。特别是在直播课开课提醒、作业提交通知等高频场景下,消息延迟甚至会导致用户流失。于是我们决定自建一套轻量级、可扩展的消息推送系统。
这篇文章就来聊聊这个系统的构建过程——从选型到落地,从踩坑到优化,都是我们真实项目中的经历。
问题描述:我们需要什么样的系统?

我们的业务需求很明确:
- 支持百万级并发连接
- 需要支持多种协议(WebSocket、MQTT),适配不同客户端
- 消息投递要求低延迟,且具备可靠的重试机制
- 系统架构可扩展性强,能随业务增长灵活伸缩
- 同时考虑未来对IoT设备的支持能力
最初尝试使用 RocketMQ 来实现推送逻辑,结果发现它更适合后端异步任务处理,并不适合长连接实时通信的场景。于是我们开始调研新的方案。
解决方案:选型与架构设计

我们对比了几种主流技术方案:
| 技术方案 | 优点 | 缺点 |
|---|---|---|
| WebSocket + Netty | 灵活控制,适合定制化需求 | 开发复杂度高,维护成本大 |
| MQTT Broker | 轻量级协议,支持离线缓存 | 对非物联网场景不够通用 |
| Apache Pulsar | 分布式消息队列,天生支持推送 | 学习曲线陡峭,配置复杂 |
最终我们选择了以 Netty + 自研协议 + Redis 实时状态管理 的组合方式来构建推送层。
架构概览如下:
+-------------------+
| 消息生产方 |
| (后台业务服务) |
+---------+----------+
|
v
+---------v----------+
| 推送网关 (Netty) |
| - 处理长连接 |
| - 协议解析 |
| - 消息路由 |
+---------+----------+
|
v
+---------v----------+
| Redis 缓存集群 |
| - 用户连接状态 |
| - 消息暂存 |
+---------+----------+
|
v
+---------v----------+
| 客户端(Web/APP) |
+--------------------+
代码实践:关键模块实现思路

1. 建立Netty连接池
我们在连接建立阶段加入白名单校验,防止恶意连接攻击:
public class AuthHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
String token = buf.toString(CharsetUtil.UTF_8);
if (!isValidToken(token)) {
ctx.close(); // 非法token直接断开连接
return;
}
// 添加后续handler
ctx.pipeline().addLast(new MessageHandler());
ctx.pipeline().remove(this);
}
private boolean isValidToken(String token) {
// 校验逻辑,如检查是否过期、是否存在Redis中
return Redis.exists("auth:" + token);
}
}
2. 使用Redis记录连接信息
我们使用Redis存储每个用户的当前连接所在的服务器节点:
SET user:1001 "gateway-server-1" EX 3600
这样当需要推送时,先查Redis定位到具体服务器再转发,而不是广播整个集群。
踩坑经验:那些年我们遇到的坑
💣 问题一:内存泄漏
Netty默认使用堆外内存进行数据传输,初期未正确释放 ByteBuf,导致 JVM 内存占用异常升高。
解决方法:
- 在每次读取完数据后手动调用
release(); - 使用
ResourceLeakDetector工具检测内存泄漏风险。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
ByteBuf in = (ByteBuf) msg;
// process logic
} finally {
ReferenceCountUtil.release(msg); // 必须显式释放
}
}
💣 问题二:心跳机制失效
我们最开始使用TCP的keepalive机制作为心跳检测,但发现移动端网络切换频繁导致大量假死连接。
解决方案:
- 自定义心跳协议,在应用层发送 ping/pong;
- 设置合理的超时时间(建议 30s ping,90s timeout);
- 收不到心跳回复时主动断开连接并通知客户端重连。
效果总结:上线后的收益
经过两个月的迭代和压测,系统上线后带来了以下明显提升:
| 指标 | 上线前 | 上线后 |
|---|---|---|
| 平均推送延迟 | 300ms | <50ms |
| 推送成功率 | 92% | 99.5% |
| 连接维持稳定性 | 容易断线 | 日均掉线率 < 0.1% |
| 扩展性 | 不支持水平扩容 | 可弹性扩容至万级连接 |
并且这套系统还被其他产品线复用,成为公司内部的基础推送组件之一。
经验分享:给同行的一些建议
如果你也在考虑构建自己的推送系统,我想分享几个切身体会:
✅ 技术选型要“适度”
不要一味追求“新技术”,比如Kafka、Pulsar这些虽然功能强大,但上手门槛较高。如果只是简单的推送需求,Netty 就可以搞定。
✅ 异常处理是关键
网络通信存在各种不稳定因素,务必要做好以下几点:
- 连接超时、重试机制设计
- 断线自动恢复
- 消息幂等性保证(避免重复推送)
- 日志埋点与监控报警
✅ 设计要有前瞻性
即使当前没有 IoT 业务,也可以预留支持 MQTT 等协议的可能性。未来的扩展性和现在的稳定性之间要做好权衡。
结语:技术和成长总是在路上
说实话,刚开始接手这个项目的时候我也有点发怵。毕竟之前没做过这种级别的高并发系统。但在一步步调研、设计、上线的过程中,我不仅掌握了Netty、Redis高级用法,也学会了如何做技术方案选型、如何组织团队推进、如何评估系统瓶颈。
我觉得这就是工程师的日常——不断遇到新问题,不断解决问题,在这个过程中积累经验和信心。
希望这篇来自实战的真实分享,能够帮你少走弯路,少踩坑。
如有兴趣欢迎留言交流,我会持续输出更多一线开发经验。

评论 0