ELI5 · 说人话

12 万 TPS 的
物联网链路

100 万台设备 每 8.3 秒 说一句话 12 万 条 / 秒 = 12 万 TPS 一天 103.7 亿条 · 约 24 MB/s · 192 Mbps 入向带宽

100 万台设备,每台每 8 秒说一句话,加起来就是每秒 12 万条。
这个数不吓人。吓人的是它们同时掉线又同时回来的那一刻。

EMQX + Go · 一条能压出来的链路

全景

这条链路长什么样

从设备到数据库,一条消息要过八道关。

100 万台 IoT 设备 MQTT over TLS · 长连接 L4 负载均衡 SLB · NLB · HAProxy EMQX-01 EMQX-02 EMQX-0N 约 25 万连接 约 25 万连接 约 25 万连接 MQTT 共享订阅($share) Go Worker Go Worker Go Worker Kafka / Pulsar 削峰 · 持久化 · 解耦 Go Consumer Go Consumer Go Consumer Redis ClickHouse PostgreSQL 设备状态 时序 / 日志 业务数据

逐层拆

每层卡的不是同一个数

「12 万 TPS」是一个总数。落到每一层,它变成七个完全不同的问题。

1

L4 负载均衡

盯 CPS,不是吞吐

稳态它其实很闲——只转发 TCP,不解析 MQTT。真正会打死它的是「新建连接速率」:100 万设备一起重连时,每秒几万次握手,连接跟踪表和端口范围先撑爆。

2

EMQX 集群

盯连接数与内存,不是消息数

12 万 msg/s 摊到 4 台,每台才 3 万——消息根本不是瓶颈。瓶颈是那 100 万条长连接本身:每条几 KB 到几十 KB 常驻内存,再乘 100 万。按每台 25 万连接规划,4 台起步。

3

MQTT 共享订阅

盯分配是否均匀

普通订阅是「每个订阅者都收到一份」,共享订阅($share)是「一群订阅者轮流分」——天然的负载均衡,加 worker 就能扩。代价:跨设备的全局顺序没了,只能保单设备有序。

4

Go Worker

盯在途并发 = TPS × RT

单条处理 1ms → 在途 120 个,一台 8 核机器都富余。可一旦同步等 Kafka 的 ack,RT 变 5ms,在途立刻 600 个——goroutine 不心疼,但 producer 缓冲和连接池先炸。必须异步批量投递。

5

Kafka / Pulsar

盯分区数与 Lag

12 万/s 单分区扛不住,按 device_id 分 24~48 个分区。Lag(堆积)是这条链路最灵敏的告警——它一路涨,说明消费端追不上,比 CPU 曲线早好几分钟报警。

6

Go Consumer

盯消费速率 vs 生产速率

有 Kafka 兜着,它允许短时间慢,不允许长期慢。判断标准只有一条:峰值过去之后,Lag 能不能在可接受的时间里收敛回 0。

7

存储三兄弟

盯批量大小

ClickHouse 逐行 INSERT 直接写死,必须攒批:1 万行一批、每秒 10 批。Redis 盯 QPS 和大 key(设备状态别塞成一个巨型 hash),PostgreSQL 只接业务数据,别让它碰时序流。

这些「盯哪个数」的说法出自 QPS、TPS、RT、并发数 那一页,第 4 层用的就是那页的利特尔法则。

先定这个

QoS 一变,数字全变

同一套硬件,QoS 0 和 QoS 1 之间能差三到五倍。

QoS 0 设备 Broker PUBLISH 最快 · 可能丢 1 次交互 QoS 1 设备 Broker PUBLISH PUBACK 要 ACK · 可能重复 2 次交互 QoS 2 设备 Broker PUBLISH PUBREC PUBREL PUBCOMP 四次握手 · 最慢 4 次交互

没写明 QoS 的压测目标是废的。「我们能扛 12 万 TPS」——QoS 0 还是 QoS 1?差一档,机器数就差一倍。

遥测这类高频数据(温度、电量、心跳)走 QoS 0,丢一条无所谓,下一条 8 秒后就来。指令下发、告警上报走 QoS 1,但消费端必须做幂等——QoS 1 保证的是「至少一次」,意味着重复是常态,不是异常。

QoS 2 在这个量级基本没人用。四次握手换来的「恰好一次」,用「QoS 1 + 业务侧幂等」实现更便宜。

中间那一层

Kafka 在这儿干嘛

它不加速。它只负责「接得住」。

进:早高峰能冲到 30 万/s Kafka 先全接住,再按下游的节奏慢慢放 堆积 Lag 出:稳定 12 万/s

没有 Kafka:早高峰 30 万/s 直接砸到 ClickHouse,写崩,然后 Go Worker 开始重试,重试又制造新流量——雪崩。

有 Kafka:先全接住,堆积几百万条无所谓(它本质就是块磁盘),下游按自己 12 万/s 的节奏慢慢消化。峰值过去,Lag 自己降回 0。

代价是延迟。端到端从毫秒变成秒级。所以实时告警那条路不能跟着走 Kafka——Go Worker 判断出「温度超限」时应当直接推告警服务,另开一条快路,别和批量落库挤同一根管子。

怎么验

该压的不是 12 万

「12 万 TPS 能不能到」是最容易通过、也最没用的一个目标。

「稳态:12 万 msg/sQoS 1,payload 200B,
  端到端 P99 < 2s(设备发出 → ClickHouse 可查),
  错误率 < 0.01%,连续跑 4 小时不衰减,
  预热 5 分钟后再开始取数。

重连风暴:100 万连接在 60 秒内全部重建
  LB 与 EMQX 不出现拒连,恢复时间 < 3 分钟。

堆积恢复:人为堆到 Lag 1000 万条
  停止注入后,Lag 必须在 15 分钟内收敛回 0。」

三条目标各测一件事:稳态测的是容量重连风暴测的是最脆的那一刻堆积恢复测的是「出事之后能不能自己爬起来」。只做第一条的压测报告,在真实故障面前基本没有参考价值。

别踩

四个会真炸的地方

重连风暴:100 万设备同时回来

机房网络抖一下,全体掉线,然后在同一秒一起重连——CPS 尖峰能到稳态的几十倍。对策:客户端必须随机退避重连(比如 5~60 秒内随机),别用固定 3 秒。这一条不做,前面所有容量规划都白算。

Go Worker 同步等 Kafka ack

RT 从 1ms 变 5ms,在途并发从 120 变 600,producer 缓冲和 goroutine 一起涨。对策:异步批量投递 + 有界 channel,channel 满了就降级丢弃或落本地盘,绝不能用无界缓冲——那只是把 OOM 推迟几分钟。

ClickHouse 一行一行地写

它是列存,逐行 INSERT 会生成海量小 part,后台 merge 追不上,最后 Too many parts 直接拒写。对策:攒批,1 万行一批、每秒 10 批;或者干脆用 Kafka 表引擎让它自己批量拉。

以为共享订阅还能保证顺序

$share 把消息轮流分给不同 worker,跨设备的先后顺序当场就没了。对策:device_id 做 Kafka 分区键,只对外承诺「单设备有序」。需要全局顺序的业务,这条链路本身就不适合。

稳态的 12 万不难,
难的是掉线重连的那 60 秒