Broker-Tests

MQTT-Lasttests mit Gatling: Setup, Szenario und Reports

MQTT-Broker mit Gatling unter Last testen: MQTT-Plugin einbinden, Java-Simulation mit Connect, Subscribe und Publish-Checks schreiben, HTML-Report auswerten.

Aktualisiert · 9 Min. Lesezeit

MQTT-Lasttests mit Gatling: Setup, Szenario und Reports

Gatling kann einen MQTT-Broker mit seinem MQTT-Plugin unter Last testen: Du beschreibst das Verhalten eines Geräts als Szenario in Java, Kotlin oder Scala — Connect, Subscribe, Publish, auf die passende Nachricht warten —, und Gatling injiziert virtuelle User und erzeugt seinen HTML-Report mit Antwortzeit-Perzentilen und Durchsatz. Das passt zu Teams, die Gatling schon für HTTP nutzen und denselben Simulation-as-Code-Workflow für MQTT wollen.

Kläre vorher die Lizenz: Die MQTT-Komponente steht unter der Gatling Enterprise Component License. Lokal mit der Community Edition ist sie auf 5 virtuelle User und 5-minütige Tests begrenzt — genug, um eine Simulation zu entwickeln und zu validieren. Für echten Maßstab brauchst du Gatling Enterprise. Passt das nicht, sind k6 mit xk6-mqtt, JMeter oder emqtt-bench Open-Source-Alternativen.

Projekt-Setup

Am einfachsten startest du mit einem der Maven- oder Gradle-Demoprojekte von Gatling. Füge dann das MQTT-Plugin neben den Standard-Gatling-Abhängigkeiten hinzu:

pom.xml (Auszug)
<dependency>
  <groupId>io.gatling</groupId>
  <artifactId>gatling-mqtt-java</artifactId>
  <version>3.16.0</version>
  <scope>test</scope>
</dependency>

Verwende dieselbe Version wie für deine übrigen Gatling-Artefakte. Scala-Projekte nutzen gatling-mqtt statt gatling-mqtt-java; in Gradle lautet die Deklaration gatlingImplementation("io.gatling:gatling-mqtt-java:3.16.0").

Die MQTT-DSL wird nicht standardmäßig importiert, jede Simulation braucht also:

Imports
import io.gatling.javaapi.mqtt.*;
import static io.gatling.javaapi.mqtt.MqttDsl.*;

Bevor du die Simulation schreibst, prüfe Broker, Port und Zugangsdaten mit einem einzelnen Client im Online-MQTT-Client. Dann weißt du, dass spätere Fehler von der Last kommen und nicht von der Konfiguration. Nutze den geteilten Endpoint broker.connectmq.com nur, um das Szenario zu validieren; für echte Last setzt du .broker(...) und die Zugangsdaten auf deine eigene private Broker-Instanz.

Das Protokoll: Broker, Zugangsdaten, QoS

Der Protocol Builder enthält die Einstellungen, die alle virtuellen User teilen:

Protokoll-Konfiguration
MqttProtocolBuilder mqttProtocol = mqtt
    .mqttVersion_5()                         // oder mqttVersion_3_1_1() (Standard)
    .broker("broker.connectmq.com", 1883)    // TLS: Port 8883 + .useTls(true)
    .credentials(System.getenv("MQTT_USER"), System.getenv("MQTT_PASS"))
    .keepAlive(30)
    .qosAtLeastOnce()                        // Standard ist QoS 0
    .cleanSession(true)
    .reconnectAttemptsMax(3)
    .correlateBy(jsonPath("$.id"));

Zwei Einstellungen verdienen Aufmerksamkeit:

  • Client-ID. Setzt du kein clientId(...), erzeugt Gatling pro virtuellem User eine zufällige — der sichere Standard für Lasttests. Kommt sie aus einem Feeder, müssen die Werte eindeutig sein, sonst übernehmen sich die User gegenseitig die Sessions.
  • correlateBy. Sagt Gatling, wie eine eingehende Nachricht der wartenden Anfrage zugeordnet wird. Hier trägt jeder Payload ein id-Feld, sodass die empfangene Nachricht dem gesendeten Publish zugeordnet werden kann.

Ein Szenario für Gerätetelemetrie

Das folgende Szenario modelliert ein Gerät: Es verbindet sich, subscribt auf sein eigenes Topic und publisht dann eine Minute lang jede Sekunde einen Messwert, wobei es jeweils wartet, bis er über den Broker zurückkommt.

src/test/java/mqtt/MqttTelemetrySimulation.java
package mqtt;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.mqtt.MqttDsl.*;

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.mqtt.*;
import java.time.Duration;
import java.util.Iterator;
import java.util.Map;
import java.util.stream.Stream;

public class MqttTelemetrySimulation extends Simulation {

  MqttProtocolBuilder mqttProtocol = mqtt
      .mqttVersion_5()
      .broker("broker.connectmq.com", 1883)
      .credentials(System.getenv("MQTT_USER"), System.getenv("MQTT_PASS"))
      .keepAlive(30)
      .qosAtLeastOnce()
      .correlateBy(jsonPath("$.id"));

  Iterator<Map<String, Object>> devices =
      Stream.iterate(1, i -> i + 1)
          .map(i -> Map.<String, Object>of("deviceId", "dev-" + i))
          .iterator();

  ScenarioBuilder telemetry = scenario("Device telemetry")
      .feed(devices)
      .exec(mqtt("Connect").connect())
      .exec(mqtt("Subscribe").subscribe("testmqtt/gatling/#{deviceId}/data"))
      .repeat(60, "n").on(
          exec(
              mqtt("Publish")
                  .publish("testmqtt/gatling/#{deviceId}/data")
                  .message(StringBody("{\"id\":\"#{deviceId}-#{n}\",\"temp\":21.5}"))
                  .await(Duration.ofSeconds(2))
                  .check(jsonPath("$.temp").exists())
          ).pause(Duration.ofSeconds(1))
      );

  {
    int users = Integer.getInteger("users", 5);
    setUp(
        telemetry.injectOpen(rampUsers(users).during(Duration.ofSeconds(30)))
    ).protocols(mqttProtocol)
     .assertions(
         global().failedRequests().percent().lt(1.0),
         details("Publish").responseTime().percentile3().lt(200)
     );
  }
}

Was die einzelnen Teile tun:

  • Feeder. Gibt jedem virtuellen User eine eindeutige deviceId, die im Topic und in der Nachrichten-ID verwendet wird.
  • repeat(60, "n"). Läuft 60-mal und stellt den Zähler als #{n} bereit, sodass jede Nachrichten-ID eindeutig ist — Voraussetzung für die Korrelation.
  • .await(Duration.ofSeconds(2)). Ein blockierender Check: Der User wartet, bis auf einem subscribten Topic eine Nachricht mit passender Korrelations-ID eintrifft, oder schlägt nach 2 Sekunden fehl. .expect(...) ist die nicht blockierende Variante; kombiniere sie mit mqtt.waitForMessages(), wenn du mehrere Nachrichten publishen willst, bevor du wartest.
  • .check(jsonPath("$.temp").exists()). Validiert den Inhalt der zugeordneten Nachricht, nicht nur ihr Eintreffen.
  • Assertions. Lassen den Lauf scheitern, wenn mehr als 1 % der Anfragen fehlschlagen oder das 95. Perzentil von Publish über 200 ms liegt (percentile3() ist standardmäßig das 95. Perzentil). Passe beides an deine eigene Baseline an.

Die Topics folgen testmqtt/gatling/<device>/data; fügst du später Wildcard-Subscriptions hinzu, prüfe die Muster im Topic Matcher.

Das Injektionsprofil wählen

In einer MQTT-Simulation ist jeder virtuelle User ein Gerät, das während des gesamten Szenarios eine Verbindung hält. Das Injektionsprofil bestimmt also, wie viele Verbindungen gleichzeitig bestehen und wie schnell sie hinzukommen:

ProfilDamit testest duBeispiel
atOnceUsers(n)Einen Reconnect-Sturm: Alle Geräte verbinden sich im selben MomentatOnceUsers(5)
rampUsers(n).during(d)Eine stabile Flotte, die online geht; der übliche StartpunktrampUsers(5).during(Duration.ofSeconds(30))
constantUsersPerSec(r).during(d)Kontinuierlich neu ankommende Geräte (Sessions, die beginnen und enden)constantUsersPerSec(1).during(Duration.ofMinutes(2))
rampUsersPerSec(a).to(b).during(d)Eine steigende Ankunftsrate, um den Punkt zu finden, an dem Verbindungen langsamer werdenrampUsersPerSec(1).to(5).during(Duration.ofMinutes(1))

Gleichzeitige Verbindungen ergeben sich ungefähr aus Ankunftsrate × Szenariodauer. Mit 60 Publishes im Sekundentakt pro User bleibt jeder User etwa eine Minute verbunden, also pendelt sich constantUsersPerSec(10) bei rund 600 gleichzeitigen Geräten ein. Behalte diese Rechnung bei der Dimensionierung eines Laufs im Kopf — und denk daran, dass die Gesamtzahl mit der Community Edition auf 5 User begrenzt bleibt.

Du kannst auch mehrere Szenarien in einem setUp kombinieren, etwa eine Rampe aus Telemetriegeräten plus eine kleine Gruppe Dashboard-User, die auf eine Wildcard subscriben. Jedes Szenario behält seine eigenen Request-Namen, sodass der Report beide Lasten getrennt zeigt.

Die Simulation starten

Mit Maven ausführen
export MQTT_USER=username MQTT_PASS=password
./mvnw gatling:test \
  -Dgatling.simulationClass=mqtt.MqttTelemetrySimulation \
  -Dusers=5

Mit der Community Edition hältst du users bei höchstens 5 und die Dauer unter 5 Minuten — der Lauf stoppt an diesen Grenzen. Das reicht, um zu beweisen, dass Szenario, Korrelation und Checks funktionieren. Lade dasselbe Projekt in Gatling Enterprise hoch, um es mit Tausenden Usern und mehreren Lastgeneratoren auszuführen.

Den HTML-Report lesen

Am Ende gibt Gatling den Pfad zu target/gatling/<simulation>-<timestamp>/index.html aus. Der Report gruppiert alles nach Request-Namen, die Namen in mqtt("…") sind also deine Überschriften:

RequestDie Antwortzeit bedeutetWorauf achten
ConnectBis der Broker die Verbindung akzeptiert (CONNACK), inklusive TLSFehler oder langsame Verbindungen während des Ramp-ups
SubscribeBis der Broker die Subscription bestätigt (SUBACK)Selten ein Engpass; Fehler deuten meist auf ACL-Probleme
Publish (mit await)Vom Publish bis zum Eintreffen der korrelierten NachrichtEnd-to-End-Latenzperzentile und Timeouts

Nützliche Diagramme:

  • Antwortzeit-Perzentile über die Zeit. Ein steigendes p95 oder p99 beim Hochfahren der User zeigt, wo Broker oder Netzwerk zu sättigen beginnen.
  • Requests pro Sekunde. Vergleiche sie mit der geplanten Rate (hier User × 1 Nachricht/s). Stagniert sie unter dem Ziel, drosselt etwas.
  • Fehlertabelle. Timeouts bei await heißen, dass Nachrichten nicht rechtzeitig zurückkamen — prüfe QoS, Queue-Limits und warum Nachrichten verloren gehen. Verbindungsfehler bei einer runden User-Zahl deuten oft auf ein Limit hin; entschlüssle die Reason Codes des Brokers mit dem MQTT-Reason-Code-Tool.

Eine umfassendere Methode, Broker-Limits von Lastgenerator-Limits zu trennen, findest du unter MQTT-Benchmark-Ergebnisse interpretieren.

Tipps für realistische Simulationen

  1. Modelliere Sessions, nicht Requests. Echte Geräte bleiben verbunden; halte die Verbindung über die Schleife offen, statt pro Nachricht neu zu verbinden.
  2. Nutze echte Payload-Größen. Ersetze das Mini-JSON durch Payloads in der Form deiner eigenen; prüfe ihre Größe auf der Leitung mit dem Packet-Size-Rechner.
  3. Trenne Fan-out von Eins-zu-eins. Ein Dashboard-Szenario, das auf testmqtt/gatling/+/data subscribt, erhält die Nachrichten aller Geräte; führe es als eigenes Szenario aus, damit seine Last sichtbar bleibt.
  4. Beobachte den Lastgenerator. Eine ausgelastete JVM bläht Antwortzeiten auf. Prüfe CPU und GC auf der Maschine, die Gatling ausführt.
  5. Versioniere die Simulation. Führe sie nach Broker-Upgrades und Konfigurationsänderungen erneut aus, um Regressionen zu erkennen.

Gatling-Traffic im Online-Client ansehen

Das MQTT-Plugin von Gatling liefert lesbare, versionierte Szenarien mit Korrelation und Checks, die den meisten MQTT-Tools fehlen. Baue und validiere die Simulation innerhalb der Grenzen der Community Edition, führe sie im großen Maßstab aus, wo deine Lizenz es erlaubt — und lies Publish-Zeiten immer mit await-Checks, denn das ist die Latenz, die deine Geräte spüren.

Häufig gestellte Fragen

Ist der MQTT-Support von Gatling kostenlos?

Das MQTT-Plugin (gatling-mqtt-java) liegt auf Maven Central unter der Gatling Enterprise Component License. Mit der Community Edition ist es auf 5 virtuelle User und 5-minütige Tests begrenzt; die Limits entfallen, wenn die Simulation auf Gatling Enterprise läuft.

Unterstützt Gatling MQTT 5?

Ja, der Protocol Builder bietet mqttVersion_3_1(), mqttVersion_3_1_1() (Standard) und mqttVersion_5(). Gatling weist darauf hin, dass eventuell noch nicht alle MQTT-5-Features verfügbar sind — prüfe die Doku, wenn du ein bestimmtes brauchst.

Wie misst Gatling die MQTT-Antwortzeit?

Connect und Subscribe werden bis zur Antwort des Brokers gemessen. Für Publish fügst du einen await- oder expect-Check hinzu: Dann erfasst Gatling die Zeit bis zum Eintreffen einer passenden Nachricht und liefert so die End-to-End-Latenz im Report.