Machen wir doch mal wieder ein interessanteres Thema auf:
Scheduling / Eventqueue / Dispatching
Nun, ich stelle mir gerade die Frage, wie genau ich meine backend <> daemon Kommunikation gestalte und wie der daemon dann weiter mit den Daten verfährt.
Imho kommt man ab einem gewissen Grad an Spielintelligenz und automatisch ablaufenden Vorgängen nicht mehr um einen daemon herum und das Thema hatten wir ja schon an anderer Stelle, daher soll das hier nicht Punkt der Diskussion sein.
Scheduling
Die Frage ist nun wie mein Backend (bei mir also mein Perl MVC Code, Frontend wäre GUI/Javascript libs/PHP Scripts, die GUI/libs dynamisch einbingen - Frontend und Backend sprechen nur JSON miteinander; Ich habe also je nach Definition irgendwas zwischen 4-tier, 5-tier und 7-tier architecture, genau genommen letzteres) am sinnvollsten mit dem daemon kommuniziert und wie der daemon dann am effektivsten dispatcht.
Für die Kommunikation habe ich ja im Grunde zwei Möglichkeiten.
a) Direkte Kommunikation Backend <> server über sockets/named pipes
b) Datenbank als Zwischenschritt
Vergleichen wir also die beiden Möglichkeiten bei verschiedenen Anwendungsfällen:
Kommunikation
a) Backend und daemon sprechen direkt miteinander. Die Kommunikation ist also instant und der Server weiss sofort was los ist, ohne sich selbst um die Informationen kümmern zu müssen. Es fallen IO Operationen für sockets/pipes an. Das ist zwar bei Datenbanken auch so, aber die sind professionell programmiert und stark optimiert, und es ist zu erwarten ist, dass mein Perl Code langsamer performt.
b) Das Backend schreibt erst einmal in die Datenbank, der Server pollt regelmäßig aus der Datenbank. Das Polling ist ein Mehraufwand und es kann Szenarien geben in denen das für den User zu einer Verzögerung führt, wenn das Polling Interval zu groß ist. Pollt man jedoch sekündlich, sollte das nicht mehr auffallen und man täuscht Echtzeit vor. Sekündliche Polls sind natürlich eine Mehrbelastung, allerdings ist die Datenbank optimiert, die Eventqueue hat indexierte Keys, daher dürfte der Performance Impact gering ausfallen und je mehr das System nach oben skaliert, umso geringer wird der Anteil des Pollings.
Clustering
a) Die direkte Kommunikation erlaubt es einfach weitere daemon Server aufzusetzen, sofern das ganze über sockets läuft. Jeder daemon Server könnte ohne Mehraufwand das komplette Repertoire an Funktionen abdecken, sofern das Dispatching per se erst einmal stateless ist. Wenn der Server jedoch aus Performancegründen selbst Informationen vorhält, wird das komplizierter.
b) Man kann jederzeit weitere Datenbank Server hinzufügen um hier Last abzufangen. Allerdings ist es schwieriger den daemon zu clustern, da ja kein einfacher select auf die Eventqueue mehr ausreicht, weil sonst die verschiedenen daemons dieselben Daten abarbeiten würden. Das heißt man müsste entweder mehrere verschiedene Queue Datenbanken haben oder innerhalb einer Datenbank differenzieren. Dann könnte beispielsweise ein daemon sich um die Abwicklung von Kämpfen, ein anderer um das Arbarbeiten von Auktionen kümmern.
Persistenz
a) Persistenz ist nicht gegeben und muss zusätzlich implementiert werden.
b) Grundsätzlich sind die Daten persistent, möchte man jedoch aus Performancegründen Heap Tables nutzen, ist das wiederum nicht gegeben, allerdings zumindest von den daemon servern gekapselt, müsste aber dennoch zusätzlich implementiert werden. Man könnte aber um erst einmal anzufangen persistente Tabellen nutzen.
Skalierung
a) Die Speicheranforderungen des daemon skalieren linear mit der Menge an Gesamtdaten.
Komplizierter wird es hier, wenn man die Queue bearbeiten möchte, z.B. ein Element an einer bestimmten Position einfügen. Hier müsstem an um performant zu sein im Grunde eine Datenbank nachbauen, was in Perl aber nie an die Performance der echten DB kommen wird. Normalerweise wäre das Einfügen an einer beliebigen Position der lokalen Queue der einzige Anwendungsfall, jedoch einer der nicht unterschätzt werden sollte.
b) Die Speicheranforderungen des daemon skalieren linear mit der Menge an Daten im jeweiligen Zeitfenster der Polls. Sicherheitshalber wird man etwas Daten vorhalten wollen, um im Fehlerfall der Verbindung zur DB nicht sofort auf dem Trockenen zu sitzen. Ich würde sekündlich die Daten der nächsten zehn Sekunden pollen.
Das Bearbeiten der Queue ist hier absolut problemlos, da sich die Datenbank darum kümmert und WHERE, sowie ORDER BY alle normalen Anforderungen abfangen.
Implementierungsaufwand
Das bezieht sich jetzt rein auf die Kommunikation Backend <> daemon.
a) Komplett eigene Implementierung und Codewartung, allerdings auch nicht zu komplex, jedoch schon mit etwas Arbeit verbunden. Schätze mal 10-12 Stunden, um es sauber zu machen.
b) Weniger Aufwand, weil ich bestehende DB Connectoren nutzen kann. Schätze mal 3-5 Stunden, um es sauber zu machen.
Fazit
Ich tendiere zu Variante b), also die Datenbank als Zwischenschritt zu verwenden, weil es einfach Arbeit spart und bewährte Technologie nutzt. Allerdings wäre Clustering komplizierter zu realisieren.
Eventqueue
Meine Idee ist sämtliche Events in einer zentralen Eventqueue abzuarbeiten. Ich bin mir noch nicht sicher, ob ich das Event selbst dabei serialisiere oder fremde Datenbanken referenziere, tendiere aus Performancegründen zu letzterem.
Im Grunde reicht also eine Zuordnung "event_id, event_time, event_type, event_params/event_foreignkey".
Der Eventqueue-Manager im Server würde also immer eine Liste der nächsten Events vorhalten und mit usleep / select pausieren bis das Event aktuell wird (könnte auf Microsekundenbasis geschrieben sein). Daraufhin würde er dispatchen und das Event wird abgearbeitet. Also nun zum nächsten großen Punkt.
Dispatching
Nun, was wäre am sinnvollsten? Fork ist recht performant, threads in Perl eher weniger (weil wie bei einem fork die gesamte Speicherstruktur des parent processes mit übernommen wird und noch weiterer Overhead dazu kommt). Ich habe selbst noch keinen scheduler geschrieben und kann das schlecht einschätzen, aber ich bin mir ziemlich sicher, dass es nicht gerade schlau wäre jedes einzelne Event zu forken. Dass die Events in sich selbst thread-safe sein müssen ist an dieser Stelle ja selbstverständlich. Dennoch wäre es u.U. möglich mehrere ähnliche events zusammenzufassen, jedoch relativ unrealistisch, weil sie zeitlich ja eher nicht zusammen fallen.
Wenn ich also an die Struktur von Software ala Apache denke, arbeitet man dort meist mit einer vorinstanziierten Menge an Threads, die jeweils mit der neuen aufgabe betraut werden. Ich gehe davon aus, dass ich am besten einen Pool an Threads erstelle (20?), an die ich jeweils meine Events dispatche, so dass der Scheduler weiterlaufen kann und die nächsten Events aus der Queue holt. Nur was mache ich, wenn der Threadpool erschöpft und alle Prozesse beschäftigt sind? Wartet die Queue? Erstelle ich neue Threads? Bau ich die daraufhin wieder ab, oder behalte ich einen größeren Pool? Soll das System selbstständig lernen? Bin für Meinungen und vor allem für Erfahrungsberichte sehr dankbar.
Nun meine weiteren Fragen:
- welches Kommunikationsmodell würdet ihr wählen? weshalb?
- denkt ihr das Ganze ist so durchdacht oder hab ich Denkfehler drin?
- fehlt etwas Wichtiges?
- Wie realisiere ich das Threading/Forking zum Dispatchen am besten?
- Habt ihr sonstige Anmerkungen?
Danke für eure Aufmerksamkeit und ich bitte um Kommentare. ;)