Broker-Tests

MQTT-Verbindungs-Stresstest: Wie viele Clients schafft ein Broker?

MQTT-Verbindungs-Stresstest: Zehntausende Clients öffnen, ulimit und ephemere Ports anpassen, mehrere Quell-IPs nutzen und Reconnect-Stürme simulieren.

Aktualisiert · 9 Min. Lesezeit

MQTT-Verbindungs-Stresstest: Wie viele Clients schafft ein Broker?

Ein MQTT-Verbindungs-Stresstest beantwortet eine einfache Frage: Wie viele Clients kann dieser Broker gleichzeitig halten, und wie schnell nimmt er sie an? Die Methode: Du erhöhst untätige Verbindungen mit kontrollierter Rate und beobachtest dabei Speicher, CPU und Verbindungslatenz des Brokers — und stellst sicher, dass das Limit, auf das du stößt, zum Broker gehört und nicht zur Maschine, die die Last erzeugt.

Die meisten ersten Versuche bleiben bei einer verdächtig runden Zahl stehen. Dieser Guide zeigt, wie du diese clientseitigen Limits beseitigst, den Test mit emqtt-bench oder einem Node.js-Skript ausführst und den Reconnect-Sturm simulierst, der auf jeden Broker-Neustart folgt.

Was du eigentlich misst

MetrikWarum sie wichtig ist
Maximale gleichzeitige VerbindungenFlottengröße, die der Broker im Leerlauf halten kann
Verbindungsrate (CONNECT/s)Wie schnell eine Flotte nach einem Ausfall zurückkommt
Verbindungslatenz (p50/p99)Zeit vom TCP-SYN bis zum CONNACK unter Last
Broker-Speicher pro VerbindungKapazitätsplanung; messen, nicht raten
FehlerartenAbgelehnt, Timeout, CONNACK-Reason-Codes

Verbindungen kosten den Broker Speicher für Socket-Puffer, Session-State und Subscriptions sowie CPU für TLS-Handshakes und Keep-Alive-Verarbeitung. All das hängt von Broker, Version und Konfiguration ab, daher zählt nur die Zahl, die du mit deinen Einstellungen misst: gleiches TLS, gleicher Keep-Alive, gleiche Authentifizierung und ungefähr gleich viele Subscriptions pro Client.

Zuerst den Lastgenerator reparieren

Eine einzelne Linux-Maschine hat drei Limits, die ausgehende Verbindungen lange vor dem Ende eines ordentlichen Brokers deckeln.

File-Deskriptoren

Jeder TCP-Socket ist ein File-Deskriptor. Das Standard-Soft-Limit liegt oft bei 1.024:

Limit für offene Dateien prüfen und erhöhen
ulimit -n                 # aktuelles Soft-Limit
ulimit -n 200000          # für diese Shell erhöhen (bis zum Hard-Limit)
cat /proc/sys/fs/file-max # systemweite Obergrenze

Für eine dauerhafte Änderung setzt du nofile in /etc/security/limits.conf oder übergibst in Docker --ulimit nofile=200000:200000.

Ephemere Ports

Jede Verbindung von einer Quell-IP zu derselben Broker-IP und demselben Port braucht einen eigenen lokalen Port. Der Linux-Standardbereich ist 32768–60999, also rund 28.000 Ports — die klassische Stelle, an der Tests stagnieren.

Lokalen Portbereich erweitern
sysctl net.ipv4.ip_local_port_range
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65000"

Damit kommst du auf etwa 64.000 Verbindungen pro Quell-IP. Darüber hinaus brauchst du weitere Quell-IPs (oder mehr Broker-Ports/-IPs oder mehr Lastmaschinen).

Connection Tracking

Läuft auf dem Lastgenerator eine Firewall mit Connection Tracking (üblich mit Docker und iptables), kann die Conntrack-Tabelle volllaufen. Im Kernel-Log steht dann nf_conntrack: table full, dropping packet.

Conntrack-Limits prüfen und erhöhen
sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
sudo sysctl -w net.netfilter.nf_conntrack_max=262144

Der Test mit emqtt-bench

emqtt-bench ist genau dafür gebaut. Der Befehl conn öffnet Verbindungen mit fester Rate und hält sie offen:

10.000 Verbindungen mit 500 pro Sekunde
docker run --rm -it --ulimit nofile=200000:200000 emqx/emqtt-bench conn \
  -h "$MQTT_HOST" -p 1883 \
  -c 10000 -R 500 -k 60 \
  -u "$MQTT_USER" -P "$MQTT_PASS"

Setze MQTT_HOST auf deinen eigenen Broker; der geteilte Endpunkt broker.connectmq.com ist nur für kleine Smoke-Tests gedacht. -c ist die Anzahl der Clients, -R die Verbindungsrate pro Sekunde und -k der Keep-Alive in Sekunden. Nutze denselben Keep-Alive wie deine Geräte: Kürzere Keep-Alives bedeuten mehr PINGREQ-Traffic pro untätigem Client.

Um über eine Quell-IP hinauszukommen, fügst du der Lastmaschine sekundäre Adressen hinzu und lässt emqtt-bench die Clients mit --ifaddr darauf verteilen. Nutze Host-Networking, damit der Container diese Adressen sieht:

50.000 Verbindungen von zwei Quell-IPs
docker run --rm -it --network host --ulimit nofile=200000:200000 \
  emqx/emqtt-bench conn \
  -h "$MQTT_HOST" -p 1883 \
  -c 50000 -R 1000 -k 60 \
  --ifaddr 10.0.0.11,10.0.0.12

Erhöhe in Stufen — zum Beispiel 10k, 25k, 50k — und halte jede Stufe einige Minuten. Ein Broker, der 50.000 Verbindungen annimmt, sie aber im nächsten Keep-Alive-Zyklus verliert, hat den Test nicht bestanden.

Ein Node.js-Skript zum Anpassen

Wenn du echte Authentifizierungsabläufe, Subscriptions pro Client oder eigene Client-IDs brauchst, ist ein kleines MQTT.js-5-Skript leichter anzupassen als ein Benchmark-Tool:

conn-stress.mjs
import mqtt from "mqtt";

const URL = process.env.MQTT_URL ?? "mqtt://broker.connectmq.com:1883";
const TOTAL = Number(process.argv[2] ?? 100); // Standard: ein kleiner Smoke-Test
const RATE = Number(process.argv[3] ?? 200); // neue Verbindungen pro Sekunde
let opened = 0, up = 0, errors = 0, closes = 0;

function open(i) {
  const c = mqtt.connect(URL, {
    clientId: `stress-${process.pid}-${i}`,
    username: process.env.MQTT_USER,
    password: process.env.MQTT_PASS,
    keepalive: 60,
    connectTimeout: 15_000,
    reconnectPeriod: 2000 + Math.floor(Math.random() * 8000), // Jitter pro Client
  });
  let online = false;
  c.on("connect", () => {
    if (!online) { online = true; up++; }
    c.subscribe(`testmqtt/stress/${i}/cmd`, { qos: 1 });
  });
  c.on("close", () => { if (online) { online = false; up--; } closes++; });
  c.on("error", () => errors++);
}

const timer = setInterval(() => {
  for (let k = 0; k < RATE / 10 && opened < TOTAL; k++) open(opened++);
  if (opened >= TOTAL) clearInterval(timer);
}, 100);

setInterval(() => {
  console.log(`${new Date().toISOString()} opened=${opened} connected=${up} closes=${closes} errors=${errors}`);
}, 1000);
Mit erhöhtem Datei-Limit ausführen
npm install mqtt@5
ulimit -n 100000
MQTT_URL="mqtts://$MQTT_HOST:8883" node conn-stress.mjs 20000 300

Ein Node.js-Prozess hält problemlos einige Tausend untätige Verbindungen; für mehr startest du mehrere Prozesse oder Maschinen. Behalte die CPU des Lastgenerators im Blick — läuft sie am Anschlag, messen deine Zahlen den Client, nicht den Broker. Um die Antwort des Brokers auf abgelehnte Verbindungen zu deuten, schlage den CONNACK-Code in der Reason-Code-Referenz nach.

Die Broker-Seite beobachten

Clientseitige Zähler erzählen nur die halbe Geschichte. Zeichne bei jeder Stufe auf dem Broker-Host auf:

Broker-Host
ss -s                                            # Socket-Übersicht
ss -tn state established '( sport = :1883 )' | wc -l
ps -o rss=,pcpu= -p "$(pgrep -d, mosquitto)"      # Speicher (KB) und CPU des Broker-Prozesses

Mosquitto publisht außerdem $SYS/broker/clients/connected; abonniere es mit dem Online-Client oder mit mosquitto_sub -t '$SYS/broker/clients/connected' -v — siehe MQTT-$SYS-Topics. Denk daran, dass der Broker-Host denselben Spielraum bei File-Deskriptoren braucht: Für Mosquitto unter systemd setzt du LimitNOFILE= in einem Drop-in des Dienstes und prüfst max_connections in der mosquitto.conf (der Mosquitto-Config-Generator liefert eine Basis). Fehler, die nur in den Broker-Logs auftauchen, behandelt Too many connections.

Den Speicher pro Verbindung berechnest du leicht: (RSS bei N Verbindungen − RSS im Leerlauf) ÷ N. Miss ihn mit und ohne TLS und mit deiner realen Anzahl an Subscriptions, denn beides verändert das Ergebnis.

Einen Reconnect-Sturm simulieren

50.000 untätige Clients zu halten ist der leichte Teil. Gefährlich wird es, wenn sich alle gleichzeitig neu verbinden — nach einem Broker-Neustart, einem Load-Balancer-Failover oder einem kurzen regionalen Netzwerkaussetzer. Das testet Verbindungsrate, TLS-Handshake-Durchsatz und die Kapazität des Authentifizierungs-Backends gleichzeitig.

Zwei Wege, ihn auf deinem eigenen Broker auszulösen:

  1. Starte den Broker neu, während das Skript die Verbindungen hält, und beobachte, wie connected= wieder steigt.
  2. Unterbrich das Netzwerk zwischen Lastgenerator und Broker länger als den Keep-Alive (etwa mit einer iptables-Regel oder mit Toxiproxy) und stelle es dann wieder her.

Halte fest, wie lange es dauert, bis wieder alle Verbindungen stehen, und wie viele Versuche unterwegs scheitern. Mit einem festen Reconnect-Intervall versuchen alle Clients es im selben Moment, und der Broker sieht synchronisierte Wellen. Der reconnectPeriod mit Jitter im Skript oben verteilt sie; vergleiche beide Varianten und du siehst, warum Reconnect-Stürme ebenso ein Problem des Client-Designs wie der Broker-Kapazität sind.

Checkliste für den Verbindungs-Stresstest

  1. Erhöhe ulimit -n, erweitere ip_local_port_range und prüfe Conntrack auf jeder Lastmaschine.
  2. Plane Quell-IPs ein: etwa 64.000 Verbindungen pro Quell-IP und Broker-Endpunkt.
  3. Bilde die Produktion nach: TLS, Keep-Alive, Authentifizierung und Subscriptions pro Client.
  4. Erhöhe in Stufen und halte jede über mehrere Keep-Alive-Intervalle.
  5. Zeichne Verbindungen, Speicher, CPU und Verbindungslatenz des Brokers bei jeder Stufe auf.
  6. Löse einen Reconnect-Sturm aus und miss die Erholungszeit.
  7. Wiederhole den Test nach Broker-Upgrades oder Konfigurationsänderungen.

Ein Verbindungs-Stresstest ist vor allem eine Frage der Ehrlichkeit: Beseitige die Limits auf deiner Seite, bilde echtes Client-Verhalten nach und erhöhe die Last, bis der Broker — nicht der Lastgenerator — dir zeigt, wo die Decke liegt. Plane deine Flotte dann mit reichlich Abstand darunter und sorge dafür, dass sich deine Clients mit Jitter neu verbinden, damit diese Decke nie aus Versehen getestet wird.

Häufig gestellte Fragen

Warum bleibt mein MQTT-Verbindungstest bei etwa 28.000 Clients stehen?

Meist gehen dem Lastgenerator die ephemeren Ports aus: Linux nutzt standardmäßig rund 28.000 lokale Ports für Verbindungen von einer Quell-IP zu derselben Broker-IP und demselben Port. Erweitere net.ipv4.ip_local_port_range oder nutze weitere Quell-IPs.

Wie viele Verbindungen schafft ein MQTT-Broker?

Das hängt von Broker, Speicher, CPU, TLS, Keep-Alive und Nachrichtenaufkommen ab, eine universelle Zahl gibt es nicht. Miss es: Erhöhe die Verbindungen schrittweise, zeichne Speicher und CPU des Brokers auf und finde den Punkt, an dem Verbindungslatenz oder Fehler steigen.

Darf ich einen öffentlichen MQTT-Broker einem Stresstest unterziehen?

Nein. Tausende Verbindungen zu einem geteilten öffentlichen Broker verschlechtern ihn für alle und führen zu Rate-Limits oder Sperren. Führe Verbindungs-Stresstests nur gegen eigene Broker oder dafür bereitgestellte Instanzen aus.