把自己搭的 MQTT 消息中枢接上几千台设备,很容易;接上十万台设备还不掉线,是另一门学问。做物联网、车联网、工业采集的工程师在选型服务器上最容易犯的错误,是拿 Web 服务器的容量模型去估 MQTT:算带宽、算 QPS、挑一颗主频高的 CPU,结果压测时连接数刚爬到几万,进程就开始报 "Too many open files",或者内存一路飙高而 CPU 还闲着发呆。本文围绕 Mosquitto 这一个具体软件,把长连接模型、单进程事件循环、文件描述符与内核参数、QoS 队列、持久化写盘、PPS 与 CPS 压力、六维硬件对比、避坑清单和选型分界点讲透,给一份可以直接照着核对的落地手册。以下为典型部署思路,并非特指某一真实客户。
估算 MQTT 服务器容量时套用 Web 思路会翻车。一条 MQTT 连接建立后,设备可能十分钟才上报一条两百字节的报文,但在这十分钟里,它一直占着一条 TCP 连接、一个文件描述符、一份会话状态和一个心跳定时器。带宽用得极少,资源却被长期占用。所以 MQTT 场景的容量公式里,分母是连接数而不是吞吐。
MQTT 的连接模型由四件事共同决定。第一是长连接:客户端 CONNECT 之后保持 TCP 不拆,靠周期性的 PINGREQ / PINGRESP 维持,服务端在约 1.5 倍 keepalive 时间内没收到任何报文,就判定客户端失联并触发后续动作。第二是心跳,keepalive 是客户端在 CONNECT 报文里自己声明的秒数,服务端只是被动参照,它不会主动探测;keepalive 设得越短,服务端每秒要处理的 PING 报文越多,这笔开销和真实业务报文一样要过同一条事件循环。第三是遗嘱消息(Will),客户端在 CONNECT 时把一条"遗嘱"挂在服务端,一旦客户端非正常断开(TCP 异常、keepalive 超时)就由服务端代为发布到指定主题,正常发送 DISCONNECT 则不触发,这是设备掉线告警最常用的实现方式。第四是会话,clean session 标志决定重连后是否复用之前的订阅与未确认消息;置为 true 时服务端丢弃会话状态,置为 false(持久会话)时服务端要保留订阅关系、QoS 1/2 的 inflight 消息和排队的离线消息,代价是内存或磁盘上的常驻占用。
把四件事合起来看,容量模型就清楚了:总内存约等于连接数乘以每连接常驻开销,再加上会话队列占用;CPU 主要消耗在每秒报文数(PPS)和每秒新建连接数(CPS)上;带宽反而是最不紧张的一项。因此在这个场景里,第一句话应该问"这台机器能稳定撑住多少条长连接",而不是"能跑多少兆带宽"。
Mosquitto 是 C 语言实现的单进程、基于 epoll 事件循环的 broker。这个设计带来极低的单连接开销和很好的延迟表现,也带来一个绕不开的限制:它通常只跑在一个线程上,一颗核跑到百分百就到顶了,机器上剩下的核再闲也帮不上忙。理解这一点,后面所有的横向扩展方案才讲得通。
单连接的开销可以拆成三层。第一层是内核侧的 TCP socket,每个连接有发送缓冲区与接收缓冲区,内核依 net.ipv4.tcp_rmem 与 net.ipv4.tcp_wmem 的动态范围自动调节,小报文场景下通常落在几 KB 量级,但十万连接乘起来就是几百 MB 甚至上 GB。第二层是文件描述符,一条连接就是一个 fd,进程级 nofile 与系统级 file-max、nr_open 共同决定天花板。第三层是 Mosquitto 自己的结构体:连接上下文、会话状态、订阅树节点、inflight 与离线队列条目,这部分通常在每连接几百字节到几 KB 之间浮动,一旦把队列开大,总内存就会被队列主导,而不是被连接本身主导。
单线程事件循环还有个容易被忽略的含义:任何一次阻塞都会拖慢全部连接。持久化写盘、日志写盘、TLS 握手中的大数运算,都发生在同一个循环里。TLS 握手尤其典型——不做加密时 CPU 通常很闲,一旦开启 TLS,握手阶段的非对称运算会直接把那个核打满,握手密集时新连接排队等待,表现为"连接建立变慢,但已建连的消息延迟没变"。这个现象是判断瓶颈在握手而不是在转发的关键线索。
要把多核吃满,有三条路。第一条是在同一台机器上起多个 Mosquitto 实例,监听不同端口(1883、1884、1885 这样排下去),客户端按设备 ID 哈希分流,每个实例独立占用一个核;代价是实例之间不共享订阅状态,跨实例通信要靠 bridge 转发。第二条是直接用多台机器,靠 DNS 轮询或四层负载均衡把设备分散,运维复杂度上升但隔离性最好。第三条是当业务要求"任意一个节点都能收到任意主题的消息"时,Mosquitto 的多实例已经不够,需要换具备原生集群能力的 broker。这三条路的取舍,在后面的选型建议里会给出明确分界点。
压测第一次失败,多半不是败在 Mosquitto 本身,而是败在还没到十万连接就报出 "Too many open files"。Linux 常见发行版的默认 fd 限制是进程级 1024,这个数字连一万连接都撑不到。而且很多团队在 shell 里 ulimit -n 调大了,再用 systemctl 启动服务,发现限制没生效——因为 systemd 服务并不继承你当前 shell 的限制,它读的是自己的配置。
需要同步调整的地方有这些。进程级限制写在 Mosquitto 的 systemd unit 里,用 LimitNOFILE 显式指定,软硬限制一起给,做十万连接规划时通常要提到十万以上量级;同时确认 /etc/security/limits.conf 与 systemd 的 system.conf 不会因为优先级问题把它覆盖回去。系统级看 fs.file-max(全系统 fd 总量上限)与 fs.nr_open(单进程 fd 硬上限),前者要能覆盖所有进程的需求总和,后者要不低于你给 Mosquitto 设的值,否则 LimitNOFILE 会被静默截断。
TCP 侧的参数同样关键。net.ipv4.tcp_mem 是内核允许所有 TCP 连接消耗的总内存页数,按 min、pressure、max 三段生效,连接规模很大而 max 偏小时,内核会进入内存压力模式并主动收缩缓冲区,表现为收发变慢、重传上升、延迟抖动。net.ipv4.tcp_rmem 与 tcp_wmem 决定每连接收发缓冲区的动态范围,物联网报文普遍很小,可以把默认值往下压、把最大值留够,避免十万连接乘出巨额内存占用。net.core.somaxconn 与 net.ipv4.tcp_max_syn_backlog 决定全连接队列与半连接队列长度,断线重连风暴时这两个值偏小会让新连接被直接丢弃,客户端侧看到的是超时而不是被拒绝,排查起来很容易误判成网络问题。需要大量出站连接(比如 bridge 到上游节点)时,net.ipv4.ip_local_port_range 也要一并放宽。
还有一个容易漏掉的点:epoll 本身也有成本。每条被监听的连接都要占用内核内存,加上 socket 结构体、定时器、路由缓存等,十万连接规模下内核态内存往往比 Mosquitto 的用户态内存更可观。规划容量时不能只算进程 RSS,要把 slab、TCP 缓冲区、页表一起算进去,否则就会出现"内存还剩很多但连接建不上"的怪现象。
QoS 0 是发完不管:PUBLISH 发出去,没有确认报文,服务端不保留任何状态,最快也最省资源。QoS 1 是至少一次:发送方发出 PUBLISH 后把它放进 inflight 队列并等待 PUBACK,超时未收到就重发(重发时 DUP 标志置位),所以接收方可能收到重复消息,业务层需要做幂等处理。QoS 2 是恰好一次:PUBLISH、PUBREC、PUBREL、PUBCOMP 四次握手,双方都要维护状态,报文数是 QoS 0 的数倍、驻留时间也更长,换来的是不重复也不丢失。
代价差异可以直接换算成资源。同样的业务消息速率下,QoS 2 带来的 PPS 远高于 QoS 0,inflight 队列的驻留条目也更多,内存占用随之抬升。多数遥测上报场景用 QoS 1 就足够;控制指令下行(开锁、下发参数、固件触发)可以按业务容忍度选 QoS 1 加应用层去重,把 QoS 2 留给真正既不能重复也不能丢失的场景,比如计费相关的状态同步。
队列放在哪里,由几个配置项共同决定。queue_qos0_messages 决定 QoS 0 消息是否也给离线持久会话排队,默认不给 QoS 0 排队,只有 QoS 1/2 会排;一旦打开,离线客户端在重连瞬间会收到大量历史消息,内存与网络同时承压。max_queued_messages 限制每个客户端的排队消息条数,超出后按配置丢弃旧消息或拒收新消息,这是防止个别慢客户端拖垮整机的关键闸门,必须显式设置,不能留着默认不限制。max_inflight_messages 则限制同一时刻未确认的在途消息数量,它对单客户端的发送速率形成硬约束。persistence 打开后,Mosquitto 会把 inflight 消息与 retained 消息写进 mosquitto.db,重启后重新加载;但它不是每条消息同步落盘的 WAL 式设计,而是周期性刷写加变更触发,因此持久化提升的是重启后的可恢复性,而不是每条消息的零丢失保证。这个区别一定要跟业务方讲清楚,否则会出现"开了持久化却还是丢消息"的信任危机。
停电、机房网络抖动、broker 版本升级重启,都会造成同一件事:几万台甚至几十万台设备在同一个时间窗口里重新上线。这是 MQTT 部署中最危险的时刻,而且危险来自两个方向,很多人只防住了第一个。
第一个方向是连接风暴。所有设备几乎同时发起 TCP 握手与 CONNECT,瞬时 CPS 冲到平时的几十倍。此时最先被打满的往往不是带宽,而是四件东西:监听队列(somaxconn 与 tcp_max_syn_backlog)、CPU(开了 TLS 时握手算不过来)、Mosquitto 的单线程事件循环(CONNECT 解析、会话恢复、订阅树插入都在这里)、以及磁盘(连接日志同步写入)。
第二个方向是消息风暴,它比连接风暴更隐蔽。大量设备使用持久会话并累积了大量离线消息,重连成功后服务端会把积压一股脑推给客户端;如果同时用了 retained 消息,每个订阅者上线还要先收一份 retained。几万台设备乘以每台几百条积压,瞬时下行 PPS 可以轻松冲到百万级,网卡、CPU、客户端固件三者同时吃不消,于是设备收不完又断开,断开再重连,形成雪崩循环。雪崩一旦形成,单纯重启 broker 是没用的,重启只会带来新一轮风暴。
处置思路有三条。第一条是给客户端固件加随机退避重连:基础间隔加上随机抖动,失败后按指数退避拉长,把瞬时冲击摊平成几十秒甚至几分钟的斜坡,这是投入产出比最高的一条,服务端做再多优化也比不上客户端错峰。第二条是限制 max_queued_messages 并给排队消息设过期策略,让陈旧的遥测数据在服务端自然丢弃,只保留有实效性的控制类消息。第三条是在架构上做拆分,把订阅树按业务主题分到不同实例,避免所有设备的积压集中在同一份队列里;必要时在 broker 前置四层负载均衡做接入缓冲。
Mosquitto 的持久化把三样东西写进 mosquitto.db:订阅树(谁订阅了哪些主题)、retained 消息(每个主题保留下来的最新一条消息)、以及 inflight 中的 QoS 1/2 消息状态。它采用周期性刷写加关键变更触发刷写的策略,autosave_interval 控制周期,变更次数达到 autosave_on_changes 时也会触发一次写盘。
这种写入特征对存储介质的要求,和数据库完全不是一回事。它不是持续的大块顺序写,而是频繁的小块随机写加整个数据库的重写,写放大非常明显。对固态硬盘而言,频繁小 IO 叠加整文件重写会加速 NAND 磨损、占用写带宽,并在垃圾回收压力上来时出现写延迟抖动;抖动发生的那一刻正好卡在同一个事件循环里,表现为 broker 短暂无响应、心跳超时、设备批量掉线。所以持久化盘的选型要看三件事:4K 随机写 IOPS、写延迟的稳定性(而不是峰值带宽)、以及介质的 DWPD 耐久指标。消费级固态在"每秒几十次小刷写乘以常年运行"的负载下,寿命和延迟稳定性都远不如企业级固态。
掉电会丢多少,取决于刷写周期。假设 autosave_interval 设成 1800 秒,那么最坏情况下会丢失上一次刷写之后最多约半小时的 inflight 状态变更与 retained 变更;把周期调小可以缩小丢失窗口,代价是刷写更频繁、写放大更严重、抖动概率更高。这里不存在既零丢失又低成本的选项,只能按业务容忍度取平衡:遥测数据丢了可以重采,就把周期放宽、把写放大降下来;控制指令状态必须准确,就把周期压到分钟级并把持久化盘换成高耐久介质,同时接受写放大的代价,并配合 UPS 与文件系统的正确挂载参数(例如避免为了性能关掉屏障而放大掉电风险)。
MQTT 服务器对磁盘的需求可以分成互不相干的三块,混在一块盘上会互相拖累。第一块是操作系统与 Mosquitto 程序本身,写入量极小,但在读写被打满时会直接影响系统命令、监控 agent 和远程登录的响应速度,让你在故障当时连排查手段都失去。第二块是消息与持久化盘,承担 mosquitto.db 的周期性重写与 inflight 落盘,是小随机写加大块重写的混合负载。第三块是日志盘,特点是纯顺序写、量大、几乎不读。
日志量是最容易被低估的一项。Mosquitto 的日志级别从 error、warning、notice、information 一路到 debug,默认级别下每个连接只产生少量记录;但一旦为了排查问题把 log_type 开成 all 或把级别调到 debug,每次收发报文、每次心跳都会产生一行,几万连接规模下写入量可以达到每小时数 GB 甚至数十 GB。这个量级下先用完的往往不是容量,而是磁盘的写 IOPS 与文件系统的元数据锁,进而拖慢同一块盘上的 mosquitto.db 刷写,把排查动作本身变成故障放大器。
规划上的建议是:系统盘与消息盘物理分离,至少做到独立分区与独立文件系统;日志单独落到第三块盘或独立日志分区;日志必须配 logrotate 按大小或时间轮转并压缩归档,不能任其无限增长;生产环境默认级别保持在 notice 或 information,需要 debug 时只在单台机器、短时间、明确的时间窗口内开启,窗口结束立即回落;操作系统侧的 syslog 与 journald 也要一并限制上限,避免它把根分区写满导致整机异常。容量上给日志盘留的余量,应该按"最坏情况下几天的量"来算,而不是按平时的量。
把连接数、报文速率、带宽三件事分开看,MQTT 的网络画像就非常清晰。单条连接的带宽极小,一条两百字节的遥测报文、十万设备每 60 秒上报一次,换算下来也不过每秒几兆比特,百兆网卡都嫌浪费。但同样是这个场景,每秒报文数在几千量级;如果 keepalive 设成 30 秒,光心跳就要再加几千 PPS。连接建立阶段更是如此,断线重连风暴时瞬时新建连接速率可以冲到每秒数千,此时网卡带宽占用可能还不到一成,CPU 和监听队列却已经饱和。
选型和排查时要盯的指标因此是三个:每秒报文数(PPS,含心跳)、每秒新建连接数(CPS)、以及软中断在单核上的分布。小包高频场景下,网卡中断如果集中在少数队列上,会出现单核软中断打满而其他核空闲的假象,这时候要检查网卡多队列(RSS)是否开启、中断是否均衡绑定到多核、以及 irqbalance 是否在工作。这个现象常被误判为"CPU 不够",于是加核数,结果毫无改善。
TLS 是另一个分水岭。明文 MQTT 下 CPU 通常非常闲,瓶颈在内存和 fd;开启 TLS 之后,每个新连接都要做一次非对称握手,握手的 CPU 消耗是普通报文处理的几十倍乃至上百倍,而且无法通过多核分摊(单实例单线程)。典型表现是稳态运行时 CPU 依旧不高,一旦出现批量重连,CPU 立刻冲顶、新连接排队超时。缓解手段包括开启会话复用(session ticket 或 session cache)降低重连握手开销、选择 ECDSA 证书与合适的加密套件、把 TLS 终结放到前面的代理层或专门的接入网关、以及把设备分成多批次错峰上线。
要不要在前面放负载均衡,取决于规模与协议能力。四层负载均衡(TCP 模式)对 MQTT 是透明的,适合做连接分摊,但要注意它会改变源 IP,需要通过 Proxy Protocol 或内核 TOA 模块把真实 IP 带给后端,否则基于 IP 的限流与审计会失效。七层组件如果要做 MQTT 协议解析与主题级路由,需要确认它对 MQTT 的支持程度与长连接保持能力,不要想当然地按 HTTP 的经验去套,尤其是连接空闲超时时间的默认值,很多七层组件默认是几十秒,正好和 MQTT 的长连接心跳周期打架。
下面这张表按入门、标准、集群三档,横向对照 CPU、内存、硬盘、网络、连接规模、高可用与运维六个维度。表中数字为典型部署区间的经验值,用于选型时的量级判断,不是任何厂商的承诺值,实际以压测结果为准。
| 对比维度 | 入门档(单台 4 核 8–16G) | 标准档(单台 8–16 核 32–64G) | 集群档(多节点 + 桥接或换集群型 broker) |
|---|---|---|---|
| CPU | 明文场景长期很闲;单实例只吃一核,余量留给系统与其他服务;一旦上 TLS,重连期容易冲顶 | 靠多实例拆端口吃满多核,每实例绑定一核;TLS 终结建议前置到代理层,减轻 broker 握手压力 | CPU 压力被摊到多节点;握手与转发分离,接入层可独立扩容 |
| 内存 | 主要被每连接内核缓冲区与会话结构体吃掉;队列必须限流,否则先爆的是内存而不是 CPU | 按目标连接数反推容量,内核 TCP 内存与 slab 要留足;持久会话队列按条数硬限制 | 单节点内存压力下降,但订阅状态复制会引入新的内存开销 |
| 硬盘 | 持久化可选;若开启,系统盘与消息盘至少分区隔离,日志单独轮转 | 消息盘用企业级固态,看 4K 随机写与延迟稳定性;日志盘独立,按最坏量留余量 | 持久化退居次要,状态由集群层同步;本地盘更多承担日志与临时队列 |
| 网络 | 带宽几乎用不满;压力在 PPS 与 CPS,关注软中断是否集中单核 | 开启网卡多队列与中断均衡;前置四层负载均衡分摊连接,注意真实 IP 透传 | 接入层横向扩容,PPS 与 CPS 成为可线性扩展的量 |
| 连接规模 | 典型 1 万–5 万长连接,前提是 fd 与内核参数已按十万量级调配 | 典型 10 万–30 万长连接,靠多实例拆端口实现,单实例不宜超过单核承载能力 | 数十万以上,或要求任意节点可收任意主题时进入这一档 |
| 高可用与运维 | 单点,靠快照与快速重装恢复;可接受分钟级中断的业务适用 | 多实例互为隔离单元,单机故障影响部分设备;升级可逐实例滚动 | 节点故障自动摘除,订阅状态与会话由集群层接管,运维复杂度最高 |
入门档的 CPU 在明文 MQTT 下几乎没有存在感,报文解析与转发消耗的指令极少,四核机器跑几万连接时监控图上 CPU 曲线常常是个位数。这让很多人误以为可以再加几倍连接,直到某次批量重连把 CPU 打满。标准档的核心动作是多实例拆端口,把单线程限制绕开,每个实例绑定一个核,四实例就是四核并行,这时 CPU 才真正成为可扩展资源。集群档则把接入与转发进一步分离,握手可以在接入层横向扩容,broker 层只做转发。
内存是三档里最容易被低估的维度。入门档 8G 内存跑五万连接,看起来每连接只有一百多 KB 的预算,一旦持久会话队列放开,几十条消息就能把预算吃光。标准档 32–64G 给了缓冲,但仍要按"连接数乘每连接开销加队列开销"的公式反推,并且把内核 TCP 内存与 slab 计入总账。集群档单个节点的内存压力下降,但订阅状态的复制与同步会引入新的内存开销,这部分常常被人忽略。
入门档可以不开持久化,把磁盘压力降到最低;要开就必须分区隔离。标准档必须认真选盘,持久化盘看重 4K 随机写能力与写延迟稳定性,而不是宣传的峰值带宽;日志盘独立并按最坏情况留容量。集群档本地盘的重要性下降,磁盘更多是日志与临时队列的载体,状态一致性交给集群层去保证。
三档的带宽占用都不高,真正的差异在 PPS 与 CPS 的可扩展性。入门档是单点承受全部 PPS,一旦软中断集中在单核就会出现奇怪的瓶颈。标准档需要开启网卡多队列与中断均衡,并在必要时前置四层负载均衡分摊连接。集群档把 PPS 与 CPS 变成可线性扩展的量,但引入了真实 IP 透传与连接保持超时的新配置项。
入门档的典型区间是一万到五万长连接,前提是 fd 与内核参数已经按十万量级调配好——硬件能撑不代表默认系统能撑。标准档的典型区间是十万到三十万,靠多实例拆端口实现,单实例承载量不宜超过单核能处理的能力,否则延迟会明显劣化。集群档是数十万以上,或者业务要求"任意节点可收任意主题"时才进入的领域。
入门档是单点,靠快照与快速重装来缩短恢复时间,适合能接受分钟级中断的业务。标准档把一台机器切成多个隔离单元,单机故障只影响部分设备,版本升级可以逐实例滚动,运维灵活度明显提升。集群档能做到节点故障自动摘除、订阅状态与会话由集群层接管,代价是运维复杂度最高,需要专门的部署与监控能力。
现象是连接数爬到一万上下就拒绝新连接,日志里满是文件描述符耗尽的报错,而内存和 CPU 都很闲。原因是进程级 nofile 停留在默认值,或者虽然改了 limits.conf 但 systemd unit 里没有写 LimitNOFILE,服务启动时继承的是 systemd 的默认限制。处置是在 Mosquitto 的 systemd unit 里显式写 LimitNOFILE,同步调高 fs.file-max 与 fs.nr_open,重启服务后用 cat /proc/进程号/limits 复核生效值,确认无误再开始压测。
现象是业务报文很少但 CPU 不低,设备越多越明显,抓包看到大量 PINGREQ。原因是 keepalive 被设成 10 秒甚至更短,十万设备每秒就要产生上万次心跳请求与响应,全部要过同一条事件循环。处置是把遥测类设备的 keepalive 放宽到 60 秒以上甚至数分钟(需结合运营商 NAT 超时时间权衡),对实时性要求高的控制类设备再单独设短值,分层处理而不是一刀切。
现象是运行几天后内存持续上涨不回落,重启后回落,然后再涨。原因是大量设备使用持久会话且长期不上线,服务端为它们保留了不断累积的离线消息队列,这些队列既占内存又无法释放。处置是显式设置 max_queued_messages 的条数上限并给队列消息设过期时间,对纯遥测类设备直接让它们用 clean session,只对需要接收离线指令的设备保留持久会话。
现象是排查问题时打开 debug,几小时后磁盘写满,同时 broker 响应变慢、设备批量掉线。原因是 debug 级别下每次收发与每次心跳都会产生一条日志,几万连接的写入量达到每小时数 GB 到数十 GB,且日志盘与持久化盘共用一块盘,写 IOPS 被打满后拖慢 mosquitto.db 刷写。处置是日志单独分区或独立盘、配 logrotate 轮转压缩、debug 只在单台机器的明确时间窗口内开启,窗口结束立即回落到 notice 或 information。
现象是连接数勉强上去了,但延迟明显变大、心跳超时变多、偶发批量掉线,加内存加核数都收效甚微。原因是单实例单线程事件循环已经饱和,任何一次写盘抖动或握手峰值都会让循环停摆,堆硬件解决不了串行瓶颈。处置是按端口拆分多实例并让客户端按设备 ID 哈希分流,或者干脆上多节点,把压力摊到多个独立的事件循环上。
现象是个别固件异常的设备以极高频率发布消息,把整机 PPS 拉满,其他正常设备被饿死。原因是没有对单客户端的发布速率与在途消息数设限,一个客户端就能消耗掉全部处理能力。处置是设置 max_inflight_messages 与单客户端消息速率限制,在接入层做发布频率配额,并对异常设备做自动隔离与告警,把"坏邻居"挡在共享资源之外。
现象是设备批量报证书错误或握手失败,但证书本身没过期,换设备、换网络都无效。原因是服务器或设备侧的系统时钟偏移超出了证书的有效期窗口(尤其是短周期证书),校验证书有效性时直接失败。处置是broker 与接入层全部启用 NTP 并监控时钟偏移量,证书有效期留出足够余量,同时监控证书剩余天数,避免"没到过期时间却先用不了"的情况。
现象是机房断电或升级重启后,设备同时上线,broker 起不来或者起来后立刻被冲垮,反复重启反复崩溃。原因是从未在真实规模上验证过重连行为,客户端没有退避、服务端没有接入缓冲、队列没有上限,三者叠加形成正反馈。处置是在上线前专门做断线重连风暴演练:模拟全部设备同时掉线再同时上线,观察 CPS、CPU、内存、磁盘四项曲线,据此调整客户端退避策略与服务端队列上限,并把演练纳入每次大版本变更的例行项。
现象是运行数月后写延迟抖动加剧,偶尔出现短时间无响应,健康度指标下降。原因是消费级固态在频繁小 IO 加整文件重写的负载下磨损快、垃圾回收抖动大,而默认的刷写周期与业务容忍度不匹配。处置是把持久化盘换成企业级固态并关注 DWPD 指标,按业务容忍度重设 autosave_interval 与 autosave_on_changes,把持久化盘与日志盘、系统盘分开,并监控磁盘写延迟的尾部分布而不只是平均值。
现象是多节点通过 bridge 互联后,跨节点的消息时有时无,设备重连到另一个节点就收不到离线消息。原因是 Mosquitto 本身不具备原生集群能力,bridge 只是在节点之间按配置转发匹配主题的消息,它不共享订阅树、不共享会话状态、也不共享离线队列。处置是明确 bridge 的定位为"主题级转发通道",用它做跨机房或跨业务的消息搬运;如果需要任意节点都能服务任意客户端、会话与订阅在节点间漂移,应当换用具备原生集群能力的 broker,或者在接入层用一致的路由规则把客户端固定到节点。
分界点可以按三条线来划。第一条线是连接规模:几万连接以内、业务能接受分钟级中断、消息速率不高,Mosquitto 单机足够,把精力放在 fd 与内核参数、队列上限、日志轮转这三件基础功上,性价比最高。第二条线是单机多实例:当连接数进入十万量级、或者单核在重连期被打满时,先把一台机器切成多个实例、按端口拆分并让客户端哈希分流,这一步不需要改架构就能把多核用起来,但要注意实例之间不共享订阅状态,业务上要能接受"订阅关系被端口划分"这一约束。第三条线是换集群型 broker:当业务要求任意节点可服务任意客户端、会话与订阅需要在节点间漂移、或者连接规模已经超出单机多实例的运维舒适区时,Mosquitto 无论怎么拆都解决不了状态共享问题,此时必须换具备原生集群能力的 broker。
关于 bridge 需要把话说死:Mosquitto 本身不具备原生集群能力,bridge 只能做主题级转发,不能共享订阅状态、不能共享会话、不能共享离线队列。把三台 Mosquitto 用 bridge 连起来,得到的是"三个独立的 broker 加上几条转发规则",不是集群。这个认知差是很多团队踩坑的根源,务必在架构评审时讲清楚。
上生产前必须做完的三件事。第一件是用真实报文速率压测:不要只压连接数,要按真实的发布频率、真实的 QoS 分布、真实的 keepalive 去压,同时记录 PPS、CPS、内存增长曲线与写盘延迟,压到目标规模的 1.5 倍再留余量。第二件是断线重连风暴演练:模拟全部设备同时掉线再同时上线,验证客户端退避是否生效、服务端是否会被冲垮、雪崩是否能自愈。第三件是持久化恢复后的消息一致性核对:重启 broker 之后,核对订阅关系是否完整、retained 消息是否正确、inflight 状态是否符合预期,确认丢失窗口在业务可接受范围内。这三件事做完,才算具备上线条件。
在硬件形态上,一万网络(深耕 IDC 19 年,成立于 2007 年)承接这类物联网接入型需求时,通常按目标连接规模反向推配置:先定连接数与每连接内存预算,再定核数与实例数,末位才定磁盘形态(系统盘、消息盘、日志盘三分)与网卡队列能力,而不是先挑 CPU 型号。对需要自建消息中枢的团队,可提供 7×24 中文工单与平均 5 分钟响应、硬件故障 10 分钟自动迁移、免费系统盘快照、免费备案协助、5–20G 免费 DDoS 防护、自营机柜最快 1 分钟上架、BGP 多线加 CN2 GIA 回国线路等官网明示服务。具体机型与价格受 CPU、内存、存储、带宽、IP 与线路影响,需实时询价,以官网实时报价为准。
没有脱离参数配置的统一答案。硬件只决定上限的一半,另一半由 fd 限制、TCP 内存参数、每连接队列大小和消息速率决定。行业公开资料里常见的量级是:默认参数下几万连接就会遇到限制,做过完整内核调优且队列严格限流的单实例,可以进入十万量级。实际能撑多少,必须用真实报文模型压测出来,不能引用别人的数字。
因为 MQTT 的成本是常驻型而不是计算型。每条连接都要常驻一份内核 socket 缓冲区、一个文件描述符和一份会话结构体,这些只占内存不做计算;而 CPU 只在报文到达、心跳到达、连接建立时才工作。当设备上报频率很低时,CPU 自然闲着。内存先于 CPU 触顶,是这个场景的正常现象。
不能自动互通。两个实例是两套独立的订阅树,需要用 bridge 配置把主题在实例之间转发。要提前规划主题划分:尽量让互相通信的设备落在同一个实例,把必须跨实例的主题收敛成少数几条明确的 bridge 规则,否则 bridge 的转发量会成为新的瓶颈,而且排查起来非常困难。
三个信号出现任意一个就该考虑:业务要求任意节点都能服务任意客户端且会话可漂移;单集群连接规模已经超出单机多实例的运维舒适区(比如滚动升级窗口越来越难找);或者需要跨机房多活且要求状态一致。Mosquitto 靠 bridge 无法解决这三类问题,因为 bridge 不共享订阅与会话状态。
写入量取决于刷写周期与变更频率,不是固定值。风险不在于总字节数,而在于频繁小 IO 加整文件重写带来的写放大与延迟抖动。把持久化盘独立出来、选用企业级固态、按业务容忍度设置 autosave_interval、并监控写延迟的尾部分布,这四件事做完,写穿的概率就大幅下降。遥测类数据其实可以考虑不开持久化,用业务层重采来兜底。
多半不是。明文场景 CPU 很闲,开了 TLS 之后握手的非对称运算集中在一个线程上,批量重连时必然冲顶。先做三件事再谈加配置:开启会话复用降低重连握手开销、换用 ECDSA 证书与合适的加密套件、把 TLS 终结前置到代理层。做完之后如果稳态转发仍占满单核,再考虑拆实例或多节点。
在测试环境用压测客户端模拟目标规模的全部设备,先让它们全部建立连接,然后统一断线,再统一发起重连,观察四项曲线:每秒新建连接数、CPU 单核占用、内存增长、磁盘写延迟。演练要覆盖两种客户端策略(无退避与带随机退避)的对比,用数据证明退避的收益。演练应纳入每次大版本变更的例行项,而不是只在上线前做一次。
不要只报 CPU 型号。把需求拆成六项提给服务商:目标长连接数、每连接平均报文速率与峰值 CPS、是否启用 TLS 与握手峰值、持久化是否开启及容忍的丢失窗口、磁盘三分(系统盘、消息盘、日志盘)的容量与介质要求、以及网卡是否需要多队列与多 IP。服务商据此反推配置比你自己挑型号靠谱得多。价格受 CPU、内存、存储、带宽、IP 和线路影响,需实时询价,以签约时的最新报价为准。
本文涉及的 Mosquitto 配置行为、MQTT 协议流程与 Linux 内核参数说明,参考 Mosquitto 官方文档与 MQTT 协议规范等公开资料;硬件形态与容量区间为典型部署经验值,用于量级判断,不代表任何厂商的承诺。一万网络相关机型、线路与服务承诺以官网 https://www.idc10000.net/ 对应页面为准,具体以签约时最新报价与合同为准。
把全文收成一句话:Mosquitto 的容量瓶颈在连接数、内存和单线程事件循环,不在带宽和 CPU 主频。落地顺序应当是——先把 fd 与内核参数、队列上限、日志轮转这三件基础功做扎实;再按连接规模决定是否拆多实例、拆端口;当业务要求订阅与会话状态跨节点共享时,明确承认 Mosquitto 不具备原生集群能力,bridge 只能做转发,必须换集群型 broker。上生产前完成真实报文速率压测、断线重连风暴演练、持久化恢复后的消息一致性核对这三件事,比多买几核 CPU 更能决定系统能不能扛住。
一万网络深耕 IDC 19 年(成立于 2007 年),可针对 MQTT 消息中枢这类长连接场景提供按连接规模反推的服务器配置方案:核数与实例数规划、内存容量按每连接开销测算、系统盘与消息盘与日志盘的介质与容量划分、网卡多队列与多 IP 支持。官网明示服务包括 7×24 中文工单、平均 5 分钟响应、硬件故障 10 分钟自动迁移、免费系统盘快照、免费备案协助、5–20G 免费 DDoS 防护、自营机柜最快 1 分钟上架、BGP 多线加 CN2 GIA 回国线路,以及工程师 1 对 1 部署支持。机型与线路不同,价格主要受 CPU、内存、存储、带宽、IP 和线路影响,需实时询价,以官网 https://www.idc10000.net/ 的实时报价与签约合同为准。
上一篇:2026 服务器租用 MySQL 备份体系落地全解:XtraBackup、mydumper 与恢复演练六维对比 + 避坑避雷手册
下一篇:2026 服务器租用数据集成 SeaTunnel 落地全解:并发度、脏数据落盘与出网带宽六维对比 + 避坑避雷手册
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品