ELI5 · 说人话
100 万台设备,每台每 8 秒说一句话,加起来就是每秒 12 万条。
这个数不吓人。吓人的是它们同时掉线又同时回来的那一刻。
全景
从设备到数据库,一条消息要过八道关。
逐层拆
「12 万 TPS」是一个总数。落到每一层,它变成七个完全不同的问题。
L4 负载均衡
盯 CPS,不是吞吐
稳态它其实很闲——只转发 TCP,不解析 MQTT。真正会打死它的是「新建连接速率」:100 万设备一起重连时,每秒几万次握手,连接跟踪表和端口范围先撑爆。
EMQX 集群
盯连接数与内存,不是消息数
12 万 msg/s 摊到 4 台,每台才 3 万——消息根本不是瓶颈。瓶颈是那 100 万条长连接本身:每条几 KB 到几十 KB 常驻内存,再乘 100 万。按每台 25 万连接规划,4 台起步。
MQTT 共享订阅
盯分配是否均匀
普通订阅是「每个订阅者都收到一份」,共享订阅($share)是「一群订阅者轮流分」——天然的负载均衡,加 worker 就能扩。代价:跨设备的全局顺序没了,只能保单设备有序。
Go Worker
盯在途并发 = TPS × RT
单条处理 1ms → 在途 120 个,一台 8 核机器都富余。可一旦同步等 Kafka 的 ack,RT 变 5ms,在途立刻 600 个——goroutine 不心疼,但 producer 缓冲和连接池先炸。必须异步批量投递。
Kafka / Pulsar
盯分区数与 Lag
12 万/s 单分区扛不住,按 device_id 分 24~48 个分区。Lag(堆积)是这条链路最灵敏的告警——它一路涨,说明消费端追不上,比 CPU 曲线早好几分钟报警。
Go Consumer
盯消费速率 vs 生产速率
有 Kafka 兜着,它允许短时间慢,不允许长期慢。判断标准只有一条:峰值过去之后,Lag 能不能在可接受的时间里收敛回 0。
存储三兄弟
盯批量大小
ClickHouse 逐行 INSERT 直接写死,必须攒批:1 万行一批、每秒 10 批。Redis 盯 QPS 和大 key(设备状态别塞成一个巨型 hash),PostgreSQL 只接业务数据,别让它碰时序流。
这些「盯哪个数」的说法出自 QPS、TPS、RT、并发数 那一页,第 4 层用的就是那页的利特尔法则。
先定这个
同一套硬件,QoS 0 和 QoS 1 之间能差三到五倍。
没写明 QoS 的压测目标是废的。「我们能扛 12 万 TPS」——QoS 0 还是 QoS 1?差一档,机器数就差一倍。
遥测这类高频数据(温度、电量、心跳)走 QoS 0,丢一条无所谓,下一条 8 秒后就来。指令下发、告警上报走 QoS 1,但消费端必须做幂等——QoS 1 保证的是「至少一次」,意味着重复是常态,不是异常。
QoS 2 在这个量级基本没人用。四次握手换来的「恰好一次」,用「QoS 1 + 业务侧幂等」实现更便宜。
中间那一层
它不加速。它只负责「接得住」。
没有 Kafka:早高峰 30 万/s 直接砸到 ClickHouse,写崩,然后 Go Worker 开始重试,重试又制造新流量——雪崩。
有 Kafka:先全接住,堆积几百万条无所谓(它本质就是块磁盘),下游按自己 12 万/s 的节奏慢慢消化。峰值过去,Lag 自己降回 0。
代价是延迟。端到端从毫秒变成秒级。所以实时告警那条路不能跟着走 Kafka——Go Worker 判断出「温度超限」时应当直接推告警服务,另开一条快路,别和批量落库挤同一根管子。
怎么验
「12 万 TPS 能不能到」是最容易通过、也最没用的一个目标。
「稳态:12 万 msg/s,QoS 1,payload 200B,
端到端 P99 < 2s(设备发出 → ClickHouse 可查),
错误率 < 0.01%,连续跑 4 小时不衰减,
预热 5 分钟后再开始取数。
重连风暴:100 万连接在 60 秒内全部重建,
LB 与 EMQX 不出现拒连,恢复时间 < 3 分钟。
堆积恢复:人为堆到 Lag 1000 万条,
停止注入后,Lag 必须在 15 分钟内收敛回 0。」
三条目标各测一件事:稳态测的是容量,重连风暴测的是最脆的那一刻,堆积恢复测的是「出事之后能不能自己爬起来」。只做第一条的压测报告,在真实故障面前基本没有参考价值。
别踩
机房网络抖一下,全体掉线,然后在同一秒一起重连——CPS 尖峰能到稳态的几十倍。对策:客户端必须随机退避重连(比如 5~60 秒内随机),别用固定 3 秒。这一条不做,前面所有容量规划都白算。
RT 从 1ms 变 5ms,在途并发从 120 变 600,producer 缓冲和 goroutine 一起涨。对策:异步批量投递 + 有界 channel,channel 满了就降级丢弃或落本地盘,绝不能用无界缓冲——那只是把 OOM 推迟几分钟。
它是列存,逐行 INSERT 会生成海量小 part,后台 merge 追不上,最后 Too many parts 直接拒写。对策:攒批,1 万行一批、每秒 10 批;或者干脆用 Kafka 表引擎让它自己批量拉。
$share 把消息轮流分给不同 worker,跨设备的先后顺序当场就没了。对策:按 device_id 做 Kafka 分区键,只对外承诺「单设备有序」。需要全局顺序的业务,这条链路本身就不适合。
稳态的 12 万不难,
难的是掉线重连的那 60 秒。