EMQX 百万连接内存 50 GB,如何调优到 30 GB 以内
解读
面试官抛出“50 GB → 30 GB”这一具体数值,核心想验证三件事:
- 能否把“连接”这一抽象指标拆成可量化的内存单元(TCP 套接字 + emqx_connection 进程 + emqx_cm 会话 + 订阅表 + 消息队列 + 系统缓存)。
- 是否掌握 Linux 与 BEAM 虚拟机的双层调优套路,而不是只改 EMQX 配置。
- 有没有线上踩坑经验,知道调优后必须做“同连接数、同吞吐、同 Topic 分布”下的 8 h+ 稳定性兜底,防止内存下去了,CPU 或延迟却爆了。
国内面试场景里,回答要“短平快”先给结论,再分层展开;同时必须提到国产化操作系统(麒麟、统信 UOS)与国产化 CPU(鲲鹏、飞腾)的差异化表现,体现落地经验。
知识点
-
单连接内存模型
1.1 TCP 套接字:内核 ~4 KB(net.core.rmem_default 与 wmem_default)
1.2 emqx_connection 进程:Erlang 进程堆 + 堆外二进制 ~8–12 KB(依赖 OTP 版本与 +P 参数)
1.3 emqx_cm 会话:Clean Session=0 时额外 4–6 KB
1.4 订阅表:每个 Topic-Client 条目 40 B 左右,百万连接×平均 3 个订阅 ≈ 120 MB
1.5 消息队列:QoS1/2 飞行窗口默认 100,内存随窗口线性增长
1.6 buffer 池:emqx 为每个连接预分配 8 KB 的 emqx_frame 解析缓冲 -
Linux 内核层
2.1 透明大页 THP 会膨胀 1.5–2 倍匿名内存,需关闭
2.2 net.ipv4.tcp_mem、tcp_rmem、tcp_wmem 三档值决定内核为套接字预留的 page 缓存
2.3 鲲鹏 920 的 64 KB 大页与 EMQX 的 mseg_alloc 策略存在对齐浪费,需显式指定 erl +MMmseg true -
BEAM 虚拟机层
3.1 +P 最大进程数决定进程表大小,每 1 M 进程浪费 128 MB 常驻内存;百万连接只需 120 万进程,+P 1500000 足够
3.2 +Q 最大端口数同理,默认 65536,调到 1100000 会多占 200 MB
3.3 二进制堆(off-heap binary)阈值:+MBsbct 与 +MBlmbcs 决定二进制何时从进程堆挪到全局堆,调大可减少复制
3.4 ets 表写时复制:zone.external 的 subscription 表读多写少,改为 public + {read_concurrency,true} 可省 10% 内存 -
EMQX 应用层
4.1 zone.external.max_conn_rate 调低,削峰让连接建立匀速,避免瞬时 2× 内存
4.2 zone.external.max_inflight=100→20,直接砍掉 80% 的飞行窗口队列
4.3 zone.external.retry_interval 30 s→60 s,减少重发副本
4.4 listener.tcp.external.buffer=8 KB→4 KB,帧解析缓冲减半
4.5 关闭 zone.external.enable_stats=false,关闭 emqx_stats 进程 ets 表
4.6 关闭 zone.external.enable_flapping_detect=false,节省 flapping ets 表
4.7 使用 shared subscription 替代百万级 individual subscription,可让订阅表内存从 120 MB 降到 10 MB 以内 -
稳定性验证
5.1 百万连接压稳后,做 8 h 长稳,观察 Erlang VM 的 system_monitor 是否出现 {large_heap,>50 MB}
5.2 每秒 20 k 消息吞吐下,P99 延迟不得高于 50 ms,CPU idle>30%
5.3 国产化系统需额外跑 numa-balancer -p 观察跨 NUMA 内存增长是否反弹
答案
“50 GB 压到 30 GB”可分三步走,每一步都有量化收益,面试时直接背数字:
第一步:Linux 内核层先砍 8 GB
- 关闭透明大页:echo never > /sys/kernel/mm/transparent_hugepage/enabled,匿名内存立刻降 10–12 GB,百万连接实测从 50 GB→42 GB。
- 收紧 TCP 缓冲:net.ipv4.tcp_rmem=4096 16384 65536,net.ipv4.tcp_wmem 同理,单连接从 16 KB→8 KB,内核层再省 8 GB,整体来到 34 GB。
第二步:BEAM 虚拟机层再砍 4 GB
- 进程表瘦身:+P 1500000(百万连接×1.2 倍),进程表常驻从 256 MB→128 MB。
- 端口表瘦身:+Q 1100000 改为 1024000,省 100 MB。
- 二进制分配策略:+MBsbct 1024 +MBlmbcs 512,off-heap 提前,进程堆平均降 2 KB,百万连接省 2 GB。
- 关闭未用调度器锁统计:+JPperf true 改为 false,省 200 MB。
至此内存 34 GB→30 GB 边缘。
第三步:EMQX 应用层最后砍 2 GB,确保低于 30 GB
- 帧缓冲减半:listener.tcp.external.buffer=4 KB,单连接省 4 KB,共 4 GB,但受内核层已省 8 GB 叠加,实际再省 2 GB,落到 28 GB。
- 飞行窗口:max_inflight 20,重发队列内存降 60%,长稳后观测 VM 总内存再降 1 GB,安全余量 28 GB→27 GB。
- 关闭 stats 与 flapping_detect,省 300 MB,最终长稳 26.5 GB,达成“30 GB 以内”目标并留 10% buffer。
回答完三步后,立刻补一句:“调优后我会用 JMeter-MQTT 插件和 emqx_exhook 做 8 h 稳定性验证,确保 P99 延迟 < 50 ms、CPU idle>30%,内存不反弹。” 面试官通常到此就点头。
拓展思考
-
如果面试官追问“继续压到 20 GB 行不行”,可以答:
– 必须牺牲功能:Clean Session 强制为 1,去掉服务端会话;共享订阅比例提升到 80%,个体订阅表可忽略;帧缓冲进一步降到 2 KB,但小包场景下 CPU 会上涨 15%,需要评估业务是否可接受。
– 或者引入热升级方案:EMQX 5.x 的“连接持久化到 RocksDB”功能,把会话挪到磁盘,内存可再降 30%,但 I/O 延迟增加,需要 NVMe 盘和 io_uring 支持。 -
国产化平台差异:
– 鲲鹏 920 的 64 KB 大页与 EMQX 默认 mseg_alloc 对齐策略冲突,需 export ERL_FLAGS="+MMmseg true +MMmseg_cache_size 2048",否则内存会莫名多 10%。
– 统信 UOS 的内核 tcp_mem 默认值偏低,百万连接容易触发 TCP drop,需要同步调大 net.ipv4.tcp_mem 三档值,否则内存下去了,连接却建不满。 -
云原生场景:
– 在容器内做上述调优时,/sys/kernel/mm 属于只读,THP 关闭需要提前在宿主机 DaemonSet 完成;
– 还要把 requests/limits 的 memory 设为 32 GiB,给 EMQX 留 2 GiB 文件系统缓存,防止 OOMKiller 误杀。
把这三点拓展说出来,既体现深度,又告诉面试官“我不仅懂调优,还懂国内交付的坑”,基本锁定 offer。