Microservices StrukturPlanung

Diskutiere Microservices StrukturPlanung im Application Tier Bereich.
mrBrown

mrBrown

Ich hab das mal getestet mit lokaler Postgres-DB (ohne Publishen über RabbitMQ, und mit nur Preis und Anzahl), das braucht konstant unter 10s für das „Berechnen“ der Änderungen von ~100_000 Artikel.

Ich stell den Code gleich mal Online und schreib was dazu.
 
NicoDeluxe

NicoDeluxe

Es gab eine Anforderung, dass Artikel wieder aktiviert werden können indem sie wieder in der Datei vorkommen. Manche GH nehmen die raus wenn sie für eine gewisse Zeit nicht zur Verfügung stehen. Mit dem Update kommen wir da gut klar und dem dem temp_status. Wenn Die Artikel wieder aktiviert werden, können wir drauf reagieren und den Artikel auch wieder aktiv setzen
 
mrBrown

mrBrown


Statt Hibernate nutzt das JOOQ. Hibernate verursacht da relativ viel Overhead, den man nicht braucht, mit JOOQ läuft das deutlich leichtgewichtiger.

Erkennen der Änderungen wird gemacht, indem jeder Eintrag eine Versionsnummer bekommt, die DB kann dann die Änderungen "berechnen", etwa wie in dem SqlFiddle weiter oben. Den FULL OUTER JOIN kann man sicher noch optimieren, das Einfügen ist allerdings das deutlich langsamere.
Postgres läuft dabei mit weniger als 200Mb Ram (±, docker stats lag zumindest deutlich drunter.)
 
NicoDeluxe

NicoDeluxe

oh man das ist ja nett, danke für deine Hilfe. Hast ein Bier gut bei mir :D schau ich mir dann mal an. Du nutzt Gitlab? Ich überlege Bitbucket abzusägen und auf github umzusteigen, hab mich aber noch nicht damit beschäftigt was es kostet usw.
 
NicoDeluxe

NicoDeluxe

ne nich wirklich :D Probier nur immer gern neues aus.
Werde die Services dann auch jeweils als eigenes Repo checken. Derzeit hab ich ein Repo wo alle Services drin sind. Wobei ja Jira mit Bitbucket gut zusammenarbeiten kann (angeblich, ich bekomms nicht hin :p)
 
NicoDeluxe

NicoDeluxe

Halöööle :D

Sitz grad vor folgender Situation:

Ein Scheduler wird jede Stunde aufgerufen und liest UserSettings. Diese Usersettings hängt er an eine Message. Der Listener holt das ab, macht seine Arbeit und schiebt die geänderten Produkte in einen anderen Queue und hängt die Settings wieder an und sendet die mit. Idee dabei ist, beim "UpdateListener" nicht pro Message die Settings jedesmal zu lesen um die DB zu schonen.

Dumm ist jetzt aber, dass wenn ich die UserSettings um ein Feld erweitere, die anderen Services noch das alte "UserSettings" Objekt kennen wor das neue Feld fehlt.

Heißt ich müsste jetzt jeden Service neu bauen und neu starten.

Ich könnte auch im UpdateService eine Map einfügen welche die UserSettings zwischen speichert, dann ist aber das Problem dass während der Laufzeit die UserSettings sich ändern können und in der Map falsche Einstellungen liegen.

Am einfachsten wäre es ja die UserSettings jedesmal frisch zu lesen, aber das frisst doch unheimlich Resource bei 10.000 Messages und mehr.

Hat jemand ne andere Idee?
 
NicoDeluxe

NicoDeluxe

Ich könnte doch eigentlich die Settings als Map aufbauen

Map <String, Object> settings;

und darin

settings.put("calculate_price", true);
settings.put("update_stock", false);

So bleibts dynamisch. Wenn eine neue Einstellung dazu kommen, stecke ich die im Scheduler in die Map. Der UpdateService muss dann nur erweitert werden, den neuen Key zu kapieren
 
mrBrown

mrBrown

Ein Scheduler wird jede Stunde aufgerufen und liest UserSettings. Diese Usersettings hängt er an eine Message.
In welchem Service liegt der Scheduler und wer braucht die UserSettings?

Potentiell kann man völlig auf Scheduler verzichten. Eine Änderung der UserSettings fällt ja nicht plötzlich vom Himmel, sondern wird irgendwie ausgelöst - an der Stelle könnte man direkt das Event werfen.

Idee dabei ist, beim "UpdateListener" nicht pro Message die Settings jedesmal zu lesen um die DB zu schonen.
Was ist der UpdateListener?

Man kann die Settings auch einfach direkt im Service halten - wenn die Änderungen immer über ein Event kommen, muss der die nie aus der DB holen.

Dumm ist jetzt aber, dass wenn ich die UserSettings um ein Feld erweitere, die anderen Services noch das alte "UserSettings" Objekt kennen wor das neue Feld fehlt.

Heißt ich müsste jetzt jeden Service neu bauen und neu starten.
Das muss man bei jeder Änderung. Du musst nur drauf achten, dass die Änderung möglichst Abwärtskompatibel ist, größere Änderungen muss man dann uU über zwei Updates ausrollen.
Ich könnte auch im UpdateService eine Map einfügen welche die UserSettings zwischen speichert, dann ist aber das Problem dass während der Laufzeit die UserSettings sich ändern können und in der Map falsche Einstellungen liegen.

Am einfachsten wäre es ja die UserSettings jedesmal frisch zu lesen, aber das frisst doch unheimlich Resource bei 10.000 Messages und mehr.
Wie oben gesagt: der Service bekommt das einfach per Event, aus der DB muss das nie gelesen werden. Einfach direkt im Service halten, bei Änderungen kommt ein Event welches die Daten dann ändert.

Ich könnte doch eigentlich die Settings als Map aufbauen

[...]

So bleibts dynamisch. Wenn eine neue Einstellung dazu kommen, stecke ich die im Scheduler in die Map. Der UpdateService muss dann nur erweitert werden, den neuen Key zu kapieren
Den gleichen Effekt kannst du mit jeder Datenstruktur haben - du musst immer nur darauf achten, dass aktuelle Messages auch von eine Version zurück hängenden Services verstanden haben.
Die Messages selber müssen nur in einem „generischen“ Format vorliegen, also zB nicht die normale Java-Serialisierung.
Jeder Service deserialisiert sich das dann in das Format, welches er braucht.
Service 1 kann ein normales Java-Objekt draus machen, Service 2 ne Map, und Service 3 und C ein Struct.
 
NicoDeluxe

NicoDeluxe

Danke für deine (wieder) ausführliche Antwort. Der Scheduler ist ein Service der nur steuert welcher Hersteller wann importieren soll. Das haben wir ausgelagert damit neue Hersteller dazukommen können ohne irgendeinen anderen Service neustarten zu müssen.

Hab es jetzt so gemacht, dass der Scheduler die Settings holt, eine Map draus macht und dem HerstellerService per JMS übergibt. Der HerstellerService holt sich dann dort die Logindaten raus zb.

Wenn der Hesteller fertig ist, gibt der Die Settings wieder mit an den "Master" der dann die anderen Settings braucht.

so kann ein User die Einstellungen ändern und beim nächsten Updatelauf werden die neuen Settings geladen und beachtet. Sollte erstmal reichen. Abwärtskompatibilität sollllllte erstmal noch kein Thema sein hoff ich. Werd ich im weiteren Verlauf merken :D
 
mrBrown

mrBrown

Der Scheduler ist ein Service der nur steuert welcher Hersteller wann importieren soll. Das haben wir ausgelagert damit neue Hersteller dazukommen können ohne irgendeinen anderen Service neustarten zu müssen.
Und was macht der Scheduler, was die Services nicht selbst könnten? Aktuell klingt das wie eine zentrale Abhängigkeit, die man nach Möglichkeit vermeiden will.

Hab es jetzt so gemacht, dass der Scheduler die Settings holt, eine Map draus macht und dem HerstellerService per JMS übergibt. Der HerstellerService holt sich dann dort die Logindaten raus zb.
Warum bekommt der Service die Daten nicht direkt, wenn die Daten erstellt/geändert werden?

Das regelmäßige schicken der Daten ist doch überflüssig - es muss nur sicher gestellt werden, dass der Service jeweils die aktuellen hat, dafür recht ja einmaliges Schicken bei Änderungen.

Wenn der Hesteller fertig ist, gibt der Die Settings wieder mit an den "Master" der dann die anderen Settings braucht.
Das klingt auch unnötig. Der Master kann die Daten auch einfach direkt bekommen, anstatt jedes Mal auf's Neue.

Ansonsten baut man sich nur einen verteilten Monolithen.
 
NicoDeluxe

NicoDeluxe

Der Scheduler informiert die Hersteller Services, dass sie arbeiten sollen. Dabei gibts paar Einschränkungen zb Wartungsmodus, dann darf kein Hersteller beginnen. Der Scheduler sendet dann keine Messages an die Hersteller. Zudem prüft er ob die Einstellungen valide sind, wenn nicht bekommt der User eine Info dass das Update aufgrund XXX nicht starten konnte.
Das @Scheduled und die Validation Prüfung könnte auch direkt in den Herstellerservice, aber dann hab ich so viele Stellen wo was angepasst werden müsste

Mir sind da auch noch ein paar zu viele Abhängigkiten, aber bekomme die nicht alle gelöst ohne dass es nachher mehr Aufwand ist wenn was neues eingebaut wird.
 
mrBrown

mrBrown

Sehr grob und unübersichtlich, aber ich hoffe man versteht es:
Untitled Diagram.png

Jeder Service bekommt über Events (/Messages) das, was für ihn interessant ist, und speichert das alles lokal bei sich ab.

Der User-Service veröffentlicht alle Änderungen an Kunden-Daten, zB Login-Daten oder Preis-Berechnung. Hersteller und Master-Service subscriben sich an der Topic dafür.
Der Hersteller-Service interessiert sich zb für die Login-Daten eines Kunden für den Hersteller, wenn er das Event bekommt, und speichert alles was er baucht lokal bei sich ab.
Der Master-Service macht das gleiche für die Daten die ihn interessieren, zB die Preisberechnung.

Brauchen Master-/Hersteller-Service dann welche von den Daten, haben sie die lokal direkt verfügbar.

Der Scheduler informiert die Hersteller Services, dass sie arbeiten sollen.
Das könnten die Services auch einfach selbst machen, damit sind die tendenziell auch freier in der Entscheidung und können das zB von lokaler Last abhängig machen.

Dabei gibts paar Einschränkungen zb Wartungsmodus, dann darf kein Hersteller beginnen.
Ist der Wartungsmodus Teil der Business-Logik, also in der Fachlichkeit vorhanden, sowas wie "Hersteller X macht Urlaub, in der Zeit darf man keine Anfragen stellen"?
Oder eher auf Infrastruktur/Anwedungsseite, sowas wie "Hardware wird getauscht"?

Ersteres kann man auch einfach über Events löse, Service bekommt das Event über Start/Ende des "Wartungsmodus" und reagiert entsprechend.
Letzeres würd ich unabhängig davon lösen, und von der Business-Logik trennen.

Zudem prüft er ob die Einstellungen valide sind, wenn nicht bekommt der User eine Info dass das Update aufgrund XXX nicht starten konnte.
Warum muss das stündlich geprüft werden? Kann es passieren, das valide Daten plötzlich, ohne äußeres Ereignis, invalide werden?

Wenn Nein -> dann brauchts keine stündliche Prüfung, Invalide Daten sollten den Hersteller-Service auch gar nicht erreichen.
Wenn Ja -> Event für "Daten wieder gültig"/"Daten nicht gültig".
 
NicoDeluxe

NicoDeluxe

Guten Morgen,

oh man vielen Dank für Deine Mühe! Der Wartungsmodus ist eher von unserer Seite aus zu sehen, Hardwartausch ist ein gutes Bespiel.
Das stündliche prüfen der Einstellungen ist nötig, da der Kunde Schrott eingeben kann / Unlogische Einstellungen setzen. Das sollte eigentlich das Frontend verhindern aber soweit sind wir noch nicht.

Zu der Grafik: Verstehe ich es richtig, dass du die Settings jeden Herstellers im Hersteller Service speicherst und der Hersteller die Settings in sich anwendet? Zb Kunde hat eingestellt, dass alle Artikel mit Bestand 0 deaktiviert werden - der Hersteller Service überprüft den Bestand beim einlesen und setzt auf inaktiv? So wäre es sehr logisch abgetrennt, was mich da aber stört ist wenn wir da an der Logik etwas ändern, muss das in jedem Herstellerservice nachgebaut werden. Das kann sehr aufwändig werden oder?
Daher die Idee diese unspezifischen Einstellungen (die ja für jeden Hersteller gleich sein können) in den Master auszulagern.

Du sendest in der Grafik die Artikeländerungen jeweils an einen Query eines Users, wie kommt man da mit nem Listener ran wenn während der Laufzeit ein neuer Kunde dazu kommt? Bisher kenne ich nur das statische Angeben von Desinantions in @JmsListener (destinaation="blabla-queue")
 
mrBrown

mrBrown

oh man vielen Dank für Deine Mühe! Der Wartungsmodus ist eher von unserer Seite aus zu sehen, Hardwartausch ist ein gutes Bespiel.
Dann würde ich das nicht über Messages abhandeln, die nutzt man besser für Domain-Logik.

Theoretisch kann man sich überlegen, ob es so einen "Wartungsmodus" überhaupt geben muss. In obigem Szenario kann man zur Laufzeit beliebig Services ergänzen oder entfernen, das ist für die Gesamt-Anwendung egal.

Das stündliche prüfen der Einstellungen ist nötig, da der Kunde Schrott eingeben kann / Unlogische Einstellungen setzen. Das sollte eigentlich das Frontend verhindern aber soweit sind wir noch nicht.
Und warum dann stündlich prüfen und nicht direkt beim Eingeben? Im Simpelste Fall ergänzt man den stündlichen Scheduler um ein (App-Server-internes) Event, welches das Prüfen auslöst. Das stündliche klingt zumindest nicht sehr sinnvoll.

Zu der Grafik: Verstehe ich es richtig, dass du die Settings jeden Herstellers im Hersteller Service speicherst und der Hersteller die Settings in sich anwendet? Zb Kunde hat eingestellt, dass alle Artikel mit Bestand 0 deaktiviert werden - der Hersteller Service überprüft den Bestand beim einlesen und setzt auf inaktiv? So wäre es sehr logisch abgetrennt, was mich da aber stört ist wenn wir da an der Logik etwas ändern, muss das in jedem Herstellerservice nachgebaut werden. Das kann sehr aufwändig werden oder?
Daher die Idee diese unspezifischen Einstellungen (die ja für jeden Hersteller gleich sein können) in den Master auszulagern.
Jeder Service speichert genau die Daten, die er selbst braucht - was genau das für Daten sind kannst du ja frei definieren :)

Das aktivieren/deaktivieren das Bestands kann auch oben durchaus im Master passiert (in dem Fall speichert sich der Master die Einstellungen dafür), das muss nicht der Herstelller-Service machen. Der Hersteller-Service würde sich dann aber sowas wie Login-Daten für den Hersteller speichern, oder für welche Kunden es überhaupt abgefragt wird.

Du sendest in der Grafik die Artikeländerungen jeweils an einen Query eines Users, wie kommt man da mit nem Listener ran wenn während der Laufzeit ein neuer Kunde dazu kommt? Bisher kenne ich nur das statische Angeben von Desinantions in @JmsListener (destinaation="blabla-queue")
Irgendwie lassen sich auch Listener dynamisch zur Laufzeit hinzufügen, aus'm Kopf hab ich allerdings keine Ahnung wie...

Kann man allerdings auch durch eine eine einzelne Queue ersetzen, solange der Kunde in der Message hinterlegt ist.
 
NicoDeluxe

NicoDeluxe

Auf kurz oder lang kann der Wartungsmodus weg, da hast Recht. Aktuell blockt er, dass Großhändler nochmal neu beginnen. Hauptgrund ist auch wenn wir ein Update eines Service machen. Da haben wir die Wartung eingeschaltet, gewartet bis alle fertig waren und den Service neu gestartet.

Hab jetzt das SettingsProblem mal wie folgt umgebaut:

Der Hersteller wird getriggert (Scheduler 9 Uhr zb) holt die Settings (api Login zb) und legt los. Wenn eine Productänderung festgestellt wurde (Interceptor) wird eine Message erstellt -> mit dem geänderten Wert, Username.

Im Listener:
Hab ich eine Map <String, Settings> in der Hauptklasse. Beim @JmsListener Aufruf wird geprüft ob da Settings drin sind für den User, wenn nein werden die frisch geholt. Zb nach einem Restart ist die Map ja erstmal wieder leer, also werden die aktuellen Settings geholt.

Im Frontend (muss noch gebaut werden, da kommt dann auch die eingabeprüfung rein damit erst gar kein Scheiß gespeichert wird) wird ein Event ausgelöst, wenn der User seine Daten neu speichert kommt nicht oft vor (1x Woche?)-> Message in das og Topic

Der Listener nimmt den Message Inhalt, prüft ob der User überhaupt diesen Wert geändert haben will (anhand der Settings aus der Map).
Zusätzlich habe ich einen @JmsListener auf eine Topic "import-settings-topic" welcher (siehe Frontend-Event) die Settingsdaten in der Map gegen die neuen austauscht.

Das sieht doch schon mal ziemlich nach deinem Bild aus und funktioniert soweit gut.

Zum Thema Wartung fällt mir noch was ein:

Angenommen ich will den Master Updaten, weil neues Feature. Der arbeitet grad 100.000 Nachrichten ab. Wie kann ich dem sagen "Haaaaalt Stop" ich will dich neu starten ohne zu warten bis der iwie fertig ist :rolleyes:
 
NicoDeluxe

NicoDeluxe

Hallo, hab hierzu noch ein Problemchen. Und zwar angenommen 2 User mit einem Hersteller. Der Hersteller ändert 42.000 Artikel jeweils den Bestand.
Nun sendet der Herstellerservice 84.000 Messages. Der Consumer arbeitet aber erst den einen, dann den anderen User ab.

Das ist nich wirklich parallel. Nun habe ich 2 Ansätze:

1. Irgendwie die geänderten Daten im HerstellerService sammeln, diese in eine List stecken und für jeden User die List senden, macht dann 2 Messages mit je einer List. Bei 2 Usern ok kein Problem aber bei 200 Usern, ist das auch wieder nicht parallel (dann müsste man den Consumer klonen oder sonst was vermutlich)

2. Jeder User bekommt einen eigenen Queue in den alle Änderungen rein gehen, Bestand, Namensänderungen , Preisänderungen und es gibt für jeden UserQueue einen Consumer. Das klingt mir irgendwie am logischsten. Aber ich finde keine Möglichkeit dynamisch Listener auf Queues zu erstellen, ohne dass ich explizit die Destinaion angebe.
 
mihe7

mihe7

Soweit ich das verstehe: die Queue dient nur als Kommunikationskanal. Davon unabhängig kannst Du die Arbeit auf mehrere Threads verteilen. Da das JMS-Zeug nicht thread-safe ist, musst Du entweder einen JMSContext je Thread verwenden oder Du entkoppelst den Spaß auf Producer-Seite über eine Queue. Auf der Consumer-Seite dagegen kann der Listener dagegen die Worker-Threads erstellen. Ich weiß nicht, wie das in Spring ist, in Java EE gibt es einen Thread-Pool für die Message Driven Beans, man kann also einfach festlegen, wie viele Instanzen an MDBs die Nachrichten parallel verarbeiten (s. z. B. https://www.baeldung.com/ejb-message-driven-bean-concurrency)
 
NicoDeluxe

NicoDeluxe

Hallo zusammen, möchte hier gern nochmal drauf zurück kommen.

Habe es jetzt so, dass die daten "dumm" und realtiv wenig normalisiert in der Hersteller DB liegen. Änderungen werden dort mittels Envers getracked und in eine eigene tabelle geschieben, soweit so gut.

Sobald neue Produkte reinkommen werden die in einen Query "neue Artikel" gschoben mit dem user als property in der Message, das selbe mit den Änderungen.

Allerdings sind mit einerm Kunden da jetzt schon 400MB Daten in der DB :/ das wird natürlich immer mehr je mehr User diesen Hersteller wollen. Irgendwann sind da millionen von Datensätzen, da hab ich die Befürchtung, dass das immer langsamer wird.

Nun zum ersten Problem:
Wenn ich jedes neue Produkt einzeln sende, arbeitet der Listener parallel Messages ab und legt Kategorien zb doppelt an bzw verursacht einer einen Fehler, duplicate Entry bla. Kann man das überhaupt in den Griff bekommen da die Threads ja parallel arbeiten.

Am liebsten wäre es mir, wenn jeder User einen eigenen Query hätte, das geht auf Producerseite aber ich kann auf der Consumerseite zur Laufzeit keinen Listener auf einen userQuery hinzufügen.

Hab mir schon Bücher und Videos reingezogen, aber ich bekomme die Services nicht wirklich sinnvoll unter einen Hut ohne Daten doppelt zu haben oder doppelte Arbeit zu haben wenn ich etwas einbaue

Hat noch jemand eine Idee wir man die ganzen Importservices unter einen Hut bekommen könnte?
 
mrBrown

mrBrown

Allerdings sind mit einerm Kunden da jetzt schon 400MB Daten in der DB :/ das wird natürlich immer mehr je mehr User diesen Hersteller wollen. Irgendwann sind da millionen von Datensätzen, da hab ich die Befürchtung, dass das immer langsamer wird.
Die Daten brauchst du aber doch in jedem Fall?

Durch die Trennung in verschiedene Services speicherst du sie zwar doppelt, aber man bekommt halt nichts kostenlos, in diesem Fall gewinnt man dadurch Schnelligkeit und Ausfallsicherheit.

Man kann versuchen die gespeicherten Daten möglichst zu minimieren. Reicht es uU, einfach nur einen Hash zu speichern? Man sieht dann zwar nur noch, ob sich Daten geändert haben, und nicht mehr welche, aber vielleicht ist das ja egal?

Wenn ich jedes neue Produkt einzeln sende, arbeitet der Listener parallel Messages ab und legt Kategorien zb doppelt an bzw verursacht einer einen Fehler, duplicate Entry bla. Kann man das überhaupt in den Griff bekommen da die Threads ja parallel arbeiten.

Am liebsten wäre es mir, wenn jeder User einen eigenen Query hätte, das geht auf Producerseite aber ich kann auf der Consumerseite zur Laufzeit keinen Listener auf einen userQuery hinzufügen.
Ich wüsste nicht, warum du nicht Listener zur Laufzeit erzeugen können sollst? Irgendwie geht das in jedem Fall, ich müsste auch mal nachgucken, aber grundsätzlich gibt JMS das her.

Hab mir schon Bücher und Videos reingezogen, aber ich bekomme die Services nicht wirklich sinnvoll unter einen Hut ohne Daten doppelt zu haben oder doppelte Arbeit zu haben wenn ich etwas einbaue
Daten doppelt haben ist dabei ja ganz bewusst so - erst dadurch gewinnt man die Unabhängigkeit und Ausfallsicherheit zur Laufzeit. Bis auf den größeren Speicherplatzbedarf scheint das in deinem Fall ja auch kein Problem zu sein?
 
Thema: 

Microservices StrukturPlanung

Passende Stellenanzeigen aus deiner Region:
Anzeige

Neue Themen

Anzeige

Anzeige
Oben