Laden...

Single vs. Multi-Prozess Architektur bei DataFlow Anforderungen

Erstellt von JimStark vor 3 Jahren Letzter Beitrag vor 3 Jahren 435 Views
Hinweis von Abt vor 3 Jahren

Abgespalten von https://mycsharp.de/forum/threads/123749/mit-task-factory-startnew-eine-class-auf

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren
Single vs. Multi-Prozess Architektur bei DataFlow Anforderungen

Mit diesem meinem Programm begann ich 2013 C# zu lernen - es sollte drei Aufgaben erledigen, dann kamen Webseitenanalysen mit Down- und Uploads dazu. Mittlerweile ist es auf eine Größe angewachsen, bei der es mir echt schwerfällt, den Überblick zu behalten: etwa 2,5MB an selbstgeschrieben .cs-Dateien.
Deswegen dachte ich auch schon an das:

3-Schicht-Modell: Ich bin am Umbau. Da das Programm aber weiter täglich funktionieren muss, ist das nicht ganz trivial. Und da ich es damals noch nicht kannte, hatte ich den KISS-Weg gewählt - überaus erfolgreich, wesegen ich jetzt vor dem Dilemma stehe.

Bei so einer umfangreichen Anwedung würde ich soweit gehen, das alles, neben der 3-Schicht-Trennung, auch in unterschiedliche Anwendungen zu trennen.
Z.b. Consolen-Anwedung oder Windows-Dienst für Scraping (z.B. mit Interceptor-Pattern), WinForms Anwendung für Client-Applikation, HttpApi für Server App usw. Und die dann halt über Datenbank managen. Server App erzeugt Jobs, Scraper arbeitet sie ab, so in der Art.

16.807 Beiträge seit 2008
vor 3 Jahren

Davon abgesehen, dass wir das nur von Außen betrachten können JimStark, stimm ich Dir basierend auf den Infos, die wir hier haben, null zu.
Es macht keinen Sinn für jeweils 10 Zeilen Code unterschiedliche Anwendungen zu schreiben, die dann über IPC/Queues kommunizieren müssen, um Folgeaufgaben leisten zu können.
Am Ende ein riesiger Overhead für Null Vorteil.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Ja das ist deine Meinung 😁
Ohne zu wissen was und wie er es macht, aber bei einer Anwendung die 2,5 MB an cs Klassen hat und ihm der Überblick selbst schwer fällt, würde ich fast davon ausgehen dass das ein bisschen umfangreicher als 10 Zeilen ist und nicht gerade dem KISS-Prinzip entspricht. m.M. lohnt sich bei so komplett unterschiedlichen Aufgaben, Präsentation, Datenbeschaffung, Verarbeitung,... , wenn möglich, immer eine Aufteilung.
Wahrscheinlich alles innerhalb eines einzelnen WinForms Projektes, ohne DLLs,... Das gibts in der Praxis öfter als man denkt.

16.807 Beiträge seit 2008
vor 3 Jahren

Ja das ist deine Meinung

Klar, auf der Ebene kann man immer argumentieren; förderlich ist das nicht. Beleuchten wir doch einfach die Fakten 😉

Das gibts in der Praxis öfter als man denkt.

Und das ist auch nicht schlimm.
Für die Trennung von Verantwortlichkeiten, wie es hier der Fall ist, gibt es in C# primär mal Klassen und Namespaces, Grundlagen dazu hier: https://docs.microsoft.com/de-de/dotnet/standard/design-guidelines/names-of-namespaces
Eine physikalische Trennung macht man in .NET primär dann, wenn man es muss - zB. aufgrund von Wiederverwendbarkeit, Technologie-Trennung etc. Also Anforderungen der Solution Architektur, die wir hier nicht kennen und an der Stelle mit dem Problem nichts zutun hat.

Mit KISS-Prinzip hat das null, also wirklich null zutun - im Gegenteil: Durch Dein Vorschlag mit Prozess-Trennung wirst Du einen riesen Wulst brauchen, damit die Flows miteinernader sprechen können. Also -> ganz weit weg von KISS.
Genau um solchen, oft unnötigen Overhead (IPC und Co...) zu vermeiden, gibt es Tasks, TPL und DataFlows.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Für mich würde die Trennung hier trotzdem Sinn machen. Vorallem dass das Scraping, um was es hier wahrscheinlich zum Teil geht, unabhängig läuft.
Schon allein aus Entwicklungs- und Wiederverwendbarkeitsgründen.
Aber das mag dann wohl Geschmackssache sein. Die Diskussion ist hier aber wahrscheinlich sowieso sinnlos und bringt den Ersteller nicht weiter.

Und ja, dass es Anfangs mehr Arbeit ist, ist klar, aber ich denke es würde langfristig die Komplexität trotzdem deutlich verringern.

2.078 Beiträge seit 2012
vor 3 Jahren

dass es Anfangs mehr Arbeit ist, ist klar

Und später ist es noch mehr Arbeit, als anfangs, weil dann die ganzen Probleme auffallen, die Du übersehen hast 😉
Belies dich mal zu den ganzen Vor- und vorallem Nachteile der Microservices-Architektur, das klingt für mich ein wenig danach.

Aber ja, das hilft dem Fragesteller nicht weiter, das stimmt wohl.
Oder Abt macht ein neues Thema daraus.

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Und später ist es noch mehr Arbeit, als anfangs, weil dann die ganzen Probleme auffallen, die Du übersehen hast 😉

Ich versehe da ehrlich gesagt euer Problem nicht ganz 😁 In der Praxis durfe ich zumindest schon in mehrere Projekte Einblick nehmen, die eine sehr ähnliche Struktur hatten aber halt deutlich umfangreicher. Und da funktioniert das ganze wunderbar einfach, da alles klar getrennt ist (Übersicht, Entwicklung, Abhängigkeiten,...). Bei der Kommunikation muss man sich halt was passendes überlegen.
Dass der ursprüngliche Author das in seinem Projekt nicht anwenden wird ist mir schon klar 😁 Aber für mich bleibt es trotzdem eleganter sowas bei so einer umfangreichen Anwendung zu trennen (Client UI, Server, Services,...). Mehr kann ich dazu aber auch nicht mehr sagen. 👍

2.078 Beiträge seit 2012
vor 3 Jahren

Vielleicht reden wir auch aneinander vorbei.

Ich bin gedanklich gerade bei Microservices, die - richtig umgesetzt - noch einige Anforderungen mehr hat, die viele vergessen.
Oder wir reden hier von einer Client-Server-Architektur, die natürlich deutlich einfacher ist und tatsächlich in sehr vielen Projekten sinnvoll ist.

16.807 Beiträge seit 2008
vor 3 Jahren

Ich hab mich fürs Abspalten entschieden und beantworte das nun mal in ausführlicherer Form als in den Offtopic-Style Beiträgen.

den Überblick zu behalten: etwa 2,5MB an selbstgeschrieben .cs-Dateien.

Bei so einer umfangreichen Anwedung

Die Menge an Quellcode ist kein Maß, um Umfang einer Anwendung zu definieren oder darüber zu entscheiden, ob man Quellcode modularisieren sollte.
Das hat man mal vor 35 Jahren gemacht, als man Komplexität von Quellcode auch in Anzahl Zeilen definiert hat; wobei man heute schlauer ist, dass man genau das nicht tun sollte, weil die Aussage dahinter quasi gleich Null ist.
Das kann man entsprechend in Architektur Büchern führender Köpfe nachlesen oder zB. sich Metriken von sehr schlauen Menschen wie zB. McCabe-Metrik nachlesen.

Daher auch der Spruch

The number of lines of program code is wonderful metric: it's so easy to measure and almost impossible to interpret.

Namespace Design - nicht nur in C# bzw. Programmiersprachen, sondern quasi in allen Targets von Namespaces - richtet sich immer nach Anforderungen (an den Quellcode/Umgebung), und nicht nach dem Umfang. Auch dazu gibt es Lektüre, angefangen bei den MS Docs.

Wir haben also außer dem Unfang, was eben kein Indiz ist, kein Grund in den Raum zu werfen, dass eine Implementierung in mehrere Apps "besser" wär.
Und von KISS ist das eben auch ganz weit weg.

neben der 3-Schicht-Trennung, auch in unterschiedliche Anwendungen zu trennen.

Das ist ein Relikt aus den Anfängen von monolitischen Architekturen, das man so nicht mehr macht bzw nicht mehr machen muss.
Gerade auf Windows haben Multi-Prozess-Anwendungen durchaus ihre Berechtigung (jeder Browser tickt aus Sicherheitsgründen so, da jeder Tab aufgrund der Isolation in einem eigenen Prozess läuft) - ist aber immer der letzte Punkt, den man bei einer Gesamt-Lösung in Betracht zieht.

Bei Flow-Anwendungen (wie die Quelle dieses Themas) versucht man Multi-Prozess-Anwendungen prinzipiell zu vermeiden, wenn es die Anforderungen zulassen.
Grund ist, dass IPC-Anwendungen immer einen enormen Aufwand bedeuten, angefangen bei der Infrastruktur, bei der Implementierung oder eben auch bei der Security.

Mit unter wurde daher insgesamt das Thema TPL Pipelines angegangen und konzeptionell geformt, aus dem heute, ca. 6 Jahre später die TPL Dataflows wurden.

m.M. lohnt sich bei so komplett unterschiedlichen Aufgaben, Präsentation, Datenbeschaffung, Verarbeitung,... , wenn möglich, immer eine Aufteilung.

Ich versuche bei meinen Beiträgen immer entsprechende Referenzen (Docs, Lektüre, Bücher..) anzugeben, auf denen meine Meinung aka Erfahrungsgrundlagen oder meine Aussage basieren.

Für das UI spielt keine Rolle, ob die Informationen aus einem Datenfluss stammen, oder nicht. Daher fokussiere ich im folgenden nun auf die Datenbeschaffung.

Z.b. Consolen-Anwedung oder Windows-Dienst für Scraping (z.B. mit Interceptor-Pattern)

Der Inteceptor-Pattern ist ein Pattern, der für die Organisator von Code-Aufgaben zuständig ist; zB. die Middleware von ASP.NET Core ist ein solcher.
Der Sinn dieses Pattern ist, dass eine fixe Reihenfolge bei der Prozess-Verarbeitung nicht gegeben ist, sondern entsprechende Zwischenschritte "ausgetauscht" werden können; dies hat die Folge, dass In- und Output bei diesen Komponenten meist identisch ist (HTTP: Request und Response).
Bei einem Datenfluss ist dies meist nicht gegeben, weil sich der Output pro Schritt für den Folgeprozess ändern soll. Daher findet man diesen Pattern in Datenfluss-Konzepten eigentlich eher nicht.

Beispiel:

  1. Schritt: URL zur Verarbeitung in eine Liste werfen
  2. Schritt: URL aus der Liste nehmen, aufgrufen und Ergebnis (Response) in eine weitere Liste werfen
  3. Schritt: Response aus der Liste nehmen und zum Beispiel alle Bilder-URLs ermitteln; Bilder-Url in eine weitere Liste werfen
  4. Schritt: Bilder-Url aus der Liste entnehmen, laden und in eine weitere Liste werfen
  5. Schritt: Bilder-Content entnehmen und analyisieren; Ergebnis in eine weitere Liste werfen
  6. Schritt: Bild-Ergebnis anzeigen / verarbeiten, whatever

Wir sehen an den Schritten: Interceptor passt (auf dieses Datenfluss-Beispiel) nicht, weil wir die Schritte untereinander nicht tauschen können. Mit dem TPL-Dataflow Konzept ist dies problemlos umsetzbar - in den Geburtsstunden von TPL Pipelining ist sogar dieses Beispiel das damalige Konzept-Beispiel gewesen.
Es ist aber passenderweise relativ ähnlich zur Anforderung von MoaByter.

Welchen Vorteil hat das?
Dataflows hat den Vorteil, dass mit sehr wenig Overhead eine in sich skalierende, modulare Lösung umgesetzt werden kann, die keine externe IPC-Ressourcen benötigt, die eben diesen Overhead darstellen.
Overhead deswegen, weil man sehr oft diesen eben nicht will / benötigt.
Der weitere Vorteil ist, dass konzeptionell der gesamte Datenfluss in sich "sicher" ist.

Welchen Nachteil hat das?
Der Nachteil ist, dass die Zwischenspeicher-Schichten beim TPL Datenfluss auf InMemory-Umsetzungen basieren; wenn also der Prozess abstürzt, dann gehen alle nicht-konsistent gespeicherte Informationen verloren.
Das Gute an diesem Nachteil ist, dass nur die Zwischenarbeit verloren geht. Den Ausgangspunkt (hier die URL) hat man meistens in einer Quelle, über die man den Prozess für das einzelne Item neu anstoßen kann.

Alternative
Die alternative zu einer "internen" Architektur aka "internen" Skalierung, wie es in manchen Lektüren beschrieben wird, ist eben eine externe Skalierung: statt Schritte über Tasks umzusetzen, setzt man sie mit einzelnen Anwendungen um.
Die Kommunikation basiert dann auf externen Strukturen wie eben IPC oder mit Speichersystemen wie Redis, RabbitMQ und Co.

Man sieht also sofort: die Alternative zum internen Datenfluss hat sofort externe Abhängigkeiten zur Folge. Und natürlich muss man sich dann auch entsprechend über deren Operation-Verhalten (Verfügbarkeit, Restart, Management und Co...) kümmern.
KISS? Eher nicht 😉

Für mich würde die Trennung hier trotzdem Sinn machen. Vorallem dass das Scraping, um was es hier wahrscheinlich zum Teil geht, unabhängig läuft.
Schon allein aus Entwicklungs- und Wiederverwendbarkeitsgründen.

Und hier behaupte ich das krasse Gegenteil, weil eben wegen solch einer Aufgaben die TPL Dataflows "erfunden" wurden, um KISS-nah zu sein und nur wenn wirklich notwendig extern skalieren zu müssen.
Und nen bisschen HTML runterladen und in nem Grid anzeigen: selbst mit 50 MB Quellcode würde ich hier klar auf TPL setzen, wenn wir nicht gerade eine Multi-Server-Core-Anforderung an die Leistung der Anwendung haben, wovon ich hier aufgrund der Konstellation mal nicht ausgehe.

Und welche Wiederverwendbarkeit habe ich denn? Download und Scapping sind vielleicht 20 Zeilen Code - und wenns 100 wär.
Wegen 100 Zeilen Code sooo einen immensen Aufwand betreiben..? Puh.. bin ja auch Architektur-Fanatiker; aber hier überwiegt der Pragmatismus dann in mir doch sehr 😉

Und ja, dass es Anfangs mehr Arbeit ist, ist klar, aber ich denke es würde langfristig die Komplexität trotzdem deutlich verringern.

Ich hoffe durch die Beschreibung der Alternative aufgezeigt zu haben, dass es eben nicht nur am Anfang ist, sondern für den gesamten Zyklus und die gesamten Operations.

Und später ist es noch mehr Arbeit, als anfangs, weil dann die ganzen Probleme auffallen, die Du übersehen hast
Belies dich mal zu den ganzen Vor- und vorallem Nachteile der Microservices-Architektur, das klingt für mich ein wenig danach.

Nehmen wir mal die Kurzdefinition von Martin Fowler:

In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery.

Der wichtigste Satz ist: der Zweck muss im Vordergrund stehen. Ist wirklich jeder einzelen Step eine Business Capability, oder ist der Prozess selbst die Business Capability?
Das resultiert direkt in der Frage: wie klein oder groß muss oder darf ein Microservice sein? Reden wir wirklich noch von einem Microservice, wenn jeder Step eine einzelne Anwendung ist, oder sind wir hier schon im Clusterfu*k der TinyServices?

A common question people ask is “How big (or small) should my microservice be?” One common answer is that the size of a microservice can be variable, but it should be coded by no more than a dozen people (the so-called “two pizza rule”).

Wie sieht das "der Markt"? Das Problem der Monolithen ist, dass diese irgendwann nicht mehr wartbar werden. Das Problem der Microservice ist: schneidet man sie zu klein hat man am Ende mehr Overhead zu verwalten, als die eigentliche Business-Aufgabe darstellt. Viele Unternehmen fanden das Konzept der Microservices extrem geil und haben dann gemerkt: scheise, wir haben uns plötzlich um Dinge zu kümmern, die wir gar nicht machen wollen - und sind zurück zu eher als monoliothisch bezeichnenden Basis-Architekturen.

Ein guter Artikel dazu ist Goodbye Microservices: From 100s of problem children to 1 superstar, in dem Alexandra Noonan sehr gut beschrieben hat, dass Microservices und viele kleine Anwendungen nicht wirklich immer soooo eine tolle Idee ist 😉

Mein Vorgehen
Nun ein wenig Weg von eher Fakten zu meinem persönlichen Vorgehen, das ich sowohl bei meinen Anwendungen, bei myCSharp.de und auch bei meinen Kunden anwende:

Namespaces sind ein extrem mächtiges Werkezug, das viele Entwickler unterschätzen; aber bei Namespaces beginnt die Architektur und nicht bei den Anwendungen.
Je besser das Namespace-Konzept ist, desto mehr Möglichkeiten hat meine Architektur im Sinne der Skalierung, Modularisierung und Weiterentwicklung.

Ich kann problemlos mit einem Single-Task-Konzept beginnen, das sich bei einer Anforderung in ein TPL Datenfluss-Konzept weiter entwickelt. Das Datenfluss-Konzept kann am Anfang mit InMemory-Collections arbeiten und später externe Queues verwenden - und wenn ich wirklich ne richtig erfolgreiche App habe, das Task-basierte Datenfluss-Konzept verwerfen und die externen Queues mit Multi-Prozess-Anwendungen befeuern.
So gehe ich bei diesen Anforderungen immer vor; und auf diesem Prinzip basieren viele Hintergrund-Implementierungen von myCSharp.de, zB. das automatische Löschen von IP-Adressen aus Beiträgen nach X Tagen gemäß DSGVO.

Mein Fazit
Die Vorschläge von JimStark mögen durchaus ihre Berechtigung haben; von einer generellen Empfehlung sind wir aber so weit weg, so weit kann ich gar nicht schauen 😉
Sie entsprechen im Endeffekt dem Anti-Pattern der Tinyservices, wodurch Moabyter basierend aufn den Infos, die er geliefert hat, sich um sooo viel komplexen Overhead kümmern muss, dass die eigentliche Aufgabe nur noch ein Bruchteil darstellt: das klassische Not-invented-here-Syndrom.

Auch Microservices im Generellen sehe ich hier nicht, da ich nicht mal die Anforderung danach sehe. Auch wenn sie sehr im Trend sind: auch hier gibt es Overhead, den man erst mal durch die Vorteile wieder rein fahren muss.

Die gesamte Plattform von myCSharp.de ist eine monolitsche Grundarchitektur, obwohl durchaus charakterlichen Eigenschaften für einen Microservice existieren würden - aber der riesige Nachteil: der immense Overhead an eine saubere Umsetzung eines solchen steht in keinerlei Verhältnis.
Wir wären sicherlich 30% nur mit Overhead beschäftigt statt mit Inhalten

Und das sehe ich übligens auch immer wieder bei meinen Kunden: wegen nen paar tausend Requests pro Minute baut man riesige Microservice-Konzepte, die man problemlos mit Plugin-Architekturen umsetzen kann; am Ende mit 30-50% weniger Code, weniger DevOps Aufwand und vor allem weniger Kosten; ohne sich auch nur einen einzigen Nachteil erschaffen zu haben.

(Anhang von den Microsoft Docs aus den Anfängen von Pipelining mit C# Task Pipelines aus 2012)

JimStark Themenstarter:in
309 Beiträge seit 2020
vor 3 Jahren

Das ist mal eine umfangreiche Antwort 👍 Ich wollte hier eigentlich auch nicht von Microservices sprechen.
Sondern ehr Client, Server,... Den externen Scraper Service könnte man vielleicht so bezeichnen.
Für mich ist es bei sowas trotzdem angenehmer, hier z.B. Server-Docker-Container läuft weiter, Scraper-Container wird abgeschalten, etc. Und ja das mag auch irgendwie anders gehen.
Ich finds so nur am einfachsten 😁
Weiter zu diskutieren hat hier für mich aber keinen Sinn mehr, du hast da deine eigene Meinung, welche ja auch absolut okay und richtig ist. Ich weis aber zumindest auch aus der Praxis dass es erfolgreich getrennt aufgebaut werden kann. 👍

16.807 Beiträge seit 2008
vor 3 Jahren

Wenn Du das am einfachsten empfindest, dann ist das ja auch absolut in Ordnung; aber ist halt keine "generelle So-Macht-Man-Das-Immer"-Antwort.
Du darfst dabei nur nicht die Ausgangslage des Lesers / des Themenstarters vergessen; und hier war ja aufgrund von Task.StartNew() schon ersichtlich, dass wir uns nicht unbedingt wirklich direkt in der Enterprise-Multi-Cloud-World-Wide-Ausbaustufe befinden.

Weiter zu diskutieren hat hier für mich aber keinen Sinn mehr, du hast da deine eigene Meinung

Und zur Diskussion: es geht mir hier nicht um meine Meinung; ich formuliert das neutral basierend auf Fakten und unterlege das entsprechend mit Verweisen - kannst Du auch gerne mal machen 🙂
Ziel ist hier neutral Wissen zu vermitteln, sodass der entsprechende Leser für sich das beste rauspicken kann.

Wenn ich etwas basierend auf meiner Meinung ausdrücke, dann kennzeichne ich das entsprechend.

Ich weis aber zumindest auch aus der Praxis dass es erfolgreich getrennt aufgebaut werden kann.

Natürlich kann hier jeder sein Wissen vermitteln; aber sehr gerne auch entsprechend untermauern mit mehr als "so ist das" oder "ich finde" - das ist ja das Ziel.
Aber man sollte auch Kritik äußern dürfen oder Nachfragen stellen, die nicht gleich mit "Deine Meinung" abgewickelt wird... so kann man nämlich nicht sachlich diskutieren 😉

Die Trennung ist auch vollkommen okay; stellt nur nicht die kleinste Aufbaustufe dar, sondern geht ja schon in eine spezielle Richtung, die - basierend auf was Moabyter möchte - eher Oversized ist und ein komplettes Neukonzept erfordern würde -> nicht KISS 😉

2.078 Beiträge seit 2012
vor 3 Jahren

JimStark, das Problem ist mMn. nicht deine Entscheidung an sich, sondern die Begründung, dass es angenehmer oder einfacher sein soll.

Wenn man sich etwas genauer mit solchen Architekturen beschäftigt (und ich bin lange nicht so weit wie Abt), wird klar, wo sich solche Projekte mittel- bis langfristig hin bewegen und was man sich zu Beginn für Felsen in den Weg gelegt hat, ohne es zu merken.
Ich will damit nicht sagen, dass das bei dir auch der Fall ist, sondern nur, dass so eine Entscheidung immer sehr gut begründet sein sollte.
Die langfristigen Folgen können groß werden, der zusätzliche Aufwand ist schon zu Beginn groß und kann noch größer werden, deshalb sollte vorher direkt klar sein: Es gibt keinen besseren bzw. einfacheren Weg, der die Anforderungen erfüllt.
Oft lohnt sich auch die Frage bei jeder einzelnen Anforderung: Brauche ich das wirklich? Geht das vielleicht auch anders?

Und zum Thema Übersicht:
Ich trenne gerne in Ordnern (nur in der Solution), darin in Projekten und dazwischen gibt's teilweise kleinere Projekte, die eine Art Contract darstellen.
Eine Assembly will mit einer Anderen kommunizieren, darf das aber nur über die entsprechenden Schnittstellen.
Auf diese Weise habe ich zwischen den wichtigen Teilen einer Anwendung eine klare Trennung, was es leichter macht, die Zusammenhänge zu begreifen oder die Arbeit auf mehrere Leute/Teams aufzuteilen, ohne die Nachteile einer Prozess-Trennung zu haben.
Eigene Assemblies braucht man dafür nicht zwingend, allerdings kann ich dadurch genauer definieren, wer was darf oder sieht.

Ob das nun ideal ist, sei mal dahin gestellt, aber der Overhead ist verglichen einer Multi-Process-Architektur sicher um einiges geringer 😁