关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

自建 MQTT 消息中间件要多少资源:连接数、文件描述符和持久化盘怎么一起算

发布时间:2026-10-15

自建 MQTT 消息中间件要多少资源:连接数、文件描述符和持久化盘怎么一起算

两万设备连不上的那一刻:日志里只有一行 accept 失败

先说一个具体的现场。某批物联网终端分批上量,注册设备总数八万多,规划峰值在线五万。推到两万一千台时出问题了:Broker 进程活着,没有 core dump,没有重启,CPU 使用率不到三成,内存还有富余,带宽只跑了十几兆,但新设备开始握手超时,客户端侧表现为连接被重置或者干脆没响应。翻日志,只有一行——accept 失败,错误号是 EMFILE。

运维的第一反应是加机器。加了一台同样的配置,把新设备切过去,总数确实涨了一点,从两万一涨到两万三,然后第二台也开始了同样的症状。这个结果非常反直觉:明明每台机器的资源都用得很省,为什么有效连接数远远低于预期?原因就是每台机器各自撞了自己的描述符墙,加机器只是把墙的数量变多,没有把墙变高。同样的钱,本来能撑住五万连接,最后只撑住两万多。

这里还有一个容易看错的地方:监控上写的"在线连接数两万",在内核眼里不是两万个句柄。设备断线重连、网络抖动导致的半开连接、处在关闭流程中的 TIME_WAIT 套接字,都要占描述符。真实占用通常是峰值的 1.2 到 1.5 倍(预估,取决于重连频率与 keepalive 设置)。所以当你看到在线两万的时候,描述符可能已经用掉了两万六,而进程的上限是一万零二十四——这不是"快到了",这是早就超了,只是超了之后的表现不是崩溃,是静默拒绝。

这个场景里最贵的一步,是没有先把"上限到底由谁决定"这件事想清楚,就直接按经验加了机器。MQTT 这类长连接中间件,资源瓶颈的先后顺序和 Web 服务是反过来的:Web 服务通常是 CPU 先到,然后内存;MQTT 接入层是文件描述符先到,然后内存,CPU 往往是最后一个才动的。

单机连接数上限由谁决定:ulimit -n、epoll 实例与每连接内存

一台机器上能同时挂住多少条 MQTT 长连接,是三个闸门里最小的那个决定的,不是三个加起来。

第一个闸门是进程级文件描述符上限。每个 TCP 连接在服务端就是一个 fd,这条没有例外。多数 Linux 发行版默认给的是 1024,这个数字在 Web 场景下够用很久,在 MQTT 场景下几百台设备就撞墙。系统级还有 fs.file-max 和 fs.nr_open 两道,进程级上限不能超过 fs.nr_open,所以三者要一起对。

第二个闸门是事件循环的处理能力。epoll 实例本身能管理的 fd 数量很大,通常不是硬上限,真正限制你的是每秒能完成多少次新连接建立(CPS)和每秒能处理多少个报文(PPS)。一台机器在设备集中上电、集中重连的那几分钟里,CPS 会冲到平时的几十倍,这时候即使描述符够用,接受连接的队列也会先满。

第三个闸门是内存,而且这一项最容易被低估,因为很多人只算了应用层的会话对象,忘了内核侧的套接字缓冲区。TCP 收发缓冲区由 tcp_rmem / tcp_wmem 控制,取的是内核在这三个值之间动态调整的结果,默认值在部分发行版上能到百 KB 量级,而且是读写双向各一份。两万条连接,光内核缓冲区就可能吃掉 GB 级别的内存(预估,实际取决于内核是否启用了自适应调节与连接的实际数据量)。这块内存不在 Broker 进程的 RSS 统计里以"会话对象"的形式体现,所以你看到应用占用不高,机器的可用内存却在持续下降。

处理办法有两条:一是显式把 rmem / wmem 的默认值调到与你的报文大小匹配的量级,物联网报文普遍很小,几十字节到几百字节,用不着那么大的缓冲;二是确认 tcp_moderate_rcvbuf 是否开启,让内核按实际负载自适应,而不是按上限预留。改完这两处之后重新压一轮,你会发现单机可承载的连接数往往能上一个台阶,而且不需要加任何一台机器。

每连接内存开销不只是 socket 缓冲区:会话状态、订阅树、飞行窗口与 TLS 缓存

要反推内存,先得知道一条连接在 Broker 侧到底留下了什么。把 socket 缓冲区算完只完成了三分之一,剩下的部分一项一项拆开看:

会话状态。client id、clean session 标志、keepalive 计时器、遗嘱消息(will message)、会话过期间隔。遗嘱消息这条经常被漏掉——它是一条完整的消息体,要在连接存续期间一直存着,如果每个设备都设了遗嘱消息且 payload 不小,这部分是实打实的常驻内存。

订阅树条目。每个订阅会在 Broker 的全局订阅树里留下索引,一个客户端订阅十个主题就是十条。这一项在"设备订阅少、服务端下发多"的模型下不显眼,但如果设备侧订阅了大量主题,或者用了多级主题,订阅树的规模会和连接数成正比增长,而不是常数。

飞行窗口(inflight)。QoS 1 与 QoS 2 的消息在收到确认之前必须驻留,窗口大小决定了一条连接最多能同时挂多少条未确认消息。这块内存不是"连接数 × 常数",而是"连接数 × 窗口大小 × 平均消息大小",所以在高频上报场景下它会突然膨胀,而且膨胀的时点正好是你最缺内存的时候。

离线消息队列。持久会话的设备掉线期间,投给它的消息不能丢,要排队。队列长度上限设多大,直接决定了最坏情况下的内存占用。这一项和飞行窗口是叠加的,不是二选一。

TLS 会话缓存。这一项是数量级级别的差异,也是最容易拍脑袋的地方。开启 TLS 之后,每条连接除了上面的东西,还要多出 SSL 结构体、加密上下文、读写缓冲,以及会话票证或会话 ID 的缓存条目。非 TLS 与 TLS 的每连接内存开销通常相差数倍(预估),具体倍数取决于密码套件、TLS 版本、是否开启会话复用、证书链长度,不同实现之间也会有差别。网上传的那些"每连接 X KB"的精确数字,几乎都是某个特定版本、特定编译选项、特定客户端行为下测出来的,换个环境就对不上。不要照抄精确 KB 数,正确做法是在自己的机型、自己的客户端配置下压一轮,测出属于自己的系数。

测法很简单,比任何公开数字都可靠:压测增量法。先起零连接,记录进程 RSS;再起一万条空闲长连接,记录 RSS;两者之差除以一万,就是你这台机器、你这个版本、你这个配置下的每连接基础开销。想要 TLS 的系数,就把这一轮用 TLS 重做一遍,两个结果一比就出来了。想要消息堆积的影响,就在一万连接上再灌消息,看 RSS 涨多少。三次测量,你的容量模型就有了,而且是可复现的。

文件描述符的四项叠加:连接、持久化文件、日志与系统预留

文件描述符这一项,最常见的错误是只按"连接数"算,算完加个几千了事。实际上它是四项叠加:

第一项:连接占用的描述符。在线连接数 × 1,再加上处在关闭流程与 TIME_WAIT 状态的套接字。前面说过,这部分通常要按峰值的 1.2 到 1.5 倍预留(预估)。重连风暴期间这个系数会更高,如果你的设备固件在断线后是激进重试(几秒一次),系数可能要放到 2 以上。

第二项:持久化文件句柄。消息持久化引擎会打开大量数据文件——段文件、SST 文件、索引文件。这一项的特点是它会随时间增长:保留期越长、历史数据越多、节点数越多,同时打开的文件数就越多。很多团队刚上线时算得很准,跑半年之后因为持久化文件句柄膨胀,又撞了一次墙,而且这次撞墙的日志和第一次一模一样,很容易误判为"连接数又涨了"。

第三项:日志句柄。连接日志、消息日志、审计日志。按大小滚动的日志同时存在的句柄不多,但如果打开了消息级日志(每一条 PUBLISH 都打一条),写入会变成高频小 IO,句柄本身占的不多,带来的 IO 压力才是问题——这一点在后面讲磁盘时展开。需要注意的是调试期间临时打开的 debug 日志,上线时忘了关,是最常见的"描述符凭空少了一截"的原因。

第四项:系统预留。监控 agent、日志采集器、备份任务、运维通道、Broker 之外的其他进程,都会吃描述符。这一项一般留 20% 到 30%。不要把它算成固定几百个,因为监控和采集类组件在异常时会同时打开文件做抓取,预留不足会在最需要排查问题的时候失联。

四项叠加后的算式:LimitNOFILE ≥ 峰值连接数 × 1.3 + 持久化文件句柄 + 日志句柄 + 系统预留(20%~30%)。按这个式子,一台目标五万连接的机器,描述符上限往往要设到二十万量级,而不是很多教程里写的 65535。65535 这个数字本身没错,只是它对应的连接规模比大多数人以为的要小得多。

systemd 的 LimitNOFILE 与 limits.conf 为什么是两套口径

这是本篇最值得单独拿出来说的一节,因为它是"改了没生效"这种问题里占比最高的一类。

/etc/security/limits.conf(以及 limits.d 目录下的文件)是由 PAM 在用户登录会话中加载并生效的。它的作用域是"通过登录会话启动的进程"——交互式 shell、su 切换后的进程,走这条路。

而 systemd 拉起的服务不走 PAM 登录会话,它读的是 unit 文件里的 LimitNOFILE=,没有显式配置时就继承系统默认的 DefaultLimitNOFILE,这个值在很多发行版上是 1024 或 4096 量级。

于是就有了三种典型的无效操作:在命令行里执行 ulimit -n 65535,然后重启服务——没用,因为那是当前 shell 的限制,重启后的进程是新 fork 的;改了 limits.conf,重启服务——还是没用,因为服务不是 PAM 拉起来的;只在 unit 里改了 LimitNOFILE,但 fs.nr_open 还是默认值——进程起不来或者被截断到 nr_open。

正确的动作是这三步:第一,用 systemctl edit <服务名> 写一段 [Service] LimitNOFILE=<目标值>,不要直接改 /usr/lib/systemd/system 下的原始文件,那是包管理器的地盘,升级会被覆盖;第二,确认 fs.nr_open 与 fs.file-max 大于你要设的进程级上限;第三,systemctl daemon-reload 再 restart。

核对的时候不要看 ulimit -n 的输出,那个是 shell 的。要看进程实际的:cat /proc/<pid>/limits,找 "Max open files" 那一行的 Soft Limit。只有这里显示的数字变了,改动才算生效。顺手再看一眼 ls /proc/<pid>/fd | wc -l,这是当前实际占用,拿它和你设的上限一比,就知道还剩多少余量、什么时候该加。

一句话记住:描述符是两套口径,进程级(systemd unit)和系统级(sysctl)都要改,验证只看 /proc/<pid>/limits。

CPU 与内核参数:somaxconn、backlog 与 conntrack 的实际边界

CPU 在 MQTT 接入层通常是最闲的资源,只有两种情况例外:一是大规模 TLS 握手,二是大量通配符订阅匹配。设备集中上电、或者网络故障后集中重连时,握手计算会把 CPU 打满,这时候加核是有用的;平时 CPU 空着,加了核也不会让连接数变多。

真正该调的是内核参数,而且这几个参数出错时的现象各不相同:

net.core.somaxconn 是全连接队列长度。Broker 应用来不及 accept 的时候,已完成握手的连接排在这里。重连风暴时这个队列满了,内核会丢弃后续的连接请求,客户端表现为连接超时,而不是连接被拒——这个区别很重要,超时会让人以为是网络问题或者应用卡死,实际上只是队列短了。应用侧自己通常也有一个 backlog 参数(listen 的第二个参数),取的是它与 somaxconn 的较小值,两个都要调。

net.ipv4.tcp_max_syn_backlog 是半连接队列。设备上电时的 SYN 洪峰打在这里,满了之后新 SYN 被丢,现象同样是超时。

net.netfilter.nf_conntrack_max 是最阴的一个。如果 Broker 前面有 NAT 网关或者防火墙做连接跟踪,跟踪表满了之后新连接会被静默丢弃,日志里什么都不会有——既不是 accept 失败,也不是超时重试,就是连不上。这类问题排查成本最高,因为现象和描述符耗尽几乎一样。判断方法是在网关上看 conntrack 计数是否打满。这个值一般按峰值并发连接的 2 到 3 倍预留(预估),哈希桶数量按四分之一配置,改之前先在测试机上验证,不同内核版本的行为有差异。

net.ipv4.ip_local_port_range 影响的是 Broker 作为客户端发起的连接——向数据库写入、向后端 HTTP 转发、以及桥接到其他 Broker 的场景。如果规则引擎把每条消息都转发出去,转发连接数会成为新的描述符消耗点,这一项要单独算进前面的四项叠加里。

TIME_WAIT 相关的参数要特别小心:tcp_tw_recycle 在新内核上已经被移除,很多老资料还在写它,照抄会直接报错或者行为异常。TIME_WAIT 主要由主动关闭方承担,所以服务端侧的合理做法是让客户端主动断开、或者把 keepalive 与会话过期间隔设得合理,而不是去调内核回收参数。这类参数在不同内核版本上的语义有出入,改之前先验证。

给出一个判定顺序:CPU 高但连接数不涨,去看通配符订阅匹配和规则引擎转发;CPU 空闲但连不上,一定是描述符、accept 队列、conntrack 三者之一,按这个顺序查。

从连接数与心跳间隔反推 PPS:网卡队列、软中断与端口上限

物联网流量的形态和 Web 完全不同:带宽很小,包很多。一个上报报文的 payload 可能只有几十字节,加上 MQTT 固定头和 TCP/IP 头,一个包也就百字节上下。这种流量打不满带宽,但打得满 PPS。

反推方式很简单:PPS ≈ 连接数 ×(心跳频率 + 业务消息频率)× 2(进出双向)。五万连接、keepalive 六十秒、只算心跳,那就是五万除以六十再乘二,大约一千七百 PPS。看起来不多,但如果业务消息频率是每台每分钟一条,PPS 就要再加一档;如果用了 QoS 1,每条消息还要乘以确认报文的倍数。真实场景里,几万连接的接入层跑到几万 PPS 是很常见的,而这个量级对应的带宽可能只有几兆到十几兆。

这就是为什么"按带宽买机器"在物联网场景一定会买错。你买一个带宽很大的端口,结果瓶颈是 PPS;或者反过来,端口带宽够但小包处理能力跟不上。

小包高并发下最常见的症状是软中断集中在单个核上。看 top 里的 si 一项,如果某一个核的 si 长期接近百分之百,而其他核很闲,那就是网卡队列没有分散开。处理办法按优先级排:开多队列网卡(RSS)让硬件把流量哈希到多个队列;把不同队列的中断绑定到不同核;网卡不支持多队列时开 RPS 在软件层做分发。NUMA 架构的机器上还要注意网卡挂在哪个 NUMA 节点,跨节点访问内存会再吃一次时延。

在服务器与网络资源的比选上,与其按带宽档位高低挑机型,不如先把连接数目标和 PPS 目标算出来,再拿这两个数去对内存容量、端口上限、以及是否允许自定义内核参数这几项逐项比选。像一万网络这类深耕 19 年(成立于 2007 年)的服务商,在物联网接入这种非标准负载上通常能提供可调整内核参数、可拆分磁盘的方案;而纯按带宽分档的标准化套餐,参数往往锁死,描述符和 conntrack 这类值改不动,钱花在带宽上,PPS 却先撞墙了。

QoS1/QoS2 落盘、Retain 与离线队列挤在一个目录时会发生什么

消息持久化这一块的反推,很多人只算了容量,没算 IOPS 和时延。先看三类写负载各自的形态:

QoS 1 消息:Broker 收到后、发出 PUBACK 之前,消息必须处于"不会丢"的状态,这通常意味着落盘或者至少驻留在带持久化的内存结构里。写入是小尺寸、高频、随机的——消息按到达顺序写,但主题、会话分布是散的。QoS 2 要两阶段确认,落盘与状态更新的次数比 QoS 1 更多,成本最高。绝大多数物联网上报用 QoS 1 就够了,QoS 2 只在对重复投递零容忍的场景用,而且用了就要接受它的代价。

Retain 消息:每个主题保留最新一条,写入频率不高,但读取极频繁——每个新订阅者订阅时都会立刻收到。如果 retained 主题数量很多(比如每个设备的状态主题都设 retain),这部分会变成大量的小随机读,混在写负载里就是典型的读写混合 IO。

离线消息队列:持久会话设备掉线期间,投给它的消息排队等它回来。这是堆积型负载,平时写,设备回来时集中读。集中读的时刻正好是重连风暴的时刻,两件事撞在一起。

这三类负载如果都落在同一个存储目录、同一块盘上,后果是IO 抖动直接抬高消息时延。表现很有特征:时延的 p50 完全正常,p99 或者 p999 却突然拉到几百毫秒甚至秒级,而 CPU、内存、带宽全都正常。这种"别的都正常就是慢"的现象,八成是 IO 抖动。

机理不难理解:持久化引擎为了保证消息不丢,会周期性做 fsync。fsync 一旦发生,这块盘上排队的其它写入都要等它。如果同一块盘上还有日志在顺序追加、还有数据库在同步写元数据,那这些写入会一起被卡住,形成一个尖峰。尖峰过后队列排空,时延回到正常,于是监控上看到的就是周期性毛刺。

反推方式:磁盘容量 = 平均消息大小 × 峰值堆积条数 × 保留时长 × 副本系数;所需 IOPS = 峰值消息速率 ×(QoS 1 与 QoS 2 占比)× 每条消息的落盘系数。算完你会发现,容量通常很宽裕(几百 GB 对几万设备往往够用),真正卡人的是 IOPS 和写时延的稳定性。所以选型时看 IOPS 和时延,不看容量。

消息持久化目录为什么要与系统盘、日志盘分开

把消息持久化目录与系统盘、日志盘分开,不是洁癖,是避免三种不同的失效模式互相牵连。一块盘上混了四类写负载:消息持久化的随机小 IO、Broker 日志的顺序追加、系统日志与监控采集的周期写、以及元数据数据库的同步写加 fsync。

第一种牵连是时延牵连,上一节已经说过:fsync 尖峰把其它写入一起卡住,消息时延出现周期性毛刺。

第二种牵连是排查牵连。出问题时你要看日志,但日志盘正被消息写入挤占,日志采集延迟,告警延迟,你看到的监控数据本身就是滞后的。这种时候最容易做出错误判断——看着几分钟前的曲线,去调一个早就不是瓶颈的参数。

第三种牵连最严重:系统盘写满。日志滚动失败、临时文件写不进去、包管理器和 systemd 都可能异常。这时候不是"MQTT 慢一点"的问题,是整台机器失联、连 SSH 都可能登不上去。而造成写满的,往往正是放在系统盘上的消息持久化目录或者没做限流的 debug 日志。

分盘的落地方案:系统盘独立且留足余量——不要拿十几个 GB 起步的系统盘去跑 Broker 日志,日志要么单独分区、要么做限流和定期清理;消息持久化目录单独挂一块 SSD,选型依据只看 IOPS 与写时延,容量按上面的式子算出来再加一倍余量就够了;日志单独一块盘,或者至少单独分区加上滚动策略与大小上限。

判断要不要拆盘,看 iostat -x 的两个值:%util 长期接近 100% 而吞吐量(读写的 KB/s)不高,说明是 IOPS 打满而不是带宽打满。这种情况加容量没有任何用,只能换更高 IOPS 的介质或者拆盘分散压力。反过来,如果吞吐量高而 %util 不满,那是顺序写为主,通常不是问题。

集群横向扩容不等于连接数线性增长:共享订阅、桥接与 retained 同步

到这里可以回答开篇那个"加机器为什么只多两千"的问题了。集群不是"单机连接上限"的简单倍数,因为每加一个节点都会引入三类额外开销:

路由表同步。集群里每个节点都要知道"某个 client id 在哪个节点""某个订阅在哪个节点",这张全局路由表要在节点间保持一致。节点越多,同步流量越大,而且同步是固定开销——不管当前有没有业务消息,它都在跑。所以节点数增加一倍,可用连接数达不到两倍,中小规模(几个节点)时接近线性,节点数上去之后同步开销的占比会明显上升(预估)。

共享订阅的跨节点转发。共享订阅要在订阅组内选一个成员投递,如果消息到达的节点不是那个成员所在的节点,就要跨节点转发一次。转发量 = 消息量 × 跨节点比例。如果你的订阅组分布是随机的,跨节点比例会接近(节点数减一)除以节点数——节点越多,跨节点的概率越高。这是一个反直觉的结论:加节点让单机连接压力变小,却让内部转发流量变大。

retained 消息与持久会话状态的同步。retained 消息是全局的,一个节点收到新的 retained 消息要同步给其他节点;持久会话在设备跨节点重连时要迁移状态。这两类同步在设备频繁漂移(今天连 A 节点,明天连 B 节点)的场景下会持续产生流量。

桥接是另一笔账。每条桥接连接本身占用本地的描述符和内存,两端的订阅同步也会放大消息量。桥接做得越多,单机能承载的设备就越少。

结论很直接:集群的首要目的是高可用和可维护性,不是堆连接数。把"目标连接数除以单机上限"来算节点数,算出来的机器一定不够。正确顺序是先把单机调到它该有的水平(描述符、内核参数、内存、拆盘),再按可用性和运维需要决定节点数。

什么时候加节点,什么时候拆主题命名空间

加节点和拆主题空间是两种完全不同的扩容手段,判断条件也不一样。

该加节点的信号:单机内存已经接近上限,而且描述符、内核参数、磁盘都调过了;需要跨机房或者跨可用区的容灾;单机故障的影响面不可接受,接入层完全没有冗余。这三个条件里,第三个往往是最实际的理由——哪怕资源够,单点也是不可接受的。

该拆主题命名空间的信号:订阅树过大,通配符匹配成为 CPU 热点(表现为 CPU 高但消息量没涨);业务之间互相干扰,一个业务的大量消息把另一个业务的时延一起拉高;retained 主题数量爆炸,而且分属不同业务线;桥接规则和转发规则已经复杂到没人敢改。这四类问题的共同点是:它们不是资源不够,是结构不合理。加节点解决不了结构问题,只会把结构问题复制到更多机器上。

不该动任何东西的信号:连不上但描述符没调——先调参数;消息慢但磁盘没拆——先拆盘;PPS 高但软中断集中在单核——先做队列分散。这三种情况下加节点,钱花了,问题还在。

还有一个容易被忽略的判断:节点数是不是已经超过实际需要。如果集群里每个节点的负载都很低,但同步流量占比很高,那说明节点加多了。这时候正确的动作是减节点或者合并主题层级,而不是继续加。

MQTT 资源反推表:从目标连接数倒推四项资源

把前面所有内容压缩成一张可执行的反推表。用法是从左往右:先知道目标连接数,算出每一行的估算值,取最大值决定机器配置。

资源项 决定因素 常见瓶颈表现 估算方式 什么时候必须加
文件描述符 峰值连接数、持久化文件数、日志句柄、系统预留 accept 失败,日志只有一行 EMFILE,进程存活且 CPU 空闲 峰值连接数 × 1.3 + 持久化文件句柄 + 日志句柄 + 20%~30% 预留 实际占用达到进程级上限的 80%
内存 每连接常驻开销(会话状态、订阅树、飞行窗口、TLS 缓存)加上消息堆积缓冲区 RSS 持续上涨,可用内存下降,进程被 OOM 终止前连接先被拒 压测增量法测出每连接开销,乘以目标连接数,再加堆积缓冲区 RSS 超过物理内存的 70%,或堆积缓冲区无法再压缩
CPU TLS 握手速率、通配符订阅匹配次数、规则引擎转发量 重连风暴时 CPU 打满,平时长期空闲 按峰值握手 CPS 与每秒匹配次数估算,取两者较大值 稳态 CPU 长期超过 60%,或重连期间持续打满
网络 PPS 连接数乘以心跳与业务报文频率,进出双向 带宽很低却卡住,软中断集中在单个核上,si 接近 100% PPS 约等于连接数 ×(心跳频率 + 消息频率)× 2 单核软中断打满,或网卡队列已满且无法再分散
磁盘 IOPS QoS 1 与 QoS 2 落盘次数、retained 读放大、离线队列写放大 时延 p99 周期性毛刺,%util 打满但吞吐量不高 峰值消息速率 × QoS 占比 × 每条消息落盘系数 %util 长期接近 100% 且吞吐量低,加容量无效时

这张表的使用顺序就是文章的结论:从上往下逐行核对,第一行不达标就不要看后面的行。描述符没调好,加内存、加 CPU、换更快的盘都不会让连接数变多。

实施方案:从目标连接数倒推一台物理机的四项配置

给出一套可以照着做的落地步骤。

第一步,先定清楚六个输入

峰值在线连接数(注意是同时在线,不是注册设备总数,两者常常差好几倍)、平均消息速率、QoS 等级分布、是否启用 TLS、持久会话占比、消息保留时长。这六个数定不准,后面全白算。尤其是"持久会话占比",它同时影响内存和磁盘两行。

第二步,反推描述符

按四项叠加的算式算出目标值,然后在 systemd unit 里设 LimitNOFILE,同时确认 fs.nr_open 与 fs.file-max 更大。改完用 cat /proc/<pid>/limits 核对,不要看 ulimit -n。

第三步,反推内存

用压测增量法测出自己环境下的每连接开销——空闲连接测一次,加上 TLS 再测一次,灌消息再测一次。三个数分别对应基础开销、TLS 系数、堆积开销。目标连接数乘以基础开销,加上堆积缓冲区,再留 30% 余量,就是要买的内存。

第四步,反推磁盘

容量按平均消息大小 × 峰值堆积条数 × 保留时长 × 副本系数算;选型按 IOPS 和写时延选。消息持久化目录单独挂载,与系统盘、日志盘分开。系统盘容量要留足,不要让它承担任何消息写入。

第五步,定网络

按 PPS 而不是带宽来选端口。到货后先做一轮小包压测,确认网卡多队列和中断绑定生效,确认软中断分散到多个核上。如果服务商不允许自定义内核参数,这一步很可能做不了,选型时要提前问清楚。

第六步,上线前分三档压测

第一档只压连接数,不灌消息,看描述符和内存的线性度;第二档只压消息速率,连接数固定,看磁盘 IOPS 与时延;第三档混合压,并且模拟一次重连风暴(让全部连接同时断开再重连),看 CPS、CPU 和 accept 队列。每一档都记录:进程 RSS、/proc/<pid>/limits、/proc/<pid>/fd 的数量、iostat 的 %util 与 await、软中断分布、时延的 p50 与 p99。这六个指标就是上线后要长期盯的东西。

在机型比选上,一万网络深耕 19 年(成立于 2007 年)的标准化服务器租用方案可以按这四项逐项对齐:内存按每连接开销留出余量、描述符与内核参数支持自定义调整、消息持久化盘与系统盘分开挂载、端口按 PPS 而非带宽选型。具体机型与价格需询价,公开渠道差异较大,以实时报价为准。如果设备分布跨地域,中国香港这类节点可以作为跨境接入的落点之一,但选型时同样要先算连接数和 PPS,再看端口与内核参数的可调范围。

MQTT 接入层容量 FAQ:六个高频问法与判定口径

问:目标五万连接,需要多大内存?不要用网上的每连接数字直接乘。先用压测增量法测出自己环境下的系数,再乘五万,再加堆积缓冲区和 30% 余量。如果启用了 TLS,记得 TLS 与非 TLS 的每连接开销通常相差数倍(预估),两次测量都要做。

问:文件描述符设多少合适?按峰值连接数 × 1.3 起步,加上持久化文件句柄、日志句柄和 20% 到 30% 的系统预留。目标五万连接的机器,这个数往往要到二十万量级。设完必须验证 /proc/<pid>/limits。

问:为什么加机器之后连接数没按比例涨?因为瓶颈是描述符不是 CPU。新加的机器带着同样的默认描述符上限,只是复制了同样的墙。先把单机的描述符和内核参数调到位,再考虑加机器。

问:集群几个节点合适?按可用性和运维需要定,不要按"连接数除以单机上限"定。节点数增加会带来路由表同步、共享订阅跨节点转发、retained 同步三类额外开销,加太多反而降低效率。一般取奇数个以满足多数派判定。

问:消息盘要多大?容量按平均消息大小 × 峰值堆积条数 × 保留时长 × 副本系数算,通常不是瓶颈。选型要看 IOPS 和写时延,看 iostat 里 %util 高而吞吐量低就该换介质或拆盘。

问:什么时候必须换更大规模的 Broker 形态?描述符调到位了、内存按实测配足了、磁盘拆开并且换成高 IOPS 介质了、CPU 仍然在重连风暴时打满——这时才是真的需要换架构(换更擅长大规模连接的 Broker 实现,或者改造接入层做连接与消息处理分离)。前面三步没做完就换架构,只是在新架构上重复同样的配置错误。

MQTT 接入层扩容顺序:先描述符,再内存,最后才是 CPU

整篇的结论可以压成一句话:MQTT 接入层的资源瓶颈顺序和 Web 服务相反,先撞文件描述符和内核网络参数,再撞内存,最后才是 CPU。绝大多数"加机器"式的扩容在这个场景是无效的,因为单机连接数上限由 ulimit -n、epoll 事件循环的处理能力、以及每连接的内存开销共同决定,这三个都不是靠加机器能解决的。

对应的动作顺序也就固定了:连不上先查描述符(看 /proc/<pid>/limits 和 fd 数量),描述符够用再看内存(用压测增量法测真实系数,别照抄公开数字),内存够用再看磁盘(看 %util 和 p99 时延,别只看容量),最后才轮到 CPU(只在 TLS 握手和通配符匹配两个场景下才值得加核)。集群节点数的决定放在最后,而且依据是可用性和运维,不是连接数除法。

这套顺序的价值在于它省钱:开篇那个两万一千台就撞墙的案例,真正需要的不是两台新机器,是几行 systemd 配置和一次压测。

数据来源与核对方式:MQTT 中间件容量估算口径

内核参数与描述符口径:ulimit -n、systemd LimitNOFILE、fs.file-max、fs.nr_open、net.core.somaxconn、net.ipv4.tcp_max_syn_backlog、net.netfilter.nf_conntrack_max、net.ipv4.ip_local_port_range、tcp_rmem 与 tcp_wmem 等参数的作用与默认值范围,来自 Linux 内核与各发行版的官方文档。文中给出的倍数关系(连接描述符按峰值 1.2~1.5 倍预留、conntrack 按峰值并发 2~3 倍预留、系统预留 20%~30%)为工程经验值,非实测数据,需结合内核版本与实际负载验证。

资源估算算式口径:文中四组算式(描述符四项叠加、内存增量法、PPS 反推、磁盘容量与 IOPS 反推)为通用容量规划方法,不涉及特定 Broker 实现的内部参数。TLS 与非 TLS 每连接开销"通常相差数倍"为区间估计(预估),实际倍数受密码套件、TLS 版本、会话复用策略、证书链长度与具体实现影响,文中未给出精确 KB 数,读者应以自身环境的压测增量法结果为准。

自我核对方式:上线前按文中第六步做三档压测,记录进程 RSS、/proc/<pid>/limits、/proc/<pid>/fd 数量、iostat 的 %util 与 await、软中断分布、时延 p50 与 p99 共六项指标;上线后持续比对,发现某一项接近阈值即按扩容顺序处理。

价格相关说明:本文涉及服务器配置与价格的部分一律为需询价,公开渠道差异较大,以实时报价为准。文中未给出任何具体金额。


上一篇:沙特服务器选型为什么别只看国家页:吉达页只有两个档位,国家页有十条产品线

下一篇:东京和大阪的日本云服务器为什么差出两套价:500 元档与 99 元档分别买的是什么