Erklärung des Problems
In regelmäßigen Abständen habe ich die Aufgabe, Dateien in einem lokalen Netzwerk freizugeben, z. B. mit einem Projektkollegen.
Hierfür kann es viele Lösungen geben - Samba / FTP / scp. Sie können die Datei einfach an einen öffentlichen Ort wie Google Drive hochladen, an eine Aufgabe in Jira anhängen oder sie sogar per E-Mail senden.
Aber all dies ist bis zu dem einen oder anderen Grad unflexibel, irgendwo erfordert es eine vorläufige Anpassung und hat seine eigenen Einschränkungen (zum Beispiel die maximale Investitionsgröße).
Und Sie möchten etwas Leichteres und Flexibleres.
Ich war immer angenehm überrascht von der Möglichkeit unter Linux, mit den verfügbaren Mitteln schnell eine praktische Lösung zu entwickeln.
Angenommen, ich habe die oben genannte Aufgabe häufig mit dem Systempython mit der folgenden einzeiligen Aufgabe gelöst
$ python3 -mhttp.server Serving HTTP on 0.0.0.0 port 8000 ...
Mit diesem Befehl wird der Webserver im aktuellen Ordner gestartet und Sie können eine Liste der Dateien abrufen und über die Weboberfläche herunterladen. Weitere dieser Dinge können hier abgeladen werden .
Es gibt mehrere Unannehmlichkeiten. Um den Download-Link an einen Kollegen zu übertragen, müssen Sie nun Ihre IP-Adresse im Netzwerk kennen.
Es ist bequem, den Befehl zu verwenden
$ ifconfig -a
Wählen Sie dann aus der Liste der empfangenen Netzwerkschnittstellen die entsprechende aus und erstellen Sie manuell einen Link des Formulars http: // IP: 8000 , den Sie senden können.
Zweite Unannehmlichkeit: Dieser Server ist Single-Threaded. Dies bedeutet, dass einer Ihrer Kollegen die Datei herunterlädt, der zweite jedoch nicht einmal die Liste der Dateien herunterladen kann.
Drittens ist es unflexibel. Wenn Sie nur eine Datei übertragen müssen, wird der gesamte Ordner geöffnet, d. H. Sie müssen solche Gesten ausführen (und dann den Papierkorb bereinigen):
$ mkdir tmp1 $ cp file.zip tmp1 $ cd tmp1 $ python3 -mhttp.server
Die vierte Unannehmlichkeit: Es gibt keine einfache Möglichkeit, den gesamten Inhalt eines Ordners herunterzuladen.
Um den Inhalt eines Ordners zu übertragen, verwenden sie normalerweise eine Technik namens Teerpfeife .
Sie machen so etwas:
$ ssh user@host 'cd /path/to/source && tar cf - .' | cd /path/to/destination && tar xvf -
Wenn es plötzlich nicht mehr klar ist, werde ich erklären, wie es funktioniert. Der erste Teil des Befehls tar cf - .
Erstellt ein Archiv des Inhalts des aktuellen Ordners und schreibt in die Standardausgabe. Ferner wird diese Ausgabe über eine Pipe über einen sicheren SSH-Kanal an die Eingabe eines ähnlichen tar xvf -
der das entgegengesetzte Verfahren tar xvf -
, d. Liest Standardeingaben und entpackt sie in den aktuellen Ordner. Tatsächlich wird das Dateiarchiv übertragen, ohne jedoch eine Zwischendatei zu erstellen!
Die Unannehmlichkeit dieses Ansatzes ist ebenfalls offensichtlich. Wir brauchen SSH-Zugriff von einer Maschine zur anderen, und dies wird im allgemeinen Fall fast nie gemacht.
Ist es möglich, alle oben genannten Ziele zu erreichen, jedoch ohne diese beschriebenen Probleme?
Es ist also Zeit zu formalisieren, was wir bauen werden:
- Einfach zu installierendes Programm (statische Binärdatei)
- Auf diese Weise können Sie sowohl eine Datei als auch einen Ordner mit allen Inhalten übertragen
- Mit optionaler Komprimierung
- Dadurch kann der Host die Datei (en) nur mit Standard * nix-Tools (wget / curl / tar) herunterladen.
- Das Programm gibt unmittelbar nach dem Start die genauen Befehle zum Herunterladen aus
Lösung
Auf der JEEConf- Konferenz, an der ich vor nicht allzu langer Zeit teilgenommen habe, wurde das Thema Graal wiederholt angesprochen. Das Thema ist alles andere als neu, aber für mich war es ein Auslöser, dieses Biest endlich mit meiner eigenen Hand zu fühlen.
Für diejenigen, die noch nicht im Thema sind (gibt es so etwas wirklich schon? OO), möchte ich Sie daran erinnern, dass GraalVM eine so gepumpte JVM von Oracle mit zusätzlichen Funktionen ist, von denen die auffälligsten sind:
- Polyglot JVM - die Fähigkeit, Java, Javascript, Python, Ruby, R usw. nahtlos auszuführen. Code
- Unterstützung für die AOT-Kompilierung - Kompilieren von Java direkt in die native Binärdatei
- Eine weniger auffällige, aber sehr coole Funktion - der C2-Compiler wurde von C ++ auf Java umgeschrieben, um seine weitere Entwicklung bequemer zu gestalten. Dies hat bereits zu spürbaren Ergebnissen geführt. Dieser Compiler nimmt bei der Konvertierung von Java-Bytecode in nativen Code wesentlich mehr Optimierungen vor. Zum Beispiel ist es in der Lage, Zuordnungen effektiver zu eliminieren. Durch die Aktivierung dieser Einstellung konnte Twitter den CPU-Verbrauch um 11% senken, was in ihrer Größenordnung zu einer spürbaren Einsparung von Ressourcen (und Geld) führte.
Sie können die Idee von Graal zum Beispiel in diesem Habr-Artikel auffrischen .
Wir werden in Java schreiben, daher ist für uns die AOT-Kompilierung das relevanteste Feature.
Tatsächlich wird das Entwicklungsergebnis in diesem Github-Repository dargestellt .
Anwendungsbeispiel zum Übertragen einer einzelnen Datei:
$ serv '/path/to/report.pdf' To download the file please use one of the commands below: curl http://192.168.0.179:17777/dl > 'report.pdf' wget -O- http://192.168.0.179:17777/dl > 'report.pdf' curl http://192.168.0.179:17777/dl?z --compressed > 'report.pdf' wget -O- http://192.168.0.179:17777/dl?z | gunzip > 'report.pdf'
Ein Beispiel für die Verwendung beim Übertragen des Inhalts eines Ordners (alle Dateien einschließlich angehängter Dateien!):
$ serv '/path/to/folder' To download the files please use one of the commands below. NB! All files will be placed into current folder! curl http://192.168.0.179:17777/dl | tar -xvf - wget -O- http://192.168.0.179:17777/dl | tar -xvf - curl http://192.168.0.179:17777/dl?z | tar -xzvf - wget -O- http://192.168.0.179:17777/dl?z | tar -xzvf -
Ja so einfach!
Bitte beachten Sie, dass das Programm selbst die richtige IP-Adresse ermittelt, unter der Dateien zum Download zur Verfügung stehen.
Beobachtungen / Gedanken
Es ist klar, dass eines der Ziele bei der Erstellung des Programms seine Kompaktheit war. Und hier ist das Ergebnis, das erzielt wurde:
$ du -hs `which serv` 2.4M /usr/local/bin/serv
Unglaublicherweise passt die gesamte JVM zusammen mit dem Anwendungscode in ein paar miserable Megabyte! Natürlich ist alles etwas falsch, aber dazu später mehr.
Tatsächlich erzeugt der Graal-Compiler eine Binärdatei, die etwas größer als 7 Megabyte ist. Ich habe beschlossen, es mit dem UPX weiter zu komprimieren .
Dies stellte sich als gute Idee heraus, da sich die Startzeit erhöhte, obwohl sie sehr unbedeutend war:
Unkomprimierte Option:
$ time ./build/com.cmlteam.serv.serv -v 0.1 real 0m0.001s user 0m0.001s sys 0m0.000s
Komprimiert:
$ time ./build/serv -v 0.1 real 0m0.021s user 0m0.021s sys 0m0.000s
Zum Vergleich die Startzeit auf "traditionelle Weise":
$ time java -cp "/home/xonix/proj/serv/target/classes:/home/xonix/.m2/repository/commons-cli/commons-cli/1.4/commons-cli-1.4.jar:/home/xonix/.m2/repository/org/apache/commons/commons-compress/1.18/commons-compress-1.18.jar" com.cmlteam.serv.Serv -v 0.1 real 0m0.040s user 0m0.030s sys 0m0.019s
Wie Sie sehen, doppelt so langsam wie die UPX-Version.
Im Allgemeinen ist eine kurze Startzeit eine der Stärken von GraalVM. Dies sowie der geringe Speicherverbrauch führen zu einer erheblichen Begeisterung für die Verwendung dieser Technologie für Microservices und Serverless.
Ich habe versucht, die Programmlogik so minimal wie möglich zu gestalten und ein Minimum an Bibliotheken zu verwenden. Im Prinzip ist dieser Ansatz im Allgemeinen gerechtfertigt, und in diesem Fall hatte ich Bedenken, dass das Hinzufügen von Maven-Abhängigkeiten von Drittanbietern die resultierende Programmdatei erheblich „gewichten“ würde.
Aus diesem Grund habe ich beispielsweise keine Abhängigkeit von Drittanbietern für einen Java-Webserver verwendet (und es gibt viele für jeden Geschmack und jede Farbe), sondern die JDK-Implementierung eines Webservers von com.sun.net.httpserver.*
Package. Eigentlich wird die Verwendung des com.sun.*
als com.sun.*
, aber ich habe dies in diesem Fall für zulässig gehalten, da ich in nativen Code kompiliere und daher keine Frage der Kompatibilität zwischen der JVM besteht.
Meine Befürchtungen waren jedoch völlig vergebens. Im Programm habe ich der Einfachheit halber zwei Abhängigkeiten verwendet
commons-cli
- zum Parsen von Befehlszeilenargumentencommons-compress
- um einen Ordner-Tarball und eine optionale gzip commons-compress
zu generieren
Gleichzeitig nahm die Dateigröße leicht zu. Ich wage vorzuschlagen, dass der Graal-Compiler sehr klug ist, nicht alle Plug-In-JAR-Nicks in die ausführbare Datei einzufügen, sondern nur den Code von ihnen, der tatsächlich vom Anwendungscode verwendet wird.
Die Kompilierung in nativen Graal-Code wird vom Dienstprogramm native-image durchgeführt . Erwähnenswert ist, dass dieser Prozess ressourcenintensiv ist. Angenommen, bei meiner nicht so langsamen Konfiguration mit einer Intel 7700K-CPU an Bord dauert dieser Vorgang 19 Sekunden. Daher empfehle ich, das Programm bei der Entwicklung wie gewohnt (über Java) auszuführen und die Binärdatei im Endstadium zu sammeln.
Schlussfolgerungen
Das Experiment war meines Erachtens sehr erfolgreich. Bei der Entwicklung mit dem Graal-Toolkit sind keine unüberwindlichen oder gar signifikanten Probleme aufgetreten. Alles funktionierte vorhersehbar und stabil. Obwohl es fast sicher ist, dass nicht alles so reibungslos verläuft, wenn Sie versuchen, auf diese Weise etwas Komplexeres zu erstellen, z. B. eine Anwendung auf Spring Boot . Dennoch wurde bereits eine Reihe von Plattformen vorgestellt, auf denen die native Unterstützung für Graal erklärt wird. Unter ihnen sind Micronaut , Microprofile , Quarkus .
Für die weitere Entwicklung des Projekts liegt bereits eine Liste der für Version 0.2 geplanten Verbesserungen vor. Außerdem ist die Assembly der endgültigen Binärdatei derzeit nur für Linux x64 implementiert. Ich hoffe, dass dieses Versäumnis in Zukunft behoben wird, zumal der native-image
Compiler von Graal MacOS und Windows unterstützt. Leider unterstützt es noch keine Cross-Kompilierung, was die Sache viel einfacher machen könnte.
Ich hoffe, dass das vorgestellte Dienstprogramm zumindest jemandem aus der seriösen Habr-Community nützlich sein wird. Ich würde mich doppelt freuen, wenn es diejenigen gibt, die zum Projekt beitragen wollen.