• Zuhause
  • Artikel
  • Code to Customer: Vereinfachung der Backend-Tools mit Monorepo
Veröffentlicht am 29-03-2019

Code to Customer: Vereinfachung der Backend-Tools mit Monorepo

In diesem Blogbeitrag werden wir die Art und Weise des Angebots von Back-End-Services diskutieren. Wir werden besprechen, wie unser Backend-Code organisiert ist und wie er vom Text in einem Repository zu laufenden Diensten für Millionen von Benutzern wird.

Eine Einführung in Monorepo

Hier bei OfferUp verwenden wir ein Monorepo-Muster für die Organisation unserer Dienstleistungen. Dies bedeutet, dass der Code für jeden Dienst in einem einzigen Repository gespeichert wird und nicht auf mehrere verteilt ist. Zwar mag es unangemessen erscheinen, mehrere verschiedene Services und Tools in demselben Repository zu speichern, insbesondere wenn das Unternehmen immer größer wird. Monorepos bieten jedoch auch eine Reihe von Vorteilen für große Unternehmen. Die Vorteile sind signifikant genug, dass viele der größten Tech-Unternehmen, darunter Google, Facebook und Twitter, derzeit alle Monorepos verwenden.

Vorteile von Monorepos

Monorepos enthält zwar eine große Menge an Code, der über mehrere Dienste und Projekte verteilt ist, vereinfacht jedoch in gewisser Weise die Organisation.

Einige der unmittelbarsten Vorteile sind vereinfachte Abhängigkeiten und eine bessere Wiederverwendung von Code. Jeder Dienst unterscheidet sich zwar in der Funktion, teilt jedoch viele Gemeinsamkeiten als OfferUp-Dienst. Mit einem Monorepo-Muster können diese verschiedenen Dienste problemlos von denselben Kernbibliotheken abhängen, die nur einmal gewartet werden müssen. Diese Art von Abhängigkeitsstruktur erleichtert auch die Erstellung neuer Dienste. Häufig muss nur die Kernlogik des neuen Dienstes hinzugefügt werden, da die Infrastruktur zum Einrichten eines neuen Dienstes bereits vorhanden ist und von vielen vorhandenen Diensten getestet wird. Obwohl es nicht unvorstellbar ist, diese Kernbibliotheken in ein anderes Repository zu integrieren, von dem verschiedene Services abhängen, vereinfacht die Verwaltung sämtlicher Ressourcen in demselben Repository die Wartung, die Versionskontrolle und andere systemübergreifende Overheads. Aufgrund dieses Musters können viele unserer Services auf die gleichen Vorlagen und Bibliotheken zurückgreifen, was die Arbeit für unsere Ingenieure vereinfacht. Zum Beispiel haben wir die meisten Handler für Netzwerkverbindungen und Dienstkonfigurationen in einem Paket gebündelt, wodurch diese Werkzeugsätze für alle Dienste zum Standard werden.

Die Versionierung zwischen diesen Bibliotheken und Diensten wird ebenfalls viel einfacher, wie bereits erwähnt. Da sich der gesamte Code in einem Repository befindet, ist es einfach sicherzustellen, dass der gesamte Code auf dem neuesten Stand ist und nicht nur einige Teile davon. Wenn eine Kernbibliothek aktualisiert wird, können die Ingenieure, die die Aktualisierung durchführen, problemlos alle Dienste abhängig von der Bibliothek testen, um sicherzustellen, dass ihre Änderungen keine Auswirkungen auf andere Teams haben.

Schließlich vereinfacht ein Monorepo auch die Werkzeuge für die Backend-Ingenieure. Wenn sich der gesamte Code im selben Repository befindet, führt dies natürlich zu einer ähnlichen Organisations- und Werkzeugstruktur für mehrere Projekte. Auf diese Weise können wir die gleichen Tools und Verfahren für mehrere Projekte verwenden, wodurch das Leben der Ingenieure einfacher und einfacher wird. Die meisten unserer Backend-Services folgen derselben Code-Organisation, Toolkette und Build-Pipeline. Ingenieure, die Interaktionen zwischen verschiedenen Diensten debuggen, können Dienste, die ihnen unbekannt sind, leicht lesen und debuggen, da sie davon ausgehen können, wie diese unbekannten Dienste erstellt und gestartet werden.

Herausforderungen von Monorepo

Die Verwendung eines Monorepo bietet viele Vorteile, aber auch Herausforderungen. Code zwischen Projekten kann manchmal eng miteinander gekoppelt sein, wenn wir nicht mit der Gestaltung der richtigen Schnittstellen vorsichtig sind und Änderungen manchmal schwierig werden, insbesondere bei Core-Bibliotheken. Selbst wenn Änderungen vorgenommen werden können, kann es aufgrund der großen Anzahl von Verbrauchern schwierig sein, deren Richtigkeit zu überprüfen. Es kann auch schwierig sein, Engineering-Anstrengungen zu organisieren, wenn Dutzende von Ingenieuren an derselben Codebase arbeiten und Änderungen an den gleichen Dateien vornehmen. Im folgenden Abschnitt werden wir einige der Werkzeuge diskutieren, die wir zur Minderung dieser Probleme verwenden.

Werkzeuge für Monorepos

Um den verschiedenen Herausforderungen, die mit dem Monorepo-Ansatz verbunden sind, zu begegnen, haben wir eine Reihe von Instrumenten zur Bewältigung dieser Herausforderungen geschaffen und verabschiedet.

Warteschlange zusammenführen

Die erste Herausforderung, der wir uns stellen wollen, ist die Herausforderung, Dutzende von Ingenieuren zu koordinieren, die Codeänderungen im selben Repository durchführen. Bei so vielen Ingenieuren ist es schwierig, manuell zu koordinieren, wer welchen Code ändern soll. Dies wird noch schwieriger, wenn mehrere Projekte oder Aufgaben verschiedene Ingenieure benötigen, um dieselben wenigen Dateien zu berühren.

Wir gehen dieses Problem mit einer Merge-Warteschlange an. Zunächst bestimmen wir den Hauptzweig als „Quelle der Wahrheit“ und blockieren alle Ingenieure daran, in den Zweig zu schreiben. Zweitens haben wir einen Jenkins-Job eingerichtet, der zum Zusammenführen von Code in den Master-Zweig bestimmt ist. Dieser Job führt die Tests aus, um zu überprüfen, ob der festgeschriebene Code nichts beschädigt, bevor der neue Code in den Master-Zweig eingefügt wird. Die Techniker fügen ihre Änderungen einer Warteschlange hinzu, die nacheinander mit einem Commit verarbeitet wird, um sicherzustellen, dass keine Zusammenführungskonflikte und keine fehlgeschlagenen Tests auftreten. Dies ist die Merge-Warteschlange.

Hosenbau

Das nächste Problem, das wir ansprechen möchten, ist die Organisation eines großen Repositorys. Wir lösen dieses Problem mit einem Drittanbieter-Tool namens pantsbuild, einem Build-System für Monorepos. Wie bereits erwähnt, kann Monorepos zwar das Abhängigkeitsmanagement vereinfachen, es ist jedoch auch einfach, Code unnötig zu koppeln, wenn wir nicht aufpassen, und wir werden eine systematische Methode benötigen, um die großen Mengen an Code voneinander zu trennen.

Pantsbuild ist ein Build-System, mit dem wir diese Probleme angehen können, indem die Abhängigkeiten explizit mit BUILD-Dateien abgebildet werden. BUILD-Dateien ermöglichen es Ingenieuren, die Beziehungen zwischen verschiedenen Quelldateien explizit anzugeben. Dadurch kann Pantsbuild einen vollständigen Abhängigkeitsbaum für das Repository erstellen, und wie wir sehen werden, gibt es viele vorteilhafte Funktionen, die dieser Abhängigkeitsbaum ermöglicht.

Ein Abhängigkeitsbaum ermöglicht zunächst Verbesserungen der Build-Effizienz. Mit explizit deklarierten Abhängigkeiten kann pantsbuild jetzt nur Code kompilieren, der bekanntermaßen beim Erstellen eines Projekts erforderlich ist, anstatt das gesamte Repository zu kompilieren, was sehr umfangreich sein kann. Darüber hinaus kann es Zwischenartefakte für zukünftige Kompilierungen zwischenspeichern, wobei die Wiederholung identischer Arbeit übersprungen wird, wenn sich der Code für bestimmte Zwischenabhängigkeiten nicht geändert hat. Zum Beispiel müssen Kernbibliotheken, die sich häufig nicht ändern, nicht jedes Mal neu kompiliert werden. Das spart viel Zeit, da die Zeit für die Erstellung eines Projekts verkürzt wird.

In ähnlicher Weise verbessert ein Abhängigkeitsbaum auch die Testeffizienz, indem nur Ziele getestet werden, die von den letzten Änderungen betroffen sind. Während Monorepos die einfache Wiederverwendung von Code ermöglichen, kann das Testen von Codeänderungen oft langwierig und zeitaufwändig sein, wenn nicht klar ist, welche Dateien von der Codeänderung betroffen sind. Mithilfe eines Abhängigkeitsbaums können wir genau ermitteln, welche Projekte betroffen sind, und nur die Tests ausführen, die für die von uns getestete Änderung relevant sind. Nachdem mit dem Testen nur betroffener Dateien begonnen wurde, haben sich die Aufträge für die Zusammenführungswarteschlange erheblich beschleunigt, da die Testzeit von 12 Minuten auf 3 sank.

Schließlich hilft Pantsbuild uns, unsere Abhängigkeiten von Drittanbietern zu verwalten. Es gibt Hunderte von Bibliotheken von Drittanbietern, auf die wir hier bei OfferUp angewiesen sind. Wenn Sie versuchen, den Quellcode zu verwalten und / oder Links zu diesen Artefakten herunterzuladen, wird dies ein Albtraum sein. Glücklicherweise setzt Pantsbuild Paketmanager von Drittanbietern wie Garne, Pip und Maven ein, um diese Abhängigkeiten von Drittanbietern für uns zu verwalten. Dadurch können wir uns auf diese Bibliotheken mit nur wenigen Codezeilen wie den folgenden verlassen, anstatt den eigentlichen Quellcode oder die Artefakte beizubehalten:

jar_library (name = ’commons-lang3 ')
Gläser = [
jar (org = ’org.apache.commons’, name = ’commons-lang3 ', rev =’ 3.4')
]
)

Konfigurierte Abhängigkeiten haben einige Vorteile: Wir können die Version leicht ändern und sogar den Namen der Bibliothek umbenennen, um widersprüchliche Namen oder Versionen aufzulösen. Dies macht die Handhabung von Abhängigkeiten von Drittanbietern unglaublich einfach und ermöglicht das Einchecken dieser Konfigurationen als Teil des Codes.

Die Verwendung von Pantsbuild hat die Verwaltung unserer Monorepo vereinfacht, und wir versuchen ständig, mehr Teile unserer Codebasis mit den Funktionalitäten zu integrieren, die sie bieten. Eine Lektion, die wir in diesem Prozess gelernt haben, ist sicherzustellen, dass wir ihre Funktionen wie vorgesehen einsetzen. BUILD-Dateien sollen beispielsweise detaillierte und fein abgestufte Abhängigkeiten deklarieren, wodurch die Effizienz maximiert wird, indem möglichst viele irrelevante Teile ausgeschlossen werden. Gleichzeitig funktioniert eine BUILD-Datei, die nicht so detailliert oder sorgfältig geschrieben wurde, häufig noch, einschließlich aller erforderlichen Abhängigkeiten und einer Vielzahl nicht zusammenhängender Abhängigkeiten. Dies verringert häufig die Wirksamkeit der Pantsbuild-Algorithmen und verringert unsere Effizienz, und wir müssen sowohl bei der Codewartung als auch bei der Ausbildung der Ingenieure vorsichtig sein, wenn wir unsere Codebasis weiter verbessern.

Dienstbereitstellung

Nun, da wir darüber gesprochen haben, wie wir unsere Codebase verwalten, ist es an der Zeit zu prüfen, wie der Code funktioniert. Was nützt der Code in einem Repository und dient nicht den Kunden? Da unser Code in einem Monorepo organisiert ist, können wir eine Reihe von Tools entwickeln, um die meisten unserer Dienste bereitzustellen. In diesem Abschnitt werden wir kurz darüber sprechen.

Wir hosten die meisten unserer Services auf AWS, viele auf EC2-Instanzen. Für solche Dienste starten wir eine Autoskalierungsgruppe (ASG) mit benutzerdefinierten Regeln für jede dieser Services, sodass sie je nach Verkehrsaufkommen skaliert werden kann. So stellen wir sicher, dass wir stets die Bedürfnisse unserer Kunden bedienen, ohne unnötige Ressourcen zu verschwenden.

Jede ASG benötigt eine Startkonfiguration, um bei Bedarf neue Instanzen hochzufahren. Die Startkonfiguration gibt das Amazon Machine Image (AMI) an, das die neuen Instanzen beim Hochlauf verwenden sollen, sowie einige andere Informationen, z. B. den Instanztyp, der aktiviert werden soll. Das Einrichten der Startkonfigurationen und AMI ist keine triviale Aufgabe und kann sehr fehleranfällig und zeitaufwändig sein, wenn wir sie von Hand machen, was unsere schnelle Iteration erschwert. Um dies zu mildern, haben wir Schritte unternommen, um diese Prozesse zu automatisieren, sodass sich unsere Ingenieure nur auf den Code konzentrieren können und nicht auf die Logistik, um ihren Code in die Produktion zu bringen.

AMI Creation

Der erste wichtige Schritt, um sicherzustellen, dass wir Code in Service umwandeln können, ist das Erstellen des AMI, das den Code des bestimmten Dienstes hostet, für den er erstellt wird. Wir haben das Gebäude des AMI in zwei große Schritte aufgeteilt: das Gebäude des Basis-AMI und das Gebäude des letzten AMI.

Das Basis-AMI enthält das Basisbetriebssystem und alle gängigen Tools und Bibliotheken, die von allen Diensten gemeinsam genutzt werden. Dies wird nicht sehr regelmäßig erstellt, da Änderungen nicht sehr häufig vorkommen. Anstatt es jedes Mal neu aufzubauen, haben wir eine Aufzeichnung der neuesten Basis-Ami und können einfach darauf aufbauen.

Das Gebäude des endgültigen AMI enthält einige Teile. Zuerst müssen wir das Artefakt für den bestimmten Dienst erstellen, für den wir ein AMI erstellen. Zweitens müssen wir die AMI mit der richtigen Konfiguration konfigurieren, um das Artefakt mit allen erforderlichen Abhängigkeiten zu starten. Wir automatisieren diese Teile mit einem Jenkins-Job, um diese zugrundeliegenden Aufgaben vom normalen Backend-Ingenieur abzugrenzen. Sie müssen sich nur um das Schreiben des Codes und die Auswahl des Jobs für die Erstellung des AMI kümmern. Unter der Haube definieren wir eine Reihe von Konfigurationen für den Packer, ein Werkzeug zum Packen von Artefakten in AMIs, und führen den Packer mit der Konfiguration für die Umgebung und den Dienst gemäß den Jenkins-Jobparametern aus. Kombiniert mit der Basis-AMI kombiniert das Artefakt und seine jeweilige Konfiguration die endgültige AMI, die wir auf unseren Maschinen einsetzen werden.

Alle unsere AMIs setzen sich aus zwei separaten Teilen zusammen: dem Basis-AMI, das von vielen Diensten gemeinsam genutzt wird, sowie den dienstspezifischen Teilen, die jedes Mal neu erstellt werden

Konfigurationen starten

Jetzt, da wir die AMI mit dem Service-Artefakt haben, müssen wir die richtige Startkonfiguration erstellen und sie in die ASG hochladen, damit wir mit dem aktualisierten Code EC2-Instanzen starten können. Wie bei der AMI-Erstellung automatisieren wir diesen Schritt auch von Backend-Entwicklern mit einem Jenkins-Job, sodass sie sich auf die Entwicklung ihrer Funktionen konzentrieren können. Unter dem Jenkins-Job automatisieren wir diesen Schritt mit einem anderen Drittanbieter-Tool, Ansible, das die richtige Konfiguration liest, die Startkonfiguration erstellt und die Startkonfiguration in die entsprechende ASG lädt. Sobald die ASGs aktualisiert wurden, werden alte Instanzen heruntergefahren und ihre Ersetzungen mit der aktualisierten Startkonfiguration aktiviert.

Gelegentlich möchten wir eine genauere Kontrolle über die Einführung dieser Updates. Dies könnte sein, dass wir eine neue Funktion nur auf einer Teilmenge der Maschinen testen oder einfach nur ein Update für die Produktion bereitstellen und sicherstellen, dass alles in Ordnung ist. Für diese Anlässe haben wir ein internes Tool entwickelt, mit dem der Ingenieur die Änderung freigeben kann, um die Rollout-Strategie sorgfältig zu steuern.

Zwar gibt es viele Schritte unter diesen Jenkins-Jobs und anderen Tools, die Bedienung ist für den normalen Ingenieur jedoch sehr einfach. Meistens müssen Sie lediglich den richtigen Service und die richtige Umgebung aus dem Dropdown-Menü auswählen. Unsere Automatisierungstools kümmern sich um die Codekompilierung, die AMI-Erstellung, die Startkonfiguration und die Bereitstellung. Mit diesen Tools können unsere Backend-Ingenieure ihre Energie auf die Entwicklung von Kernfunktionen konzentrieren und ihre Änderungen problemlos für Millionen von Kunden bereitstellen.

Zukünftige Arbeit

Neben dem Aufbau von AMIs und dem Start von Services mithilfe von EC2-Instanzen arbeiten wir auch an der Containerisierung unserer Services. Um dieses Ziel zu erreichen, arbeiten wir neben EC2-AMIs auch daran, Containerbilder zu erstellen. Zukünftig können wir sowohl AMI- als auch Container-Images für jeden Service erstellen.

Fazit

Das OfferUp-Engineering-Team hat erhebliche Anstrengungen unternommen, um sicherzustellen, dass unser Code so organisiert ist, dass wir uns auf das Wesentliche konzentrieren können, nämlich den besten Service für unsere Kunden aufzubauen. Ein großer Teil davon besteht darin, unseren Code mit der Monorepo-Struktur zu organisieren, die richtigen Tools zu finden, um seine Vorteile zu nutzen, und einen großen Teil des Bereitstellungsprozesses automatisieren. Wir sind zwar stolz auf das Erreichte, aber wir bemühen uns immer, besser zu werden!

Verfasst von Paul Yao

Siehe auch

DOWNLOAD (PDF) Die People's Platform: Macht und Kultur im digitalen Zeitalter wieder frei - kostenlose Testversion…Hybrid-Clustering-Ansatz für die Affinitätsausbreitung für die Erkennung von Named-EntityIntel Optane Memory: Was ist das und warum brauchen Sie es?Wie können Hotels Food & Beverage verbessern und stärken, um die Gewinne zu steigern und den Gästeservice zu erhöhenVerwenden Sie Typescript in React Native in nur 2 ZeilenCS 373 Frühjahr 2019 - Woche 9: Matthew Zhao