技术探索与实践:从零构建高并发消息推送系统

醉卧花间
2025-06-14 14:57
阅读 412

开篇:为什么选择这个方向?

开篇:为什么选择这个方向?

我在一家互联网教育公司工作了五年,早期主要是做后台服务开发和一些基础架构的搭建。随着业务的发展,我们逐步面临一个核心痛点:如何在用户在线量快速增长的情况下,高效、稳定地将实时消息推送给终端用户?

这不仅是技术上的挑战,也是影响用户体验的核心因素。特别是在直播课开课提醒、作业提交通知等高频场景下,消息延迟甚至会导致用户流失。于是我们决定自建一套轻量级、可扩展的消息推送系统。

这篇文章就来聊聊这个系统的构建过程——从选型到落地,从踩坑到优化,都是我们真实项目中的经历。


问题描述:我们需要什么样的系统?

问题描述:我们需要什么样的系统?

我们的业务需求很明确:

  • 支持百万级并发连接
  • 需要支持多种协议(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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝