Vivo Global Mall: Design und Praxis der Architektur des Order Centers

1. Hintergrund

Mit dem rasanten Wachstum der Benutzer hat die monolithische Architektur des offiziellen Einkaufszentrums v1.0 in vivo allmählich ihre Nachteile aufgedeckt: Module werden aufgeblähter, die Entwicklungseffizienz ist gering, Leistungsengpässe treten auf und die Systemwartung ist schwierig.

Das Upgrade der v2.0-Architektur begann im Jahr 2017, und die physische Aufteilung des vertikalen Systems basierend auf Geschäftsmodulen, die aufgeteilten Geschäftsbereiche erfüllen jeweils ihre Aufgaben, bieten serviceorientierte Funktionen und unterstützen gemeinsam das Geschäft des Hauptstandorts.

Das Bestellmodul ist der Kern des E-Commerce-Systems. Die gesammelten Daten erreichen bald den Speicherengpass von einem Meter. Das System kann den Fluss neuer Produktfreigaben und wichtiger Werbeaktionen nicht unterstützen. Eine Service-Transformation ist unerlässlich.

In diesem Artikel werden die Probleme und Lösungen vorgestellt, die beim Aufbau des Bestellsystems für Einkaufszentren in vivo auftreten, und es werden Erfahrungen mit dem Architekturdesign ausgetauscht.

Zweitens die Systemarchitektur

Trennen Sie das Bestellmodul vom Einkaufszentrum und werden Sie unabhängig zu einem Bestellsystem. Verwenden Sie dazu eine unabhängige Datenbank, um standardisierte Services wie Bestellung, Zahlung, Logistik und Kundendienst für verwandte Systeme im Einkaufszentrum bereitzustellen.

Die Systemarchitektur ist in der folgenden Abbildung dargestellt:

Vivo Global Mall: Design und Praxis der Architektur des Order Centers

3. Technische Herausforderungen

3.1 Datenvolumen und Probleme mit hoher Parallelität

Die erste Herausforderung ist das Speichersystem:

  • Datenvolumenproblem

    Mit der Anhäufung historischer Aufträge hat die Datenmenge in der Auftragstabelle in MySQL mehrere zehn Millionen erreicht.

    Wir wissen, dass die Speicherstruktur der InnoDB-Speicher-Engine ein B + -Baum ist und die Suchzeitkomplexität O (log n) ist. Wenn daher die Gesamtdatenmenge n größer wird, verlangsamt sich die Abrufgeschwindigkeit zwangsläufig. Unabhängig davon, wie ein Index hinzugefügt oder optimiert wird, kann sie nicht gelöst werden. Ich kann mir nur Möglichkeiten vorstellen, die Datenmenge in einer einzelnen Tabelle zu reduzieren.

    Lösungen für große Datenmengen umfassen: Datenarchivierung, Untertabelle

  • Problem mit hoher Parallelität

    Das Mall-Geschäft befindet sich in einer Phase rasanter Entwicklung, die Anzahl der Bestellungen hat wiederholt neue Höhen erreicht, die Komplexität des Geschäfts nimmt ebenfalls zu und die Anzahl der Besuche von Anwendungen bei MySQL nimmt zu.

    Die Verarbeitungskapazität eines eigenständigen MySQL ist begrenzt. Wenn der Druck zu groß ist, verringert sich die Zugriffsgeschwindigkeit aller Anforderungen, und die Datenbank ist möglicherweise sogar ausgefallen.

    Lösungen mit hoher Parallelität sind: Cache, Lese- und Schreibtrennung und Unterdatenbank verwenden

Das Folgende ist eine kurze Beschreibung dieser Lösungen:

  • Datenarchivierung

    Auftragsdaten haben Zeitattribute und es gibt einen Hot-Tail-Effekt. In den meisten Fällen werden die neuesten Aufträge abgerufen, während eine große Menge alter Daten, die weniger häufig verwendet werden, in der Auftragstabelle gespeichert wird.

    Anschließend können Sie die neuen und alten Daten getrennt speichern, die historischen Aufträge in eine andere Tabelle verschieben und einige entsprechende Änderungen am Abfragemodul im Code vornehmen, wodurch das Problem des großen Datenvolumens effektiv gelöst werden kann.

  • Cache verwenden

    Die Verwendung von Redis als Pre-Cache von MySQL kann die meisten Abfrageanforderungen blockieren und die Antwortverzögerung verringern.

    Der Cache ist besonders effektiv für Systeme, die wenig mit Benutzern zu tun haben, wie z. B. Warensysteme. Bei Bestellsystemen sind die Bestelldaten jedes Benutzers jedoch unterschiedlich, und die Cache-Trefferquote ist nicht hoch und der Effekt ist nicht sehr gut.

Vivo Global Mall: Design und Praxis der Architektur des Order Centers

  • Lese- und Schreibtrennung

    Die Master-Bibliothek ist dafür verantwortlich, Datenaktualisierungsanforderungen auszuführen, die Datenänderungen dann in Echtzeit mit allen Slave-Bibliotheken zu synchronisieren und mehrere Slave-Bibliotheken zum Teilen von Abfrageanforderungen zu verwenden.

    Es gibt jedoch viele Aktualisierungsvorgänge für Auftragsdaten, und der Druck der Hauptbibliothek während der Spitzenbestellung ist immer noch nicht gelöst. Und es gibt eine Master-Slave-Synchronisationsverzögerung. Normalerweise ist die Verzögerung sehr gering, nicht mehr als 1 ms, aber sie kann zu einem bestimmten Zeitpunkt auch inkonsistente Master-Slave-Daten verursachen.

    Anschließend müssen alle betroffenen Geschäftsszenarien kompatibel behandelt werden, und es können einige Kompromisse eingegangen werden. Nachdem die Bestellung erfolgreich aufgegeben wurde, wird zunächst eine erfolgreiche Bestellseite aufgerufen, und der Benutzer kann manuell klicken, um die Bestellung anzuzeigen und die Bestellung anzuzeigen.

Vivo Global Mall: Design und Praxis der Architektur des Order Centers

  • Unterbibliothek

    Die Unterbibliothek umfasst die vertikale Unterbibliothek und die horizontale Unterbibliothek.

    ① Horizontale Unterdatenbank: Teilen Sie die Daten derselben Tabelle nach bestimmten Regeln in verschiedene Datenbanken auf, und jede Datenbank kann auf einem anderen Server abgelegt werden.

    ② Vertikale Unterdatenbank: Die Tabellen werden nach Unternehmen klassifiziert und auf verschiedene Datenbanken verteilt. Jede Datenbank kann auf einem anderen Server abgelegt werden. Das Kernkonzept ist speziellen Datenbanken gewidmet.

  • Untertabelle

    Die Untertabellen umfassen vertikale Untertabellen und horizontale Untertabellen.

    ** ①Horizontale Untertabelle  : ** In derselben Datenbank werden die Daten einer Tabelle nach bestimmten Regeln in mehrere Tabellen unterteilt.

    ** ②Vertikale Untertabelle  : ** Teilen Sie eine Tabelle nach Feldern in mehrere Tabellen auf, und jede Tabelle speichert einige der Felder.

Wir haben die Kosten, Auswirkungen und Auswirkungen der Umwandlung in das bestehende Geschäft umfassend berücksichtigt und beschlossen, den letzten Trick direkt anzuwenden: Unterdatenbank und Untertabelle

3.2 Auswahl der Subbibliothek und der Submeter-Technologie

Die technische Auswahl der Unterbibliothek und der Untertabelle wird hauptsächlich aus folgenden Richtungen betrachtet:

  1. Client SDK Open Source-Lösung

  2. Open Source-Lösung für Middleware-Proxy

  3. Selbstforschungs-Framework vom Middleware-Team des Unternehmens

  4. Bauen Sie Ihre eigenen Räder

Nach Bezugnahme auf frühere Projekterfahrungen und Kommunikation mit dem Middleware-Team des Unternehmens wurde die Open-Source - Lösung Sharding-JDBC übernommen. Es wurde jetzt in Sharding-Sphere umbenannt.

  • Github:https://github.com/sharding-sphere/

  • Dokumente: Die offiziellen Dokumente sind grob, aber Online-Materialien, Quellcode-Analysen und Demos sind relativ umfangreich

  • Community: Aktiv

  • Features: Wird im JAR-Paket bereitgestellt, gehört zur clientseitigen Fragmentierung und unterstützt die xa-Transaktion

Vivo Global Mall: Design und Praxis der Architektur des Order Centers  

3.2.1 Subdatenbank-Subtabellenstrategie

Kombinieren Sie Geschäftsmerkmale, wählen Sie die Benutzer-ID als Sharding-Schlüssel aus und ermitteln Sie die Datenbanktabellennummer der Benutzerauftragsdaten, indem Sie den Hash-Wert der Benutzer-ID berechnen und dann den Modul verwenden.
Angenommen, es gibt insgesamt n Bibliotheken, und jede Bibliothek verfügt über m Tabellen.

Die Berechnungsmethode für die Nummer der Bibliothekstabelle lautet:

-Bibliotheks-Seriennummer: Hash (Benutzer-ID) / m% n

-Tabellen-Seriennummer: Hash (userId)% m

Der Routing-Prozess ist in der folgenden Abbildung dargestellt:

Vivo Global Mall: Design und Praxis der Architektur des Order Centers

3.2.2 Einschränkungen von Unterdatenbanken und Untertabellen und Lösungen

Die Subtabelle der Subdatenbank löst das Problem des Datenvolumens und der Parallelität, schränkt jedoch die Abfragefähigkeit der Datenbank erheblich ein. Einige zuvor einfache verwandte Abfragen werden möglicherweise nach der Subtabelle der Subdatenbank und der Tabelle nicht realisiert, sodass Sie sie separat prüfen müssen Diese von Sharding-JDBC nicht unterstützten SQLs werden neu geschrieben.

Darüber hinaus wurden diese Herausforderungen angetroffen:

(1) Weltweit einzigartiges ID-Design

Nach dem Sharding der Datenbank und der Tabellen ist der Primärschlüssel für die automatische Inkrementierung der Datenbank nicht mehr global eindeutig und kann nicht als Bestellnummer verwendet werden. Viele Interaktionsschnittstellen zwischen internen Systemen haben jedoch nur eine Bestellnummer. Für diesen Shard-Schlüssel gibt es keine Benutzeridentifikation. So finden Sie die entsprechende Bestellnummer Was ist mit dem Bibliothekstisch?

Es stellte sich heraus, dass wir bei der Generierung der Bestellnummer implizit die Nummer der Bibliothekstabelle angegeben haben. Auf diese Weise kann die Bibliothekstabellennummer aus der Bestellnummer ohne Benutzeridentifikation erhalten werden.

(2) Die historische Bestellnummer enthält keine impliziten Informationen zur Bibliothekstabelle

In einer einzelnen Tabelle wird die Zuordnungsbeziehung zwischen historischen Bestellnummern und Benutzer-IDs gespeichert. Mit der Zeit werden diese Aufträge nach und nach nicht mehr für die Interaktion zwischen Systemen verwendet.

(3) Der Verwaltungshintergrund muss alle Aufträge, die die Bedingungen erfüllen, nach verschiedenen Filterbedingungen abfragen

Die Bestelldaten werden redundant in der Suchmaschine Elasticsearch gespeichert, die nur für Hintergrundabfragen verwendet wird.

3.3 So synchronisieren Sie Daten von MySQL nach ES

Wie oben erwähnt, speichern wir die Bestelldaten redundant in Elasticsearch, um die Verwaltung von Back-End-Abfragen zu vereinfachen. Wie synchronisieren Sie dann die Bestelldaten in MySQL mit ES, nachdem sie geändert wurden?

Was hier berücksichtigt werden muss, ist die Aktualität und Konsistenz der Datensynchronisation, ein geringer Eingriff in den Geschäftscode und hat keinen Einfluss auf die Leistung des Dienstes selbst.

  • MQ-Schema

    ES Update Service als Verbraucher, aktualisieren Sie ES nach Erhalt der MQ-Nachricht zur Auftragsänderung

Vivo Global Mall: Design und Praxis der Architektur des Order Centers

  • Binlog-Lösung

    Der ES-Aktualisierungsdienst verwendet Open-Source-Projekte wie canal, um sich als Slave-Knoten von MySQL auszugeben, empfängt Binlog und analysiert es, um Echtzeit-Datenänderungsinformationen zu erhalten, und aktualisiert ES dann basierend auf diesen Änderungsinformationen.

Vivo Global Mall: Design und Praxis der Architektur des Order Centers

Unter diesen ist die BinLog-Lösung allgemeiner, aber auch komplizierter zu implementieren. Wir haben uns schließlich für die MQ-Lösung entschieden.

Da ES-Daten nur im Verwaltungshintergrund verwendet werden, sind die Anforderungen an Datenzuverlässigkeit und Echtzeitsynchronisation nicht besonders hoch.

Unter Berücksichtigung extremer Situationen wie Ausfallzeiten und Nachrichtenverlust wird im Hintergrund die Funktion zum manuellen Synchronisieren von ES-Daten unter bestimmten Bedingungen hinzugefügt, um dies zu kompensieren.

3.4 So ersetzen Sie die Datenbank sicher

Das Migrieren von Daten aus der ursprünglichen Einzelinstanzdatenbank in den neuen Datenbankcluster ist ebenfalls eine große technische Herausforderung

Nicht nur, um die Richtigkeit der Daten sicherzustellen, sondern auch, um sicherzustellen, dass Sie nach jedem Schritt, sobald ein Problem auftritt, schnell zum vorherigen Schritt zurückkehren können.

Wir haben zwei Optionen für die Migration mit Ausfallzeit und die Migration ohne Ausfallzeit in Betracht gezogen:

(1) Non-Stop-Migrationsplan:

  • Kopieren Sie die Daten aus der alten Bibliothek in die neue Bibliothek, starten Sie ein Synchronisierungsprogramm und verwenden Sie Binlog und andere Lösungen, um die alten Bibliotheksdaten in Echtzeit mit der neuen Bibliothek zu synchronisieren.

  • Der neue und alte Bibliotheksdienst für Doppelschreibaufträge wird gestartet, und nur die alte Bibliothek wird gelesen und geschrieben.

  • Aktivieren Sie das doppelte Schreiben, stoppen Sie gleichzeitig das Synchronisationsprogramm und starten Sie das Vergleichskompensationsprogramm, um sicherzustellen, dass die neuen Bibliotheksdaten mit den alten Bibliotheken übereinstimmen.

  • Schalten Sie die Leseanforderungen schrittweise in die neue Bibliothek.

  • Sowohl Lesen als auch Schreiben werden auf die neue Bibliothek umgeschaltet, und das Kompensationsprogramm wird verglichen, um sicherzustellen, dass die alten Bibliotheksdaten mit der neuen Bibliothek übereinstimmen.

  • Offline-Bibliotheken, Double-Write-Funktionen für Offline-Bestellungen, Offline-Synchronisationsverfahren und Vergleichskompensationsverfahren.

Vivo Global Mall: Design und Praxis der Architektur des Order Centers Vivo Global Mall: Design und Praxis der Architektur des Order Centers

(2) Migrationsplan zum Herunterfahren:

  • Das neue Bestellsystem wurde gestartet, das Migrationsprogramm wurde ausgeführt, um die Bestellungen vor zwei Monaten mit der neuen Datenbank zu synchronisieren, und die Daten wurden geprüft.

  • Fahren Sie die Mall V1-Anwendung herunter, um sicherzustellen, dass sich die alten Datenbankdaten nicht ändern.

  • Führen Sie den Migrationsprozess durch, synchronisieren Sie die Aufträge, die im ersten Schritt nicht migriert wurden, in die neue Bibliothek und führen Sie Audits durch.

  • Starten Sie die Mall V2-Anwendung, starten Sie den Test und die Überprüfung. Wenn dies fehlschlägt, kehren Sie zur Mall V1-Anwendung zurück (das neue Bestellsystem verfügt über einen Schalter zum doppelten Schreiben der alten Bibliothek).

Vivo Global Mall: Design und Praxis der Architektur des Order Centers Vivo Global Mall: Design und Praxis der Architektur des Order Centers

Unter Berücksichtigung der Tatsache, dass die Kosten für die Non-Stop-Lösung hoch sind und der Geschäftsverlust der Nacht-Shutdown-Lösung nicht groß ist, ist die endgültige Wahl die Shutdown-Migrationslösung.

3.5 Problem mit verteilten Transaktionen

Im Transaktionsprozess des E-Commerce sind verteilte Transaktionen ein klassisches Problem, wie z.

  • Nachdem der Benutzer erfolgreich bezahlt hat, muss er das Liefersystem benachrichtigen, um die Waren an den Benutzer zu liefern.

  • Nachdem der Benutzer die Quittung bestätigt hat, muss er das Punktesystem benachrichtigen, um dem Benutzer Punkte für Einkaufsbelohnungen auszustellen.

Wie stellen wir die Datenkonsistenz unter der Microservice-Architektur sicher?

Unterschiedliche Geschäftsszenarien stellen unterschiedliche Anforderungen an die Datenkonsistenz. Zu den gängigen Lösungen in der Branche gehören die zweiphasige Übermittlung (2PC) und die dreiphasige Übermittlung (3PC) zur Lösung einer starken Konsistenz sowie die lokale TCC-Nachricht. Transaktionsnachrichten und Best-Effort-Benachrichtigungen usw.

Hier finden Sie keine detaillierte Beschreibung des obigen Schemas, sondern stellen das von uns verwendete lokale Nachrichtentabellenschema vor: Die in der lokalen Transaktion auszuführende asynchrone Operation wird in der Nachrichtentabelle aufgezeichnet. Wenn die Ausführung fehlschlägt, kann sie durch Zeitsteuerungsaufgaben kompensiert werden.

Die folgende Abbildung zeigt ein Beispiel für die Benachrichtigung des Punktesystems, um nach Abschluss der Bestellung Punkte zu vergeben.

Vivo Global Mall: Design und Praxis der Architektur des Order Centers Vivo Global Mall: Design und Praxis der Architektur des Order Centers

3.6 Systemsicherheit und Stabilität

  • Netzwerkisolation

    Über das externe Netzwerk kann nur auf eine sehr kleine Anzahl von Schnittstellen von Drittanbietern zugegriffen werden, und alle überprüfen die Signatur. Das interne System interagiert mit dem internen Netzwerkdomänennamen und der RPC-Schnittstelle.

  • Gleichzeitige Sperre

    Vor jeder Auftragsaktualisierung wird sie durch die Sperre auf Datenbankzeilenebene eingeschränkt, um gleichzeitige Aktualisierungen zu verhindern.

  • Idempotenz

    Alle Schnittstellen sind idempotent, sodass Sie sich keine Gedanken über die Auswirkungen der Netzwerk-Timeout-Wiederholung der anderen Partei machen müssen.

  • Sicherung

    Verwenden Sie Hystrix-Komponenten, um Echtzeitaufrufen an externe Systeme einen Sicherungsschutz hinzuzufügen, um zu verhindern, dass sich die Auswirkungen eines Systemfehlers auf das gesamte verteilte System ausbreiten.

  • Überwachung und Alarmierung

    Durch die Konfiguration des Fehlerprotokollalarms der Protokollplattform, des Serviceanalysealarms der Anrufkette sowie der Überwachungs- und Alarmfunktionen der Middleware und der Basiskomponenten des Unternehmens können erstmals Systemanomalien festgestellt werden.

3.7 Stufengrube

Bei Verwendung der MQ-Verbrauchsmethode zum Synchronisieren der auftragsbezogenen Daten der Datenbank mit dem ES sind die gefundenen geschriebenen Daten nicht die neuesten Daten der Bestellung

Die linke Seite der Abbildung unten zeigt den ursprünglichen Plan:

Wenn beim Laden des MQ der Auftragsdatensynchronisierung Thread A zuerst ausgeführt wird und die Daten herausfindet, werden die Auftragsdaten zu diesem Zeitpunkt aktualisiert, und Thread B beginnt mit der Synchronisierungsoperation. Nachdem die Auftragsdaten gefunden wurden, werden sie vor Thread A in ES geschrieben. Wenn Thread A das Schreiben ausführt, werden die von Thread B geschriebenen Daten überschrieben, was dazu führt, dass die Bestelldaten in ES nicht die neuesten sind.

Die Lösung besteht darin, beim Abfragen der Auftragsdaten eine Zeilensperre hinzuzufügen. Das gesamte Geschäft wird in einer Transaktion ausgeführt, und der nächste Thread wird ausgeführt, nachdem die Ausführung abgeschlossen ist.

Vivo Global Mall: Design und Praxis der Architektur des Order Centers Vivo Global Mall: Design und Praxis der Architektur des Order Centers

sharding-jdbc Nach dem Gruppieren, Sortieren und Paging werden alle Datenprobleme abgefragt

示例 : Wählen Sie eine aus der temporären Gruppe in einer Reihenfolge von , b nach einer absteigenden Grenze von 1,10。 aus

Die Implementierung besteht darin, dass die Felder "Gruppieren nach" und "Sortieren nach" in Sharding-jdbc nicht mit der Reihenfolge übereinstimmen und 10 auf Integer.MAX_VALUE festgelegt ist, wodurch die Paging-Abfrage fehlschlägt.


io.shardingsphere.core.routing.router.sharding.ParsingSQLRouter#processLimit

private void processLimit(final List<Object> parameters, final SelectStatement selectStatement, final boolean isSingleRouting) {
     boolean isNeedFetchAll = (!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) && !selectStatement.isSameGroupByAndOrderByItems();
    selectStatement.getLimit().processParameters(parameters, isNeedFetchAll, databaseType, isSingleRouting);
}

io.shardingsphere.core.parsing.parser.context.limit.Limit#processParameters

/**
* Fill parameters for rewrite limit.
*
* @param parameters parameters
* @param isFetchAll is fetch all data or not
* @param databaseType database type
* @param isSingleRouting is single routing or not
*/
public void processParameters(final List<Object> parameters, final boolean isFetchAll, final DatabaseType databaseType, final boolean isSingleRouting) {
    fill(parameters);
    rewrite(parameters, isFetchAll, databaseType, isSingleRouting);
}

private void rewrite(final List<Object> parameters, final boolean isFetchAll, final DatabaseType databaseType, final boolean isSingleRouting) {
    int rewriteOffset = 0;
    int rewriteRowCount;
    if (isFetchAll) {
        rewriteRowCount = Integer.MAX_VALUE;
    } else if (isNeedRewriteRowCount(databaseType) && !isSingleRouting) {
         rewriteRowCount = null == rowCount ? -1 : getOffsetValue() + rowCount.getValue();
    } else {
       rewriteRowCount = rowCount.getValue();
    }
    if (null != offset && offset.getIndex() > -1 && !isSingleRouting) {
       parameters.set(offset.getIndex(), rewriteOffset);
     }
     if (null != rowCount && rowCount.getIndex() > -1) {
        parameters.set(rowCount.getIndex(), rewriteRowCount);
      }
}

Der korrekte Wortlaut sollte a aus der temporären Gruppe mit einem absteigenden b-Limit von 1,10 sein. Die verwendete Version ist 3.1.1 von shared-jdbc.

Wenn die ES-Paging-Abfrage doppelte Werte im Sortierfeld enthält, fügen Sie am besten ein eindeutiges Feld als zweite Sortierbedingung hinzu, um zu vermeiden, dass Daten fehlen und doppelte Daten während der Paging-Abfrage erkannt werden. Beispielsweise wird die Erstellungszeit der Bestellung als einzige Sortierbedingung verwendet. Wenn im Laufe der Zeit viele Daten vorhanden sind, werden die abgefragten Bestellungen weggelassen oder dupliziert. Es ist erforderlich, einen eindeutigen Wert als zweite Sortierbedingung hinzuzufügen oder den eindeutigen Wert direkt als Sortierbedingung zu verwenden.

4. Ergebnisse

  • Einmaliger Online-Erfolg, stabiler Betrieb seit mehr als einem Jahr

  • Die Leistung des Kerndienstes wurde um mehr als das Zehnfache verbessert

  • Systementkopplung, iterative Effizienz wird stark verbessert

  • Kann die rasante Entwicklung des Einkaufszentrums für mindestens fünf Jahre unterstützen

Fünf, Fazit

Wir haben bei der Entwicklung des Systems nicht blindlings die neuesten Technologien und Ideen verfolgt oder bei Problemen direkt die gängigen E-Commerce-Lösungen übernommen, sondern die am besten geeignete Methode entsprechend den tatsächlichen Geschäftsbedingungen ausgewählt.

Persönlich bin ich der Meinung, dass ein gutes System zu Beginn nicht von Daniel entworfen wurde, sondern schrittweise mit der Entwicklung und Entwicklung des Geschäfts wiederholt werden muss, um weiterhin die Richtung der Geschäftsentwicklung vorherzusagen und im Voraus einen Plan für die Architekturentwicklung zu formulieren Das heißt: Gehen Sie an die Spitze des Geschäfts!

Autor: vivo offizielle Website Mall Entwicklungsteam

Ich denke du magst

Origin blog.51cto.com/14291117/2575533
Empfohlen
Rangfolge