Node.js Entwicklertools. Mqtt-Protokoll für die Arbeit mit Web-Sockets

Die Web-Socket-Technologie ermöglicht das Senden von Nachrichten von einem Server an einen Client in einer Webanwendung oder in einer mobilen Anwendung, was mit der REST-API nicht möglich ist. Um mit Web-Sockets zu arbeiten, verwenden sie häufig die Bibliothek socket.io, oder Entwickler arbeiten mit nativen Browser-Web-Socket-Objekten. In diesem Beitrag werde ich versuchen zu zeigen, dass beide Methoden nicht alle Probleme lösen. Es ist viel besser, spezialisierte Server zu verwenden, z. B. den mqtt-Server (zuvor mqtt-Broker genannt), um mit Web-Sockets zu arbeiten.

Um fair zu sein und um unnötige Streitigkeiten zu vermeiden, stelle ich fest, dass zusätzlich zum mqtt-Server eine Reihe anderer Server verwendet werden können, beispielsweise rabbitmq.

Das Entwickeln von Anwendungen mithilfe von Web-Sockets scheint sehr einfach zu sein, bis wir auf eine Realität stoßen, in der häufig Verbindungsunterbrechungen auftreten. Die erste Aufgabe, die gelöst werden muss, besteht darin, Verbindungsunterbrechungen zu verfolgen und wiederherzustellen. Die Situation wird durch die Tatsache kompliziert, dass der Client während des Trennens und erneuten Verbindens weiterhin neue Nachrichten sendet und neue Nachrichten an den Client gesendet werden können, die mit größerer Wahrscheinlichkeit verloren gehen.

Auf Anwendungsebene ist es erforderlich, den Empfang von Nachrichten zu überwachen und deren wiederholte Zustellung zu implementieren. Ich möchte besonders auf den Ausdruck „auf Anwendungsebene“ aufmerksam machen (aber ich möchte, dass er auf Protokollebene ist).

Sobald wir eine Logik zum Verfolgen der Nachrichtenübermittlung hinzugefügt hatten, kamen alle Nachrichten an, aber es wurde sofort klar, dass es doppelte Nachrichten gab, da die Nachricht hätte empfangen werden können, und die Bestätigung dieser Tatsache ging aufgrund einer getrennten Verbindung verloren. Und Sie müssen die Menge des Programmcodes verdoppeln, um doppelte Nachrichten auszuschließen.

Mit der Komplikation des Programmcodes nimmt auch dessen Effizienz ab, wofür sie häufig die Bibliothek socket.io kritisieren. Dies ist natürlich weniger effektiv als die Arbeit mit nativen Web-Sockets, insbesondere aufgrund der vorhandenen Wiederverbindungslogik und der Bestätigung der Nachrichtenübermittlung (ich stelle sofort fest, dass die Logik für die erneute Übermittlung in socket.io nicht implementiert ist).

Ein zuverlässigerer und effizienterer Weg wäre, diese Logik auf die Protokollebene zu bringen. Und ein solches Protokoll existiert - es ist mqtt. Die erste Version des mqtt-Protokolls wurde 1999 von Andy Stanford-Clark (IBM) und Arlene Nipper (Arcom) entwickelt. Die MQTT 3.1.1-Spezifikation wurde 2014 vom OASIS-Konsortium standardisiert.

Das mqtt-Protokoll verfügt über einen qos-Parameter (Quality of Service), der folgende Werte annehmen kann:
0 - Nachricht wird wenn möglich zugestellt;
1 - Die Nachricht wird garantiert zugestellt, es können jedoch Duplikate vorhanden sein.
2 - Die Nachricht wird garantiert und einmal garantiert zugestellt.

Das heißt, das mqtt-Protokoll löst das Problem mit der garantierten Nachrichtenübermittlung, und dieses Problem wird von der Tagesordnung entfernt. Aber nicht nur diese Frage.

Leistung und Skalierung.

Bei der Arbeit mit Web-Sockets lassen alle verbundenen Clients offene Verbindungen zum Server, auch wenn keine echten Nachrichten vorhanden sind. Diese Last unterscheidet sich von Natur aus von der Last in der REST-API, die durch den Anforderungsfluss bestimmt wird. Die Belastung durch offene Verbindungen an Web-Sockets ist während der Testphase schwer zu emulieren. Daher wird häufig eine falsche Annahme über die ausreichende Leistung der Anwendung durch die Anzahl der gesendeten und empfangenen Nachrichten getroffen, ohne die Belastung für die Aufrechterhaltung einer großen Anzahl offener Verbindungen mit Clients zu berücksichtigen.

Wenn wir alle Arbeiten mit Web-Sockets auf einen spezialisierten mqtt-Server übertragen, öffnet unsere nodejs-Anwendung nur eine Verbindung auf einem Web-Socket (oder tcp, da mqtt beide Protokolle unterstützt) mit einem mqtt-Server, und wir können Skalieren Sie unsere Anwendung, indem Sie mehrere Instanzen von nodejs mit dem mqtt-Server verbinden.

Wenn die Ressourcen eines mqtt-Servers erschöpft sind, können Sie einen Cluster von mqtt-Servern organisieren, ohne die Anwendungen auf nodejs zu beeinträchtigen.

Kommen wir nun zu einem Beispiel.

Der mqtt-Server oder Broker, wie er in den vorherigen Spezifikationen genannt wurde, arbeitet nach dem Modell des Sendens / Abonnierens von Nachrichten. Jede Nachricht wird an das Thema gesendet. Der Empfänger abonniert den Nachrichtenthread. Sowohl der Absender als auch der Empfänger haben zwei Bezeichner: clientId (Gerätekennung) und userName (Benutzername).

Die Gerätekennung ist wichtig, da sie dem Abonnement zugeordnet ist und Nachrichten an das Abonnement gesendet werden. Der Benutzername spielt im Gegensatz zur Gerätekennung keine entscheidende Rolle bei der Nachrichtenübermittlung und wird verwendet, um den Zugriff auf Themen zu differenzieren.

Um mit dem mqtt-Protokoll auf der Clientseite zu arbeiten, wird die Bibliothek github.com/eclipse/paho.mqtt.javascript verwendet. Es gibt mehrere Serverimplementierungen, einschließlich kostenloser. In diesem Beispiel verwenden wir den emqx-Server, der über Docker-Compose ausgeführt wird (siehe github.com/apapacy/tut-mqtt ).

Erstellen Sie zum Testen ein Dokument, in dem wir die Client-ID, den Benutzernamen und den Nachrichtentext festlegen:

<script src="/paho-mqtt.js"></script> <script src="/messages.js"></script> <form name="sender" onsubmit="return false"> <input type="text" name="user"> <input type="text" name="client"> <input type="text" name="message"> <input type="button" onclick="connect()" value="connect"> <input type="button" onclick="send()" value="send"> </form> 


Das Senden von Nachrichten ist in der Datei message.js implementiert:

 var client; var connectOptions = { timeout: 30, reconnect: true, cleanSession: false, mqttVersion: 4, keepAliveInterval: 10, onSuccess: onConnect, onFailure: onFailure } function connect() { try { client = new Paho.Client('localhost', 8083, '/mqtt', document.forms.sender.client.value); connectOptions.userName = document.forms.sender.user.value; client.connect(connectOptions); } catch (ex) { console.log(ex); } } function onConnect() { console.log('on connect'); client.onMessageArrived = function(message) { console.log("onMessageArrived: " + message.payloadString); } client.subscribe("test", { qos: 2 }); } function onFailure(err) { console.log('on failure', JSON.stringify(err)); } function send() { var message = new Paho.Message(document.forms.sender.message.value); message.destinationName = "test"; message.qos = 2; client.send(message); } 


Öffnen Sie zur Überprüfung die Datei index.html im Browser, geben Sie clientId, userName, Nachrichtentext an und senden Sie mehrere Nachrichten (Sie können sie in der Konsole lesen, wenn der Client Nachrichten an das Testthema sendet und dieses Thema selbst abonniert).

Öffnen Sie nun einen anderen Browser oder eine andere Browser-Registerkarte und verbinden Sie sich mit einer anderen (dies ist wichtig) clientId. Senden Sie ein paar weitere Nachrichten vom ersten Browser und stellen Sie sicher, dass sie an beide Clients gesendet werden, da sie unterschiedliche clientId haben und beide den Thementest abonniert haben.

Schließen Sie nun den zweiten Browser (oder die zweite Browser-Registerkarte) und senden Sie einige weitere Nachrichten. Öffnen Sie danach den zweiten Browser erneut und verbinden Sie sich mit derselben clientId. Stellen Sie in den Konsolenprotokollen sicher, dass Sie alle Nachrichten erhalten haben, die während des Zeitraums gesendet wurden, in dem der zweite Browser (zweite Registerkarte) geschlossen wurde. Dies geschah, weil:

  • Beim Senden einer Nachricht wurde die Qualitätsstufe qos = 2 eingestellt;
  • Sie haben zuvor dasselbe Thema mit derselben clientId verbunden, indem Sie qos = 2 gesetzt haben.
  • Die Verbindungsoptionen sind auf cleanSession: false festgelegt.

Beispielcode kann aus dem Repository heruntergeladen werden .

apapacy@gmail.com
29. September 2019

Source: https://habr.com/ru/post/de469315/


All Articles