gnet不能直接解决mqtt网关十万连接问题,因其仅提供字节流收发,不实现mqtt协议栈(如connect解析、clientid会话、qos1重传、lwt等),真实瓶颈在于协议状态与业务语义的耦合,而非网络层性能。

直接上 gnet 替换 net.Conn 基础层不能解决 MQTT 网关的十万连接问题——它会把协议解析、心跳管理、会话恢复这些业务逻辑全甩给你自己写,而你真正卡住的地方从来不是 accept 性能,而是连接状态与消息语义的耦合。
为什么 gnet 不是 MQTT 网关的“银弹”
gnet 是高性能异步网络引擎,擅长吞吐和连接数,但它不理解 MQTT。它只管字节流收发,不识别 CONNECT 报文结构,不维护 clientID 会话,不处理 QoS1 的 PUBACK 重传,也不做遗嘱消息(LWT)触发。你用它接进来一个 TCP 连接,得到的是一堆 raw bytes,后面所有 MQTT 协议栈都得从零造轮子。
真实瓶颈不在网络层:EMQX 单节点轻松撑 10 万 MQTT 连接,是因为它把协议状态机、会话存储、主题树索引、ACL 检查全做在了应用层;你用 gnet 自研,等于重写半个 EMQX。
- MQTT 连接不是裸 TCP 连接:需要解析固定头、可变头、payload,校验剩余长度字段,识别 packet type,跳过非法报文
- 心跳超时 ≠ 连接关闭:MQTT keepalive 是应用层语义,
gnet的conn.SetReadDeadline只能帮你断物理连接,但 clientID 会话是否保留、未确认消息是否重发,得你自己存、自己判、自己推 - 订阅关系无法靠
gnet维护:主题过滤(如devices/+/status)、通配符匹配、ACL 权限检查,都依赖内存中的一棵 topic tree 或 trie 结构,gnet不提供任何抽象
什么时候该考虑 gnet + 自研 MQTT 协议栈
仅当满足以下全部条件时,才值得投入:
- 硬件资源极度受限(如单核 ARMv7、
- 协议必须私有化:例如强制加密 payload 头部、定制二进制帧格式、禁用标准 QoS 流程
- 已有成熟 MQTT 状态机模块(比如从 Eclipse Paho C 移植过来),只需换底层 IO 引擎
- 团队具备协议开发经验,且接受至少 3 个月封闭开发周期来覆盖 CONNECT/PUBLISH/SUBSCRIBE/UNSUBSCRIBE/PING/PUBACK 全流程
否则,直接用 eclipse/paho.mqtt.golang + net.Conn 启动 goroutine,配合 sync.Pool 复用 bytes.Buffer 和 MQTT packet struct,实测 8 核机器轻松跑满 6 万稳定连接,延迟稳定在
如果硬要用 gnet,必须补的三块拼图
你绕不开的不是性能,而是协议一致性。这三件事不做,连接数上万就会开始丢包、假离线、重复投递:
-
Packet 解析器必须带缓冲回退:MQTT 报文可能被 TCP 分片,
gnet的React回调收到的 data 不一定是完整 packet。得自己实现 buffer 累积 + 剩余长度解析 +buffer.Next(n)切片,不能直接binary.Read - 每个连接绑定独立 session state:包括 clientID、clean session 标志、当前 QoS 等级、in-flight message map(key=packetID)、will message、last seen timestamp。别存在全局 map 里——并发读写要加锁,一锁就成瓶颈
-
PINGREQ/PINGRESP 必须走应用层定时器:
gnet的OnTick是全局 tick,不能按连接粒度设 keepalive;得为每个 conn 启一个time.Ticker(但注意别泄漏),并在React中刷新 lastPingTime,超时则主动 close
最常被忽略的一点:MQTT 的“连接数”不是 accept() 次数,而是 clientID + clean session = false 的活跃会话数。哪怕你用 gnet 撑住了 10 万 TCP 连接,只要 broker 层没做持久会话恢复,设备断网重连后所有订阅都会丢失——这时用户感知到的不是“高并发”,而是“不可用”。











