Warum Google und Facebook Docker nicht verwenden

Warum Google und Facebook Docker nicht verwenden

Der Grund für das Schreiben dieses Artikels besteht darin, dass ich den Start des geänderten verteilten PyTorch-Programms im Cluster von Facebook beschleunigen möchte. Der Erkundungsprozess war interessant und demonstrierte auch das für industrielles maschinelles Lernen erforderliche Wissenssystem.

Direkt nach meinem Abschluss im Jahr 2007 habe ich drei Jahre lang bei Google gearbeitet. Damals dachte ich, dass das verteilte Betriebssystem Borg wirklich einfach zu bedienen sei.

Seit ich Google 2010 verlassen habe, habe ich mich auf die Open-Source-Entwicklung des Unternehmens bis zum Aufkommen von Kubernetes gefreut.

Die von Kubernetes geplante Recheneinheit ist ein Container (die genaue Übersetzung lautet „Container“, nicht allgemein „Container“. Sie können die Absicht des Autors verstehen, wenn Sie sich ansehen, was auf dem Logo der Firma Docker abgebildet ist).

Ein Container führt ein Image aus, genauso wie ein Prozess ein Programm ausführt.

Ich fürchte, weder Google-Mitarbeiter noch ehemalige Google-Mitarbeiter sind bei der Verwendung von Borg jemals mit den Konzepten von Container und Image in Berührung gekommen. Warum sind diese beiden Konzepte in Borg nicht verfügbar, werden aber in Kubernetes eingeführt?

Diese Frage schoss mir durch den Kopf und wurde ignoriert. Schließlich war ich später für weitere Open-Source-Projekte verantwortlich, beispielsweise Baidu Paddle und Ants SQLFlow und ElasticDL, und Docker war sehr einfach zu verwenden. Also habe ich nicht viel darüber nachgedacht.

Anfang dieses Jahres (2021) bin ich Facebook beigetreten. Zufällig veröffentlichte Facebook ein Papier [1], in dem sein verteiltes Cluster-Management-System Tupperware vorgestellt wurde.

Allerdings ist Tupperware eine 1946 eingetragene Marke https://en.wikipedia.org/wiki/Tupperware_Brands, deshalb musste ich in dem Dokument einen anderen Namen verwenden: Twine.

Da der Name Tupperware vielen Leuten in der Branche ein Begriff ist, werde ich in diesem Artikel nicht auf Twine eingehen.

Kurz gesagt, die Veröffentlichung dieses Dokuments hat mich dazu veranlasst, das vorherige Problem noch einmal zu überprüfen: Facebook verfügt nicht über Docker!

Nach einem ausführlichen Gespräch mit mehreren neuen und alten Kollegen aus dem Facebook-Tuppware-Team und von Google Borg dämmerte es mir endlich. Da es in der Branche keine entsprechende Rezension gibt, dient dieser Artikel nur zur Dokumentation.

Kurz gesagt

Einfach ausgedrückt: Wenn Sie zur Verwaltung Ihres Codes ein monolithisches Repository verwenden, benötigen Sie keine „Pakete“ wie Docker-Images (oder ZIP, Tarball, RPM, Deb).

Ein monolithisches Repo ist ein Repo, bei dem der gesamte Code für alle Projekte eines Unternehmens in einem (oder sehr wenigen) Repo konzentriert ist.

Denn ein monolithisches Repository muss über ein unterstützendes einheitliches Build-System verfügen, da es sonst nicht möglich wäre, eine so große Menge an Code zu kompilieren.

Da ein einheitliches Build-System vorhanden ist, kann das Modul mit diesem Knoten synchronisiert werden, sobald festgestellt wird, dass sich ein Modul geändert hat, von dem ein Programm abhängt, das ein Clusterknoten ausführen muss. Es ist überhaupt kein Verpacken und erneutes Synchronisieren erforderlich.

Wenn sich im Gegenteil jedes Projekt in einem separaten Git/SVN-Repository befindet und ein anderes Build-System verwendet, z. B. jedes Open-Source-Projekt in einem anderen GitHub-Repository, müssen die Build-Ergebnisse jedes Projekts verpackt werden.

Das Docker-Image unterstützt ein geschichtetes Paketformat, daher müssen wir nur die oberen Schichten übertragen, die die geänderten Elemente enthalten, und versuchen, die unteren Schichten wiederzuverwenden, die vom Knoten zwischengespeichert werden.

Sowohl Google als auch Facebook verwenden monolithische Repositories und verfügen über eigene Build-Systeme (in meinem alten Artikel Finding Google Blaze[2] wird das Build-System von Google erläutert). Daher besteht kein Bedarf an „Paketen“ und natürlich auch nicht an Docker-Images.

Allerdings verfügen sowohl Borg als auch Tupperware über Container (unter Verwendung einiger vom Linux-Kernel bereitgestellter Systemaufrufe, wie etwa der cgroup, die das Google Borg-Team vor über zehn Jahren zum Linux-Kernel beigesteuert hat), um eine Isolation zwischen Jobs zu erreichen.

Wenn die Leute keine Docker-Images erstellen müssen, fällt die Existenz von Containern einfach nicht so leicht auf.

Wenn Sie sich durch das oben Gesagte nicht blenden lassen und sich eingehend mit diesem Thema befassen möchten, dann warten Sie, bis ich das F&E-Technologiesystem und das Computertechnologiesystem von Google und Facebook Schicht für Schicht freilege.

Verpackung

Wenn wir einen verteilten Job zur Ausführung an einen Cluster senden, müssen wir das auszuführende Programm (einschließlich einer ausführbaren Datei und zugehöriger Dateien wie *.so, *.py) auf einige Maschinen (Knoten) übertragen, die diesem Job vom Planungssystem zugewiesen wurden.

Woher stammen diese zu verpackenden Dateien? Es wurde damals gebaut. Bei Google gibt es Blaze, bei Facebook gibt es Buck.

Interessierte können einen Blick auf die Open-Source-Version von Google Blaze, Bazel[3], sowie die Open-Source-Version von Facebook Buck[4] werfen.

Aber zur Erinnerung: Die internen Versionen von Blaze und Facebook Buck werden für monolithische Repos verwendet, während die Open-Source-Versionen für jedermann bestimmt sind, um nicht-monolithische Repos zu verwenden. Es gibt also Unterschiede in Konzepten und Implementierungen, aber Sie können trotzdem ein Gefühl für die grundlegenden Verwendungsmethoden bekommen.

Angenommen, wir haben die folgenden Modulabhängigkeiten, beschrieben in der Buck- oder Bazel-Syntax (die Syntax ist fast dieselbe):

python_binary(name="A", srcs=["A.py"], deps=["B", "C"], ...)
python_library(name="B", srcs=["B.py"], deps=["D"], ...)
python_library(name="C", srcs=["C.py"], deps=["E"], ...)
cxx_library(name="D", srcs=["D.cxx", "D.hpp"], deps="F", ...)
cxx_library(name="E", srcs=["E.cxx", "E.hpp"], deps="F", ...)

Dann lauten die Modulabhängigkeiten (Build-Ergebnis) wie folgt:

A.py --> B.py --> D.so -\
     \-> C.py --> E.so --> F.so

Wenn es sich um ein Open-Source-Projekt handelt, verwenden Sie bitte Ihre Fantasie und ersetzen Sie die oben genannten Module durch Projekte wie GPT-3, PyTorch, cuDNN, libc++ usw.

Natürlich enthält jedes Projekt mehrere Module und ist von anderen Projekten abhängig, genauso wie jedes Modul mehrere Untermodule hat.

Tarball

Die einfachste Möglichkeit zum Verpacken besteht darin, die obigen Dateien {A,B,C}.py, {D,E,F}.so in eine Datei A.zip oder A.tar.gz zu packen.

Genauer gesagt sollte der Dateiname die Versionsnummer enthalten. Beispielsweise A-953bc.zip, wobei die Versionsnummer 953bc die Commit-ID von Git/Mercurial ist.

Durch die Eingabe der Versionsnummer kann die Datei lokal auf dem Knoten zwischengespeichert werden, sodass Sie die Datei beim nächsten Ausführen desselben Tarballs nicht herunterladen müssen.

Bitte beachten Sie, dass ich hier das Konzept des Paket-Cachings vorgestellt habe. Bereiten Sie sich auf die folgende Erklärung von Docker vor.

XAR

Nachdem die ZIP- oder Tarball-Datei auf den Clusterknoten kopiert wurde, muss sie irgendwo im lokalen Dateisystem entpackt werden, zum Beispiel: /var/packages/A-953bc/{A,B,C}.py,{D,E,F}.so.

Eine etwas coolere Methode besteht darin, kein Tarball zu verwenden, sondern die obigen Dateien in ein Loopback-Geräteimage eines Overlay-Dateisystems zu legen. Auf diese Weise wird aus „entpacken“ „mounten“.

Bitte beachten Sie, dass ich hier das Konzept des Loopback-Geräteabbilds vorgestellt habe. Bereiten Sie sich auf die folgende Erklärung von Docker vor.

Was ist ein Loopback-Geräteimage? Unter Unix wird ein Verzeichnisbaum von Dateien als Dateisystem bezeichnet.

Normalerweise wird ein Dateisystem auf einem Blockgerät gespeichert. Was ist ein Blockgerät?

Einfach ausgedrückt ist jeder Speicherplatz, der als Byte-Array betrachtet werden kann, ein Blockgerät.

Eine Festplatte ist beispielsweise ein Blockgerät. Als Formatieren bezeichnet man den Vorgang des Erstellens einer leeren Verzeichnisbaumstruktur auf einer neu erworbenen Festplatte.

Da ein Blockgerät nur ein Byte-Array ist, ist eine Datei dann nicht auch ein Byte-Array?

Ja! In der Unix-Welt können wir eine leere Datei mit einer festen Größe erstellen (mit dem Befehl truncate) und die Datei dann „formatieren“, um darin ein leeres Dateisystem zu erstellen. Fügen Sie dann die obigen Dateien {A,B,C}.py,{D,E,F}.so dort ein.

Beispielsweise hat Facebook das XAR-Dateiformat[5] als Open Source freigegeben. Dies ist für die Verwendung mit Buck.

Wenn wir Buck Build A ausführen, erhalten wir A.xar. Diese Datei enthält einen Header und ein Squashfs-Loopback-Geräteabbild, das als Squanshfs-Abbild bezeichnet wird.

Dabei handelt es sich bei Squashfs um ein Open-Source-Dateisystem. Interessierte Freunde können sich auf dieses Tutorial [6] beziehen, eine leere Datei erstellen, sie in Squashfs formatieren und sie dann in einem Verzeichnis (Einhängepunkt) im lokalen Dateisystem einhängen.

Beim Umounten verbleiben die Dateien, die zum Einhängepunkt hinzugefügt wurden, in dieser „leeren Datei“.

Wir können es kopieren und an andere Personen verteilen, und jeder kann es mounten und die von uns hinzugefügten Dateien sehen.

Da XAR vor dem Squashfs-Image einen Header hinzufügt, können Sie es nicht mit dem Befehl mount -t squashf mounten. Sie müssen den Befehl mount -t xar oder xarexec -m verwenden.

Wenn ein Knoten beispielsweise /packages/A-953bc.xar hat, können wir den folgenden Befehl verwenden, um seinen Inhalt anzuzeigen, ohne CPU-Ressourcen für die Dekomprimierung zu verbrauchen:

xarexec -m A-953bc.xar

Dieser Befehl druckt ein temporäres Verzeichnis aus, das den Einhängepunkt der XAR-Datei darstellt.

Schichtung

Wenn wir jetzt A.py ändern, muss das gesamte Paket aktualisiert werden, unabhängig davon, ob es in ein Tarball oder XAR integriert ist.

Solange das Build-System Cache unterstützt, müssen wir natürlich nicht jede *.so-Datei neu generieren.

Dies löst jedoch nicht das Problem, dass die .tar.gz- und .xar-Dateien an jeden Knoten im Cluster neu verteilt werden müssen.

Die alte Version von A-953bc87fe.{tar.gz,xar} war möglicherweise bereits auf dem Knoten vorhanden, kann jedoch nicht wiederverwendet werden. Für die Wiederverwendung ist eine Schichtung erforderlich.

Für die obige Situation können wir mehrere XAR-Dateien basierend auf dem Modulabhängigkeitsdiagramm erstellen.

A-953bc.xar --> B-953bc.xar --> D-953bc.xar -\
            \-> C-953bc.xar --> E-953bc.xar --> F-953bc.xar

Jede XAR-Datei enthält nur die von der entsprechenden Build-Regel generierten Dateien. Beispielsweise enthält F-953bc.xar nur F.so.

Wenn wir auf diese Weise nur A.py ändern, muss nur A.xar neu erstellt und auf die Clusterknoten übertragen werden. Dieser Knoten kann die zuvor zwischengespeicherte Datei {B,C,D,E,F}-953bc.xar wiederverwenden.

Angenommen, ein Knoten hat bereits /packages/{A,B,C,D,E,F}-953bc.xar. Können wir den Befehl xarexec -m in der Reihenfolge der Modulabhängigkeiten ausführen, um diese XAR-Dateien nacheinander im selben Einhängepunktverzeichnis einzuhängen und so den gesamten Inhalt abzurufen?

Leider nein. Denn der nächste xarexec/mount-Befehl wird einen Fehler melden – weil der Einhängepunkt durch den vorherigen xarexec/mount-Befehl belegt wurde.

Aus diesem Grund sind Dateisystemabbilder besser als Tarballs.

Gehen wir also einen Schritt zurück und warum verwenden wir statt XAR nicht ZIP oder tar.gz? Ja, aber langsam. Wir können alle .tar.gz-Dateien in dasselbe Verzeichnis extrahieren.

Wenn A.py jedoch aktualisiert wird, können wir das alte A.py nicht identifizieren und durch das neue ersetzen. Stattdessen müssen wir alle .tar.gz-Dateien erneut entpacken, um einen neuen Ordner zu erhalten. Und das erneute Extrahieren aller {B,C,D,E,F}.tar.gz ist langsam.

Overlay-Dateisystem

Es gibt ein Open-Source-Tool fuse-overlayfs. Es kann mehrere Verzeichnisse „überlagern“.

Beispielsweise „überlagert“ der folgende Befehl den Inhalt der Verzeichnisse /tmp/{A,B,C,D,E,F}-953bc in das Verzeichnis /pacakges/A-953bc.

Sicherung-Overlays -o \
  lowerdir="/tmp/A-953bc:/tmp/B-953bc:..." \
  /Pakete/A-953bc

Die Verzeichnisse /tmp/{A,B,C,D,E,F}-953bc stammen von xarcexec -m /packages/{A,B,C,D,E,F}-953bc.xar.

Bitte beachten Sie, dass ich hier das Konzept des Overlay-Dateisystems eingeführt habe. Bereiten Sie sich auf die folgende Erklärung von Docker vor. Wie macht fuse-overlayfs das?

Wenn wir auf ein beliebiges Dateisystemverzeichnis wie etwa /packages/A zugreifen, rufen die von uns verwendeten Befehlszeilentools (wie etwa ls) Systemaufrufe (wie etwa Öffnen/Schließen/Lesen/Schreiben) auf, um auf die darin enthaltenen Dateien zuzugreifen.

Diese Systemaufrufe befassen sich mit dem Dateisystemtreiber. Sie fragen den Treiber: Gibt es eine Datei namens A.py im Verzeichnis /packages/A?

Wenn wir Linux verwenden, ist das Dateisystem auf der Festplatte im Allgemeinen ext4 oder btrfs. Mit anderen Worten: Der universelle Dateisystemtreiber von Linux sieht sich das Dateisystem jeder Partition an und leitet den Systemaufruf dann zur Verarbeitung an den entsprechenden ext4/btrfs-Treiber weiter.

Allgemeine Dateisystemtreiber werden wie andere Gerätetreiber im Kernelmodus ausgeführt.

Aus diesem Grund benötigen wir normalerweise sudo, wenn wir Befehle wie mount und umount ausführen, die Dateisysteme bedienen. FUSE ist eine Bibliothek zur Entwicklung von Dateisystemtreibern im Userland.

Der Befehl fuse-overlayfs verwendet die FUSE-Bibliothek, um einen fuse-overlayfs-Treiber zu entwickeln, der im Userland ausgeführt wird.

Wenn der Befehl ls den Overlayfs-Treiber fragt, was sich im Verzeichnis /packages/A-953bc befindet, merkt sich der Fuse-Overlayfs-Treiber, dass der Benutzer zuvor den Befehl Fuse-Overlayfs ausgeführt hat, um die Verzeichnisse /tmp/{A,B,C,D,E}-953bc zu überlagern, und gibt daher die Dateien in diesen Verzeichnissen zurück.

Da es sich bei den Verzeichnissen /tmp/{A,B,C,D,E}-953bc tatsächlich um Einhängepunkte von /packages/{A,B,C,D,E,F}-953bc.xar handelt, entspricht derzeit jedes XAR einer Ebene.

Ein Dateisystemtreiber, der mehrere Verzeichnisse „überlagert“, wie der Fuse-Overlayfs-Treiber, wird als Overlay-Dateisystemtreiber bezeichnet, manchmal auch als Overlay-Dateisystem.

Docker-Image und -Ebene

Wie oben erwähnt wird zur Schichtung ein Overlay-Dateisystem verwendet. Jeder, der Docker verwendet hat, weiß, dass ein Docker-Image aus mehreren Ebenen besteht.

Wenn wir den Befehl „docker pull <image-name>“ ausführen und die lokale Maschine bereits einige Ebenen dieses Images zwischengespeichert hat, überspringen wir das Herunterladen dieser Ebenen. Dies wird tatsächlich durch die Verwendung des Overlay-Dateisystems erreicht.

Das Docker-Team hat ein Dateisystem (Treiber) namens overlayfs entwickelt – dies ist der Name eines bestimmten Dateisystems.

Wie der Name schon sagt, implementiert Docker overlayfs auch die „Overlay“-Funktion, weshalb wir sehen, dass jedes Docker-Image mehrere Ebenen haben kann.

Dockers Overlayfs und sein Nachfolger Overlayfs2 werden beide im Kernelmodus ausgeführt.

Dies ist einer der Gründe, warum Docker Root-Rechte auf dem Rechner erfordert, was wiederum der Grund dafür ist, dass Docker dafür kritisiert wird, leicht Sicherheitslücken zu verursachen.

Es gibt ein Dateisystem namens btrfs, das sich in den letzten Jahren in der Linux-Welt rasant entwickelt hat und bei der Verwaltung von Festplatten sehr effektiv ist.

Dieser Dateisystemtreiber unterstützt auch Overlay. Daher kann Docker auch so konfiguriert werden, dass dieses Dateisystem anstelle von Overlayfs verwendet wird.

Docker kann btrfs jedoch nur dann zum Überlagern von Ebenen verwenden, wenn das lokale Dateisystem des Computers des Docker-Benutzers btrfs ist.

Wenn Sie macOS oder Windows verwenden, können Sie btrfs definitiv nicht mit Docker verwenden.

Wenn Sie jedoch Fuse-Overlayfs verwenden, verwenden Sie ein Allheilmittel. Die Leistung eines Dateisystems, das im Userland über FUSE ausgeführt wird, ist sehr durchschnittlich, aber die in diesem Artikel besprochene Situation erfordert nicht viel Leistung.

Tatsächlich kann Docker auch so konfiguriert werden, dass Fuse-Overlayfs verwendet wird. Eine Liste der von Docker unterstützten hierarchischen Dateisysteme ist unter Docker-Speichertreiber[7] verfügbar.

Warum brauchen wir ein Docker-Image?

Um das oben Gesagte zusammenzufassen: Von der Programmierung bis zur Ausführung im Cluster müssen wir mehrere Schritte ausführen:

  1. Kompilieren: Quellcode in ausführbare Form kompilieren.
  2. Verpackung: Packen Sie die Kompilierungsergebnisse in ein „Paket“ für die Bereitstellung und Verteilung
  3. Transport: Normalerweise ein Cluster-Managementsystem (Borg, Kubernetes, Tupperware). Wenn Sie einen Container auf einem Clusterknoten starten möchten, müssen Sie das „Paket“ auf diesen Knoten übertragen, es sei denn, dieser Knoten hat dieses Programm zuvor ausgeführt und verfügt bereits über einen Cache des Pakets.
  4. Auspacken: Wenn es sich bei dem Paket um eine Tarball- oder ZIP-Datei handelt, muss es nach dem Eintreffen auf dem Clusterknoten entpackt werden. Wenn es sich bei dem Paket um ein Dateisystem-Image handelt, muss es gemountet werden.

Durch die Aufteilung des Quellcodes in Module kann beim Kompilierungsschritt die Tatsache, dass bei jeder Änderung nur ein kleiner Teil des Codes geändert wird, voll ausgenutzt werden. Außerdem müssen nur die geänderten Module neu kompiliert werden, was Zeit spart.

Um bei 2, 3 und 4 Zeit zu sparen, möchten wir, dass die „Pakete“ hierarchisch sind. Jede Schicht sollte vorzugsweise nur ein oder wenige Codemodule enthalten. Auf diese Weise können die Abhängigkeiten zwischen Modulen ausgenutzt werden, um die „Schichten“, die die zugrunde liegenden Module enthalten, so weit wie möglich wiederzuverwenden.

In der Open-Source-Welt verwenden wir Docker-Images, um mehrschichtige Funktionen zu unterstützen. Eine Basisschicht kann nur Userland-Programme einer bestimmten Linux-Distribution (z. B. CentOS) enthalten, z. B. ls, cat, grep usw.

Darüber hinaus kann es eine Schicht mit CUDA geben. Installieren Sie dann Python und PyTorch darauf. Die nächste Ebene darüber ist das Trainingsverfahren für das GPT-3-Modell.

Wenn wir auf diese Weise nur das GPT-3-Trainingsverfahren ändern, müssen die folgenden drei Ebenen nicht neu verpackt und übertragen werden.

Der Kern der Logik besteht hier darin, dass es das Konzept „Projekt“ gibt. Jedes Projekt kann sein eigenes Repo, sein eigenes Erstellungssystem (GNU Make, CMake, Buck, Bazel usw.) und seine eigene Version haben.

Daher wird die Version jedes Projekts in eine Ebene des Docker-Image eingefügt. Zusammen mit den vorhergehenden Schichten wird es als Bild bezeichnet.

Warum Google und Facebook Docker nicht brauchen

Nach all der oben erwähnten Wissensvorbereitung können wir endlich zur Sache kommen.

Weil Google und Facebook ein monolithisches Repository und ein einheitliches Build-System (Google Blaze oder Facebook Buck) verwenden.

Sie können allerdings auch das Konzept „Projekt“ verwenden, um das Build-Ergebnis jedes Projekts in eine Ebene des Docker-Image zu laden. Aber eigentlich ist das nicht nötig.

Indem wir die durch die Build-Regeln von Blaze und Buck definierten Module und die Abhängigkeiten zwischen Modulen verwenden, können wir das Konzept des Verpackens und Auspackens vollständig loswerden.

Ohne Pakete sind Zip, Tarball, Docker-Image und Ebenen nicht erforderlich.

Behandeln Sie jedes Modul einfach als eine Ebene. Wenn D.so neu kompiliert wird, weil wir D.cpp geändert haben, müssen wir nur D.so erneut übertragen, ohne eine Schicht übertragen zu müssen, die D.so enthält.

Daher profitieren Google und Facebook von monolithischen Repositories und einheitlichen Build-Tools.

Wir haben die oben genannten vier Schritte auf zwei verkürzt:

  1. Kompilieren: Quellcode in ausführbare Form kompilieren.
  2. Übertragen: Wenn ein Modul neu kompiliert wird, übertragen Sie dieses Modul.

Google und Facebook verwenden Docker nicht

Im vorherigen Abschnitt wurde erwähnt, dass Google und Facebook dank monolithischer Repositorys auf Docker-Images verzichten können.

Die Realität ist, dass Google und Facebook Docker nicht verwenden. Es gibt einen Unterschied zwischen diesen beiden Konzepten.

Sagen wir zunächst „nicht in Gebrauch“. In der Vergangenheit haben Google und Facebook vor der Einführung von Docker und Kubernetes Hyperscale-Cluster verwendet. Aus Verpackungsgründen gab es damals nicht einmal ein Tarball.

Für C/C++-Programme: direktes, vollständig statisches Verknüpfen, überhaupt kein *.so. Eine ausführbare Binärdatei ist also ein „Paket“.

Wenn Benutzer die Open-Source-Programme Bazel und Buck verwenden, können sie bis heute erkennen, dass die Standardverknüpfungsmethode die vollständig statische Verknüpfung ist.

Obwohl Java eine „vollständig dynamisch verknüpfte“ Sprache ist, fielen ihre Entstehung und Entwicklung mit den historischen Möglichkeiten des Internets zusammen. Seine Entwickler erfanden das JAR-Dateiformat, das vollständig statische Verknüpfungen unterstützt.

Die Python-Sprache selbst verfügt nicht über ein JAR-Paket, daher haben Blaze und Bazel das PAR-Dateiformat (auf Englisch „subpar“) erfunden, das dem Entwerfen eines JAR für Python entspricht. Eine Open-Source-Implementierung ist hier verfügbar [8].

In ähnlicher Weise hat Buck das XAR-Format erfunden, das ich oben erwähnt habe, mit einem Header, der vor dem Squashfs-Image hinzugefügt wird. Die Open-Source-Implementierung ist hier verfügbar [9].

Die Sprache Go ist standardmäßig vollständig statisch verknüpft. In einigen der frühen Zusammenfassungen von Rob Pike wurde erwähnt, dass das Design von Go, einschließlich der vollständigen statischen Verknüpfung, im Wesentlichen eine Umgehung der verschiedenen Fallstricke darstellte, die bei Googles C/C++-Praxis auftreten.

Freunde, die mit dem Google C++-Styleguide vertraut sind, dürften den Eindruck haben, dass die Go-Syntax die im Guide angegebene „C++-Syntax, die verwendet werden sollte“ abdeckt, aber die im Guide angegebenen „C++-Teile, die nicht verwendet werden sollten“ nicht unterstützt.

Einfach ausgedrückt haben Google und Facebook bisher keine Docker-Images verwendet. Ein wichtiger Grund ist, dass ihre Build-Systeme Programme in verschiedenen gängigen Sprachen vollständig statisch verknüpfen können, sodass die ausführbare Datei das „Paket“ ist.

Dies ist allerdings nicht die beste Lösung, da eine Schichtung nicht gegeben ist. Selbst wenn ich nur eine Codezeile in der Hauptfunktion geändert hätte, würde das Neukompilieren und Veröffentlichen lange dauern, zehn Minuten oder sogar Dutzende von Minuten. Sie sollten wissen, dass die Größe der ausführbaren Datei, die durch vollständiges statisches Verknüpfen erhalten wird, häufig in GB gemessen wird.

Obwohl die vollständig statische Verknüpfung einer der Gründe ist, warum Google und Facebook Docker nicht verwenden, ist es keine gute Wahl.

Aus diesem Grund folgten ihm keine anderen Unternehmen. Die Leute bevorzugen immer noch die Verwendung von Docker-Images, die hierarchisches Caching unterstützen.

Technische Herausforderungen einer perfekten Lösung

Die perfekte Lösung würde hierarchisches Caching (oder genauer gesagt Block-Caching) unterstützen. Daher sollten Sie weiterhin die oben vorgestellten Funktionen des monolithischen Repos und des einheitlichen Build-Systems verwenden.

Hier besteht jedoch eine technische Herausforderung: Das Build-System wird in Modulen beschrieben und Module sind normalerweise viel feinkörniger als „Projekte“.

Am Beispiel der Programmiersprache C/C++ lässt sich sagen: Wenn jedes Modul eine SO-Datei als „Schicht“ oder „Block“ generiert, die als Cache-Einheit dient, ist die Anzahl der SO-Dateien, die eine Anwendung möglicherweise benötigt, zu groß.

Wenn Sie die Anwendung starten, kann es Dutzende von Minuten dauern, bis die Symbole aufgelöst und die Verknüpfung hergestellt ist.

Obwohl das monolithische Repository viele Vorteile bietet, hat es auch einen Nachteil. Anders als in der Open-Source-Welt zerlegt jeder den Code manuell in „Projekte“.

Jedes Projekt ist normalerweise ein GitHub-Repository, das viele Module enthalten kann, aber alle Module in jedem Projekt sind als Cache-Einheit in ein *.so integriert.

Weil die Anzahl der Projekte, von denen eine Anwendung abhängt, nie zu groß sein wird, kann die Gesamtzahl der Ebenen kontrolliert werden.

Glücklicherweise ist dieses Problem nicht unlösbar. Da die Abhängigkeit einer Anwendung von verschiedenen Modulen ein DAG ist, können wir immer eine Möglichkeit finden, eine Graphpartitionierung durchzuführen, um diesen DAG in mehrere Untergraphen mit weniger Zahlen zu zerlegen.

Wenn wir weiterhin das C/C++-Programm als Beispiel nehmen, können wir jedes Modul in jedem Teilgraphen in eine .a kompilieren und alle .a in jedem Teilgraphen in eine *.so-Datei als Cache-Einheit verknüpfen.

Deshalb ist die wichtigste Frage, wie der Graphpartitionierungsalgorithmus entworfen werden soll.

Verwandte Links:

https://engineering.fb.com/2019/06/06/data-center-engineering/twine/

https://zhuanlan.zhihu.com/p/55452964

https://bazel.build/

https://buck.build/

https://github.com/facebookincubator/xar

https://tldp.org/HOWTO/SquashFS-HOWTO/creatingandusing.html

https://docs.docker.com/storage/storagedriver/select-storage-driver/

https://github.com/google/subpar

https://github.com/facebookincubator/xar

Dies ist das Ende dieses Artikels über die prinzipielle Analyse der Tatsache, dass Google und Facebook Docker nicht verwenden. Weitere relevante Inhalte zu Google und Facebook, die Docker nicht verwenden, finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den verwandten Artikeln weiter unten. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Verwenden Sie Beaker, damit das Bottle-Framework von Facebook die Sitzungsfunktion unterstützt
  • Implementieren Sie ein Ajax-Update ohne Aktualisierung ähnlich wie bei Facebook
  • Konkrete Schritte zum Abrufen der Anzahl von Twitter- und Facebook-Artikeln mithilfe von JSONP
  • Spezifische Implementierung der Facebook-Sharing-Funktion auf Webseiten
  • So zeigen Sie die Anzahl der Facebook-Fans mit PHP an

<<:  Details zum JavaScript-Timer

>>:  Interpretieren von MySQL-Client- und Serverprotokollen

Artikel empfehlen

Vue2-Implementierungen bieten Injection für Reaktionsfähigkeit

1. Konventionelles Schreiben in vue2 // Die überg...

Implementierung der MySQL8.0.11-Datenverzeichnismigration

Das Standardspeicherverzeichnis von MySQL ist /va...

Detaillierte Erläuterung der Verwendung von MySQL Explain (Analyseindex)

EXPLAIN zeigt, wie MySQL Indizes verwendet, um Au...

MySQL-Limit-Leistungsanalyse und -Optimierung

1. Fazit Syntax: Limit-Offset, Zeilen Schlussfolg...

So benennen Sie die Tabelle in MySQL um und worauf Sie achten müssen

Inhaltsverzeichnis 1. Tabellenmethode umbenennen ...

js, um den Popup-Effekt zu erzielen

In diesem Artikelbeispiel wird der spezifische Co...

Erste Schritte mit dem Animieren von SVG-Pfadstrichen mit CSS3

Ohne auf JavaScript angewiesen zu sein, wird rein...

Detaillierte Erläuterung der Vue-Formularbindung und -Komponenten

Inhaltsverzeichnis 1. Was ist bidirektionale Date...

CSS zum Erzielen des Effekts einer rotierenden Flip-Card-Animation

Die CSS-Animation des rotierenden Flip-Effekts, d...

Implementierung der Nginx-Domänennamenweiterleitung

Einführung in Nginx Nginx („engine x“) ist ein le...

JavaScript realisiert Lupen-Spezialeffekte

Der zu erzielende Effekt: Wenn die Maus auf das k...

Linux Yum-Paketverwaltungsmethode

Einführung yum (Yellow dog Updater, Modified) ist...